aboutsummaryrefslogtreecommitdiffstats
path: root/tests/check.py
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-30 15:25:02 +0100
committereunhhu <[email protected]>2026-09-30 15:25:02 +0100
commitccfd73d6a30e841f40f94bd199fe3ae72fbbad6c (patch)
treef6c2f7bc5fabd6186edcf3a67c3e054067c82abd /tests/check.py
parentbfa54e02881f56b3a969220ecaac706542046149 (diff)
downloadpure-ccfd73d6a30e841f40f94bd199fe3ae72fbbad6c.tar.gz
pure-ccfd73d6a30e841f40f94bd199fe3ae72fbbad6c.zip
Pin a bool's meaning on every path (015)
server_meaning gives each of its 67 values a bool field, true or false: in as JSON, a query, a message's rows; over the link to B (taking it by name among reordered fields) and over HTTP to C; out through A's view and query, B's and C's views, a restart, a restart after a checkpoint and B with its module replaced. A bool must come out as true or false, never 1 or 0. Refused on every way in, with the reason checked: 1, 0, "true", null or an array as json_type, True as json (no JSON at all), a query's 1, 0, True, TRUE, "true " or nothing as query, a row's 1 as json_type: 52 refusals, 39 before. 015 says what a bool is at each boundary. This check found the stored outbox refusing bool fields (a node could not start again with a bool send waiting), fixed in the commit before.
Diffstat (limited to 'tests/check.py')
-rw-r--r--tests/check.py96
1 files changed, 64 insertions, 32 deletions
diff --git a/tests/check.py b/tests/check.py
index 0c52cf4..117beff 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -3305,6 +3305,7 @@ MEANING_KEPT = """record Kept {
t: text
x = 0
label: text
+ ok = false
}
state kept: list of Kept by id
"""
@@ -3315,6 +3316,7 @@ MEANING_ROWS = """view rows = each k in kept {
t = k.t
x = k.x
label = k.label
+ ok = k.ok
}
"""
# 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
@@ -3324,12 +3326,14 @@ MEANING_ORIGIN = MEANING_KEPT + """record Part {
n = 0
u = 0
t: text
+ ok = false
}
input put = message {
id: u32
n: number
u: u32
t: text
+ ok: bool
at: {
x: number
label: text
@@ -3340,6 +3344,7 @@ input put_plain = message {
n: number
u: u32
t: text
+ ok: bool
}
input put_rows = message {
parts: list of Part
@@ -3350,19 +3355,19 @@ input touch = message {
}
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 })
+ kept.add(Kept { id: put.id, n: put.n, u: put.u, t: put.t, x: put.at.x, label: put.at.label, ok: put.ok })
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" } }
+ kept.add(Kept { id: p.id, n: p.n, u: p.u, t: p.t, x: 0, label: "rows", ok: p.ok })
+ send passed { id: p.id, n: p.n, u: p.u, t: p.t, ok: p.ok, 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: "" } }
+ kept.add(Kept { id: put_plain.id, n: put_plain.n, u: put_plain.u, t: put_plain.t, x: 0, label: "", ok: put_plain.ok })
+ send passed { id: put_plain.id, n: put_plain.n, u: put_plain.u, t: put_plain.t, ok: put_plain.ok, at: { x: 0, label: "" } }
}
on touch {
kept[touch.id].t = touch.t
@@ -3370,6 +3375,7 @@ on touch {
""" + 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 {
+ ok: bool
at: {
label: text
x: number
@@ -3381,7 +3387,8 @@ MEANING_MIRROR = MEANING_KEPT + """input passed = message {
}
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 })
+ kept.add(Kept { id: passed.id, n: passed.n, u: passed.u, t: passed.t, x: passed.at.x, label: passed.at.label,
+ ok: passed.ok })
send onward { ..passed }
}
""" + MEANING_ROWS
@@ -3395,9 +3402,11 @@ MEANING_END = MEANING_KEPT + """input onward = message {
x: number
label: text
}
+ ok: bool
}
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 })
+ kept.add(Kept { id: onward.id, n: onward.n, u: onward.u, t: onward.t, x: onward.at.x, label: onward.at.label,
+ ok: onward.ok })
}
""" + MEANING_ROWS
# The value set: every number a JSON reader must round correctly, u32s to its ends, texts JSON escapes and UTF-8.
@@ -3419,8 +3428,9 @@ def server_meaning(aup, pure, work):
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)."""
+ a text its bytes, a bool is true or false (never 1 or 0). What must be
+ refused is refused on every way in, and leaves nothing behind (no row, no
+ send)."""
import socket
import struct
import time
@@ -3428,8 +3438,8 @@ def server_meaning(aup, pure, work):
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'))):
+ ('mirror-2', MEANING_MIRROR.replace(' id = 0\n n = 0\n u = 0\n t: text\n x = 0\n label: text\n ok = false\n',
+ ' ok = false\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'))
@@ -3467,6 +3477,16 @@ def server_meaning(aup, pure, work):
finally:
connection.close()
+ def refused_as(port, path, body, kind): # the status and X-Pure-Error of a request that must fail
+ 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()
+ response.read()
+ return response.status, response.getheader('X-Pure-Error')
+ 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])
@@ -3476,9 +3496,10 @@ def server_meaning(aup, pure, work):
return float(shown) == want == 0 or struct.pack('<d', float(shown)) == struct.pack('<d', want)
def differing(found, expected):
- wrong = [(key, found.get(key)) for key, (n, u, t, x, label) in expected.items()
+ wrong = [(key, found.get(key)) for key, (n, u, t, x, label, ok) in expected.items()
if key not in found or not (same(found[key]['n'], n) and float(found[key]['u']) == u and
- found[key]['t'] == t and same(found[key]['x'], x) and found[key]['label'] == label)]
+ found[key]['t'] == t and same(found[key]['x'], x) and found[key]['label'] == label
+ and found[key]['ok'] is ok)]
return wrong + [(key, 'not sent') for key in set(found) - set(expected)]
hosts, expected, refused = {}, {}, 0
@@ -3492,26 +3513,28 @@ def server_meaning(aup, pure, work):
for i, n in enumerate(MEANING_NUMBERS):
u, t = MEANING_U32[i % len(MEANING_U32)], MEANING_TEXTS[i % len(MEANING_TEXTS)]
x, label = MEANING_NUMBERS[(i + 5) % count], MEANING_TEXTS[(i + 3) % len(MEANING_TEXTS)]
- ascii_only = i % 2 == 0
- body = '{"id": %d, "n": %s, "u": %d, "t": %s, "at": {"x": %s, "label": %s}}' % (
- i + 1, n, u, json.dumps(t, ensure_ascii=ascii_only), x, json.dumps(label, ensure_ascii=ascii_only))
+ ascii_only, ok = i % 2 == 0, i % 3 != 0
+ body = '{"id": %d, "n": %s, "u": %d, "t": %s, "ok": %s, "at": {"x": %s, "label": %s}}' % (
+ i + 1, n, u, json.dumps(t, ensure_ascii=ascii_only), json.dumps(ok), x, json.dumps(label, ensure_ascii=ascii_only))
assert send(a, '/inputs/put', body.encode(), 'application/json', t.encode()) == 200, body[:200]
- expected[i + 1] = (float(n), u, t, float(x), label)
+ expected[i + 1] = (float(n), u, t, float(x), label, ok)
# In as rows of one message: a JSON array, placed as its list's stored form.
- parts = [(501 + i, n, MEANING_U32[(i + 2) % len(MEANING_U32)], MEANING_TEXTS[(i + 4) % len(MEANING_TEXTS)])
+ parts = [(501 + i, n, MEANING_U32[(i + 2) % len(MEANING_U32)], MEANING_TEXTS[(i + 4) % len(MEANING_TEXTS)], i % 2 == 0)
for i, n in enumerate(MEANING_NUMBERS)]
- body = '{"parts": [%s]}' % ', '.join('{"id": %d, "n": %s, "u": %d, "t": %s}' % (key, n, u, json.dumps(t))
- for key, n, u, t in parts)
+ body = '{"parts": [%s]}' % ', '.join('{"id": %d, "n": %s, "u": %d, "t": %s, "ok": %s}' % (key, n, u, json.dumps(t),
+ json.dumps(ok))
+ for key, n, u, t, ok in parts)
assert send(a, '/inputs/put_rows', body.encode(), 'application/json') == 200, body[:200]
- expected.update({key: (float(n), u, t, 0.0, 'rows') for key, n, u, t in parts})
+ expected.update({key: (float(n), u, t, 0.0, 'rows', ok) for key, n, u, t, ok in parts})
# In as a query (%XX where a client would write it so) and a plain body.
for i, n in enumerate(MEANING_NUMBERS):
- u, t = MEANING_U32[(i + 1) % len(MEANING_U32)], MEANING_TEXTS[(i + 2) % len(MEANING_TEXTS)]
- assert send(a, f'/inputs/put_plain?id={101 + i}&n={quote(n, safe="")}&u={u}', t.encode(),
+ u, t, ok = MEANING_U32[(i + 1) % len(MEANING_U32)], MEANING_TEXTS[(i + 2) % len(MEANING_TEXTS)], i % 2 == 1
+ assert send(a, f'/inputs/put_plain?id={101 + i}&n={quote(n, safe="")}&u={u}&ok={json.dumps(ok)}', t.encode(),
'text/plain; charset=utf-8') == 200, n
- expected[101 + i] = (float(n), u, t, 0.0, '')
- # What must be refused: numbers past the finite, u32s out of range or not whole, texts that are no UTF-8.
- good = '{"id": %d, "n": %s, "u": %s, "t": %s, "at": {"x": 1, "label": ""}}'
+ expected[101 + i] = (float(n), u, t, 0.0, '', ok)
+ # What must be refused: numbers past the finite, u32s out of range or not whole, texts that are no UTF-8,
+ # bools that are not true or false.
+ good = '{"id": %d, "n": %s, "u": %s, "t": %s, "ok": true, "at": {"x": 1, "label": ""}}'
bad_json = [good % (201, '1e999', '1', '"t"'), good % (202, '-1e999', '1', '"t"'), good % (203, 'NaN', '1', '"t"'),
good % (204, '00', '1', '"t"'), good % (205, '+1', '1', '"t"'), good % (206, '.5', '1', '"t"'),
good % (207, '1', '4294967296', '"t"'), good % (208, '1', '-1', '"t"'), good % (209, '1', '1.5', '"t"'),
@@ -3519,27 +3542,36 @@ def server_meaning(aup, pure, work):
good % (213, '1', '1', '"a\\ud83d"'), good % (214, '1', '1', '"\\udc00\\ud800"')]
for body in bad_json:
assert send(a, '/inputs/put', body.encode(), 'application/json') == 400, body
+ not_bools = [('1', 'json_type'), ('0', 'json_type'), ('"true"', 'json_type'), ('null', 'json_type'), ('[]', 'json_type'),
+ ('True', 'json')] # no JSON at all
+ for i, (bad, why) in enumerate(not_bools):
+ body = '{"id": %d, "n": 1, "u": 1, "t": "t", "ok": %s, "at": {"x": 1, "label": ""}}' % (231 + i, bad)
+ assert refused_as(a, '/inputs/put', body.encode(), 'application/json') == (400, why), body
no_utf8 = [b'\xff', b'\xc3', b'\xed\xa0\x80', b'\xc0\xaf', b'\xf4\x90\x80\x80'] # a surrogate, too long, past U+10FFFF
for i, bad in enumerate(no_utf8):
- body = b'{"id": %d, "n": 1, "u": 1, "t": "' % (215 + i) + bad + b'", "at": {"x": 1, "label": ""}}'
+ body = b'{"id": %d, "n": 1, "u": 1, "t": "' % (215 + i) + bad + b'", "ok": true, "at": {"x": 1, "label": ""}}'
assert send(a, '/inputs/put', body, 'application/json') == 400, body
bad_query = [('1e999', '1'), ('inf', '1'), ('nan', '1'), ('0x10', '1'), ('', '1'), ('1', '4294967296'), ('1', '-1'),
('1', '1.5'), ('1', '1e3'), ('1', '%2D0'), ('1', '%201'), ('1', '%zz')]
for i, (n, u) in enumerate(bad_query):
- assert send(a, f'/inputs/put_plain?id={301 + i}&n={n}&u={u}', b't', 'text/plain') == 400, (n, u)
+ assert send(a, f'/inputs/put_plain?id={301 + i}&n={n}&u={u}&ok=true', b't', 'text/plain') == 400, (n, u)
+ for i, bad in enumerate(['1', '0', 'True', 'TRUE', '%74rue%20', '']):
+ assert refused_as(a, f'/inputs/put_plain?id={331 + i}&n=1&u=1&ok={bad}', b't', 'text/plain') == (400, 'query'), bad
for i, bad in enumerate(no_utf8 + [b'ok \xc3']):
- assert send(a, f'/inputs/put_plain?id={401 + i}&n=1&u=1', bad, 'text/plain') == 422, bad
+ assert send(a, f'/inputs/put_plain?id={401 + i}&n=1&u=1&ok=false', bad, 'text/plain') == 422, bad
# Rows go whole or not at all: one number past the finite refuses them all; a u that a row holds (a
# number) but a u32 cannot fails the event where it is sent, and none is kept.
assert send(a, '/inputs/put_rows', b'{"parts": [{"id": 601, "n": 1, "u": 1, "t": "a"}, '
b'{"id": 602, "n": 1e999, "u": 1, "t": "b"}]}', 'application/json') == 400
assert send(a, '/inputs/put_rows', b'{"parts": [{"id": 603, "n": 1, "u": 1, "t": "a"}, '
b'{"id": 604, "n": 1, "u": 1.5, "t": "b"}]}', 'application/json') != 200
- refused = len(bad_json) + len(no_utf8) + len(bad_query) + len(no_utf8) + 1 + 2
+ assert refused_as(a, '/inputs/put_rows', b'{"parts": [{"id": 605, "ok": true}, {"id": 606, "ok": 1}]}',
+ 'application/json') == (400, 'json_type')
+ refused = len(bad_json) + len(not_bools) + len(no_utf8) + len(bad_query) + 6 + len(no_utf8) + 1 + 2 + 1
# A row to change often on A alone, until a checkpoint writes its list anew; it ends as it began.
- assert send(a, '/inputs/put', json.dumps({'id': 900, 'n': 9, 'u': 9, 't': 'filler',
+ assert send(a, '/inputs/put', json.dumps({'id': 900, 'n': 9, 'u': 9, 't': 'filler', 'ok': True,
'at': {'x': 9, 'label': 'filler'}}).encode(), 'application/json') == 200
- expected[900] = (9.0, 9, 'filler', 9.0, 'filler')
+ expected[900] = (9.0, 9, 'filler', 9.0, 'filler', True)
exits = {'a': rows('a')}
deadline = time.time() + 30
while time.time() < deadline and (len(rows('b')) < len(expected) or len(rows('c')) < len(expected)):