tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

io_v2_zig_files.py (8286B)


      1 #!/usr/bin/env python3
      2 """Private Zig regular-file host: actual source, independent bytes and trees."""
      3 import argparse
      4 import hashlib
      5 import json
      6 import subprocess
      7 from pathlib import Path
      8 from source_order import Runner, field
      9 from http_headers import result, tree_list, number
     10 from stable_bundle import byte_tree
     11 from process_rss import run_guarded
     12 
     13 ROOT = Path(__file__).resolve().parent.parent
     14 
     15 def main():
     16     p = argparse.ArgumentParser(description=__doc__)
     17     p.add_argument('tricu', type=Path)
     18     p.add_argument('probe', type=Path)
     19     p.add_argument('--directory', type=Path, required=True)
     20     args = p.parse_args()
     21     d = args.directory.resolve(); d.mkdir(parents=True, exist_ok=False)
     22     r = Runner(args.tricu, d)
     23     bundle = d/'copy.arboricx'
     24     r.run('compile', ['compile', '--file', ROOT/'demos/io-v2/copy.tri', '--store', r.store, '--output', bundle])
     25     pin = r.imported(bundle, 'import')
     26     checks = []
     27     def invoke(name, source, target, label):
     28         command = [str(args.probe.resolve()), str(bundle), name, 'text:'+str(source), 'text:'+str(target)]
     29         output, peak = run_guarded(command, memory_mib=128, seconds=180)
     30         (d/(label+'.log')).write_text(output)
     31         assert output.endswith('\n') and set(output.strip()) <= set('012'), (label, output)
     32         return bytes(map(int, output.strip())), peak
     33     for name in ('copy', 'handledCopy', 'borrowedCopy', 'transferredCopy', 'joinedOnExitCopy', 'handledChildCopy', 'cancelledCopy'):
     34         for size in (0, 1, 4095, 4096, 4097, 65536, 1048576):
     35             label = f'{name}-{size}'
     36             source, target = d/(label+'.in'), d/(label+'.out')
     37             body = (bytes(range(256))*((size+255)//256))[:size]
     38             source.write_bytes(body)
     39             expected = result(True, b'\0')
     40             if name == 'handledCopy': expected = result(True, expected)
     41             if name == 'cancelledCopy': expected = tree_list([byte_tree(b'cancelled'), byte_tree(b'requested'), b'\0'])
     42             actual, peak = invoke(name, source, target, label)
     43             assert actual == expected, (label, actual)
     44             assert target.read_bytes() == (body[:4096] if name == 'cancelledCopy' else body), label
     45             # Actual Haskell comparison on each composition at the boundary; the
     46             # complete corpus is independently checked by io_v2_files.py too.
     47             if size == 4097:
     48                 argv = ['--io']
     49                 for i, path in enumerate((source, d/(label+'.haskell'))):
     50                     argument = d/f'{label}-{i}.tree'; argument.write_bytes(tree_list([number(ord(c)) for c in str(path)]))
     51                     argv += ['--arg-tree-file', argument]
     52                 assert r.invoke('haskell-'+label, pin, name, argv) == expected
     53                 assert (d/(label+'.haskell')).read_bytes() == target.read_bytes()
     54             checks.append(dict(label=label, peak_kib=peak, sha256=hashlib.sha256(target.read_bytes()).hexdigest()))
     55     source, target = d/'source-é😀', d/'target-é😀'
     56     source.write_bytes(b'\x00\xffunicode-path')
     57     actual, peak = invoke('copy', source, target, 'unicode-path')
     58     assert actual == result(True, b'\0') and target.read_bytes() == source.read_bytes()
     59     flags = ['--io']
     60     target.unlink()
     61     for i, path in enumerate((source, target)):
     62         arg = d/f'unicode-{i}.tree'; arg.write_bytes(tree_list([number(ord(c)) for c in str(path)]))
     63         flags += ['--arg-tree-file', arg]
     64     assert r.invoke('unicode-path-haskell', pin, 'copy', flags) == result(True, b'\0')
     65     assert target.read_bytes() == source.read_bytes()
     66     checks.append(dict(label='unicode-path', peak_kib=peak))
     67     for case in ('missing-source', 'missing-parent', 'fifo', 'directory', 'device'):
     68         source, target = d/(case+'.in'), d/(case+'.out')
     69         source.write_bytes(b'keep')
     70         category = b'not-found'; operation = b'file/open-reader'
     71         if case == 'missing-source': source = d/'does-not-exist'
     72         elif case == 'missing-parent': target = d/'missing-parent'/'out'; operation = b'file/open-writer'
     73         elif case == 'fifo':
     74             import os
     75             source.unlink(); os.mkfifo(source); category = b'unsupported'
     76         elif case == 'directory': source = d; category = b'unsupported'
     77         elif case == 'device': source = Path('/dev/null'); category = b'unsupported'
     78         actual, peak = invoke('copy', source, target, case)
     79         assert field(actual, 0) == b'\0', case
     80         problem = field(actual, 1, 0)
     81         assert field(problem, 0) == byte_tree(category), case
     82         assert field(problem, 1, 0) == byte_tree(operation), case
     83         assert not target.exists(), case
     84         checks.append(dict(label=case, peak_kib=peak))
     85     controls = d/'controls.arboricx'
     86     r.run('compile-controls', ['compile', '--file', ROOT/'test/io-v2/zig-files.tri', '--store', r.store, '--output', controls])
     87     control_pin = r.imported(controls, 'import-controls')
     88     def problem(category, operation, detail=b''):
     89         return result(False, tree_list([byte_tree(category), byte_tree(operation), byte_tree(detail), byte_tree(b'none')]))
     90     data = bytes([2])+byte_tree(b'data')+byte_tree(b'bc')
     91     eof = bytes([2])+byte_tree(b'eof')+b'\0'
     92     control_cases = {
     93         'positioned': (b'', tree_list([data, number(3), number(10), eof]), b'a'),
     94         'appended': (b'k', tree_list([number(3), problem(b'unsupported', b'file/seek', b'operation unavailable in this file mode')]), b'kxy'),
     95         'emptyWrite': (b'previous', problem(b'invalid-bound', b'stream/write'), b''),
     96         'closed': (b'keep', problem(b'stale-handle', b'resource/close'), b'keep'),
     97         'readerWrite': (b'keep', problem(b'wrong-kind', b'stream/write'), b'keep'),
     98     }
     99     for name, (before, expected, after) in control_cases.items():
    100         for backend in ('zig', 'haskell'):
    101             target = d/(name+'-'+backend+'.file'); target.write_bytes(before)
    102             if backend == 'zig':
    103                 output, peak = run_guarded([str(args.probe.resolve()), str(controls), name, 'text:'+str(target)], memory_mib=128, seconds=60)
    104                 actual = bytes(map(int, output.strip()))
    105             else:
    106                 argument = d/(name+'.tree'); argument.write_bytes(tree_list([number(ord(c)) for c in str(target)]))
    107                 actual = r.invoke(name, control_pin, name, ['--io', '--arg-tree-file', argument])
    108             assert actual == expected, (name, backend, actual, expected)
    109             assert target.read_bytes() == after, (name, backend)
    110         checks.append(dict(label=name))
    111     for backend in ('zig', 'haskell'):
    112         target = d/('failed-'+backend)
    113         if backend == 'zig':
    114             process = subprocess.run([str(args.probe.resolve()), str(controls), 'failedAfterWrite', 'text:'+str(target)], capture_output=True, timeout=60)
    115             assert process.returncode == 1 and b'not forced' in process.stderr
    116         else:
    117             argument = d/'failed.tree'; argument.write_bytes(tree_list([number(ord(c)) for c in str(target)]))
    118             try: r.invoke('failedAfterWrite', control_pin, 'failedAfterWrite', ['--io', '--arg-tree-file', argument])
    119             except subprocess.CalledProcessError as error: assert 'IO v2 task failed' in error.output
    120             else: raise AssertionError('failedAfterWrite succeeded')
    121         assert target.read_bytes() == b'*'
    122     checks.append(dict(label='failed-after-write-no-rollback'))
    123     output, _ = run_guarded([str(args.probe.resolve()), str(controls), 'unknownWithPoison', '0'], memory_mib=128, seconds=10)
    124     assert bytes(map(int, output.strip())) == problem(b'unsupported', b'file/open-reader', b'group revision 99')
    125     checks.append(dict(label='unknown-revision-no-demand'))
    126     paths = [ROOT/'test/io-v2/zig-files.tri', ROOT/'demos/io-v2/copy.tri', ROOT/'ext/zig/src/io_v2_files.zig', ROOT/'ext/zig/src/io_v2_file_host.zig', ROOT/'ext/zig/src/io_v2_core.zig', args.probe.resolve()]
    127     (d/'report.json').write_text(json.dumps(dict(checks=checks, sources={str(p):hashlib.sha256(p.read_bytes()).hexdigest() for p in paths}, haskell=r.records, scope='private-regular-file-host-no-public-v2'), indent=2)+'\n')
    128     print(f'PASS {len(checks)} private file-host source/byte controls; 14 Haskell boundary/mode/failure comparisons')
    129 if __name__ == '__main__': main()