Skip to content

Commit cb23206

Browse files
committed
FEATURE-ci-windows-consumer-fixtures: Correct native Windows consumer fixtures
Match the selected absolute Bash and cygpath at the inspection producer subprocess boundary while preserving real compiler and failure evidence. Select configure receipts by invocation semantics and use canonical temporary roots for matrix path fixtures. Verified all 70 affected consumer tests, the direct package/archive failure-receipt recovery test, Python compilation and diff checks. Native Windows post-fix CI evidence remains incomplete and CI/v3 PR owned.
1 parent 6b2f596 commit cb23206

2 files changed

Lines changed: 185 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: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# FEATURE-ci-windows-consumer-fixtures
2+
3+
Status: In Progress (implementation complete; caller validation/finalization 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+
- [ ] 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.

0 commit comments

Comments
 (0)