From 36010b4db5b1866529c3e2544a0f2274273071e2 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Wed, 30 Sep 2026 13:06:41 +0100 Subject: Check that a value comes out the same on every path (108) Values at the edges of what JSON, rows, a query and a plain body can say go in to one node, over a link to a second that takes them by name in another order and wider, and over HTTP to a third; each must come out the same from views, a query, an answer, a restart, a restart after a checkpoint and a replaced module: a number's bits (zero without its sign), a text's bytes. What must be refused is refused on every way in and leaves nothing behind. --- tests/check.py | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 281 insertions(+), 1 deletion(-) diff --git a/tests/check.py b/tests/check.py index 7ccafc6..6fbf1a8 100644 --- a/tests/check.py +++ b/tests/check.py @@ -63,7 +63,8 @@ class HttpHost: assert ready, 'HTTP host did not start' line = self.process.stdout.readline() assert line, self.process.communicate(timeout=5) - self.port = json.loads(line)['port'] + self.info = json.loads(line) # its port, name, public key + self.port = self.info['port'] def request(self, method, key, body=b'', expected=None): import http.client @@ -3296,6 +3297,284 @@ def server_app(aup, pure, work, seeds, operations=200, threads=1): return compared, sum(acknowledged) +MEANING_KEPT = """record Kept { + id = 0 + n = 0 + u = 0 + t: text + x = 0 + label: text +} +state kept: list of Kept by id +""" +MEANING_ROWS = """view rows = each k in kept { + id = k.id + n = k.n + u = k.u + t = k.t + x = k.x + label = k.label +} +""" +# A: what comes in (a JSON body, its rows, or a query and a plain body) is kept, and sent on; a put is answered +# with its text. +MEANING_ORIGIN = MEANING_KEPT + """record Part { + id = 0 + n = 0 + u = 0 + t: text +} +input put = message { + id: u32 + n: number + u: u32 + t: text + at: { + x: number + label: text + } +} +input put_plain = message { + id: u32 + n: number + u: u32 + t: text +} +input put_rows = message { + parts: list of Part +} +input touch = message { + id: u32 + t: text +} +output passed = message like put +on put { + kept.add(Kept { id: put.id, n: put.n, u: put.u, t: put.t, x: put.at.x, label: put.at.label }) + send passed { ..put } + reply put.t +} +on put_rows { + for p in put_rows.parts { + kept.add(Kept { id: p.id, n: p.n, u: p.u, t: p.t, x: 0, label: "rows" }) + send passed { id: p.id, n: p.n, u: p.u, t: p.t, at: { x: 0, label: "rows" } } + } +} +on put_plain { + kept.add(Kept { id: put_plain.id, n: put_plain.n, u: put_plain.u, t: put_plain.t, x: 0, label: "" }) + send passed { id: put_plain.id, n: put_plain.n, u: put_plain.u, t: put_plain.t, at: { x: 0, label: "" } } +} +on touch { + kept[touch.id].t = touch.t +} +""" + MEANING_ROWS +# B: A's sends over the link, taken by name (fields in another order, u a number), kept and sent on over HTTP. +MEANING_MIRROR = MEANING_KEPT + """input passed = message { + at: { + label: text + x: number + } + t: text + u: number + n: number + id: u32 +} +output onward = message like passed +on passed { + kept.add(Kept { id: passed.id, n: passed.n, u: passed.u, t: passed.t, x: passed.at.x, label: passed.at.label }) + send onward { ..passed } +} +""" + MEANING_ROWS +# C: B's sends over HTTP. +MEANING_END = MEANING_KEPT + """input onward = message { + id: u32 + n: number + u: number + t: text + at: { + x: number + label: text + } +} +on onward { + kept.add(Kept { id: onward.id, n: onward.n, u: onward.u, t: onward.t, x: onward.at.x, label: onward.at.label }) +} +""" + MEANING_ROWS +# The value set: every number a JSON reader must round correctly, u32s to its ends, texts JSON escapes and UTF-8. +MEANING_NUMBERS = ['0', '-0', '1', '-1', '0.1', '0.3333333333333333', '9007199254740991', '9007199254740992', + '9007199254740993', '-9007199254740993', '1e21', '1e20', '1e-7', '123456789012345678901234567890', + '1.7976931348623157e308', '1.7976931348623158e308', '2.2250738585072014e-308', + '2.2250738585072011e-308', '5e-324', '2.4703282292062328e-324', '1E+2', '-2.5e-10'] +MEANING_U32 = [0, 1, 2147483648, 4294967295] +MEANING_TEXTS = ['', 'a', '한글 텍스트', 'emoji 🎉 and 𝄞', 'quote " backslash \\ slash /', 'line\nfeed\ttab\rreturn', + 'control \x01\x1f\x7f', 'nul \x00 inside', 'separator \u2028 and \u2029', '\ufeffbom', + '\uffff not a character', 'x' * 4096] + + +def server_meaning(aup, pure, work): + """One value, every path (docs/research/108): values at the edges of what + JSON, a query and a plain body can say come in to A; A keeps them and sends + them over a link to B, which takes them by name (fields in another order, + u widened to a number) and sends them over HTTP to C. Each value must come + out the same at every exit: A's view, a query on A, B's and C's views, A + after a restart and after a checkpoint, B after its module was replaced. + A number keeps its bits (zero has no sign: -0 shows as 0 and counts as 0), + a text its bytes. What must be refused is refused on every way in, and + leaves nothing behind (no row, no send).""" + import socket + import struct + import time + from http.client import HTTPConnection + from urllib.parse import quote + modules = {} + for name, text in (('origin', MEANING_ORIGIN), ('mirror', MEANING_MIRROR), ('end', MEANING_END), + ('mirror-2', MEANING_MIRROR.replace(' id = 0\n n = 0\n u = 0\n t: text\n x = 0\n label: text\n', + ' label: text\n x = 0\n t: text\n u = 0\n n = 0\n id = 0\n'))): + source = work / f'meaning-{name}.aup' + source.write_text(text) + compile_module(aup, source, source.with_suffix('.pmod')) + modules[name] = source.with_suffix('.pmod') + serve = pure / 'build/pure-serve' + keys = {} + for name in ('a', 'b', 'c'): + host = HttpHost([serve, '--module', modules['end'], '--store', work / 'meaning-keys', '--key', work / f'meaning-{name}.key']) + keys[name] = host.info['public_key'] + host.stop() + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as probe: + probe.bind(('127.0.0.1', 0)) + link_port = probe.getsockname()[1] + + def start(name, module): + args = [serve, '--module', modules[module], '--store', work / f'meaning-{name}', '--key', work / f'meaning-{name}.key', + '--name', name] + if name == 'a': + args += ['--send', f'passed=pure://127.0.0.1:{link_port}/passed', '--seal', f'passed={keys["b"]}'] + if name == 'b': + args += ['--link-port', link_port, '--sender', f'a.passed={keys["a"]}', + '--send', f'onward=http://127.0.0.1:{hosts["c"].port}/inputs/onward', '--seal', f'onward={keys["c"]}'] + if name == 'c': + args += ['--sender', f'b.onward={keys["b"]}'] + hosts[name] = HttpHost(args) + + def send(port, path, body, kind, answer=None): + connection = HTTPConnection('127.0.0.1', port, timeout=30) + try: + connection.request('POST', path, body, {'Content-Length': str(len(body)), 'Content-Type': kind}) + response = connection.getresponse() + got = response.read() + assert answer is None or response.status != 200 or got == answer, (path, got[:80], answer[:80]) + return response.status + finally: + connection.close() + + def rows(name, path='/views/rows'): + status, _, _, body = http(hosts[name].port, 'GET', path) + assert status == 200, (name, path, status, body[:200]) + return {row['id']: row for row in json.loads(body)['items']} + + def same(shown, want): # a number's bits, zero having no sign + return float(shown) == want == 0 or struct.pack('= 1, checkpoints + start('a', 'origin') + exits['a after a checkpoint'] = rows('a') + hosts.pop('b').stop() + start('b', 'mirror-2') + exits['b replaced'] = rows('b') + for where, found in exits.items(): + wrong = differing(found, expected) + assert not wrong, (where, wrong[:3]) + finally: + for host in hosts.values(): + host.stop() + return {'values': len(expected), 'refused': refused, 'exits': len(exits), 'checkpoints': checkpoints} + + def main(): global EDITOR parser = argparse.ArgumentParser() @@ -3343,6 +3622,7 @@ def main(): report['server_arguments'] = server_arguments(aup, pure, work) report['server_answers'] = server_answers(aup, pure, work) report['server_answers_memory'] = server_answers_memory(aup, pure, work) + report['server_meaning'] = server_meaning(aup, pure, work) print(json.dumps(report)) -- cgit v1.2.3