Skip to content

Commit cf15f14

Browse files
committed
Merge FEATURE-ci-windows-consumer-fixtures: Correct native fixture assumptions
2 parents 6b2f596 + beb81b9 commit cf15f14

2 files changed

Lines changed: 193 additions & 30 deletions

File tree

‎scripts/test-v3-installed-consumer.py‎

Lines changed: 102 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,19 @@ def setUp(self):
227227
self.diagnostic = 'inspector diagnostic\n'
228228
self.inspector_exit = 0
229229
self.popen = subprocess.Popen
230+
self.bash = Path(shutil.which('bash')).resolve()
231+
self.converted_configure = '/fixture source/configure'
230232
environment = patch.dict(os.environ)
231233
environment.start()
232234
self.addCleanup(environment.stop)
233235

234236
def create_process(self, argv, **options):
235-
if argv[0] == 'bash':
236-
prefix = self.output / 'tls-no/prefix/lib/pkgconfig'
237-
code = f'from pathlib import Path; p=Path({str(prefix)!r}); p.mkdir(parents=True); (p/"libhttpserver.pc").write_text("fixture metadata")'
237+
if argv[0] in ('bash', str(self.bash)):
238+
if argv[1:3] == ['-c', 'cygpath -u "$1"']:
239+
code = f'print({self.converted_configure!r})'
240+
else:
241+
prefix = self.output / 'tls-no/prefix/lib/pkgconfig'
242+
code = f'from pathlib import Path; p=Path({str(prefix)!r}); p.mkdir(parents=True); (p/"libhttpserver.pc").write_text("fixture metadata")'
238243
elif argv[0] == 'fixture-make':
239244
code = ''
240245
elif argv[0] == 'pkg-config':
@@ -322,28 +327,91 @@ def write(path, *args, **kwargs):
322327
self.assertEqual(self.runner.audit.COMMANDS[-1]['stdout'], self.dynamic)
323328

324329

330+
class InstalledInspectionWindowsShellTest(InstalledInspectionFailureTest):
331+
"""Selected Windows shell branch with real host compilation and recording.
332+
333+
The shell/converter outputs are controlled; this is not a Windows receipt.
334+
"""
335+
def setUp(self):
336+
super().setUp()
337+
selected = self.root / 'selected tools'
338+
selected.mkdir()
339+
self.bash = selected / ('bash.exe' if os.name == 'nt' else 'bash')
340+
self.bash.write_text('controlled producer shell')
341+
self.bash.chmod(0o755)
342+
self.runner.platform = SimpleNamespace(system=lambda: 'Windows')
343+
os.environ['PATH'] = str(selected) + os.pathsep + os.environ['PATH']
344+
345+
def fail_lane(self):
346+
error = super().fail_lane()
347+
records = json.loads((self.output / 'tls-no/commands.json').read_text())
348+
self.assertEqual(records[0]['argv'], [str(self.bash), '-c', 'cygpath -u "$1"',
349+
'configure-path', (self.source / 'configure').as_posix()])
350+
self.assertEqual(records[0]['exit'], 0)
351+
self.assertEqual(records[1]['argv'][0:2], [str(self.bash), self.converted_configure])
352+
self.assertEqual(records[1]['exit'], 0)
353+
return error
354+
355+
325356
class MatrixTest(unittest.TestCase):
326357
def setUp(self):
327358
self.inventory = matrix.load()
359+
self.temp = tempfile.TemporaryDirectory()
360+
self.addCleanup(self.temp.cleanup)
361+
self.root = Path(self.temp.name).resolve()
328362

