tricu

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

prepare_formatter.py (6458B)


      1 #!/usr/bin/env python3
      2 """Build/check the current formatter using the shipped accepted stable compiler.
      3 
      4 One explicit application workspace; no Git history, differential baseline,
      5 compiler generation, fixture publication or recovery frontend.
      6 """
      7 import argparse
      8 import hashlib
      9 import json
     10 from pathlib import Path
     11 import re
     12 import subprocess
     13 import time
     14 from shipped_compiler import verify, ROOT
     15 
     16 
     17 def main():
     18     parser = argparse.ArgumentParser(description=__doc__)
     19     parser.add_argument('tricu', type=Path)
     20     parser.add_argument('--directory', required=True, type=Path, help='New output directory')
     21     args = parser.parse_args()
     22     metadata = verify()
     23     tricu = args.tricu.resolve()
     24     root = args.directory.resolve()
     25     root.mkdir(parents=True, exist_ok=False)
     26     workspace = root / 'workspace'
     27     workspace.mkdir()
     28     (workspace / 'tricu.workspace').write_text('tricu.workspace.v1\n')
     29     tools, store = root / 'tools', root / 'store'
     30     report = dict(complete=False, compiler_sha256=metadata['sha256'], sources={}, commands=[], cases=[])
     31     started = time.monotonic()
     32 
     33     def run(arguments, seconds=15, success=True):
     34         remaining = 1200 - (time.monotonic() - started)
     35         if remaining <= 0:
     36             raise TimeoutError('formatter preparation whole-job guard exceeded')
     37         command = [str(tricu), *map(str, arguments), '+RTS', '-N1', '-M8192m', '-RTS']
     38         reply = subprocess.run(command, cwd=ROOT, capture_output=True,
     39                                timeout=min(seconds, remaining))
     40         stdout, stderr = reply.stdout.decode('utf-8'), reply.stderr.decode('utf-8')
     41         report['commands'].append(dict(argv=command[1:], code=reply.returncode,
     42                                        stdout=stdout, stderr=stderr))
     43         (root / 'report.json').write_text(json.dumps(report, indent=2)+'\n')
     44         assert (reply.returncode == 0) == success, report['commands'][-1]
     45         return stdout
     46 
     47     sources = {row['alias']: row['path'] for row in metadata['sources']}
     48     sources.update(recursion='lib/recursion/list.tri', formatter='lib/tools/format-tri.tri')
     49     for alias, path in sources.items():
     50         data = (ROOT / path).read_bytes()
     51         (workspace / (alias + '.tri')).write_bytes(data)
     52         report['sources'][path] = hashlib.sha256(data).hexdigest()
     53     (workspace / 'recursion_support.tri').write_text('!import base as B\n!export pair\npair = B.pair\n')
     54     (workspace / 'format_support.tri').write_text(''.join(
     55         '!export * from ' + name + '\n' for name in ['prelude', 'parser', 'source_lexer', 'source_equations', 'source_ast']))
     56     output = run(['arboricx', 'import', '--indexed', '--file', ROOT / metadata['bundle'], '--store', tools])
     57     compiler = re.search(r'Created module alias .* -> ([0-9a-f]{64})', output)[1]
     58     bundle = root / 'formatter.arboricx'
     59     run(['arboricx', 'compile', '--file', workspace / 'formatter.tri', '--toolchain', compiler,
     60          '--tool-store', tools, '--store', store, '--name', 'formatTri', '--output', bundle,
     61          '--compile-timeout', '900'], seconds=930)
     62     output = run(['arboricx', 'import', '--indexed', '--file', bundle, '--store', store])
     63     formatter = re.search(r'Created module alias .* -> ([0-9a-f]{64})', output)[1]
     64     report['formatter_manifest'] = formatter
     65     report['formatter_sha256'] = hashlib.sha256(bundle.read_bytes()).hexdigest()
     66     deadline_source = workspace / 'deadline_probe.tri'
     67     deadline_source.write_text('!export formatTri\nspin x = spin x\nformatTri x = spin x\n')
     68     deadline_bundle = root / 'deadline.arboricx'
     69     run(['arboricx', 'compile', '--file', deadline_source, '--toolchain', compiler,
     70          '--tool-store', tools, '--store', store, '--name', 'formatTri', '--output', deadline_bundle,
     71          '--compile-timeout', '60'], seconds=90)
     72     output = run(['arboricx', 'import', '--indexed', '--file', deadline_bundle, '--store', store])
     73     diverging = re.search(r'Created module alias .* -> ([0-9a-f]{64})', output)[1]
     74     report['deadline_manifest'] = diverging
     75     report['workspace_sha256'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
     76                                   for p in workspace.glob('*.tri')}
     77     report['cases'] = check_formatter(root, formatter, store, run, diverging)
     78     report['complete'] = True
     79     report['elapsed_seconds'] = time.monotonic() - started
     80     (root / 'report.json').write_text(json.dumps(report, indent=2)+'\n')
     81     print('Formatter manifest:', formatter)
     82     print('Tool store:', store)
     83 
     84 
     85 def check_formatter(root, formatter, store, run, diverging):
     86     labels = []
     87     source = root / 'input.tri'
     88     for label, before, after in json.loads((ROOT / 'test/formatter_cases.json').read_text()):
     89         source.write_text(before)
     90         assert run(['format', source, '--formatter', formatter, '--tool-store', store]) == after, label
     91         assert source.read_bytes() == before.encode('utf-8')
     92         source.write_text(after)
     93         assert run(['format', source, '--formatter', formatter, '--tool-store', store]) == after, label + ' idempotence'
     94         labels.append(label)
     95     before, after = json.loads((ROOT / 'test/formatter_cases.json').read_text())[0][1:]
     96     source.write_text(before)
     97     options = ['format', source, '--formatter', formatter, '--tool-store', store]
     98     run([*options, '--check'], success=False)
     99     assert source.read_bytes() == before.encode('utf-8')
    100     source.chmod(0o640)
    101     run([*options, '--write'])
    102     assert source.read_bytes() == after.encode('utf-8')
    103     assert source.stat().st_mode & 0o777 == 0o640
    104     run([*options, '--check'])
    105     run([*options, '--write', '--check'], success=False)
    106     link = root / 'link.tri'
    107     if not link.is_symlink():
    108         link.symlink_to(source)
    109     run(['format', link, '--formatter', formatter, '--tool-store', store, '--write'], success=False)
    110     assert link.is_symlink() and source.read_bytes() == after.encode('utf-8')
    111     source.write_bytes(b'\xff')
    112     run([*options, '--write'], success=False)
    113     assert source.read_bytes() == b'\xff'
    114     source.write_text(before)
    115     run(['format', source, '--formatter', '0' * 64, '--tool-store', store, '--write'], success=False)
    116     assert source.read_bytes() == before.encode('utf-8')
    117     run(['format', source, '--formatter', diverging, '--tool-store', store,
    118          '--write', '--seconds', '1'], success=False)
    119     assert source.read_bytes() == before.encode('utf-8')
    120     return labels
    121 
    122 
    123 if __name__ == '__main__':
    124     main()