aboutsummaryrefslogtreecommitdiffstats
path: root/tests/check.py
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-18 18:52:48 +0100
committereunhhu <[email protected]>2026-10-18 18:52:48 +0100
commit8709c7fdb9d18e30e6bb776f536aa230389fa527 (patch)
tree94f08b1646ae39fd56568ca029d4c457fe0b957d /tests/check.py
parent3d7765e116c7852c63237bf25dbfb785843de916 (diff)
downloadaup-8709c7fdb9d18e30e6bb776f536aa230389fa527.tar.gz
aup-8709c7fdb9d18e30e6bb776f536aa230389fa527.zip
Bytes in a record field (Pure 027's 4b-1): body: bytes (= "..." its default's bytes), x.body.bytes its length (LEN), a text going there only as bytes(t); no list keyed by bytes or by a list; examples/documents.aup: keyed byte documents as a keyed list of records, each edit checked against the revision it was made on; documents_model: random shows and edits (stale revisions, past the end, any bytes, up to 5,000 at once) against a model, in memory and on a store restarting six times (half of the runs in bursts), four seeds alike (1,280 steps); four diagnostics
Diffstat (limited to 'tests/check.py')
-rw-r--r--tests/check.py71
1 files changed, 70 insertions, 1 deletions
diff --git a/tests/check.py b/tests/check.py
index 5a2a462..26cc3f8 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -125,6 +125,13 @@ DIAGNOSTICS = [
' }\n }\n}\n', '16:7', 'an arm after _ is never taken'),
(JSON_M + 'state n = 0\non frame {\n let xs: list of Json\n let ms: list of M\n xs.append(Json.arr(ms))\n}\n', '14:22',
"'arr' holds a list of Json, not of M"),
+ # Bytes in a field (Pure 027's 4b): a length alone, a string's bytes as its default, never a text where bytes go, no key.
+ ('record D {\n b: bytes\n}\nstate ds: list of D\nstate n = 0\non frame {\n for d in ds {\n n = n + d.b.chars\n }\n}\n',
+ '8:13', "bytes have bytes, not 'chars'"),
+ ('record D {\n b: bytes = 3\n}\nstate ds: list of D\n', '2:14', 'a bytes field starts from a string'),
+ ('record D {\n b: bytes\n}\nstate ds: list of D\non frame {\n ds.append(D { b: "x" })\n}\n', '6:20',
+ 'expected bytes, not text'),
+ ('record D {\n b: bytes\n}\nstate ds: list of D by b\n', '4:24', "a list is keyed by a number or text; 'b' is bytes"),
(JSON_M + 'input m = message {\n docs: list of Json\n}\non m {\n for d in m.docs {\n d.arr.append(Json.null)\n }\n}\n',
'15:5', "not of a message's rows or one read from a record"),
(JSON_M + 'state n = 0\non frame {\n let ms: list of M\n for m in ms {\n n = m.value\n }\n}\n', '14:9',
@@ -237,7 +244,8 @@ DIAGNOSTICS = [
# records, lists and indices
('record T {\n a = 0\n a = 1\n}\n', '3:3', "duplicate field 'a'"),
('record T {\n}\n', '1:8', 'a record needs at least one field'),
- ('record T {\n a: number\n}\n', '2:6', 'a field is a number (field = 0), a bool (field = false), text (field: text), an i64 (field: i64), a record'),
+ ('record T {\n a: number\n}\n', '2:6',
+ 'a field is a number (field = 0), a bool (field = false), text (field: text), bytes (field: bytes), an i64 (field: i64), a record'),
('record T {\n a = 0\n}\nstate T = 0\n', '4:7', "'T' is already declared as a record"),
('state xs: list of Missing\n', '1:7', "a list holds a record; 'Missing' is not one"),
('state x = 0\nstate at: index of x\n', '2:7', "an index points into a list state; 'x' is not one"),
@@ -1771,6 +1779,66 @@ view tags {
"""
+def documents_model(aup, pure_run, work, seeds, steps=160):
+ """Pure 027's 4b: keyed byte documents as a keyed list of records holding bytes (examples/documents.aup), each edit
+ checked against the revision it was made on, run on a store restarting at random points (some runs saving in bursts)
+ and in memory, against a model: every step's fault and documents alike (bodies of any bytes, up to 5,000 at once),
+ a failed event leaving nothing."""
+ import shutil
+ import struct
+ module = work / 'documents.pmod'
+ compile_module(aup, ROOT / 'examples/documents.aup', module)
+ ids = ['a', 'b', 'long-key_1', '문서', '']
+ compared = restarts = 0
+ for seed in seeds:
+ rng = random.Random(seed + 41000)
+ docs = {}
+ commands, expected = [], []
+ for _ in range(steps):
+ i = rng.choice(ids)
+ body, revision = docs.get(i, (b'', 0))
+ fault = 'none'
+ if rng.random() < 0.25:
+ commands.append(f"message show {i.encode().hex() or '-'}")
+ if i not in docs: fault = 'not_found'
+ else:
+ pick = rng.random()
+ base = (revision if pick < 0.7 else revision - 1 if pick < 0.8 else 0 if pick < 0.9
+ else rng.choice([7, -1, 9223372036854775807]))
+ start = rng.randint(0, len(body) + 2) if rng.random() < 0.8 else 0
+ remove = rng.choice([0, 0, 1, 2, 5, len(body)])
+ insert = bytes(rng.randrange(256) for _ in range(rng.choice([0, 1, 5, 300, 5000])))
+ encoded = i.encode()
+ commands.append('message edit ' + (struct.pack('<IqII', len(encoded), base, start, remove) + encoded + insert).hex())
+ if i in docs:
+ if base != revision or start + remove > len(body): fault = 'limit'
+ else: docs[i] = (body[:start] + insert + body[start + remove:], revision + 1)
+ elif base != 0 or start or remove: fault = 'limit'
+ else: docs[i] = (insert, 1)
+ expected.append((fault, [{'id': k, 'body': docs[k][0].hex(), 'revision': docs[k][1]}
+ for k in sorted(docs, key=lambda k: k.encode())]))
+ runs = {'memory': run(pure_run, module, stdin='\n'.join(commands) + '\n', check=False).stdout.splitlines()}
+ store = work / f'store-documents-{seed}'
+ if store.exists():
+ shutil.rmtree(store)
+ cuts, lines = sorted(rng.sample(range(1, steps), 6)), []
+ for number_, (start, end) in enumerate(zip([0] + cuts, cuts + [steps])):
+ batch = ['--batch', str(rng.randint(2, 9))] if number_ % 2 else []
+ lines += run(pure_run, module, '--store', store, *batch, stdin='\n'.join(commands[start:end]) + '\n',
+ check=False).stdout.splitlines()
+ restarts += 1
+ runs['stored'] = lines
+ for way, outputs in runs.items():
+ assert len(outputs) == steps, (way, seed, len(outputs))
+ for line, command, (fault, want) in zip(outputs, commands, expected):
+ snapshot = json.loads(line)
+ where = (way, seed, command[:40])
+ assert snapshot['fault'] == fault, (where, snapshot['fault'])
+ assert snapshot['states']['docs'] == want, (where, snapshot['states']['docs'], want)
+ compared += 1
+ return [compared, restarts]
+
+
def stored_nested(aup, pure_run, work, seeds, steps=200):
"""Pure 027's stage 3: a stored list whose items hold lists whose items hold lists (columns of cards of tags), run
on a store restarting at random points (some runs saving in bursts) and in memory, against a model: every step's
@@ -2499,6 +2567,7 @@ def main():
report['nested_values'] = nested_values(aup, pure_run, work)
report['functions'] = functions(aup, pure_run, work)
report['stored_nested'] = stored_nested(aup, pure_run, work, range(6))
+ report['documents'] = documents_model(aup, pure_run, work, range(4))
report['editor_parity_operations'], report['editor_parity_outcomes'] = \
editor_parity(aup, args.pure.resolve(), work, range(args.editor_seeds))
print(json.dumps(report))