Skip to content

Commit ec18afd

Browse files
committed
FEATURE-ci-darwin-python-host-fixtures: Correct Python host fixture assumptions
Canonicalize valid corpus fixtures, resolve caller-selected make, assert retained argument aliases, and control deadline tool availability. Preserve strict production policies and add adapter assertion context for the unresolved Darwin dependency failure. All 137 affected Python module and replay tests pass locally; Python compilation and diff checks pass. Native platform checks remain CI owned.
1 parent cf15f14 commit ec18afd

6 files changed

Lines changed: 152 additions & 21 deletions

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,11 @@ def test_unused_selected_static_archive_cannot_supply_link_identity(self):
439439
identity['archive']['members']['test/installed/consumer.cpp'] = compiled['sources'][str(unit)]
440440
identity['package'] = {p: bench.artifact(p)['sha256'] for p in identity['package']}
441441
artifacts = {str(p): bench.artifact(p) for p in directory.iterdir() if p.is_file()}
442-
with self.assertRaisesRegex(ValueError, 'static archive'):
442+
context = json.dumps(dict(compiled=compiled, identity=identity, artifacts=artifacts,
443+
commands=commands, libraries=[bench.artifact(path) for path in libraries],
444+
depfiles={str(path): path.read_text() for path in directory.glob('*.d')}),
445+
indent=2, sort_keys=True)
446+
with self.assertRaisesRegex(ValueError, 'static archive', msg=context):
443447
adapter.validate_compile(compiled, 'static', identity, artifacts, commands)
444448
# The same real consumer succeeds when the selected archive contributes.
445449
commands[1]['argv'].remove(libraries[0])

‎scripts/test-v3-platform-ci.py‎

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -258,19 +258,29 @@ def test_explicit_tools_reach_real_make_and_python_compiler_checks(self):
258258
@unittest.skipIf(os.name == 'nt', 'selected make fixture requires POSIX scripts and GNU make')
259259
def test_native_fixture_uses_callers_selected_make_for_test_enumeration(self):
260260
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 + '\nimport 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)
261+
tools = Path(directory)
262+
requested = os.environ.get('MAKE') or shutil.which('gmake') or shutil.which('make')
263+
selected = shutil.which(requested)
264+
self.assertIsNotNone(selected, 'caller-selected GNU make must be available')
265+
(tools / 'gmake').symlink_to(selected)
266+
for caller_make in (requested, 'gmake'):
267+
with self.subTest(make=caller_make), tempfile.TemporaryDirectory() as fixture, \
268+
patch.dict(os.environ, MAKE=caller_make,
269+
PATH=str(tools) + os.pathsep + os.environ['PATH']):
270+
root = Path(fixture)
271+
make = root / 'caller-make'
272+
real_make = shutil.which(os.environ['MAKE'])
273+
self.assertIsNotNone(real_make, 'caller-selected GNU make must be available')
274+
calls = root / 'make-calls.jsonl'
275+
make.write_text('#!' + sys.executable + '\nimport json, os, sys\n' +
276+
'with open(' + repr(str(calls)) + ', "a") as stream: stream.write(json.dumps(sys.argv[1:]) + "\\n")\n' +
277+
'os.execv(' + repr(real_make) + ', [' + repr(real_make) + '] + sys.argv[1:])\n')
278+
make.chmod(0o755)
279+
with patch.dict(os.environ, MAKE=str(make)), self.assertRaises(self.NativeChecksComplete):
280+
self.native_checks(root)
281+
self.assertTrue(calls.is_file(), 'native fixture bypassed the caller-selected make')
282+
invocations = [json.loads(line) for line in calls.read_text().splitlines()]
283+
self.assertEqual(sum('--eval' in argv for argv in invocations), 2)
274284

