@@ -102,7 +102,8 @@ class SelectedPlatformEnvironment(unittest.TestCase):
102102 class NativeChecksComplete (Exception ):
103103 pass
104104
105- def native_checks (self , root , inherited_prefix = None , selected = True , dependency_selected = True ):
105+ def native_checks (self , root , inherited_prefix = None , selected = True , dependency_selected = True ,
106+ selected_tools = False , retain_tools = False ):
106107 """Run the archive orchestrator, real child/make and the production audit.
107108
108109 Only archive identity, the expensive package producer and native inspectors
@@ -162,7 +163,7 @@ def native_checks(self, root, inherited_prefix=None, selected=True, dependency_s
162163 source = {}, files_sha256 = {})))
163164 args = argparse .Namespace (selection = selection , output = root / 'output' , archive = root / 'source.tar.gz' ,
164165 provider_prefix = root / 'provider' , compiler = str (sdk / 'bin/clang++' ), cc = '/llvm/bin/clang' ,
165- make = shutil .which ('gmake' ) or shutil .which ('make' ), jobs = 2 , client_python = sys .executable ,
166+ make = os . environ . get ( 'MAKE' ) or shutil .which ('gmake' ) or shutil .which ('make' ), jobs = 2 , client_python = sys .executable ,
166167 node = 'node' , pkg_config_dir = [], toolchain_prefix = sdk if selected else None )
167168 release = dict (source_root = str (source ), source = {}, files_sha256 = {})
168169 counts = dict (tests = 1 , checks = 1 )
@@ -171,19 +172,120 @@ def native_checks(self, root, inherited_prefix=None, selected=True, dependency_s
171172 env .pop ('V3_PACKAGE_TOOLCHAIN' , None )
172173 if inherited_prefix is not None :
173174 env ['V3_PACKAGE_TOOLCHAIN' ] = str (inherited_prefix )
175+ if selected_tools :
176+ compiler = root / 'selected compiler.py'
177+ compiler .write_text (textwrap .dedent ('''\
178+ import json, os, shlex, sys
179+ assert sys.argv[1:3] == ['--selected-toolchain', 'argument with spaces'], sys.argv
180+ with open(os.environ['COMPILER_CALLS'], 'a') as stream:
181+ stream.write(json.dumps(sys.argv[3:]) + '\\ n')
182+ command = shlex.split(os.environ['CALLER_CXX']) + sys.argv[3:]
183+ os.execvp(command[0], command)
184+ ''' ))
185+ make = root / 'selected-make'
186+ make .symlink_to (shutil .which ('gmake' ) or shutil .which ('make' ))
187+ python = root / 'selected python'
188+ python .symlink_to (sys .executable )
189+ args .compiler = shlex .join ([str (python ), str (compiler ), '--selected-toolchain' , 'argument with spaces' ])
190+ args .cc = str (root / 'selected cc' )
191+ args .make = str (make )
192+ probe = root / 'compile probe.py'
193+ probe .write_text (textwrap .dedent ('''\
194+ import os, shlex, subprocess
195+ assert os.environ['CC'] == os.environ['EXPECTED_CC'], os.environ.get('CC')
196+ assert os.environ['MAKE'] == os.environ['EXPECTED_MAKE'], os.environ.get('MAKE')
197+ subprocess.run(shlex.split(os.environ['CXX']) + ['-std=c++20', '-fsyntax-only',
198+ '-x', 'c++', '-I' + os.environ['PUBLIC_HEADERS'], '-'],
199+ input='#include <httpserver.hpp>\\ nhttpserver::server::server_options options;\\ n',
200+ text=True, check=True)
201+ ''' ))
202+ for mode in ('no' , 'yes' ):
203+ makefile = root / 'fixture' / ('tls-' + mode ) / 'build/Makefile'
204+ with makefile .open ('a' ) as stream :
205+ stream .write ('check: selected-tools\n selected-tools:\n \t ' + shlex .join ([sys .executable , str (probe )]) + '\n ' )
206+ env .update (CXX = 'unselected-cxx' , CC = 'unselected-cc' , MAKE = 'unselected-make' ,
207+ EXPECTED_CC = args .cc , EXPECTED_MAKE = args .make , PUBLIC_HEADERS = str (ROOT / 'src' ),
208+ CALLER_CXX = os .environ .get ('CXX' , 'c++' ), COMPILER_CALLS = str (root / 'compiler-calls.jsonl' ))
209+ if retain_tools :
210+ compiler_alias = root / 'compiler alias.py'
211+ compiler_alias .symlink_to (compiler )
212+ args .compiler = shlex .join ([str (python ), str (compiler_alias ), '--selected-toolchain' , 'argument with spaces' ])
213+ cc = Path (args .cc )
214+ cc .symlink_to (shutil .which ('cc' ))
215+ args .cc = shlex .join ([str (cc ), '-DSELECTED_CC=1' ])
216+ env ['EXPECTED_CC' ] = args .cc
217+ # Exercise a spaced make executable at the retention boundary. GNU
218+ # make's recursive MAKE variable needs a shell-quoted command.
219+ make_alias = root / 'selected make'
220+ make_alias .symlink_to (make )
221+ args .make = str (make_alias )
222+ for mode in ('no' , 'yes' ):
223+ makefile = root / 'fixture' / ('tls-' + mode ) / 'build/Makefile'
224+ with makefile .open ('a' ) as stream :
225+ stream .write ('MAKE := ' + shlex .quote (str (make_alias )) + '\n ' )
226+ env ['EXPECTED_MAKE' ] = shlex .quote (str (make_alias ))
227+ args .node = sys .executable
228+ args .archive .touch ()
229+ header = args .provider_prefix / 'include/openssl/opensslv.h'
230+ header .parent .mkdir (parents = True )
231+ header .write_text ('selected provider header' )
232+ gate .artifact = lambda path : dict (path = str (path ), bytes = path .stat ().st_size ,
233+ sha256 = hashlib .sha256 (path .read_bytes ()).hexdigest ())
174234 with patch .object (entrypoint .platform , 'system' , return_value = 'FreeBSD' ), \
175235 patch .object (entrypoint , 'validate_request' ), \
176236 patch .object (entrypoint .identity , 'source_identity' , return_value = {}), \
177237 patch .object (entrypoint , 'selected_archive' , return_value = release ), \
178238 patch .object (entrypoint , 'gate_runner' , return_value = gate ), \
179239 patch .object (entrypoint .checks , 'validate_package' , return_value = counts ), \
180- patch .object (entrypoint .checks , 'automake_result' , side_effect = [counts , self .NativeChecksComplete ()]), \
240+ patch .object (entrypoint .checks , 'automake_result' , side_effect = [counts , counts if retain_tools else self .NativeChecksComplete ()]), \
241+ patch .object (entrypoint , 'validate_origin' ), \
181242 patch .dict (os .environ , env , clear = True ):
182243 try :
183244 entrypoint .platform_proof (args )
184245 finally :
185246 self .assertEqual (os .environ , env , 'archive proof must keep its selected SDK environment local' )
186247
248+ @unittest .skipIf (os .name == 'nt' , 'selected tools fixture requires POSIX scripts and GNU make' )
249+ def test_explicit_tools_reach_real_make_and_python_compiler_checks (self ):
250+ with tempfile .TemporaryDirectory () as directory :
251+ root = Path (directory )
252+ with self .assertRaises (self .NativeChecksComplete ):
253+ self .native_checks (root , selected_tools = True )
254+ calls = [json .loads (line ) for line in (root / 'compiler-calls.jsonl' ).read_text ().splitlines ()]
255+ self .assertEqual (len (calls ), 2 , 'both TLS modes must compile through the selected CXX command' )
256+ self .assertTrue (all ('-fsyntax-only' in call for call in calls ))
257+
258+ @unittest .skipIf (os .name == 'nt' , 'selected make fixture requires POSIX scripts and GNU make' )
259+ def test_native_fixture_uses_callers_selected_make_for_test_enumeration (self ):
260+ with tempfile .TemporaryDirectory () as directory :
261+ root = Path (directory )
262+ make = root / 'caller-make'
263+ real_make = os .environ .get ('MAKE' ) or shutil .which ('gmake' ) or shutil .which ('make' )
264+ calls = root / 'make-calls.jsonl'
265+ make .write_text ('#!' + sys .executable + '\n import json, os, sys\n ' +
266+ 'with open(' + repr (str (calls )) + ', "a") as stream: stream.write(json.dumps(sys.argv[1:]) + "\\ n")\n ' +
267+ 'os.execv(' + repr (real_make ) + ', [' + repr (real_make ) + '] + sys.argv[1:])\n ' )
268+ make .chmod (0o755 )
269+ with patch .dict (os .environ , MAKE = str (make )), self .assertRaises (self .NativeChecksComplete ):
270+ self .native_checks (root )
271+ self .assertTrue (calls .is_file (), 'native fixture bypassed the caller-selected make' )
272+ invocations = [json .loads (line ) for line in calls .read_text ().splitlines ()]
273+ self .assertEqual (sum ('--eval' in argv for argv in invocations ), 2 )
274+
275+ @unittest .skipIf (os .name == 'nt' , 'selected tool retention fixture uses POSIX executable aliases' )
276+ def test_archive_retains_compound_compilers_and_spaced_make_exact_bytes (self ):
277+ with tempfile .TemporaryDirectory () as directory :
278+ root = Path (directory )
279+ self .native_checks (root , selected_tools = True , retain_tools = True )
280+ sys .path .insert (0 , str (ROOT / 'scripts/lib' ))
281+ from release_platform import Catalog
282+ catalog = Catalog (root / 'output/bundle/FreeBSD' )
283+ for path in (root / 'selected python' , root / 'compiler alias.py' , root / 'selected compiler.py' ,
284+ root / 'selected cc' , root / 'selected make' ):
285+ with self .subTest (path = path ):
286+ self .assertEqual (catalog .path (str (path )).read_bytes (), path .read_bytes ())
287+ self .assertEqual (catalog .path (str (path .resolve ())).read_bytes (), path .read_bytes ())
288+
187289 @unittest .skipIf (os .name == 'nt' , 'FreeBSD audit fixtures require POSIX scripts and GNU make' )
188290 def test_explicit_sdk_reaches_both_later_make_checks (self ):
189291 for inherited in (None , '/unselected/sdk' ):
@@ -356,6 +458,7 @@ def test_workflows_pass_selected_prefix_even_when_sudo_cleans_environment(self):
356458 self .assertEqual (argv [argv .index ('--toolchain-prefix' ) + 1 ] if '--toolchain-prefix' in argv else None , str (llvm ))
357459 self .assertEqual (argv [argv .index ('--compiler' ) + 1 ], selected ['V3_CXX' ])
358460 self .assertEqual (argv [argv .index ('--cc' ) + 1 ], selected ['V3_CC' ])
461+ self .assertEqual (argv [argv .index ('--make' ) + 1 ] if '--make' in argv else None , 'gmake' )
359462 if privileged :
360463 self .assertIsNone (captured ['toolchain_environment' ])
361464
@@ -386,6 +489,30 @@ def test_missing_cancellation_support_stops_before_exporting_toolchain(self):
386489
387490@unittest .skipIf (os .name == 'nt' , 'competing compiler and symlink fixtures use POSIX paths' )
388491class InstalledPackageFixtureEnvironment (unittest .TestCase ):
492+ def test_quoted_fixture_preserves_callers_compound_compiler (self ):
493+ with tempfile .TemporaryDirectory () as directory :
494+ root = Path (directory )
495+ compiler = root / 'caller compiler.py'
496+ compiler .write_text (textwrap .dedent ('''\
497+ import json, os, shlex, sys
498+ assert sys.argv[1:3] == ['--caller-selection', 'argument with spaces'], sys.argv
499+ with open(os.environ['CALLER_INVOCATIONS'], 'a') as stream:
500+ stream.write(json.dumps(sys.argv[3:]) + '\\ n')
501+ command = shlex.split(os.environ['CALLER_CXX']) + sys.argv[3:]
502+ os.execvp(command[0], command)
503+ ''' ))
504+ env = dict (os .environ , CXX = shlex .join ([sys .executable , str (compiler ), '--caller-selection' , 'argument with spaces' ]),
505+ CALLER_CXX = os .environ .get ('CXX' ) or shlex .join ([shutil .which ('c++' )]),
506+ CALLER_INVOCATIONS = str (root / 'calls.jsonl' ))
507+ result = subprocess .run ([sys .executable , str (Path (__file__ ).resolve ()),
508+ 'InstalledPackageFixtureEnvironment.test_fixture_entrypoint_honors_quoted_cxx_command' ],
509+ env = env , capture_output = True , text = True )
510+ self .assertEqual (result .returncode , 0 , result .stdout + result .stderr )
511+ self .assertTrue ((root / 'calls.jsonl' ).exists (), 'quoted fixture bypassed the caller-selected compiler' )
512+ calls = [json .loads (line ) for line in (root / 'calls.jsonl' ).read_text ().splitlines ()]
513+ self .assertTrue (any ('-fsyntax-only' in call for call in calls ))
514+ self .assertTrue (any ('-E' in call for call in calls ))
515+
389516 def test_fixture_entrypoint_honors_quoted_cxx_command (self ):
390517 with tempfile .TemporaryDirectory () as directory :
391518 root = Path (directory )
@@ -395,14 +522,18 @@ def test_fixture_entrypoint_honors_quoted_cxx_command(self):
395522 sys.exit(72)
396523with open(os.environ['INVOCATIONS'], 'a') as stream:
397524 stream.write(json.dumps(sys.argv[2:]) + '\\ n')
398- os.execv(os.environ['REAL_CXX'], [os.environ['REAL_CXX']] + sys.argv[2:])
525+ import shlex
526+ command = shlex.split(os.environ['REAL_CXX']) + sys.argv[2:]
527+ os.execvp(command[0], command)
399528""" )
400529 competing = root / 'c++'
401530 competing .write_text ('#!/bin/sh\n printf "unselected compiler invoked\\ n" >&2\n exit 71\n ' )
402531 competing .chmod (0o755 )
532+ real_cxx = shlex .split (os .environ .get ('CXX' , 'c++' ))
533+ real_cxx [0 ] = shutil .which (real_cxx [0 ]) or real_cxx [0 ]
403534 env = dict (os .environ , CXX = shlex .join ([sys .executable , str (selected ), '--selected-toolchain' ]),
404535 PATH = str (root ) + os .pathsep + os .environ ['PATH' ],
405- INVOCATIONS = str (root / 'calls.jsonl' ), REAL_CXX = shutil . which ( 'c++' ))
536+ INVOCATIONS = str (root / 'calls.jsonl' ), REAL_CXX = shlex . join ( real_cxx ))
406537 result = subprocess .run ([sys .executable , str (ROOT / 'scripts/test-v3-installed-package.py' ),
407538 'AuditTest.test_native_public_api_is_available_through_umbrella' ,
408539 'AuditTest.test_current_umbrella_has_no_backend_types' ],
0 commit comments