aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections/check_edits.py
blob: 1a6dc5363dbdd76a958dad48e2630914bc6f672c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"""Real native automation and HTTP clients editing the same authored document."""
from concurrent.futures import ThreadPoolExecutor
import json
import socket
import struct
import subprocess


def check_edits(editor, binary, root, directory, serving, rpc, header, records, checks):
    storage = directory / 'edited-objects'
    program = root / 'examples/editor.pure'
    file = directory / 'insertion.bin'

    def local(*args, body=None, code=0, source=program, budget=None):
        if body is not None:
            file.write_bytes(body)
            args = (*args, str(file))
        command = [str(editor), '--program', str(source), '--store', str(storage)]
        if budget is not None:
            command += ['--budget', str(budget)]
        result = subprocess.run(command + [str(arg) for arg in args], capture_output=True, timeout=20)
        assert result.returncode == code, (command, args, result.stdout, result.stderr)
        return result

    initial = 'Pure에서 안녕 🌏\n'.encode()
    created = local('edit', 'note', 0, 0, 0, body=initial)
    assert json.loads(created.stdout)['revision'] == '1'
    start = initial.index('안녕'.encode())
    inserted = '반가워'.encode()
    expected = initial[:start] + inserted + initial[start + len('안녕'.encode()):]
    local('edit', 'note', 1, start, len('안녕'.encode()), body=inserted)
    shown = local('show', 'note')
    assert shown.stdout == expected and json.loads(shown.stderr)['revision'] == '2'
    checks.append('native authored byte edit and fresh native process restore UTF-8 content')
    stale = local('edit', 'note', 1, 0, 0, body=b'bad', code=3)
    assert json.loads(stale.stderr) == {'error': 'conflict', 'revision': '2'}
    local('edit', 'note', 2, 0xffffffff, 0, body=b'bad', code=2)
    assert local('show', 'note').stdout == expected
    checks.append('native stale revision and invalid byte position preserve the committed document')

    reordered = directory / 'edit-reordered.pure'
    reordered.write_text('layout E { tag: bytes[1]; remove: u32le; start: u32le; body: bytes; } '
        'on get { let o = store.read(request.key); reply(o, o.body); } '
        'on put { let e = view(E, request.body); '
        'let s = store.splice(request.key, request.revision, e.start, e.remove, e.body); reply(s, e.body); }')
    local('edit', 'note', 2, len(expected), 0, body=b'!', source=reordered)
    expected += b'!'
    assert local('show', 'note', source=reordered).stdout == expected
    checks.append('native convenience editor derives unaligned reordered field positions from the authored program')

    custom = directory / 'edit-custom.pure'
    custom.write_text('on get { let o = store.read(request.key); reply(o, o.body); } '
        'on put { let s = store.splice(request.key, request.revision, 0, 0, request.body); reply(s); }')
    local('edit', 'custom', 0, 0, 0, body=b'ignored', source=custom, code=2)
    applied = local('apply', 'custom', 0, body=b'custom input', source=custom)
    assert json.loads(applied.stdout) == {'revision': '1', 'reply_bytes': 0}
    assert local('show', 'custom', source=custom).stdout == b'custom input'
    checks.append('raw native apply runs another authored input contract without changing the executor')

    with serving(binary, storage, program=program) as server:
        assert rpc(server, 'GET', 'note')[2] == expected
        status, headers, body = rpc(server, 'PUT', 'note', struct.pack('<II', len(expected), 0) + b'?', 3)
        assert status == 200 and headers['ETag'] == '"4"' and body == b'?'
        expected += b'?'
        assert local('show', 'note').stdout == expected
        local('edit', 'note', 4, 0, 4, body=b'PURE')
        expected = b'PURE' + expected[4:]
        assert rpc(server, 'PUT', 'note', struct.pack('<II', 0, 0) + b'stale', 4)[0] == 412
        assert rpc(server, 'GET', 'note')[2] == expected
        checks.append('native CLI and external HTTP share revisions and the same authored storage effect in both directions')
        with socket.create_connection(('127.0.0.1', server.port), timeout=8) as sock:
            sock.sendall(header('PUT', 'note', 12, b'If-Match: "5"\r\n') + b'partial')
            sock.shutdown(socket.SHUT_WR)
            assert sock.recv(1) == b''
        assert rpc(server, 'PUT', 'note', b'short', 5)[0] == 413
        assert rpc(server, 'PUT', 'note', struct.pack('<II', 0xffffffff, 0), 5)[0] == 413
        assert rpc(server, 'GET', 'note')[2] == expected
        checks.append('partial or invalid edit input never modifies the stored document')
        with serving(binary, storage, program=program) as other:
            with ThreadPoolExecutor(max_workers=2) as pool:
                tasks = [pool.submit(rpc, target, 'PUT', 'note', struct.pack('<II', 0, 0) + value, 5)
                         for target, value in ((server, b'A'), (other, b'B'))]
                statuses = [task.result()[0] for task in tasks]
            assert statuses.count(200) == 1 and all(status in (200, 412, 503) for status in statuses)
            status, headers, body = rpc(server, 'GET', 'note')
            assert status == 200 and headers['ETag'] == '"6"'
            assert body == (b'A' if statuses[0] == 200 else b'B') + expected
        records.append(other.stats)
        checks.append('two processes cannot both splice the same expected document revision')
    records.append(server.stats)

    # This tests retained input size for a real large edit, not throughput.
    large = bytes(range(256)) * 4096
    local('edit', 'large', 0, 0, 0, body=large)
    local('edit', 'large', 1, 524287, 3, body=b'XYZ', budget=11)
    expected_large = large[:524287] + b'XYZ' + large[524290:]
    assert local('show', 'large').stdout == expected_large
    with serving(binary, storage, program=program, budget=11) as bounded:
        status, headers, body = rpc(bounded, 'PUT', 'large', struct.pack('<II', 524287, 3) + b'abc', 2)
        assert status == 200 and headers['ETag'] == '"3"' and body == b'abc'
        assert rpc(bounded, 'PUT', 'large', struct.pack('<II', 0, 0) + b'abcd', 3)[0] == 503
    assert bounded.stats['peak_retained_payload_bytes'] == 11
    records.append(bounded.stats)
    assert local('show', 'large').stdout == large[:524287] + b'abc' + large[524290:]
    checks.append('1 MiB document changes with 11 input credits; larger input is rejected before an effect')
    return {'large_document_bytes': len(large), 'small_edit_input_bytes': 11,
            'storage_rewrites_full_extent': True, 'storage_scratch_bytes': 4096,
            'kind': 'correctness-and-admission-not-total-memory-or-performance'}