Skip to content

Commit f2f78f4

Browse files
committed
More language integration tests
This time checking the VM output of specific language features and also letting the simplifier statically evaluate the AST down to literals and then checking that these literals match the same output.
1 parent c86a126 commit f2f78f4

File tree

1 file changed

+157
-10
lines changed

1 file changed

+157
-10
lines changed

tests/test_language.py

Lines changed: 157 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,155 @@
55
from pathlib import Path
66
import unittest
77

8+
from flitter import configure_logger
9+
from flitter.language.tree import Literal, StoreGlobal, Function
810
from flitter.language.parser import parse
911
from flitter.model import Vector, Context, StateDict, DummyStateDict, null
1012

1113

12-
class TestLanguage(unittest.TestCase):
13-
def _test_integration(self, filepath):
14+
configure_logger('WARNING')
15+
16+
17+
class TestLanguageFeatures(unittest.TestCase):
18+
def assertCodeOutput(self, code, output, **names):
19+
"""
20+
Tests that the supplied code produces the supplied output when compiled and
21+
run dynamically in the VM, and also when statically evaluated by the simplifier.
22+
"""
23+
top = parse(code.strip() + "\n")
24+
output = output.strip()
25+
run_context = Context(names={name: Vector(value) for name, value in names.items()}, state=StateDict())
26+
vm_output = '\n'.join(repr(node) for node in top.compile(initial_lnames=tuple(names)).run(run_context).root.children)
27+
self.assertEqual(vm_output, output, msg="VM output is incorrect")
28+
simplifier_output = []
29+
unknown = []
30+
for expr in top.simplify(static=names).expressions:
31+
if isinstance(expr, Literal):
32+
simplifier_output.extend(repr(value) for value in expr.value)
33+
elif isinstance(expr, (StoreGlobal, Function)):
34+
continue
35+
else:
36+
unknown.append(expr)
37+
self.assertEqual(unknown, [])
38+
self.assertEqual('\n'.join(simplifier_output), output, msg="Simplifier output is incorrect")
39+
40+
def test_literal_node(self):
41+
self.assertCodeOutput(
42+
"""
43+
!foo #bar x=5 y=:baz z='Hello world!'
44+
""",
45+
"""
46+
!foo #bar x=5 y=:baz z='Hello world!'
47+
""")
48+
49+
def test_names(self):
50+
self.assertCodeOutput(
51+
"""
52+
!foo x=x y=y z=z #bar
53+
""",
54+
"""
55+
!foo #bar x=5 y=:baz z='Hello world!'
56+
""", x=5, y=Vector.symbol('baz'), z='Hello world!')
57+
58+
def test_let(self):
59+
self.assertCodeOutput(
60+
"""
61+
let x=5 y=:baz z='Hello world!'
62+
!foo #bar x=x y=y z=z
63+
""",
64+
"""
65+
!foo #bar x=5 y=:baz z='Hello world!'
66+
""")
67+
68+
def test_multibind_loop(self):
69+
self.assertCodeOutput(
70+
"""
71+
for x;y in ..9
72+
!node x=x y=y
73+
""",
74+
"""
75+
!node x=0 y=1
76+
!node x=2 y=3
77+
!node x=4 y=5
78+
!node x=6 y=7
79+
!node x=8
80+
""")
81+
82+
def test_nested_loops(self):
83+
self.assertCodeOutput(
84+
"""
85+
for i in ..n
86+
!group i=i
87+
for j in ..i
88+
!item numbers=(k*(j+1) for k in 1..i+1)
89+
""",
90+
"""
91+
!group i=0
92+
!group i=1
93+
!item numbers=1
94+
!group i=2
95+
!item numbers=1;2
96+
!item numbers=2;4
97+
!group i=3
98+
!item numbers=1;2;3
99+
!item numbers=2;4;6
100+
!item numbers=3;6;9
101+
""", n=4)
102+
103+
def test_recursive_inlined_function(self):
104+
# Also default parameter values and out-of-order named arguments
105+
self.assertCodeOutput(
106+
"""
107+
func fib(n, x=0, y=1)
108+
fib(n-1, y=y+x, x=y) if n > 0 else x
109+
110+
!fib x=fib(10)
111+
""",
112+
"""
113+
!fib x=55
114+
""")
115+
116+
def test_builtin_calls(self):
117+
self.assertCodeOutput(
118+
"""
119+
for i in ..n
120+
!color rgb=hsv(i/n;1;1)
121+
""",
122+
"""
123+
!color rgb=1;0;0
124+
!color rgb=1;1;0
125+
!color rgb=0;1;0
126+
!color rgb=0;1;1
127+
!color rgb=0;0;1
128+
!color rgb=1;0;1
129+
""", n=6)
130+
131+
def test_template_calls(self):
132+
self.assertCodeOutput(
133+
"""
134+
func foo(nodes, x=10, y, z='world')
135+
!foo x=x z='hello';z
136+
nodes #test y=y
137+
138+
@foo
139+
@foo z='you'
140+
!bar
141+
@foo y=5
142+
@foo z='me' x=99
143+
!baz
144+
""",
145+
"""
146+
!foo x=10 z='hello';'world'
147+
!foo x=10 z='hello';'you'
148+
!bar #test
149+
!foo x=10 z='hello';'world'
150+
!foo #test x=99 z='hello';'me' y=5
151+
!baz #test
152+
""")
153+
154+
155+
class TestExampleScripts(unittest.TestCase):
156+
def _test_integration_script(self, filepath):
14157
self.maxDiff = None
15158
state = StateDict()
16159
null_state = DummyStateDict()
@@ -25,27 +168,31 @@ def _test_integration(self, filepath):
25168
self.assertEqual(len(windows), 1, msg="Should be a single window node in program output")
26169
self.assertGreater(len(tuple(windows[0].children)), 0, "Output window node should have children")
27170
# Simplify AST with dynamic names, compile and execute:
28-
program2 = top.simplify(dynamic=set(names)).compile(initial_lnames=tuple(names))
171+
top2 = top.simplify(dynamic=set(names))
172+
self.assertEqual(repr(top2.simplify(dynamic=set(names))), repr(top2), msg="Dynamic simplification not complete in one step")
173+
program2 = top2.compile(initial_lnames=tuple(names))
29174
program2.set_path(filepath)
30-
self.assertNotEqual(len(program1), len(program2), msg="Program lengths should be different")
175+
self.assertNotEqual(len(program1), len(program2), msg="Dynamically-simplified program length should be different from original")
31176
root2 = program2.run(Context(names=dict(names), state=state)).root
32-
self.assertEqual(repr(root2), repr(root1), msg="Program outputs should match")
177+
self.assertEqual(repr(root2), repr(root1), msg="Dynamically-simplified program output doesn't match original")
33178
# Simplify AST with static names and null state, compile and execute:
34-
program3 = top.simplify(static=names, state=null_state).compile()
179+
top3 = top.simplify(static=names, state=null_state)
180+
self.assertEqual(repr(top3.simplify(static=names, state=null_state)), repr(top3), msg="Static simplification not complete in one step")
181+
program3 = top3.compile()
35182
program3.set_path(filepath)
36-
self.assertNotEqual(len(program3), len(program1), msg="Program lengths should be different")
183+
self.assertNotEqual(len(program3), len(program1), msg="Statically-simplified program length should be different from original")
37184
root3 = program3.run(Context(state=state)).root
38-
self.assertEqual(repr(root3), repr(root1), msg="Program outputs should match")
185+
self.assertEqual(repr(root3), repr(root1), msg="Statically-simplified program output doesn't match original")
39186

40187
def _test_integration_all_scripts(self, dirpath):
41188
count = 0
42189
for filename in dirpath.iterdir():
43190
filepath = dirpath / filename
44191
if filepath.suffix == '.fl':
45192
with self.subTest(filepath=filepath):
46-
self._test_integration(filepath)
193+
self._test_integration_script(filepath)
47194
count += 1
48-
self.assertGreater(count, 0)
195+
self.assertGreater(count, 0, msg="No scripts found")
49196

50197
def test_integration_examples(self):
51198
self._test_integration_all_scripts(Path(__file__).parent.parent / 'examples')

0 commit comments

Comments
 (0)