Skip to content

Commit 944fa19

Browse files
committed
FEATURE-ci-process-cleanup: Correct padded ps fixture and retain native parser evidence
Trim valid trailing state-column padding before appending fixture modifiers, and assert owned-zombie EPERM reach independently for each receipt case. The real padded fixture reproduced its malformed-snapshot failure twice before this fix and passes afterward. Retain bounded native snapshot failures and structured results from the complete actual CommandsTest class. Require the three named Linux-only Darwin skips exactly. Local 22 command contracts and 45 installed-consumer contracts pass; helper and Linux affinity fixture bytes are unchanged. Attempt 1 native proof remains incomplete pending fresh execution and raw-row diagnosis.
1 parent 2848ebf commit 944fa19

2 files changed

Lines changed: 60 additions & 7 deletions

File tree

‎.github/workflows/v3-cleanup-diagnostic.yml‎

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,60 @@ jobs:
1818
shell: bash
1919
run: |
2020
mkdir -p "$RUNNER_TEMP/v3-cleanup-contracts"
21-
python3 - <<'PYTHON'
22-
import hashlib, json, os, subprocess, sys
21+
cat > "$RUNNER_TEMP/v3-cleanup-command-contracts.py" <<'PYTHON'
22+
import hashlib, importlib.util, json, os, subprocess, sys, unittest
2323
from pathlib import Path
2424
source = Path.cwd()
25+
output = Path(os.environ['RUNNER_TEMP'], 'v3-cleanup-contracts')
2526
paths = ('scripts/lib/package_commands.py', 'scripts/test-v3-installed-consumer.py')
2627
receipt = dict(source_sha=subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip(),
2728
python=sys.version, hashes={p: hashlib.sha256((source / p).read_bytes()).hexdigest() for p in paths})
28-
Path(os.environ['RUNNER_TEMP'], 'v3-cleanup-contracts/source.json').write_text(json.dumps(receipt, indent=2) + '\n')
29+
(output / 'source.json').write_text(json.dumps(receipt, indent=2) + '\n')
30+
spec = importlib.util.spec_from_file_location('native_contracts', source / paths[1])
31+
contracts = importlib.util.module_from_spec(spec)
32+
spec.loader.exec_module(contracts)
33+
snapshot = contracts.commands._process_snapshot
34+
failures = []
35+
current_test = None
36+
def observed_snapshot(deadline):
37+
try:
38+
return snapshot(deadline)
39+
except Exception as error:
40+
item = dict(test=current_test, error=str(error))
41+
frame = error.__traceback__
42+
while frame:
43+
if frame.tb_frame.f_code.co_name == '_process_snapshot':
44+
values = frame.tb_frame.f_locals
45+
item.update(line=values.get('line'), fields=values.get('fields'))
46+
buffer = values.get('output')
47+
if buffer is not None:
48+
data = buffer.getvalue()
49+
path = output / ('snapshot-%04d.log' % len(failures))
50+
path.write_bytes(data)
51+
item.update(log=path.name, bytes=len(data), sha256=hashlib.sha256(data).hexdigest())
52+
frame = frame.tb_next
53+
failures.append(item)
54+
(output / 'snapshot-failures.json').write_text(json.dumps(failures, indent=2) + '\n')
55+
raise
56+
# Retain parser evidence without replacing discovery, signals, or errors.
57+
contracts.commands._process_snapshot = observed_snapshot
58+
class Result(unittest.TextTestResult):
59+
def startTest(self, test):
60+
global current_test
61+
current_test = test.id()
62+
super().startTest(test)
63+
suite = unittest.defaultTestLoader.loadTestsFromTestCase(contracts.CommandsTest)
64+
expected = [test.id() for test in suite]
65+
result = unittest.TextTestRunner(verbosity=2, resultclass=Result).run(suite)
66+
report = dict(expected=expected, tests_run=result.testsRun, successful=result.wasSuccessful(),
67+
failures=[test.id() for test, _ in result.failures],
68+
errors=[test.id() for test, _ in result.errors],
69+
skipped=[dict(test=test.id(), reason=reason) for test, reason in result.skipped])
70+
(output / 'result.json').write_text(json.dumps(report, indent=2) + '\n')
71+
raise SystemExit(not result.wasSuccessful())
2972
PYTHON
3073
set +e
31-
python3 scripts/test-v3-installed-consumer.py CommandsTest 2>&1 | tee "$RUNNER_TEMP/v3-cleanup-contracts/commands-test.log"
74+
python3 "$RUNNER_TEMP/v3-cleanup-command-contracts.py" 2>&1 | tee "$RUNNER_TEMP/v3-cleanup-contracts/commands-test.log"
3275
status=${PIPESTATUS[0]}
3376
printf '%s\n' "$status" > "$RUNNER_TEMP/v3-cleanup-contracts/exit-status"
3477
exit "$status"
@@ -166,7 +209,16 @@ jobs:
166209
assert receipt['hashes'] == binding['hashes'] == hashes
167210
assert receipt['python'] == binding['python']
168211
assert (contracts / 'exit-status').read_text().strip() == '0'
169-
assert '\nOK\n' in (contracts / 'commands-test.log').read_text()
212+
result = json.loads((contracts / 'result.json').read_text())
213+
assert result['successful'] and not result['failures'] and not result['errors']
214+
assert result['tests_run'] == len(result['expected']) == 22
215+
assert len(set(result['expected'])) == 22
216+
expected_skips = {
217+
'native_contracts.CommandsTest.test_cleanup_discovery_failure_retains_original_error',
218+
'native_contracts.CommandsTest.test_new_live_member_delays_retirement_and_zombies_do_not',
219+
'native_contracts.CommandsTest.test_discovery_consumes_cleanup_deadline_without_restarting_wait'}
220+
assert len(result['skipped']) == 3
221+
assert {item['test'] for item in result['skipped']} == expected_skips
170222
assert receipt['uname'][0] == 'Darwin'
171223
assert len(receipt['commands']) == 3
172224
assert all(item['result'] == 'success' for item in receipt['commands'])

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ def signal_group(pid, sig):
305305
for status in (0, 7):
306306
with self.subTest(fallback=fallback, status=status):
307307
scans.clear()
308+
previous_denials = len(denied)
308309
waitid = getattr(os, 'waitid', None) if fallback == 'native' else None
309310
platform = 'darwin' if fallback == 'ps' else sys.platform
310311
with patch.object(commands, '_process_snapshot', observe), patch.object(os, 'killpg', signal_group):
@@ -318,8 +319,8 @@ def signal_group(pid, sig):
318319
self.assertEqual(len(data), record['bytes'])
319320
self.assertEqual(hashlib.sha256(data).hexdigest(), record['sha256'])
320321
self.assertNotIn('error', record)
322+
self.assertGreater(len(denied), previous_denials, 'each case must reach owned-zombie EPERM')
321323
self.assertGreaterEqual(len(scans), 3, 'permission proof must retain stable retirement scans')
322-
self.assertEqual(6, len(denied))
323324

324325
def zombie_tree(self):
325326
tree = commands._CommandTree(self.command('pass'), self.root, os.environ)
@@ -513,7 +514,7 @@ def launch(argv, *args, **kwargs):
513514
if argv[0] in ('/bin/ps', '/usr/bin/ps'):
514515
code = ("import subprocess; "
515516
f"data=subprocess.check_output({argv!r}).decode('ascii'); "
516-
"print('\\n'.join(line + '>ACJ' for line in data.splitlines()))")
517+
"print('\\n'.join(line.rstrip() + '>ACJ' for line in data.splitlines()))")
517518
argv = self.command(code)
518519
return popen(argv, *args, **kwargs)
519520
with patch.object(sys, 'platform', 'darwin'), patch.object(os, 'waitid', None, create=True), patch.object(subprocess, 'Popen', launch):

0 commit comments

Comments
 (0)