io_files.py (5512B)
1 #!/usr/bin/env python3 2 """Compile/run the experimental v2 copier through the retained v1 compiler. 3 4 Nix-built host required. No grants are passed to the v2 application. Compares 5 complete output files and result trees for direct and handler-backed compositions. 6 """ 7 import argparse 8 import hashlib 9 import json 10 import re 11 from pathlib import Path 12 13 from source_order import Runner, field 14 from stable_bundle import byte_tree 15 from http_headers import result, tree_list 16 17 ROOT = Path(__file__).resolve().parent.parent 18 19 20 def main(): 21 parser = argparse.ArgumentParser(description=__doc__) 22 parser.add_argument('tricu', type=Path) 23 parser.add_argument('--directory', type=Path, required=True) 24 args = parser.parse_args() 25 args.directory.mkdir(parents=True, exist_ok=False) 26 directory = args.directory.resolve() 27 runner = Runner(args.tricu, directory) 28 bundle = directory/'copy.arboricx' 29 runner.run('compile', ['compile', '--file', ROOT/'demos/io/copy.tri', 30 '--store', runner.store, '--output', bundle]) 31 pin = runner.imported(bundle, 'import') 32 checks = [] 33 for name in ('copy', 'handledCopy', 'borrowedCopy', 'transferredCopy', 'joinedOnExitCopy', 'handledChildCopy'): 34 for size in (0, 1, 4095, 4096, 4097, 65536, 1048576): 35 label = f'{name}-{size}' 36 source, target = directory/(label+'.in'), directory/(label+'.out') 37 body = (bytes(range(256))*((size+255)//256))[:size] 38 source.write_bytes(body) 39 argv = ['--io'] 40 for index, path in enumerate((source, target)): 41 argument = directory/f'{label}-{index}.tree' 42 argument.write_bytes(byte_tree(str(path).encode())) 43 argv += ['--arg-tree-file', argument] 44 actual = runner.invoke(label, pin, name, argv) 45 expected = result(True, b'\0') 46 if name == 'handledCopy': 47 expected = result(True, expected) 48 assert actual == expected, label 49 assert target.read_bytes() == body, label 50 checks.append({'name': label, 'sha256': hashlib.sha256(body).hexdigest()}) 51 for name in ('copy', 'handledCopy'): 52 # Independent failure controls: no destination on a missing source, and 53 # a failed writer acquisition after a successful reader acquisition. 54 for case in ('missing-source', 'missing-parent'): 55 source = directory/(name+'-failure.in') 56 source.write_bytes(b'not copied') 57 target = directory/(name+'-failure.out') 58 if case == 'missing-source': 59 source = directory/'NO-SUCH-SOURCE' 60 else: 61 target = directory/'NO-SUCH-DIRECTORY'/'out' 62 label = name+'-'+case 63 argv = ['--io'] 64 for index, path in enumerate((source, target)): 65 argument = directory/f'{label}-{index}.tree' 66 argument.write_bytes(byte_tree(str(path).encode())) 67 argv += ['--arg-tree-file', argument] 68 actual = runner.invoke(label, pin, name, argv) 69 assert field(actual, 0) == b'\0', label 70 diagnostic = field(actual, 1, 0) 71 assert field(diagnostic, 0) == byte_tree(b'not-found'), label 72 assert field(diagnostic, 1, 0) == byte_tree(b'file/open-reader' if case == 'missing-source' else b'file/open-writer'), label 73 assert not target.exists(), label 74 checks.append({'name': label}) 75 for size in (0, 1, 4096, 4097, 65536): 76 label = f'cancelledCopy-{size}' 77 source, target = directory/(label+'.in'), directory/(label+'.out') 78 body = (bytes(range(256))*((size+255)//256))[:size] 79 source.write_bytes(body) 80 argv = ['--io'] 81 for index, path in enumerate((source, target)): 82 argument = directory/f'{label}-{index}.tree' 83 argument.write_bytes(byte_tree(str(path).encode())) 84 argv += ['--arg-tree-file', argument] 85 actual = runner.invoke(label, pin, 'cancelledCopy', argv) 86 assert actual == tree_list([byte_tree(b'cancelled'), byte_tree(b'requested'), b'\0']), label 87 assert target.read_bytes() == body[:4096], label 88 checks.append({'name': label, 'sha256': hashlib.sha256(target.read_bytes()).hexdigest()}) 89 # Regression guards for this selected copier, not general resource SLAs. 90 observations = [row for row in runner.records if row['label'].endswith(('-65536', '-1048576'))] 91 for row in observations: 92 assert row['peak_kib'] is not None and row['peak_kib'] <= 128*1024, row 93 match = re.search(r'([0-9,]+) bytes maximum residency', (directory/(row['label']+'.log')).read_text()) 94 assert match, row 95 row['maximum_live_bytes'] = int(match[1].replace(',', '')) 96 assert row['maximum_live_bytes'] <= 16*1024*1024, row 97 report = {'checks': checks, 'sources': {str(p.relative_to(ROOT)): hashlib.sha256(p.read_bytes()).hexdigest() 98 for p in [ROOT/'src/IO/Resources.hs', ROOT/'src/IO/Driver.hs', ROOT/'src/IO/Protocol.hs', 99 ROOT/'test/source_order.py', ROOT/'test/rts_metrics.py', ROOT/'src/IO/Tasks.hs', ROOT/'lib/io.tri', 100 ROOT/'demos/io/copy.tri', ROOT/'lib/handlers.tri']}} 101 report['observations'] = observations 102 (directory/'report.json').write_text(json.dumps(report, indent=2)+'\n') 103 print(f'PASS: {len(checks)} binary-copy and failure checks, including handler composition') 104 105 106 if __name__ == '__main__': 107 main()