Skip to content

Commit 4003ed9

Browse files
committed
Merge FEATURE-ci-darwin-python-host-fixtures: Correct Python fixture host assumptions
2 parents cf15f14 + 3c64381 commit 4003ed9

6 files changed

Lines changed: 162 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: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# FEATURE-ci-darwin-python-host-fixtures
2+
3+
Status: Complete (local validation/finalization; native CI pending)
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+
- [x] 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 primary `.debug/ci-6b2-darwin-python-assertions-wrzyeazd/implementation/`
49+
(copied byte-for-byte from task build receipts before worktree cleanup). Local success is
50+
not native Darwin evidence, and does not close its unknown adapter failure.
51+
52+
The adapter's Darwin `private or foreign dependency` failure remains UNKNOWN and
53+
its native gate INCOMPLETE. Its fixture root was already canonical; the native
54+
receipt did not retain the unexpected dependency input. The assertion still
55+
requires `ValueError` matching `static archive`; real compiler/archive/link-map
56+
negative and positive controls remain unchanged. Diagnostic context is evidence
57+
only and does not establish a fix. Next required CI on the approved correction
58+
head owns native input capture. No standalone Darwin adapter replay is authorized.
59+
60+
Darwin, Windows and FreeBSD checks remain CI/v3-PR owned and unexecuted locally.
61+
Remote 6b2 CI is preserved; no push, cancellation or publication is part of this
62+
implementation. The prior epoll batch exhausted its 64-execution budget, remains
63+
CLOSED with unknown causality, and receives no additional execution or probes.
64+
The older closed investigations remain closed. No production library, runner,
65+
workflow, build configuration, identity or deadline policy is changed.
66+
67+
Groundwork validation `7f8572e3ad4da2c8fff59ca5b7f4dde3` sealed PASS for
68+
`ec18afdeb769d7b7860f9edfcf25aa0ab0d436eb` / tree
69+
`2a6e5d93cfe73ed121c94d9f8fc349cdf9131909`: one iteration, nine fresh
70+
independent approvals, zero fixes and zero unworked findings. Passing source-bound
71+
137-test evidence reused, no report created. Original failed native gates remain
72+
failed; adapter cause UNKNOWN, context-only prerequisite retained. Native post-fix
73+
platform checks remain incomplete and CI/v3 PR owned. Existing useful 6b2 jobs
74+
remain preserved; root owns publication timing.

‎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)