329363
def test_optimized_package_configure_selects_release_flags_without_changing_default(self):
330364
spec = importlib.util.spec_from_file_location('installed_runner', Path(__file__).with_name('check-v3-installed-consumer.py'))
331365
runner = importlib.util.module_from_spec(spec); spec.loader.exec_module(runner)
332366
with tempfile.TemporaryDirectory() as temp:
333-
root = Path(temp)
334-
(root / 'configure').write_text('printf "%s\\n" "$CXXFLAGS" > selected-flags\nexit 23\n')
335-
for optimized, flags in ((False, '-O0 -g'), (True, '-O3 -g')):
336-
with self.subTest(optimized=optimized):
337-
output = root / str(optimized); output.mkdir()
338-
args = SimpleNamespace(compiler='selected-cxx', cc='selected-cc', make='make',
339-
prerequisite_prefix=[], pkg_config_dir=[], provider_prefix=root,
340-
optimized=optimized)
341-
with self.assertRaises(RuntimeError):
342-
runner.package_lane(args, root, output, 'no', self.inventory)
343-
self.assertEqual((output / 'tls-no/build/selected-flags').read_text().strip(), flags)
344-
records = json.loads((output / 'tls-no/commands.json').read_text())
345-
self.assertEqual(records[0]['exit'], 23)
346-
self.assertEqual(records[0]['environment']['CXXFLAGS'], flags)
367+
self.exercise_configure_flags(runner, Path(temp).resolve())
368+
369+
def test_configure_flags_receipt_follows_successful_path_conversion(self):
370+
spec = importlib.util.spec_from_file_location('installed_flags_runner',
371+
Path(__file__).with_name('check-v3-installed-consumer.py'))
372+
runner = importlib.util.module_from_spec(spec)
373+
spec.loader.exec_module(runner)
374+
runner.platform = SimpleNamespace(system=lambda: 'Windows')
375+
popen = subprocess.Popen
376+
377+
def create_process(argv, **options):
378+
if argv[1:3] == ['-c', 'cygpath -u "$1"']:
379+
configure = Path(os.path.relpath(argv[-1], options['cwd'])).as_posix()
380+
return popen([sys.executable, '-c', f'print({configure!r})'], **options)
381+
return popen(argv, **options)
382+
383+
with tempfile.TemporaryDirectory() as temp, \
384+
patch.object(subprocess, 'Popen', side_effect=create_process):
385+
self.exercise_configure_flags(runner, Path(temp).resolve())
386+
387+
def exercise_configure_flags(self, runner, root):
388+
(root / 'configure').write_text('printf "%s\\n" "$CXXFLAGS" > selected-flags\nexit 23\n')
389+
for optimized, flags in ((False, '-O0 -g'), (True, '-O3 -g')):
390+
with self.subTest(optimized=optimized):
391+
output = root / str(optimized); output.mkdir()
392+
args = SimpleNamespace(compiler='selected-cxx', cc='selected-cc', make='make',
393+
prerequisite_prefix=[], pkg_config_dir=[], provider_prefix=root,
394+
optimized=optimized)
395+
with self.assertRaises(RuntimeError):
396+
runner.package_lane(args, root, output, 'no', self.inventory)
397+
self.assertEqual((output / 'tls-no/build/selected-flags').read_text().strip(), flags)
398+
records = json.loads((output / 'tls-no/commands.json').read_text())
399+
configure = [record for record in records if record['argv'][2:] == [
400+
'--enable-v3-tls=no', '--disable-examples',
401+
'--prefix=' + (output / 'tls-no/prefix').as_posix()]]
402+
self.assertEqual(len(configure), 1)
403+
self.assertEqual(configure[0]['exit'], 23)
404+
self.assertEqual(configure[0]['environment']['CXXFLAGS'], flags)
405+
self.assertEqual(records[-1], configure[0])
406+
if runner.platform.system() == 'Windows':
407+
self.assertEqual(len(records), 2)
408+
conversion = records[0]
409+
self.assertEqual(conversion['argv'], [configure[0]['argv'][0], '-c',
410+
'cygpath -u "$1"', 'configure-path', (root / 'configure').as_posix()])
411+
self.assertEqual(conversion['exit'], 0)
412+
self.assertEqual(Path(conversion['log']).read_text().strip(), configure[0]['argv'][1])
413+
else:
414+
self.assertEqual(records, configure)
347415

348416
def test_complete_matrix_and_identical_cases(self):
349417
cells = matrix.cells(self.inventory, 'Linux')
@@ -408,21 +476,24 @@ def test_empty_skipped_failed_suite_is_rejected(self):
408476
matrix.suite_result(output, status)
409477

410478
def test_shared_suite_requires_installed_library_and_static_forbids_it(self):
411-
graph = {'nodes': [{'id': '/prefix/lib/libhttpserver.so'}, {'id': '/consumer'}], 'edges': []}
412-
matrix.check_linkage(graph, 'shared', Path('/prefix'))
479+
prefix = self.root / 'prefix'
480+
consumer = {'id': str(self.root / 'consumer')}
481+
graph = {'nodes': [{'id': str(prefix / 'lib/libhttpserver.so')}, consumer], 'edges': []}
482+
matrix.check_linkage(graph, 'shared', prefix)
413483
with self.assertRaises(ValueError):
414-
matrix.check_linkage(graph, 'shared', Path('/elsewhere'))
484+
matrix.check_linkage(graph, 'shared', self.root / 'elsewhere')
415485
with self.assertRaises(ValueError):
416-
matrix.check_linkage(graph, 'static', Path('/prefix'))
417-
matrix.check_linkage({'nodes': [{'id': '/consumer'}], 'edges': []}, 'static', Path('/prefix'))
486+
matrix.check_linkage(graph, 'static', prefix)
487+
matrix.check_linkage({'nodes': [consumer], 'edges': []}, 'static', prefix)
418488