275285
@unittest.skipIf(os.name == 'nt', 'selected tool retention fixture uses POSIX executable aliases')
276286
def test_archive_retains_compound_compilers_and_spaced_make_exact_bytes(self):
@@ -280,7 +290,7 @@ def test_archive_retains_compound_compilers_and_spaced_make_exact_bytes(self):
280290
sys.path.insert(0, str(ROOT / 'scripts/lib'))
281291
from release_platform import Catalog
282292
catalog = Catalog(root / 'output/bundle/FreeBSD')
283-
for path in (root / 'selected python', root / 'compiler alias.py', root / 'selected compiler.py',
293+
for path in (root / 'selected python', root / 'compiler alias.py',
284294
root / 'selected cc', root / 'selected make'):
285295
with self.subTest(path=path):
286296
self.assertEqual(catalog.path(str(path)).read_bytes(), path.read_bytes())
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# FEATURE-ci-darwin-python-host-fixtures
2+
3+
Status: In Progress
4+
Base: v3 `cf15f14590672c8fb461147635da3f535238692f`
5+
Plan: `.groundwork-plans/FEATURE-ci-darwin-python-host-fixtures-plan.md`
6+
Journal: `.debug/ci-6b2-darwin-python-assertions-wrzyeazd/journal.md` in the primary checkout.
7+
8+
Correct the four established Python fixture assumptions from Darwin archive
9+
40072260645 / job 120808170836, preserving all production validation policies.
10+
The archive reported 232 passes, two skips and five Python module failures.
11+
12+
- [x] Canonicalize the valid restored-corpus fixture root; retain restored input,
13+
all seed bytes, seed 987, reproduction and deliberate symlink rejection checks.
14+
- [x] Resolve the caller-selected make executable before fixture `os.execv`;
15+
exercise inherited selection and a real PATH-selected `MAKE=gmake`, retaining
16+
both TLS modes and exact test-enumeration calls.
17+
- [x] Require retained compiler argument aliases and their resolved target bytes;
18+
exercise the real orchestrator, child, make and retention under symlinked TMPDIR.
19+
- [x] Replace both host `/usr/bin/timeout` assumptions with controlled fixture
20+
executables; retain the exact selected deadline command, 180-second argument,
21+
failed native case logs and frozen receipt checks. Explicitly verify missing
22+
deadline selection fails before any make command. These fixtures establish
23+
command construction and availability, not native GNU timeout timing.
24+
- [x] Enrich the unchanged strict static-archive assertion with compiled dependency
25+
hashes, source/package/archive identities, recorded commands, depfile contents
26+
and selected/foreign library artifact hashes for future required CI evidence.
27+
- [x] Complete bounded local fixture verification, Python compilation and diff checks.
28+
- [ ] Complete caller-owned validation/finalization.
29+
30+
## Verification and limits
31+
32+
RED reproduced corpus-symlink rejection, the unrequested raw compiler-target
33+
catalog entry, basename `os.execv` failure, and both missing-host-deadline failures.
34+
The corresponding focused GREEN checks pass. Final affected-module runs all
35+
passed locally, without skips:
36+
37+
| Command (python3) | Tests | Retained log |
38+
| --- | ---: | --- |
39+
| test/integ/protocol_fuzz_runner_test.py | 13 | final-fuzz.log |
40+
| scripts/test-v3-platform-ci.py | 20 | final-platform.log |
41+
| test/integ/release_gate_runner_test.py | 47 | final-release-gates.log |
42+
| scripts/test-v3-packaged-adapters.py | 42 | final-adapters.log |
43+
| test/integ/release_artifact_test.py | 15 | final-release-artifact.log |
44+
45+
All five changed Python modules passed `python3 -m py_compile` and
46+
`git diff --check` passed. No Python linter is documented; the project's cpplint
47+
command applies to C++ sources, which this task does not change. Logs are retained
48+
in the task worktree at `build/ci-darwin-python-host-fixtures/`. Local success is
49+
not native Darwin evidence, and does not close its unknown adapter failure.
50+
51+
The adapter's Darwin `private or foreign dependency` failure remains UNKNOWN and
52+
its native gate INCOMPLETE. Its fixture root was already canonical; the native
53+
receipt did not retain the unexpected dependency input. The assertion still
54+
requires `ValueError` matching `static archive`; real compiler/archive/link-map
55+
negative and positive controls remain unchanged. Diagnostic context is evidence
56+
only and does not establish a fix. Next required CI on the approved correction
57+
head owns native input capture. No standalone Darwin adapter replay is authorized.
58+
59+
Darwin, Windows and FreeBSD checks remain CI/v3-PR owned and unexecuted locally.
60+
Remote 6b2 CI is preserved; no push, cancellation or publication is part of this
61+
implementation. The prior epoll batch exhausted its 64-execution budget, remains
62+
CLOSED with unknown causality, and receives no additional execution or probes.
63+
The older closed investigations remain closed. No production library, runner,
64+
workflow, build configuration, identity or deadline policy is changed.

‎test/integ/protocol_fuzz_runner_test.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def test_seed_and_corpus_arguments_reject_invalid_inputs_before_compilation(self
208208
def test_restored_corpus_is_merged_without_following_links_or_overwriting_seeds(self):
209209
import os
210210
with tempfile.TemporaryDirectory() as directory:
211-
location = pathlib.Path(directory)
211+
location = pathlib.Path(directory).resolve()
212212
compiler = location / 'compiler'
213213
compiler.write_text("#!/usr/bin/env python3\nimport pathlib,sys\n"
214214
"if '--version' in sys.argv: print('recorder'); sys.exit()\n"

‎test/integ/release_artifact_test.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ def check_fixture(self, script, *cases):
123123
capture_output=True, text=True, timeout=60)
124124
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
125125

126+
def test_restored_corpus_through_symlinked_tmpdir(self):
127+
self.check_fixture('test/integ/protocol_fuzz_runner_test.py',
128+
'FuzzRunnerTest.test_restored_corpus_is_merged_without_following_links_or_overwriting_seeds')
129+
130+
def test_selected_compiler_retention_through_symlinked_tmpdir(self):
131+
self.check_fixture('scripts/test-v3-platform-ci.py',
132+
'SelectedPlatformEnvironment.test_archive_retains_compound_compilers_and_spaced_make_exact_bytes')
133+
126134
def test_archive_parity_through_symlinked_tmpdir(self):
127135
self.check_fixture('test/integ/release_artifact_test.py', 'ArchiveContracts')
128136

‎test/integ/release_gate_runner_test.py‎

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
from pathlib import Path
99
import re
10+
import shlex
1011
import shutil
1112
import subprocess
1213
import sys
@@ -339,6 +340,39 @@ def test_tag_check_rejects_receipt_from_another_gate(self):
339340
manifest['stages'][0]['receipt']['gate'] = 'parity'
340341
with self.assertRaises(ValueError): self.gate.check_tag_ready(manifest)
341342

343+
def test_native_fixtures_do_not_require_a_host_timeout_installation(self):
344+
from unittest.mock import patch
345+
import release_execution
346+
find_tool = shutil.which
347+
348+
def without_host_timeout(command, *args, **kwargs):
349+
if command in ('/usr/bin/timeout', 'timeout', 'gtimeout'):
350+
return None
351+
return find_tool(command, *args, **kwargs)
352+
353+
cases = ('test_cxx_gate_bounds_each_native_test_before_waiting_for_aggregate',
354+
'test_package_receipt_survives_later_native_build_and_check')
355+
for case in cases:
356+
with self.subTest(case=case):
357+
fixture = type(self)(case)
358+
fixture.setUp()
359+
self.addCleanup(fixture.doCleanups)
360+
with patch.object(release_execution.shutil, 'which', side_effect=without_host_timeout):
361+
getattr(fixture, case)()
362+
363+
def test_native_check_rejects_missing_deadline_before_make(self):
364+
from types import SimpleNamespace
365+
from release_execution import ReleaseAttempt
366+
args = SimpleNamespace(output=self.directory, compiler=sys.executable, cc=sys.executable,
367+
make=sys.executable, provider_prefix=self.directory, asan_options='detect_leaks=0', jobs=1,
368+
test_timeout_tool=str(self.directory / 'missing deadline'))
369+
attempt = ReleaseAttempt(args, self.gate, {})
370+
logs = self.directory / 'logs'; logs.mkdir()
371+
commands = self.gate.Commands(logs)
372+
with self.assertRaisesRegex(ValueError, 'required per-test deadline tool unavailable'):
373+
attempt.native_check(commands, self.directory, attempt.env, 10)
374+
self.assertEqual(commands.records, [])
375+
342376
def test_cxx_gate_bounds_each_native_test_before_waiting_for_aggregate(self):
343377
from types import SimpleNamespace
344378
from release_execution import ReleaseAttempt
@@ -348,16 +382,20 @@ def test_cxx_gate_bounds_each_native_test_before_waiting_for_aggregate(self):
348382
'Path("test/integ/native_case.log").write_text("actual failed case witness")\n'
349383
'Path("test/integ/native_case.trs").write_text(":test-result: FAIL\\n")\n'
350384
'raise SystemExit(2)\n'); make.chmod(0o755)
385+
# This fixture checks command arguments; its make never executes LOG_COMPILER.
386+
deadline = self.directory / 'selected deadline'
387+
deadline.write_text('#!/bin/sh\nexit 99\n'); deadline.chmod(0o755)
351388
args = SimpleNamespace(output=self.directory / 'attempt', compiler=sys.executable, cc=sys.executable,
352-
make=str(make), provider_prefix=self.directory, asan_options='detect_leaks=0', jobs=1, test_timeout_tool='/usr/bin/timeout')
389+
make=str(make), provider_prefix=self.directory, asan_options='detect_leaks=0', jobs=1, test_timeout_tool=str(deadline))
353390
attempt = ReleaseAttempt(args, self.gate, {'stages': []}); attempt.source = ROOT
354391
lane = args.output / 'package/evidence/tls-no'; build = lane / 'build'
355392
(build / 'src').mkdir(parents=True); (build / 'config.h').write_text('/* TLS off */')
356393
(build / 'src/Makefile').write_text('abs_top_srcdir = ' + str(ROOT) + '\n')
357394
logs = self.directory / 'build-logs'; logs.mkdir()
358395
command = self.gate.Commands(logs)
359-
with self.assertRaises(ValueError): attempt.cxx(command, 'no')
360-
self.assertIn('LOG_COMPILER=/usr/bin/timeout --kill-after=5s 180s', command.records[0]['argv'])
396+
with self.assertRaisesRegex(ValueError, 'command exited 2'): attempt.cxx(command, 'no')
397+
self.assertIn('LOG_COMPILER=' + shlex.join([str(deadline), '--kill-after=5s', '180s']),
398+
command.records[0]['argv'])
361399
self.assertEqual((logs / 'native-tests/integ/native_case.log').read_text(), 'actual failed case witness')
362400
self.assertEqual((logs / 'native-tests/integ/native_case.trs').read_text(), ':test-result: FAIL\n')
363401

