aboutsummaryrefslogtreecommitdiffstats
path: root/tests/check.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/check.py')
-rw-r--r--tests/check.py193
1 files changed, 190 insertions, 3 deletions
diff --git a/tests/check.py b/tests/check.py
index b377e20..7ccafc6 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -1339,9 +1339,9 @@ UPGRADED_RECEIVER = """state orders = 0
state noted = 0
state stock = 0
input order = message {
- note: text
item: text
count: u32
+ note: text
}
on order {
orders = orders + 1
@@ -1360,8 +1360,9 @@ def server_upgrade(aup, pure, work):
to.city}; started again with ordered {note, item, count} it recasts them
by name (note empty, to.city gone) where it was refused before (review
004's F3), and a module without the output is refused naming it. The old
- receiver refuses the new shape, so the sends wait; the upgraded receiver
- takes all eight, once each."""
+ receiver lacks note, so it refuses them (it would drop the field) and
+ they wait; the upgraded receiver, its fields in another order, takes all
+ eight by name, once each."""
import select
import socket
import time
@@ -1439,6 +1440,191 @@ def server_upgrade(aup, pure, work):
assert stats['a']['link_refused'] >= 1, stats['a']
return {'recast_waiting': 5, 'delivered': 8, 'refused_while_old': stats['a']['link_refused']}
+
+FIELDS_SENDER = """input buy = message {
+ item: text
+ count: u32
+}
+output swapped = message {
+ count: u32
+ item: text
+}
+output plain = message {
+ item: text
+ count: u32
+}
+output noted = message {
+ item: text
+ count: u32
+ note: text
+}
+output over_http = message {
+ item: text
+ count: u32
+}
+output noted_http = message {
+ item: text
+ count: u32
+ note: text
+}
+on buy {
+ send swapped { count: buy.count, item: buy.item }
+ send plain { item: buy.item, count: buy.count }
+ send noted { item: buy.item, count: buy.count, note: "kept" }
+ send over_http { item: buy.item, count: buy.count }
+ send noted_http { item: buy.item, count: buy.count, note: "kept" }
+}
+"""
+
+
+def fields_receiver(narrow_notes):
+ """Inputs for FIELDS_SENDER's outputs; narrow ones lack note unless narrow_notes."""
+ note = ' note: text\n' if narrow_notes else ''
+ kept = lambda name: f' if {name}.note == "kept" {{ {name}_kept = {name}_kept + 1 }}\n' if narrow_notes else ''
+ return (f"""state swapped_n = 0
+state swapped_total = 0
+state wider_n = 0
+state wider_blank = 0
+state narrow_n = 0
+state narrow_kept = 0
+state wider_http_n = 0
+state wider_http_blank = 0
+state narrow_http_n = 0
+state narrow_http_kept = 0
+input swapped_in = message {{
+ item: text
+ count: u32
+}}
+input wider = message {{
+ note: text
+ item: text
+ count: u32
+}}
+input narrow = message {{
+ item: text
+ count: u32
+{note}}}
+input wider_http = message {{
+ item: text
+ count: u32
+ note: text
+}}
+input narrow_http = message {{
+ item: text
+ count: u32
+{note}}}
+on swapped_in {{
+ swapped_n = swapped_n + 1
+ swapped_total = swapped_total + swapped_in.count
+}}
+on wider {{
+ wider_n = wider_n + 1
+ if wider.note == "" {{ wider_blank = wider_blank + 1 }}
+}}
+on narrow {{
+ narrow_n = narrow_n + 1
+{kept('narrow')}}}
+on wider_http {{
+ wider_http_n = wider_http_n + 1
+ if wider_http.note == "" {{ wider_http_blank = wider_http_blank + 1 }}
+}}
+on narrow_http {{
+ narrow_http_n = narrow_http_n + 1
+{kept('narrow_http')}}}
+view counts = "{{swapped_n}} {{swapped_total}} {{wider_n}} {{wider_blank}} {{narrow_n}} {{narrow_kept}} {{wider_http_n}} {{wider_http_blank}} {{narrow_http_n}} {{narrow_http_kept}}"
+""")
+
+
+def server_fields(aup, pure, work):
+ """Deliveries take their fields by name (docs/design/014-fields-by-name.md).
+ A sender's five outputs, three linked and two over HTTP, to one receiver:
+ fields in another order are taken; an input with a field more takes
+ messages without it (empty); an input lacking a field the output sends
+ refuses it, over the link (REFUSED, fields lacked) and over HTTP
+ (json_field), so nothing is dropped, and once the receiver starts again
+ with the field, every waiting message comes, the field kept."""
+ import select
+ import socket
+ import time
+ modules = {}
+ for name, text in (('sender', FIELDS_SENDER), ('receiver-1', fields_receiver(False)),
+ ('receiver-2', fields_receiver(True))):
+ source = work / f'fields-{name}.aup'
+ source.write_text(text)
+ compile_module(aup, source, source.with_suffix('.pmod'))
+ modules[name] = source.with_suffix('.pmod')
+
+ def free_port(kind=socket.SOCK_STREAM):
+ with socket.socket(socket.AF_INET, kind) as probe:
+ probe.bind(('127.0.0.1', 0))
+ return probe.getsockname()[1]
+
+ def start(args, port):
+ process = subprocess.Popen([str(a) for a in args] + ['--port', str(port)], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ ready, _, _ = select.select([process.stdout], [], [], 10)
+ line = process.stdout.readline() if ready else b''
+ assert line, f'host did not start: {process.stderr.read(2000) if process.poll() is not None else ""}'
+ return process, json.loads(line)
+
+ def stop(process):
+ process.terminate()
+ _, error = process.communicate(timeout=30)
+ assert process.returncode == 0, error[-1000:]
+ return error.decode()
+
+ def counts(port):
+ return [int(n) for n in http(port, 'GET', '/views/counts')[3].split()]
+
+ keys = {}
+ for name in ('sender', 'receiver'):
+ process, info = start([pure / 'build/pure-serve', '--module', modules['receiver-1'], '--store',
+ work / 'fields-key', '--key', work / f'fields-{name}.key'], 0)
+ keys[name] = info['public_key']
+ stop(process)
+ port_a, port_b, link_port = free_port(), free_port(), free_port(socket.SOCK_DGRAM)
+ links = {'swapped': 'swapped_in', 'plain': 'wider', 'noted': 'narrow'}
+ sending = [pure / 'build/pure-serve', '--module', modules['sender'], '--store', work / 'fields-a', '--key',
+ work / 'fields-sender.key', '--name', 'up',
+ '--send', f'over_http=http://127.0.0.1:{port_b}/inputs/wider_http',
+ '--send', f'noted_http=http://127.0.0.1:{port_b}/inputs/narrow_http',
+ '--seal', f'over_http={keys["receiver"]}', '--seal', f'noted_http={keys["receiver"]}']
+ for output, input_name in links.items():
+ sending += ['--send', f'{output}=pure://127.0.0.1:{link_port}/{input_name}', '--seal', f'{output}={keys["receiver"]}']
+
+ def receiving(module):
+ args = [pure / 'build/pure-serve', '--module', modules[module], '--store', work / 'fields-b', '--key',
+ work / 'fields-receiver.key', '--link-port', link_port]
+ for output in (*links, 'over_http', 'noted_http'):
+ args += ['--sender', f'up.{output}={keys["sender"]}']
+ return args
+
+ running, said = {}, ''
+ try:
+ running['b'] = start(receiving('receiver-1'), port_b)[0]
+ running['a'] = start(sending, port_a)[0]
+ for i in range(3):
+ assert post(port_a, '/inputs/buy', {'item': f'i{i}', 'count': i + 1})[0] == 200
+ deadline = time.time() + 20
+ while counts(port_b)[:4] + counts(port_b)[6:8] != [3, 6, 3, 3, 3, 3] and time.time() < deadline:
+ time.sleep(0.05)
+ got = counts(port_b)
+ assert got == [3, 6, 3, 3, 0, 0, 3, 3, 0, 0], got # the narrow inputs took none: nothing is dropped
+ time.sleep(1.5) # the refused link tried with its fields named: the sender has said why
+ stop(running.pop('b'))
+ running['b'] = start(receiving('receiver-2'), port_b)[0]
+ deadline = time.time() + 30
+ while counts(port_b)[4:6] + counts(port_b)[8:] != [3, 3, 3, 3] and time.time() < deadline:
+ time.sleep(0.1)
+ got = counts(port_b)
+ assert got == [3, 6, 3, 3, 3, 3, 3, 3, 3, 3], got
+ finally:
+ for name, process in running.items():
+ text = stop(process)
+ said += text if name == 'a' else ''
+ assert 'its input lacks fields this output sends' in said, said[-2000:]
+ return {'links': 3, 'wires': 2, 'waited_then_taken': 6}
+
def server_links_independent(aup, pure, work):
"""A receiver written from docs/design/012-link-protocol.md alone, in
Python with the cryptography package and no code of link/: a Pure node
@@ -3146,6 +3332,7 @@ def main():
report['server_memory'] = server_memory(aup, pure, work)
report['server_windows'] = server_windows(aup, pure, work)
report['server_upgrade'] = server_upgrade(aup, pure, work)
+ report['server_fields'] = server_fields(aup, pure, work)
report['server_links_independent'] = server_links_independent(aup, pure, work)
report['server_links_moved'] = server_links_moved(aup, pure, work)
report['server_webhook'] = server_webhook(aup, pure, work)