419489
def test_public_header_contamination_is_rejected_in_private_probes(self):
420-
matrix.check_dependencies(['/prefix/include/httpserver.hpp', '/source/src/httpserver/detail/io.hpp'],
421-
Path('/prefix'), Path('/source'), Path('/build'), private=True)
490+
prefix, source, build = (self.root / name for name in ('prefix', 'source', 'build'))
491+
matrix.check_dependencies([str(prefix / 'include/httpserver.hpp'), str(source / 'src/httpserver/detail/io.hpp')],
492+
prefix, source, build, private=True)
422493
for private in (False, True):
423494
with self.assertRaises(ValueError):
424-
matrix.check_dependencies(['/source/src/httpserver/server/server.hpp'],
425-
Path('/prefix'), Path('/source'), Path('/build'), private)
495+
matrix.check_dependencies([str(source / 'src/httpserver/server/server.hpp')],
496+
prefix, source, build, private)
426497

427498
def test_missing_configuration_case_or_different_outcome_is_rejected(self):
428499
for tls in ('yes', 'no'):
@@ -448,11 +519,12 @@ def test_credentials_backed_supported_configuration_expectations(self):
448519
with self.assertRaises(ValueError): matrix.supported_configuration_results(bad, tls)
449520

450521
def test_private_external_probe_can_use_the_named_host_helper_only(self):
451-
matrix.check_dependencies(['/source/examples/host_poll.hpp'], Path('/prefix'),
452-
Path('/source'), Path('/build'), private=True)
522+
prefix, source, build = (self.root / name for name in ('prefix', 'source', 'build'))
523+
matrix.check_dependencies([str(source / 'examples/host_poll.hpp')], prefix,
524+
source, build, private=True)
453525
with self.assertRaises(ValueError):
454-
matrix.check_dependencies(['/source/examples/other.hpp'], Path('/prefix'),
455-
Path('/source'), Path('/build'), private=True)
526+
matrix.check_dependencies([str(source / 'examples/other.hpp')], prefix,
527+
source, build, private=True)
456528