@@ -740,10 +778,17 @@ def package(command):
740778
make.write_text('#!' + sys.executable + '\nfrom pathlib import Path\n'
741779
'Path("test/.deps/case.Po").write_text("compiled dependencies")\n'
742780
'Path("test/test-suite.log").write_text("# TOTAL: 1\\n# PASS: 1\\n# SKIP: 0\\n# XFAIL: 0\\n# FAIL: 0\\n# XPASS: 0\\n# ERROR: 0\\n")\n')
743-
make.chmod(0o755); args.make = str(make); args.test_timeout_tool = '/usr/bin/timeout'
781+
make.chmod(0o755); args.make = str(make)
782+
# Only availability and command construction are under test, not timeout timing.
783+
deadline = self.directory / 'selected deadline'
784+
deadline.write_text('#!/bin/sh\nexit 99\n'); deadline.chmod(0o755)
785+
args.test_timeout_tool = str(deadline)
744786
logs = output / 'later-native'; logs.mkdir()
745787
native, _ = attempt.normal_build('no')
746-
attempt.native_check(self.gate.Commands(logs), native, attempt.env, 10)
788+
command = self.gate.Commands(logs)
789+
attempt.native_check(command, native, attempt.env, 10)
790+
self.assertIn('LOG_COMPILER=' + shlex.join([str(deadline), '--kill-after=5s', '180s']),
791+
command.records[0]['argv'])
747792
checks = self.gate.load_runner('lib/release_checks.py')
748793
self.assertEqual(checks.automake_result(native, 'Linux')['tests'], 1)
749794
self.assertEqual((build / 'test/.deps/case.Po').read_text(), 'compiled dependencies')

0 commit comments

Comments
 (0)