105 lines
5.3 KiB
Python
105 lines
5.3 KiB
Python
"""Discover the first failure of the unchanged matched-root GPU test, then replay.
|
|
|
|
This script never runs the body/Jet test, changes the solver, or changes its
|
|
assertions. GPU admission remains the caller's responsibility.
|
|
"""
|
|
import argparse
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import traceback
|
|
|
|
|
|
def capture_case(exc):
|
|
"""Extract the original test frame; no inferred profile/block/configuration."""
|
|
frames = []
|
|
tb = exc.__traceback__
|
|
while tb is not None:
|
|
frames.append(tb.tb_frame)
|
|
tb = tb.tb_next
|
|
caller = next((f for f in frames if f.f_code.co_name == 'test_uniform_matched_root'), None)
|
|
if caller is None or not any(f.f_code.co_name == 'assert_array_equal' for f in frames):
|
|
return None
|
|
local = caller.f_locals
|
|
required = ('cfg', 'shifted', 'profile', 'clamp', 'damping', 'block', 'expected', 'actual')
|
|
if not all(key in local for key in required):
|
|
return None
|
|
cfg = local['cfg']
|
|
return dict(shifted=int(local['shifted']), profile=local['profile'],
|
|
clamp=int(local['clamp']), damping=float(local['damping']),
|
|
block=[int(v) for v in local['block']], steps=8,
|
|
config_repr=repr(cfg)), local['expected'].copy(), local['actual'].copy()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--checkout', type=Path, default=Path.cwd())
|
|
parser.add_argument('--output', type=Path, required=True, help='New directory; existing evidence is never overwritten')
|
|
args = parser.parse_args()
|
|
checkout, output = args.checkout.resolve(), args.output.resolve()
|
|
output.mkdir(parents=True, exist_ok=False)
|
|
import os
|
|
os.chdir(checkout)
|
|
sys.path.insert(0, str(checkout / 'src'))
|
|
import numpy as np
|
|
gate_path = checkout / 'tests/validation/multigrid/test_channel_root_gpu.py'
|
|
spec = importlib.util.spec_from_file_location('original_channel_gpu_gate', gate_path)
|
|
gate = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(gate)
|
|
revision = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True, text=True, check=True).stdout.strip()
|
|
manifest = dict(checkout=str(checkout), revision=revision,
|
|
gate_sha256=hashlib.sha256(gate_path.read_bytes()).hexdigest(),
|
|
test='ChannelRootGPU.test_uniform_matched_root')
|
|
(output / 'manifest.json').write_text(json.dumps(manifest, indent=2))
|
|
test = gate.ChannelRootGPU('test_uniform_matched_root')
|
|
try:
|
|
# The original method and np.testing.assert_array_equal are unchanged.
|
|
# Its existing loops naturally stop at the first failing assertion.
|
|
test.test_uniform_matched_root()
|
|
except AssertionError as exc:
|
|
original_trace = traceback.format_exc()
|
|
(output / 'original_failure.txt').write_text(original_trace)
|
|
print(original_trace, file=sys.stderr, flush=True)
|
|
case = capture_case(exc)
|
|
if case is None:
|
|
(output / 'status.json').write_text(json.dumps(dict(status='different_assertion_failure', replay_started=False), indent=2))
|
|
return 3
|
|
config, expected, actual = case
|
|
(output / 'failed_case.json').write_text(json.dumps(config, indent=2))
|
|
np.savez(output / 'original_failure_arrays.npz', expected=expected, actual=actual)
|
|
print('CAPTURED_CASE ' + json.dumps(config), flush=True)
|
|
except Exception:
|
|
(output / 'discovery_error.txt').write_text(traceback.format_exc())
|
|
(output / 'status.json').write_text(json.dumps(dict(status='discovery_error', replay_started=False), indent=2))
|
|
raise
|
|
else:
|
|
(output / 'status.json').write_text(json.dumps(dict(status='original_failure_not_reproduced', replay_started=False), indent=2))
|
|
print('The original matched-root test completed without reproducing the failure. No targeted replay was started.', flush=True)
|
|
return 2
|
|
|
|
# Discovery's context has closed. Run targeted diagnostics in a fresh process,
|
|
# serially, never concurrently with another compilation or GPU run here.
|
|
replay = Path(__file__).resolve().with_name('replay.py')
|
|
command = [sys.executable, str(replay), '--shifted', str(config['shifted']),
|
|
'--profile', config['profile'], '--clamp', str(config['clamp']),
|
|
'--damping', str(config['damping']), '--block', *map(str, config['block']),
|
|
'--steps', str(config['steps']), '--output', str(output / 'targeted')]
|
|
(output / 'replay_command.json').write_text(json.dumps(command, indent=2))
|
|
with (output / 'targeted_stdout.txt').open('w') as stdout, (output / 'targeted_stderr.txt').open('w') as stderr:
|
|
result = subprocess.run(command, cwd=checkout, stdout=stdout, stderr=stderr)
|
|
targeted = output / 'targeted/result.json'
|
|
details = json.loads(targeted.read_text()) if targeted.exists() else None
|
|
status = dict(status='original_assertion_failure_retained', replay_started=True,
|
|
replay_returncode=result.returncode, replay_result=details)
|
|
(output / 'status.json').write_text(json.dumps(status, indent=2))
|
|
print(json.dumps(status), flush=True)
|
|
# Preserve the original failed gate even if replay fails or does not reproduce.
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|