457529
def test_parity_backend_replays_preserve_unique_release_observations(self):
458530
case = dict(id='h1.1-tls:routing.tseq:hello', case='routing.tseq:hello', mode='h1.1-tls')
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# FEATURE-ci-windows-consumer-fixtures
2+
3+
Status: Complete (local validation/finalization; native CI pending).
4+
Base: local v3 `6b2f596e88796f1a033a770d26b07aaa7049a8ff`.
5+
Plan: primary `.groundwork-plans/FEATURE-ci-windows-consumer-fixtures-plan.md`.
6+
Journal: primary `.debug/ci-6b2-windows-consumer-fixtures-9mpis316/journal.md`.
7+
8+
Correct the three accepted native Windows fixture assumptions in
9+
`scripts/test-v3-installed-consumer.py`: selected absolute Bash and cygpath at
10+
the inspection producer subprocess boundary, semantic configure command receipt
11+
selection, and consistent canonical native path identities in matrix fixtures.
12+
Preserve real native compilation, strict inspection failure persistence, and
13+
all original linkage/header contamination negative controls.
14+
15+
- [x] Read accepted plan, owning sources, native failure log and project instructions; isolate from exact local v3.
16+
- [x] Record RED for absolute shell/conversion and configure receipt ordering through the real runner/Commands boundary.
17+
- [x] Correct the three fixture assumptions without changing production code or adding skips.
18+
- [x] Verify the affected module, direct receipt recovery, Python compilation and diff checks.
19+
- [x] Caller Groundwork validation/finalization.
20+
- [ ] Native Windows post-fix package/archive checks: CI/v3 PR owned, incomplete; not a local acceptance gate.
21+
22+
Native RED: package run 40072250923, Windows job 120808025501,
23+
artifact 12551364760 SHA256
24+
`9fcc5f1f8f02b91bb9d01268b880cc12ff4a0036957aebe5e15bcc4ddb27caec`.
25+
The retained full log ran 65 tests with two failures, seven errors and 29
26+
existing Windows POSIX skips. No post-fix native receipt is claimed. Closed
27+
epoll uncertainty and all other closed assessments remain outside this task.
28+
29+
Task-local setup notes: Python unittest fixtures run directly with
30+
`python3 scripts/test-v3-installed-consumer.py`; real compiler/Bash/Automake
31+
tools are already installed locally, with no package installation needed.
32+
No documented Python lint command exists. Existing primary checkout instructions
33+
are ignored files and must be read there. Git metadata requires the approved
34+
Git command escalation; `.worktrees/` is already ignored. No prior executor
35+
memory file was available in either configured agent-memory location.
36+
37+
Implementation evidence (2026-10-24):
38+
39+
The inspection producer now intercepts the actual selected absolute Bash and
40+
its cygpath conversion at subprocess creation. The original four inspection
41+
tests remain unchanged and additionally run through a selected-shell subclass
42+
that replaces only the runner's platform binding. The inspector retains its
43+
separate FreeBSD binding and the compiler sees the actual native host. Real
44+
consumer/object/link-map production, full inspector stdout/stderr, exact exits,
45+
artifact SHA256, stale-command clearing and receipt write-error assertions all
46+
remain in force. The controlled shell/converter outputs supply no native
47+
Windows tool or loader evidence.
48+
49+
Both default and optimized configure cases select the actual configure receipt
50+
by its TLS/examples/prefix arguments, require exactly exit 23 and the expected
51+
CXXFLAGS, and check conversion order, arguments, output and exit 0 when present.
52+
A new alternate-runner test executes the real host Bash configure script after
53+
controlled conversion, reproducing the prior first-receipt mistake locally.
54+
The three path fixtures derive graph IDs, dependency payloads and all roots from
55+
the same resolved temporary directory. Wrong-prefix, static/shared, source
56+
public-header and unapproved host-helper failures remain strict.
57+
58+
RED: the new selected-shell replays escaped the old producer boundary (four
59+
errors); both configure flag subcases read conversion exit 0 instead of 23.
60+
Native path-only RED remains the retained original Windows log, not a simulated
61+
local receipt. GREEN: all 70 affected module tests pass locally in 22.223s (65
62+
existing plus five new), and the exact direct package/archive receipt-recovery
63+
test passes in 1.967s. Python compilation and diff whitespace checks pass.
64+
AST comparison confirms all four original inspection test bodies and every
65+
existing skip decorator are unchanged. The native baseline's 29 Windows skips
66+
remain documented; no new skips were introduced. Production runner, matrix
67+
validator, compiler integration and command recorder bytes match the base.
68+
69+
Logs and hashes: primary journal directory contains `local-red.log`,
70+
`local-green.log`, `local-module.log`, `local-recovery.log` and
71+
`local-source-evidence.json`. No unrelated failure, retry, broad baseline,
72+
native probe, CI control or publication occurred. Parent reported the completed
73+
archive Windows job 120808170892 has the same 65-test symptom pattern; no
74+
duplicate artifact was downloaded or investigated. Native post-fix Windows
75+
checks remain incomplete and owned by CI/v3 PR; caller owns validation,
76+
finalization and publication decisions.
77+
78+
Reusable executor notes: fixture regressions load the real runner by
79+
`importlib.util`, substitute subprocess producer outputs with real Python child
80+
processes, and delegate compiler subprocesses unchanged. Replace a runner or
81+
auditor module's `platform` binding for branch coverage rather than mutating
82+
the shared platform module. Configure receipts can include successful path
83+
conversion before the expected failure; select them by command semantics.
84+
85+
Groundwork validation `03e31718ba1957895fa2a136563888e2` sealed PASS for
86+
`cb23206ed9e08fa9136336bc060fdcbcfe34951c` / tree
87+
`fc9edb4f1d1978b7ac0b20c308fe57ba0715024f`: one iteration, nine fresh
88+
independent approvals, zero fixes and zero unworked findings. Input-matched
89+
passing checks reused; no report created. Native Windows post-fix checks remain
90+
incomplete and CI/v3 PR owned. Existing 6b2 CI jobs are preserved; caller owns
91+
publication timing. Closed epoll uncertainty remains outside this task.

0 commit comments

Comments
 (0)