Skip to content

Commit a5a7235

Browse files
committed
Merge FEATURE-ci-darwin-canonical-fixtures: preserve native fixture and link-map identities
2 parents 1fa030d + d1e4fe7 commit a5a7235

8 files changed

Lines changed: 169 additions & 23 deletions

‎scripts/lib/release_package_provenance.py‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import re
66
import shlex
77
import os
8+
import platform
89
from native_benchmark import require, verify_artifact, parse_json
910
from package_commands import dependencies
1011
from package_matrix import check_dependencies, check_linkage
@@ -51,10 +52,18 @@ def bound(path, digest=None):
5152
row['argv'][row['argv'].index('-o') + 1] == executable]
5253
require(len(selected) == 1, 'missing actual link command')
5354
link = selected[0]['argv']
54-
require('-Wl,-Map,' + str(linkmap) in link, 'wrong link map command')
55+
map_option = '-Wl,-map,' if platform.system() == 'Darwin' else '-Wl,-Map,'
56+
require(map_option + str(linkmap) in link, 'wrong link map command')
5557
if linkage == 'static':
5658
archive = str(prefix / 'lib/libhttpserver.a')
57-
contributors = {str(Path(name).resolve()) for name in re.findall(r'([^\s()]+libhttpserver\.a)\([^()\n]+\)', linkmap.read_text())}
59+
text = linkmap.read_text()
60+
if platform.system() == 'Darwin':
61+
# ld64 lists contributing archive members in its object-file table.
62+
table = text.partition('# Object files:\n')[2].split('\n# ', 1)[0]
63+
names = re.findall(r'^\[\s*\d+\]\s+(.+libhttpserver\.a)(?:\[\d+\])?\([^()\n]+\)\s*$', table, re.M)
64+
else:
65+
names = re.findall(r'([^\s()]+libhttpserver\.a)\([^()\n]+\)', text)
66+
contributors = {str(Path(name).resolve()) for name in names}
5867
require(archive in link and contributors == {archive}, 'different or noncontributing installed static archive')
5968
compile_rows = [row for row in commands if '-c' in row['argv'] and '-o' in row['argv'] and
6069
row['argv'][row['argv'].index('-o') + 1] in link and

‎scripts/test-v3-native-benchmark.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def command(argv, *_):
122122

123123
class ReceiptTest(unittest.TestCase):
124124
def receipt(self, root, tls='no'):
125-
root = Path(root)
125+
root = Path(root).resolve()
126126
consumer = root / 'consumer'; consumer.mkdir()
127127
files = {'consumer.cpp': 'consumer', 'host_poll.hpp': 'poll', 'client.py': 'client',
128128
'fixtures/root.pem': 'certificate', 'shared': 'shared binary', 'static': 'static binary',

‎scripts/test-v3-packaged-adapters.py‎

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import importlib.util
66
import io
77
import json
8+
import os
9+
import shlex
10+
import subprocess
811
from pathlib import Path
912
import sys
1013
import tarfile
@@ -26,7 +29,7 @@ def setUp(self):
2629
self.assertIsNotNone(adapter, 'packaged adapter implementation missing')
2730
self.temp = tempfile.TemporaryDirectory()
2831
self.addCleanup(self.temp.cleanup)
29-
self.root = Path(self.temp.name)
32+
self.root = Path(self.temp.name).resolve()
3033
self.source = self.root / 'source'
3134
self.source.mkdir()
3235
(self.source / 'unit.cpp').write_text('public source\n')
@@ -99,17 +102,22 @@ def provenance(self):
99102
unit = directory / 'consumer.cpp'; unit.write_text('public source\n')
100103
executable = directory / 'static'; executable.write_text('consumer')
101104
depfile = directory / 'static-0.d'; depfile.write_text(f'static.o: {unit} {header}\n')
102-
linkmap = directory / 'static.map'; linkmap.write_text(str(library) + '(public.o)\nLOAD ' + str(library) + '\n')
105+
linkmap = directory / 'static.map'
106+
linkmap.write_text(('# Object files:\n[ 1] ' + str(library) + '[1](public.o)\n# Sections:\n')
107+
if sys.platform == 'darwin' else str(library) + '(public.o)\nLOAD ' + str(library) + '\n')
108+
compiler = os.environ.get('CXX', 'c++')
109+
compiler_argv = shlex.split(compiler)
110+
map_option = '-Wl,-map,' if sys.platform == 'darwin' else '-Wl,-Map,'
103111
obj = directory / 'static-0.o'; obj.write_text('object')
104112
log = self.root / 'command.log'; log.write_text('compile/link ok')
105-
commands = [dict(argv=['c++', '-std=c++20', '-MMD', '-MF', str(depfile), '-c', str(unit), '-o', str(obj)],
113+
commands = [dict(argv=compiler_argv + ['-std=c++20', '-MMD', '-MF', str(depfile), '-c', str(unit), '-o', str(obj)],
106114
exit=0, log=str(log), sha256=bench.artifact(log)['sha256'], bytes=log.stat().st_size),
107-
dict(argv=['c++', str(obj), str(library), '-Wl,-Map,' + str(linkmap), '-o', str(executable)],
115+
dict(argv=compiler_argv + [str(obj), str(library), map_option + str(linkmap), '-o', str(executable)],
108116
exit=0, log=str(log), sha256=bench.artifact(log)['sha256'], bytes=log.stat().st_size)]
109117
compiled = dict(private_test_headers=False, sources={str(unit): bench.artifact(unit)['sha256']},
110118
dependencies=[str(unit), str(header)], dependency_sha256={str(p): bench.artifact(p)['sha256'] for p in (unit, header)},
111119
artifact_sha256=bench.artifact(executable)['sha256'], link_map=str(linkmap))
112-
identity = dict(source=str(self.source), build=str(self.root / 'build'), prefix=str(prefix), compiler='c++',
120+
identity = dict(source=str(self.source), build=str(self.root / 'build'), prefix=str(prefix), compiler=compiler,
113121
archive={'members': {'test/installed/consumer.cpp': bench.artifact(unit)['sha256']}},
114122
package={str(p): bench.artifact(p)['sha256'] for p in (header, library)})
115123
artifacts = {str(p): bench.artifact(p) for p in directory.iterdir() if p.is_file()}
@@ -154,6 +162,29 @@ def test_empty_linkmap_and_foreign_dependency_are_rejected(self):
154162
artifacts[compiled['link_map']] = bench.artifact(compiled['link_map'])
155163
with self.assertRaises(ValueError): adapter.validate_compile(compiled, 'static', identity, artifacts, commands)
156164

165+
def test_darwin_link_map_requires_actual_selected_archive_contribution(self):
166+
from unittest.mock import patch
167+
compiled, identity, artifacts, commands = self.provenance()
168+
linkmap = Path(compiled['link_map'])
169+
archive = str(Path(identity['prefix']) / 'lib/libhttpserver.a')
170+
foreign = str(self.root / 'foreign/libhttpserver.a')
171+
commands[1]['argv'][-3] = '-Wl,-map,' + str(linkmap)
172+
with patch('platform.system', return_value='Darwin'):
173+
for member in (archive + '(public.o)', archive + '[12](public.o)'):
174+
linkmap.write_text('# Path: consumer\n# Object files:\n[ 0] linker synthesized\n[ 1] ' + member + '\n# Sections:\n# Symbols:\n')
175+
artifacts[str(linkmap)] = bench.artifact(linkmap)
176+
adapter.validate_compile(compiled, 'static', identity, artifacts, commands)
177+
for body in ('', 'LOAD ' + archive, archive + '(public.o)',
178+
'# Object files:\n[ 1] ' + foreign + '[1](public.o)\n# Sections:\n',
179+
'# Object files:\n[ 1] consumer.o\n# Sections:\n' + archive + '(unused.o)',
180+
'# Object files:\n[ 1] ' + archive + '[1](public.o)\n[ 2] ' + foreign + '[1](other.o)\n# Sections:\n'):
181+
linkmap.write_text(body); artifacts[str(linkmap)] = bench.artifact(linkmap)
182+
with self.subTest(body=body), self.assertRaisesRegex(ValueError, 'static archive'):
183+
adapter.validate_compile(compiled, 'static', identity, artifacts, commands)
184+
commands[1]['argv'][-3] = '-Wl,-Map,' + str(linkmap)
185+
with self.assertRaisesRegex(ValueError, 'link map command'):
186+
adapter.validate_compile(compiled, 'static', identity, artifacts, commands)
187+
157188
def test_artifact_catalog_rejects_pruning_and_command_tampering(self):
158189
from release_package_provenance import catalog, validate_catalog
159190
output = self.root / 'evidence'; output.mkdir()
@@ -363,6 +394,23 @@ def test_each_public_family_requires_its_compile_and_dependency_records(self):
363394
bad = copy.deepcopy(child); bad.pop('independent_client')
364395
with self.assertRaises(ValueError): provenance.validate_child_provenance(bad, identity, {}, [], family)
365396

397+
def test_static_archive_fixture_uses_selected_quoted_compiler(self):
398+
compiler = shlex.split(os.environ.get('CXX', 'c++'))
399+
wrapper = self.root / 'selected compiler.py'
400+
marker = self.root / 'compiler-commands.jsonl'
401+
wrapper.write_text('import json, os, sys\n'
402+
'with open(' + repr(str(marker)) + ', "a") as stream: stream.write(json.dumps(sys.argv[1:]) + "\\n")\n'
403+
'argv = ' + repr(compiler) + '\nos.execvp(argv[0], argv + sys.argv[1:])\n')
404+
result = subprocess.run([sys.executable, str(Path(__file__).resolve()),
405+
'AdapterContracts.test_unused_selected_static_archive_cannot_supply_link_identity'],
406+
env=dict(os.environ, CXX=shlex.join([sys.executable, str(wrapper)])),
407+
capture_output=True, text=True, timeout=30)
408+
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
409+
self.assertTrue(marker.is_file(), 'archive fixture bypassed the selected compiler')
410+
commands = [json.loads(line) for line in marker.read_text().splitlines()]
411+
self.assertEqual(sum('-c' in argv for argv in commands), 3)
412+
self.assertEqual(sum('-c' not in argv for argv in commands), 2)
413+
366414
def test_unused_selected_static_archive_cannot_supply_link_identity(self):
367415
import subprocess
368416
compiled, identity, artifacts, commands = self.provenance()
@@ -377,16 +425,17 @@ def test_unused_selected_static_archive_cannot_supply_link_identity(self):
377425
source = folder / 'implementation.cpp'; source.write_text('int implementation() { return %d; }\n' % value)
378426
obj = folder / 'implementation.o'; archive = folder / 'libhttpserver.a'
379427
archive.unlink(missing_ok=True)
380-
subprocess.run(['c++', '-c', str(source), '-o', str(obj)], check=True, capture_output=True)
428+
subprocess.run(shlex.split(identity['compiler']) + ['-c', str(source), '-o', str(obj)], check=True, capture_output=True)
381429
subprocess.run(['ar', 'rcs', str(archive), str(obj)], check=True, capture_output=True)
382430
libraries.append(str(archive))
383-
commands[0]['argv'].insert(2, '-I' + str(Path(identity['prefix']) / 'include'))
384-
commands[1]['argv'][2:3] = libraries
385-
for command in commands: subprocess.run(command['argv'], check=True, capture_output=True)
431+
from package_commands import Commands
432+
from package_integration import compile_probe
433+
command = Commands(directory)
434+
compiled = compile_probe(identity['compiler'], [unit], ['-I' + str(Path(identity['prefix']) / 'include')],
435+
libraries, directory / 'static', command, os.environ, self.source,
436+
Path(identity['build']), Path(identity['prefix']), False)
437+
commands = command.records
386438
self.assertEqual(subprocess.run([str(directory / 'static')]).returncode, 2)
387-
compiled['sources'] = {str(unit): bench.artifact(unit)['sha256']}
388-
compiled['dependency_sha256'] = {str(p): bench.artifact(p)['sha256'] for p in (unit, header)}
389-
compiled['artifact_sha256'] = bench.artifact(directory / 'static')['sha256']
390439
identity['archive']['members']['test/installed/consumer.cpp'] = compiled['sources'][str(unit)]
391440
identity['package'] = {p: bench.artifact(p)['sha256'] for p in identity['package']}
392441
artifacts = {str(p): bench.artifact(p) for p in directory.iterdir() if p.is_file()}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# FEATURE-ci-darwin-canonical-fixtures
2+
3+
Status: Complete (local validation and finalization; native CI pending)
4+
Base: local v3 `1fa030dd528e67218d7c0a6957371ba246679e96`
5+
6+
Repair the exact known Darwin Python fixture root aliases, executable discovery,
7+
selected compiler and native linker-map setup failures. Keep original child names,
8+
symlink/tampering controls and production provenance policies intact.
9+
10+
- [x] Reproduce known identity failures with real symlinked TMPDIR and actual validators.
11+
- [x] Resolve affected fixture roots before constructing identities/catalogs.
12+
- [x] Discover the success executable and use the selected compiler/native map format.
13+
- [x] Verify affected suites, syntax and diff; preserve negative controls.
14+
- [x] Caller validation and finalization.
15+
16+
Evidence: primary `.debug/ci-darwin-canonical-fixtures-2shorq1l/journal.md`.
17+
Native post-fix Darwin, Windows and FreeBSD execution remains CI/v3 PR owned.
18+
No C++ investigations, runtime changes, full suite, SDK/Bash rebuilds or CI controls.
19+
20+
Caller-approved owner expansion: `scripts/lib/release_package_provenance.py`
21+
`validate_compile` now recognizes the native Darwin linker option and contributing
22+
object-file table entries. GNU handling and exact installed archive/security bindings
23+
are preserved; actual real compilation uses unchanged `package_integration.compile_probe`.
24+
25+
Verification: all six affected suites pass locally (155 tests including six real
26+
symlinked TMPDIR regressions), plus final focused reruns after test refactoring.
27+
Python byte-compilation and `git diff --check` pass. No standalone Python linter is
28+
documented; the repository cpplint command applies to C++, which is unchanged.
29+
RED/GREEN logs cover each known alias symptom, selected executable/compiler and native
30+
map grammar. No authentic retained Darwin map exists in the selected evidence tree;
31+
small ld64 format tests prove parser behavior only. Real local GNU map/archive controls
32+
prove foreign-first rejection and selected-only acceptance. Native post-fix platform
33+
checks remain incomplete and CI/v3 PR owned. Older C++ uncertainties remain closed.
34+
35+
Groundwork validation `13002c772f224b35f4b6506f5647d66b` passed in one
36+
iteration, nine independent profiles approved, zero fixes and zero unworked
37+
findings; exact implementation HEAD `9ed911145c4bbb0eda9fdd1e5988c71373cb66c3`.
38+
Overall CI remains unsuccessful pending fresh native execution.

‎test/integ/release_artifact_test.py‎

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def setUpClass(cls):
2626
def setUp(self):
2727
self.assertTrue(HELPER.is_file(), 'verified release-artifact identity helper missing')
2828
self.tmp = tempfile.TemporaryDirectory(); self.addCleanup(self.tmp.cleanup)
29-
self.directory = Path(self.tmp.name)
29+
self.directory = Path(self.tmp.name).resolve()
3030
self.source = self.directory / 'source'; self.source.mkdir()
3131
(self.source / 'configure.ac').write_text('packaged source')
3232
self.archive = self.directory / 'release.tar.gz'; self.archive.write_bytes(b'archive identity')
@@ -111,4 +111,44 @@ def test_distribution_adapters_accept_verified_identity_instead_of_git(self):
111111
self.assertIn('release_artifact.py', (ROOT / 'scripts' / name).read_text())
112112

113113

114+
class CanonicalFixtureContracts(unittest.TestCase):
115+
def check_fixture(self, script, *cases):
116+
with tempfile.TemporaryDirectory() as directory:
117+
root = Path(directory).resolve()
118+
target = root / 'native-tmp'; target.mkdir()
119+
alias = root / 'tmp'; alias.symlink_to(target, target_is_directory=True)
120+
self.assertNotEqual(alias, alias.resolve())
121+
result = subprocess.run([sys.executable, str(ROOT / script), *cases],
122+
env=dict(os.environ, TMPDIR=str(alias), TEMP=str(alias), TMP=str(alias)),
123+
capture_output=True, text=True, timeout=60)
124+
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
125+
126+
def test_archive_parity_through_symlinked_tmpdir(self):
127+
self.check_fixture('test/integ/release_artifact_test.py', 'ArchiveContracts')
128+
129+
def test_release_execution_through_symlinked_tmpdir(self):
130+
self.check_fixture('test/integ/release_gate_runner_test.py',
131+
'ReleaseContracts.test_packaged_families_execute_both_tls_lanes_and_independent_full_validation',
132+
'ReleaseContracts.test_packaged_family_cannot_pass_when_independent_validation_rejects',
133+
'ReleaseContracts.test_failed_stage_retains_its_original_command_log',
134+
'ReleaseContracts.test_package_receipt_survives_later_native_build_and_check',
135+
'ReleaseContracts.test_cxx_gate_bounds_each_native_test_before_waiting_for_aggregate')
136+
137+
def test_benchmark_provenance_through_symlinked_tmpdir(self):
138+
self.check_fixture('scripts/test-v3-native-benchmark.py', 'ReceiptTest')
139+
140+
def test_public_compile_and_receipts_through_symlinked_tmpdir(self):
141+
self.check_fixture('scripts/test-v3-packaged-adapters.py',
142+
'AdapterContracts.test_real_public_compile_artifacts_are_rechecked',
143+
'AdapterContracts.test_unused_selected_static_archive_cannot_supply_link_identity',
144+
'AdapterContracts.test_h1_requires_exact_versions_and_two_persistent_pipeline_responses',
145+
'AdapterContracts.test_unsafe_duplicate_and_symlink_archive_members_are_rejected')
146+
147+
def test_selected_windows_shell_through_symlinked_tmpdir(self):
148+
self.check_fixture('test/integ/release_ci_test.py', 'RequestShellContracts')
149+
150+
def test_platform_origin_and_tool_aliases_through_symlinked_tmpdir(self):
151+
self.check_fixture('test/integ/release_platform_bundle_test.py', 'PlatformBundles')
152+
153+
114154
if __name__ == '__main__': unittest.main()

‎test/integ/release_ci_test.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def setUp(self):
2121
import subprocess
2222
self.temp = tempfile.TemporaryDirectory()
2323
self.addCleanup(self.temp.cleanup)
24-
self.root = Path(self.temp.name)
24+
self.root = Path(self.temp.name).resolve()
2525
self.selected = self.root / 'MSYS tools'
2626
self.selected.mkdir()
2727
self.bash = self.selected / ('bash.exe' if os.name == 'nt' else 'bash')

0 commit comments

Comments
 (0)