aboutsummaryrefslogtreecommitdiffstats
path: root/tests/check.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/check.py')
-rw-r--r--tests/check.py119
1 files changed, 117 insertions, 2 deletions
diff --git a/tests/check.py b/tests/check.py
index aa18217..eb37c35 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -249,8 +249,9 @@ DIAGNOSTICS = [
('view v = floor(1, 2)\n', '1:10', 'floor expects 1 argument'),
('state now = 1\n', '1:7', "'now' is reserved"),
# A for walks a list once; a message's rows are a list its handler reads.
- (ROWS + 'on o {\n for a in xs {\n for b in xs {\n n = n + 1\n }\n }\n}\n', '12:5',
- 'a for cannot hold another for'),
+ # Fors nest (Pure 018): an inner for changes no list an outer one walks.
+ (ROWS + 'on o {\n for a in xs {\n for b in o.rows {\n xs.append(L { item: 1 })\n }\n }\n}\n', '13:7',
+ 'a for does not add to the list it walks; add to another list, or after the for'),
(ROWS + 'on o {\n for a in xs {\n xs.append(L { item: 1 })\n }\n}\n', '12:5',
'a for does not add to the list it walks; add to another list, or after the for'),
(ROWS + 'on o {\n for a in o.rows {\n reply "x"\n }\n}\n', '12:5', 'a message replies once: reply after the for'),
@@ -296,6 +297,26 @@ DIAGNOSTICS = [
(LETS + 'on k.down {\n let i = xs.first\n i = ys.first\n}\n', '11:7', "'i' holds an index of its list"),
(LETS + 'on k.down {\n if n > 0 {\n let x = 1\n }\n n = x\n}\n', '13:7', "unknown state 'x'"),
(LETS + 'on k.down {\n for r in xs {\n let r = 1\n }\n}\n', '11:9', "'r' is the item this for walks"),
+ # Counting fors, break and a let's list (Pure 018).
+ (LETS + 'on k.down {\n break\n}\n', '10:3', "break ends a for; this one is outside any"),
+ (LETS + 'on k.down {\n if n > 0 {\n let q: list of T\n }\n}\n', '11:9',
+ "a let's list goes in the handler's own block, not in an if or a for"),
+ (LETS + 'on k.down {\n for i in 0 .. 3 {\n i = 1\n }\n}\n', '11:5', "'i' is what this for counts; it is not assigned"),
+ (LETS + 'on k.down {\n for a in xs {\n for b in ys {\n xs.remove(a)\n }\n }\n}\n', '12:7',
+ 'a for removes from the list it walks, not from one an outer for walks'),
+ (LETS + 'on k.down {\n for a in xs {\n for i in 0 .. 2 {\n xs.append(T { a: 1 })\n }\n }\n}\n', '12:7',
+ 'a for does not add to the list it walks; add to another list, or after the for'),
+ (LETS + 'record U {\n b = 0\n}\ninput j = key KeyJ\non k.down {\n let q: list of T\n}\non j.down {\n'
+ ' let q: list of U\n}\n', '17:7', "'q' is a let's list of another record or key in another handler"),
+ (LETS + 'on k.down {\n let q: list of T\n}\non k.down {\n let q: list of T\n}\n', '13:7',
+ "another handler of this event has a list 'q'; name this one apart"),
+ (LETS + 'on k.down {\n' + ''.join(' ' * (d + 1) + f'for i{d} in 0 .. 2 {{\n' for d in range(9)) + '}\n' * 10, '18:19', 'fors nest at most 8 deep'),
+ (LETS + 'on k.down {\n let x: text\n}\n', '10:10', "a let holds a list as let x: list of a record; others take '='"),
+ (LETS + 'on k.down {\n let q: list of Nope\n}\n', '10:7', "a list holds a record; 'Nope' is not one"),
+ (LETS + 'on k.down {\n let ' + 'q' * 28 + ': list of T\n}\n', '10:7', "a let's list has a name of 27 characters at most"),
+ (LETS + 'on k.down {\n for i in t .. 3 {\n }\n}\n', '10:12', 'expected a number'),
+ ('state break = 0\n', '1:7', "'break' is reserved"),
+ (LETS + 'on k.down {\n let q: list of T\n for i in 0 .. 3 {\n let q = 1\n }\n}\n', '12:9', "'q' is already a let here"),
(LETS + 'view v = x\non k.down {\n let x = 1\n}\n', '9:10', "unknown state 'x'"),
(LETS + 'on k.down {\n let let = 1\n}\n', '10:7', "'let' is reserved"),
# A slice takes a list keyed by a number.
@@ -1093,6 +1114,99 @@ def transients(aup, pure_run, work):
return {'store_files_compared': len(compared.common_files), 'type_words_differ': len(differ)}
+def computing(aup, pure_run, work, graphs=20):
+ """Computing in one event (Pure 018): examples/routes.aup's searches
+ over random graphs (breadth first, depth first, nearest by weight) and
+ examples/numbers.aup's square roots (bit for bit as Python works them out
+ in the same order) and knapsacks, each against a model. The searches'
+ lists are the event's own: none is left after it."""
+ import heapq
+ import random
+ import struct
+ routes, numbers = work / 'routes.pmod', work / 'numbers.pmod'
+ compile_module(aup, ROOT / 'examples' / 'routes.aup', routes)
+ compile_module(aup, ROOT / 'examples' / 'numbers.aup', numbers)
+
+ def message(name, layout, *values):
+ return f'message {name} {struct.pack(layout, *values).hex()}\n'
+
+ searched = 0
+ for seed in range(graphs):
+ rng = random.Random(seed)
+ n = rng.randint(5, 60)
+ edges = {}
+ for _ in range(rng.randint(n, 4 * n)):
+ a, b = rng.randrange(n), rng.randrange(n)
+ if a != b:
+ edges[(a, b)] = rng.randint(1, 9)
+ out = {v: sorted(b for (a, b) in edges if a == v) for v in range(n)}
+ commands = ''.join(message('link', '<III', a, b, w) for (a, b), w in edges.items())
+ queries = [(rng.randrange(n), rng.randrange(n)) for _ in range(10)]
+ for a, b in queries:
+ commands += message('route', '<II', a, b) + message('walk', '<I', a) + message('path', '<II', a, b)
+ lines = run(pure_run, routes, stdin=commands).stdout.splitlines()[len(edges):]
+ for k, (a, b) in enumerate(queries):
+ route, walk, path = (json.loads(line) for line in lines[3 * k:3 * k + 3])
+ assert all(r['error'] is None and r['fault'] == 'none' for r in (route, walk, path)), (seed, k)
+ assert all(not r['states'][name] for r in (route, walk, path)
+ for name in ('let.seen', 'let.queue', 'let.stack', 'let.best', 'let.pending'))
+ depth, frontier = {a: 0}, [a]
+ for v in frontier:
+ for t in out[v]:
+ if t not in depth:
+ depth[t] = depth[v] + 1
+ frontier.append(t)
+ assert route['states']['hops'] == depth.get(b, -1), (seed, a, b, route['states']['hops'], depth.get(b, -1))
+ seen, stack = {}, [a]
+ while stack:
+ v = stack.pop()
+ if v not in seen:
+ seen[v] = len(seen)
+ stack.extend(t for t in out[v] if t not in seen)
+ assert walk['states']['visited'] == len(seen), (seed, a)
+ assert walk['states']['order'] == sum(d * (v + 1) for v, d in seen.items()), (seed, a)
+ best, pending = {a: 0}, [(0, a)]
+ while pending:
+ d, v = heapq.heappop(pending)
+ if best[v] != d:
+ continue
+ for t in out[v]:
+ if d + edges[(v, t)] < best.get(t, float('inf')):
+ best[t] = d + edges[(v, t)]
+ heapq.heappush(pending, (best[t], t))
+ assert path['states']['shortest'] == best.get(b, -1), (seed, a, b, path['states']['shortest'], best.get(b, -1))
+ searched += 3
+
+ rng = random.Random(18)
+ xs = [1.0, 2.0, 4.0, 0.25, 1e-6, 1e12] + [10 ** rng.uniform(-6, 12) for _ in range(194)]
+ lines = run(pure_run, numbers, stdin=''.join(message('sqrt', '<d', x) for x in xs)).stdout.splitlines()
+ for x, line in zip(xs, lines):
+ r, steps = (x if x > 1 else 1.0), 0
+ for _ in range(200):
+ following = (r + x / r) / 2
+ if following >= r:
+ break
+ r, steps = following, steps + 1
+ states = json.loads(line)['states']
+ assert struct.pack('<d', states['root']) == struct.pack('<d', r) and states['steps'] == steps, (x, states, r, steps)
+ packs = 0
+ for seed in range(20):
+ rng = random.Random(seed)
+ items = [(rng.randint(1, 30), rng.randint(1, 50)) for _ in range(rng.randint(1, 20))]
+ capacities = [rng.randint(0, 100) for _ in range(5)]
+ commands = ''.join(message('stock', '<III', i, w, v) for i, (w, v) in enumerate(items))
+ commands += ''.join(message('pack', '<I', c) for c in capacities)
+ lines = run(pure_run, numbers, stdin=commands).stdout.splitlines()[len(items):]
+ for c, line in zip(capacities, lines):
+ table = [0] * (c + 1)
+ for w, v in items:
+ for at in range(c, w - 1, -1):
+ table[at] = max(table[at], table[at - w] + v)
+ assert json.loads(line)['states']['packed'] == table[c], (seed, c)
+ packs += 1
+ return {'searches': searched, 'square_roots': len(xs), 'knapsacks': packs}
+
+
def stored_runs(aup, pure_run, work, seeds, steps=240):
"""Declared state is stored state: the list app and the text editor run on
a store, restarting (a new pure-run process) at random points, and keep
@@ -1793,6 +1907,7 @@ def main():
report['library_programs'], report['library_diagnostics'] = libraries(aup, pure_run, work)
report['library_records'] = library_records(aup, pure_run, args.pure.resolve(), work)
report['transients'] = transients(aup, pure_run, work)
+ report['computing'] = computing(aup, pure_run, work)
report['editor_parity_operations'], report['editor_parity_outcomes'] = \
editor_parity(aup, args.pure.resolve(), work, range(args.editor_seeds))
print(json.dumps(report))