tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

bootstrap_retention_probe.py (7375B)


      1 #!/usr/bin/env python3
      2 """Isolated retained-toolchain probes, invoked only by bootstrap_retention.py.
      3 No compiler generation checkpoint or fixture publication is created here.
      4 """
      5 import argparse
      6 import hashlib
      7 import json
      8 from pathlib import Path
      9 import subprocess
     10 import sys
     11 
     12 from bootstrap_retention import REPO, sha, save, check_support_source, support_preparations
     13 from bootstrap_acceptance import verify
     14 from generation_io import versions
     15 
     16 
     17 def portable_probe(source, io_version):
     18     """Select the CLI oracle before execution; historical runtimes keep their oracle."""
     19     if io_version == 2:
     20         return REPO / 'test/portable_cli.py', ['--compiler-io-version', '2']
     21     if io_version == 1:
     22         oracle = source / 'test/portable_cli.py'
     23         if not oracle.is_file():
     24             raise ValueError('retained IO v1 source requires its frozen portable CLI oracle')
     25         return oracle, []  # Historical harnesses default to IO v1.
     26     raise ValueError('unsupported retained compiler IO version')
     27 
     28 
     29 def main():
     30     p = argparse.ArgumentParser(description=__doc__)
     31     p.add_argument('--root', type=Path, required=True)
     32     p.add_argument('--plan', type=Path, required=True)
     33     p.add_argument('--directory', type=Path, required=True)
     34     a = p.parse_args()
     35     plan = json.loads(a.plan.read_text()); config = plan['config']
     36     root, out = a.root, a.directory
     37     # Refuse a non-isolated invocation; every removal target must be invisible.
     38     if any((root / row['objects']).exists() for row in plan['stores']):
     39         raise ValueError('obsolete object stores are still visible')
     40     report = dict(complete=False, obsolete_stores_hidden=True, toolchains=[])
     41     def run(label, command, seconds=900):
     42         with (out / (label + '.log')).open('w') as log:
     43             subprocess.run(command, stdout=log, stderr=subprocess.STDOUT, check=True, timeout=seconds)
     44     try:
     45         for t in config['toolchains']:
     46             role = t['role']; generation = root / t['generation']; source = root / t['source']
     47             acceptance = root / t['acceptance']; runtime = t['runtime']
     48             accepted = json.loads(acceptance.read_text())
     49             # These cohorts still include first generation, so rerun full-pair
     50             # acceptance as well as the generation-two audit. Portable-only
     51             # retirement of first generation needs an explicitly different gate.
     52             io_version = versions(accepted['inputs'][1])[1]
     53             fresh = verify(Path(accepted['generations'][0]), generation, Path(runtime), source)
     54             if fresh['artifacts'] != accepted['artifacts']:
     55                 raise ValueError('retained acceptance artifacts differ')
     56             oracle, io_flags = portable_probe(source, io_version)
     57             run(role + '-portable', [sys.executable, str(oracle), runtime,
     58                 '--backend', 'stable', '--indexed', *io_flags, '--compiler-bundle', str(generation / 'build-stable.arboricx')])
     59             run(role + '-smoke', [sys.executable, str(REPO / 'test/bootstrap_smoke.py'), runtime,
     60                 '--generation', str(generation), '--acceptance', str(acceptance),
     61                 '--source-root', str(source), '--directory', str(out / (role + '-smoke'))])
     62             scoped = out / (role + '-map')
     63             run(role + '-map', [sys.executable, str(REPO / 'test/bootstrap_generations.py'),
     64                 str(generation), str(scoped), '--tricu', runtime, '--source-root', str(source),
     65                 '--map', str(generation / 'source-map.v1'), '--root', 'native_math', '--backend', 'stable',
     66                 '--fresh', '--produced-io-version', str(io_version), '--capabilities', '2',
     67                 '--memory-mib', '8192', '--wall-seconds', '900'])
     68             run(role + '-map-audit', [sys.executable, str(REPO / 'test/bootstrap_store_check.py'),
     69                 str(scoped), '--source-root', str(source), '--root', 'native_math'])
     70             old = (generation / 'cas/cache/source-build/native_math').read_text().split('\t')[2]
     71             new = (scoped / 'cas/cache/source-build/native_math').read_text().split('\t')[2]
     72             if old != new:
     73                 raise ValueError('scoped map-builder changed complete native_math manifest')
     74             smoke = json.loads((out / (role + '-smoke/report.json')).read_text())
     75             if not smoke['complete'] or not smoke['wrong_output_rejected']:
     76                 raise ValueError('incomplete retained compiler smoke')
     77             report['toolchains'].append(dict(role=role, artifacts=fresh['artifacts'],
     78                 acceptance_sha256=sha(acceptance), runtime_sha256=sha(runtime),
     79                 fresh_pair_audits=fresh['audits'], portable_cli=True,
     80                 portable_oracle=str(oracle), portable_oracle_sha256=sha(oracle),
     81                 complete_results=len(smoke['cases']), scoped_map_manifest=new))
     82         support = config['support']
     83         runtime = next(t['runtime'] for t in config['toolchains'] if t['role'] == support['toolchain'])
     84         source = check_support_source(root, support)
     85         publication = out / 'support-reproduced'
     86         command = [sys.executable, str(REPO / 'test/repair_support_fixture.py'), runtime,
     87             *[str(root / path) for path in support_preparations(support)], '--output-directory', str(publication),
     88             '--source-root', str(source), '--capabilities', '2', '--verification-memory-mib', '8192']
     89         if support.get('relocate_patterns'):
     90             command += ['--relocate-patterns']
     91         if support.get('compiler_io_cohort'):
     92             command += ['--compiler-io-cohort']
     93         if support.get('add_binary64_support'):
     94             command += ['--add-binary64-support']
     95         if support.get('add_io_support'):
     96             command += ['--add-io-support']
     97         run('support', command, seconds=1500)
     98         compared = {}
     99         for name in ['support-library.arboricx', 'support-rebuilt.arboricx', 'support-library.v1']:
    100             if sha(publication / name) != sha(REPO / 'test/fixtures' / name):
    101                 raise ValueError('current support fixture did not reproduce: ' + name)
    102             compared[name] = sha(publication / name)
    103         expected = REPO / 'test/fixtures/support-receipts'
    104         actual = publication / 'support-receipts'
    105         if {p.name: sha(p) for p in actual.iterdir()} != {p.name: sha(p) for p in expected.iterdir()}:
    106             raise ValueError('support receipt/manifests did not reproduce')
    107         run('fixture-freshness', [sys.executable, str(REPO / 'test/fixture_sources.py'), 'current', '--source-root', str(source)])
    108         from fixture_sources import current_mismatches
    109         working_differences = current_mismatches(REPO / 'test/fixtures', REPO)
    110         report.update(fixture_source_scope='frozen-reference' if 'source' in support else 'working-tree',
    111                       fixture_source=support.get('source'),
    112                       working_source_differences=working_differences)
    113         report.update(complete=True, support_sha256=compared,
    114                       support_receipts_reproduced=True, fixture_sources_match_selected=True,
    115                       fixture_sources_current=not working_differences)
    116     finally:
    117         save(out / 'report.json', report)
    118     print('PASS isolated pair audits, CLI integrity/diagnostics, correctness smokes, scoped map-builds and support reproduction')
    119 
    120 
    121 if __name__ == '__main__':
    122     main()