@@ -101,6 +101,130 @@ def test_compiler_wrapper_keeps_headers_and_runtime_through_clean_environment(se
101101
102102
103103class SelectedPlatformEnvironment (unittest .TestCase ):
104+ class NativeChecksComplete (Exception ):
105+ pass
106+
107+ def native_checks (self , root , inherited_prefix = None , selected = True , dependency_selected = True ):
108+ """Run the archive orchestrator, real child/make and the production audit.
109+
110+ Only archive identity, the expensive package producer and native inspectors
111+ are fixtures; this does not establish native FreeBSD ABI compatibility.
112+ """
113+ spec = importlib .util .spec_from_file_location ('platform_native_checks' , ROOT / 'scripts/run-v3-release-ci.py' )
114+ entrypoint = importlib .util .module_from_spec (spec )
115+ spec .loader .exec_module (entrypoint )
116+ sdk = root / 'selected sdk'
117+ (sdk / 'lib' ).mkdir (parents = True )
118+ (sdk / 'lib/libc++.so.1.0' ).touch ()
119+ source = root / 'source'
120+ (source / 'scripts' ).mkdir (parents = True )
121+ producer = source / 'scripts/check-v3-installed-consumer.py'
122+ producer .write_text (textwrap .dedent ('''\
123+ import json, os, pathlib, shutil, sys
124+ output = pathlib.Path(sys.argv[sys.argv.index('--output') + 1])
125+ shutil.copytree(pathlib.Path(__file__).parents[2] / 'fixture', output)
126+ (output / 'child.json').write_text(json.dumps({'argv': sys.argv[1:],
127+ 'prefix': os.environ.get('V3_PACKAGE_TOOLCHAIN')}))
128+ # Like the package producer, selecting a runtime in this child cannot
129+ # update the archive orchestrator's later make environment.
130+ if '--toolchain-prefix' in sys.argv:
131+ os.environ['V3_PACKAGE_TOOLCHAIN'] = sys.argv[sys.argv.index('--toolchain-prefix') + 1]
132+ ''' ))
133+ recipe = (ROOT / 'Makefile.am' ).read_text ().split (
134+ '\n check-v3-native-linkage:\n ' , 1 )[1 ].split ('\n \n ' , 1 )[0 ]
135+ for mode in ('no' , 'yes' ):
136+ build = root / 'fixture' / ('tls-' + mode ) / 'build'
137+ (build / 'src/.libs' ).mkdir (parents = True )
138+ (build / 'test' ).mkdir ()
139+ (build / 'src/.libs/libhttpserver_v3core.a' ).touch ()
140+ (build / 'test/native_linkage' ).write_text ('#!/bin/sh\n exit 0\n ' )
141+ (build / 'test/native_linkage' ).chmod (0o755 )
142+ (build / 'test/Makefile' ).write_text ('TEST_LOGS = native_linkage.log\n native_linkage:;@:\n ' )
143+ (build / 'src/Makefile' ).write_text (
144+ 'libhttpserver.la:\n \t libtool --tag=CXX --mode=link -o libhttpserver.la archive\n ' )
145+ (build / 'Makefile' ).write_text (
146+ 'all:;@:\n check: check-v3-native-linkage\n '
147+ 'top_srcdir := ' + str (ROOT ) + '\n top_builddir := .\n abs_top_builddir := $(CURDIR)\n '
148+ 'enable_v3_tls := ' + mode + '\n check-v3-native-linkage:\n ' + recipe + '\n ' )
149+ tools = root / 'tools'
150+ tools .mkdir ()
151+ # ldd uses whitespace-delimited paths; the alias resolves to the quoted SDK.
152+ (root / 'sdk' ).symlink_to (sdk , target_is_directory = True )
153+ dependency = root / ('sdk/lib/libc++.so.1.0' if dependency_selected else 'other/libc++.so.1.0' )
154+ for name , output in (('uname' , 'FreeBSD' ), ('nm' , '' ), ('ldd' ,
155+ 'libc++.so.1 => ' + str (dependency ) + ' (0x1)\n libcxxrt.so.1 => /lib/libcxxrt.so.1 (0x2)' )):
156+ tool = tools / name
157+ tool .write_text ("#!/bin/sh\n cat <<'OUTPUT'\n " + output + '\n OUTPUT\n ' )
158+ tool .chmod (0o755 )
159+ if not dependency_selected :
160+ dependency .parent .mkdir ()
161+ dependency .touch ()
162+ selection = root / 'selected.json'
163+ selection .write_text (json .dumps (dict (commit = 'a' * 40 , version = '3.0.0' , sha256 = 'b' * 64 ,
164+ source = {}, files_sha256 = {})))
165+ args = argparse .Namespace (selection = selection , output = root / 'output' , archive = root / 'source.tar.gz' ,
166+ provider_prefix = root / 'provider' , compiler = str (sdk / 'bin/clang++' ), cc = '/llvm/bin/clang' ,
167+ make = shutil .which ('gmake' ) or shutil .which ('make' ), jobs = 2 , client_python = sys .executable ,
168+ node = 'node' , pkg_config_dir = [], toolchain_prefix = sdk if selected else None )
169+ release = dict (source_root = str (source ), source = {}, files_sha256 = {})
170+ counts = dict (tests = 1 , checks = 1 )
171+ gate = argparse .Namespace (expected_identity = lambda * args : {}, read_json = None )
172+ env = dict (os .environ , PATH = str (tools ) + os .pathsep + os .environ ['PATH' ])
173+ env .pop ('V3_PACKAGE_TOOLCHAIN' , None )
174+ if inherited_prefix is not None :
175+ env ['V3_PACKAGE_TOOLCHAIN' ] = str (inherited_prefix )
176+ with patch .object (entrypoint .platform , 'system' , return_value = 'FreeBSD' ), \
177+ patch .object (entrypoint , 'validate_request' ), \
178+ patch .object (entrypoint .identity , 'source_identity' , return_value = {}), \
179+ patch .object (entrypoint , 'selected_archive' , return_value = release ), \
180+ patch .object (entrypoint , 'gate_runner' , return_value = gate ), \
181+ patch .object (entrypoint .checks , 'validate_package' , return_value = counts ), \
182+ patch .object (entrypoint .checks , 'automake_result' , side_effect = [counts , self .NativeChecksComplete ()]), \
183+ patch .dict (os .environ , env , clear = True ):
184+ try :
185+ entrypoint .platform_proof (args )
186+ finally :
187+ self .assertEqual (os .environ , env , 'archive proof must keep its selected SDK environment local' )
188+
189+ @unittest .skipIf (os .name == 'nt' , 'FreeBSD audit fixtures require POSIX scripts and GNU make' )
190+ def test_explicit_sdk_reaches_both_later_make_checks (self ):
191+ for inherited in (None , '/unselected/sdk' ):
192+ with self .subTest (inherited = inherited ), tempfile .TemporaryDirectory () as directory :
193+ root = Path (directory )
194+ with self .assertRaises (self .NativeChecksComplete ):
195+ self .native_checks (root , inherited_prefix = inherited )
196+ commands = json .loads ((root / 'output/commands.json' ).read_text ())
197+ checks = [command for command in commands if 'check' in command ['argv' ]]
198+ self .assertEqual (len (checks ), 2 )
199+ for command in checks :
200+ self .assertEqual (command ['exit' ], 0 )
201+ self .assertEqual (command ['environment' ]['V3_PACKAGE_TOOLCHAIN' ], str (root / 'selected sdk' ))
202+ self .assertIn ('A2 PASS' , Path (command ['log' ]).read_text ())
203+ child = json .loads ((root / 'output/matrix/child.json' ).read_text ())
204+ self .assertEqual (child ['prefix' ], str (root / 'selected sdk' ))
205+ self .assertEqual (child ['argv' ][child ['argv' ].index ('--toolchain-prefix' ) + 1 ], str (root / 'selected sdk' ))
206+ self .assertEqual (child ['argv' ][child ['argv' ].index ('--compiler' ) + 1 ], str (root / 'selected sdk/bin/clang++' ))
207+ self .assertEqual (child ['argv' ][child ['argv' ].index ('--cc' ) + 1 ], '/llvm/bin/clang' )
208+
209+ @unittest .skipIf (os .name == 'nt' , 'FreeBSD audit fixtures require POSIX scripts and GNU make' )
210+ def test_archive_checks_preserve_inherited_sdk_without_cli_override (self ):
211+ with tempfile .TemporaryDirectory () as directory :
212+ root = Path (directory )
213+ with self .assertRaises (self .NativeChecksComplete ):
214+ self .native_checks (root , inherited_prefix = root / 'selected sdk' , selected = False )
215+
216+ @unittest .skipIf (os .name == 'nt' , 'FreeBSD audit fixtures require POSIX scripts and GNU make' )
217+ def test_archive_checks_reject_runtime_outside_explicit_sdk (self ):
218+ with tempfile .TemporaryDirectory () as directory :
219+ with self .assertRaisesRegex (RuntimeError , 'unselected FreeBSD C[+][+] runtime' ):
220+ self .native_checks (Path (directory ), dependency_selected = False )
221+
222+ @unittest .skipIf (os .name == 'nt' , 'FreeBSD audit fixtures require POSIX scripts and GNU make' )
223+ def test_archive_checks_reject_missing_sdk_identity (self ):
224+ with tempfile .TemporaryDirectory () as directory :
225+ with self .assertRaisesRegex (RuntimeError , 'selected FreeBSD C[+][+] runtime prefix missing' ):
226+ self .native_checks (Path (directory ), selected = False )
227+
104228 def test_native_platform_matrix_receives_matching_provider_cli (self ):
105229 spec = importlib .util .spec_from_file_location ('platform_ci_entrypoint' , ROOT / 'scripts/run-v3-release-ci.py' )
106230 entrypoint = importlib .util .module_from_spec (spec )
0 commit comments