More framework

This commit is contained in:
Johan B.W. de Vries 2023-11-11 13:45:03 +01:00
parent ef00b3a91c
commit ff0286bcf6
6 changed files with 92 additions and 22 deletions

View File

@ -24,7 +24,7 @@ WASM2C := $(WABT_DIR)/bin/wasm2c
examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py)) examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py))
venv/bin/python3 -m http.server --directory examples venv/bin/python3 -m http.server --directory examples
test: venv/.done tests/integration/test_lang/test_generated_u32.py test: venv/.done $(subst .json,.py,$(subst /generator_,/test_generated_,$(wildcard tests/integration/test_lang/generator_*.json)))
venv/bin/pytest tests $(TEST_FLAGS) venv/bin/pytest tests $(TEST_FLAGS)
lint: venv/.done lint: venv/.done
@ -39,12 +39,15 @@ venv/.done: requirements.txt
venv/bin/python3 -m pip install -r $^ venv/bin/python3 -m pip install -r $^
touch $@ touch $@
tests/integration/test_lang/test_generated_u32.py: venv/.done tests/integration/test_lang/generator.py tests/integration/test_lang/generator.md tests/integration/test_lang/generator_u32.json tests/integration/test_lang/test_generated_%.py: venv/.done tests/integration/test_lang/generator.py tests/integration/test_lang/generator.md tests/integration/test_lang/generator_%.json
venv/bin/python3 tests/integration/test_lang/generator.py tests/integration/test_lang/generator.md tests/integration/test_lang/generator_u32.json > $@ venv/bin/python3 tests/integration/test_lang/generator.py tests/integration/test_lang/generator.md tests/integration/test_lang/generator_$*.json > $@
clean-examples: clean-examples:
rm -f examples/*.wat examples/*.wasm examples/*.wat.html examples/*.py.html rm -f examples/*.wat examples/*.wasm examples/*.wat.html examples/*.py.html
clean-generated-tests:
rm -f tests/integration/test_lang/test_generated_*.py
.SECONDARY: # Keep intermediate files .SECONDARY: # Keep intermediate files
.PHONY: examples .PHONY: examples

View File

@ -392,10 +392,10 @@ class LiteralFitsConstraint(ConstraintBase):
if isinstance(self.type3, types.AppliedType3): if isinstance(self.type3, types.AppliedType3):
if self.type3.base == types.tuple: if self.type3.base == types.tuple:
if not isinstance(self.literal, ourlang.ConstantTuple): if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple') return Error('Must be tuple', comment=self.comment)
if len(self.type3.args) != len(self.literal.value): if len(self.type3.args) != len(self.literal.value):
return Error('Tuple element count mismatch') return Error('Tuple element count mismatch', comment=self.comment)
res = [] res = []
@ -412,13 +412,13 @@ class LiteralFitsConstraint(ConstraintBase):
if self.type3.base == types.static_array: if self.type3.base == types.static_array:
if not isinstance(self.literal, ourlang.ConstantTuple): if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple') return Error('Must be tuple', comment=self.comment)
assert 2 == len(self.type3.args) assert 2 == len(self.type3.args)
assert isinstance(self.type3.args[1], types.IntType3) assert isinstance(self.type3.args[1], types.IntType3)
if self.type3.args[1].value != len(self.literal.value): if self.type3.args[1].value != len(self.literal.value):
return Error('Member count mismatch') return Error('Member count mismatch', comment=self.comment)
res = [] res = []

View File

@ -26,7 +26,10 @@ def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase
def constant(ctx: Context, inp: ourlang.Constant) -> ConstraintGenerator: def constant(ctx: Context, inp: ourlang.Constant) -> ConstraintGenerator:
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)): if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)):
yield LiteralFitsConstraint(inp.type3, inp) yield LiteralFitsConstraint(
inp.type3, inp,
comment='The given literal must fit the expected type'
)
return return
raise NotImplementedError(constant, inp) raise NotImplementedError(constant, inp)

View File

@ -1,15 +1,28 @@
# module_constant_def_ok # module_constant_def_ok
As a developer
I want to define $TYPE module constants
In order to make hardcoded values more visible
and to make it easier to change hardcoded values
```py ```py
CONSTANT: $TYPE = $VAL0 CONSTANT: $TYPE = $VAL0
@exported @exported
def testEntry() -> i32: def testEntry() -> i32:
return 0 return 9
```
```py
expect(9)
``` ```
# module_constant_def_bad # module_constant_def_bad
As a developer
I want to receive a type error on an invalid assignment on a $TYPE module constant
In order to make debugging easier
```py ```py
CONSTANT: $OTHER_TYPE = $VAL0 CONSTANT: $OTHER_TYPE = $VAL0
@ -17,3 +30,16 @@ CONSTANT: $OTHER_TYPE = $VAL0
def testEntry() -> i32: def testEntry() -> i32:
return 0 return 0
``` ```
```py
if TYPE_NAME.startswith('tuple_'):
expect_type_error(
'Tuple element count mismatch',
'The given literal must fit the expected type',
)
else:
expect_type_error(
'Must be tuple',
'The given literal must fit the expected type',
)
```

View File

@ -1,7 +1,9 @@
import functools
import json import json
import sys import sys
import marko import marko
import marko.md_renderer
def get_tests(template): def get_tests(template):
test_data = None test_data = None
@ -28,7 +30,30 @@ def apply_settings(settings, txt):
txt = txt.replace(f'${k}', v) txt = txt.replace(f'${k}', v)
return txt return txt
def generate_code(template, settings): def generate_assertion_expect(result, arg):
result.append('result = Suite(code_py).run_code()')
result.append(f'assert {repr(arg)} == result.returned_value')
def generate_assertion_expect_type_error(result, error_msg, error_comment = None):
result.append('with pytest.raises(Type3Exception) as exc_info:')
result.append(' Suite(code_py).run_code()')
result.append(f'assert {repr(error_msg)} == exc_info.value.args[0][0].msg')
result.append(f'assert {repr(error_comment)} == exc_info.value.args[0][0].comment')
def generate_assertions(settings, result_code):
result = []
locals_ = {
'TYPE_NAME': settings['TYPE_NAME'],
'expect': functools.partial(generate_assertion_expect, result),
'expect_type_error': functools.partial(generate_assertion_expect_type_error, result),
}
exec(result_code, {}, locals_)
return ' ' + '\n '.join(result) + '\n'
def generate_code(markdown, template, settings):
type_name = settings['TYPE_NAME'] type_name = settings['TYPE_NAME']
print('"""') print('"""')
@ -39,37 +64,45 @@ def generate_code(template, settings):
print('"""') print('"""')
print('import pytest') print('import pytest')
print() print()
print('from phasm.type3.entry import Type3Exception')
print()
print('from ..helpers import Suite') print('from ..helpers import Suite')
print() print()
for test in get_tests(template): for test in get_tests(template):
assert len(test) == 2, test assert len(test) == 4, test
heading, code_block = test heading, paragraph, code_block1, code_block2 = test
assert isinstance(heading, marko.block.Heading) assert isinstance(heading, marko.block.Heading)
assert isinstance(code_block, marko.block.FencedCode) assert isinstance(paragraph, marko.block.Paragraph)
assert isinstance(code_block1, marko.block.FencedCode)
assert isinstance(code_block2, marko.block.FencedCode)
test_id = apply_settings(settings, heading.children[0].children) test_id = apply_settings(settings, heading.children[0].children)
code = apply_settings(settings, code_block.children[0].children) user_story = apply_settings(settings, markdown.renderer.render(paragraph))
inp_code = apply_settings(settings, code_block1.children[0].children)
code = code.rstrip('\n')
result_code = markdown.renderer.render_children(code_block2)
print('@pytest.mark.integration_test') print('@pytest.mark.integration_test')
print(f'def test_{type_name}_{test_id}():') print(f'def test_{type_name}_{test_id}():')
print(' """')
print(' ' + user_story.replace('\n', '\n '))
print(' """')
print(' code_py = """') print(' code_py = """')
print(code) print(inp_code.rstrip('\n'))
print('"""') print('"""')
print() print()
print(' result = Suite(code_py).run_code()') print(generate_assertions(settings, result_code))
print()
print(' assert 24 == result.returned_value')
print() print()
def main(): def main():
markdown = marko.Markdown(
renderer=marko.md_renderer.MarkdownRenderer,
)
with open(sys.argv[1], 'r', encoding='utf-8') as fil: with open(sys.argv[1], 'r', encoding='utf-8') as fil:
template = marko.Markdown().parse(fil.read()) template = markdown.parse(fil.read())
with open(sys.argv[2], 'r', encoding='utf-8') as fil: with open(sys.argv[2], 'r', encoding='utf-8') as fil:
settings = json.load(fil) settings = json.load(fil)
@ -79,7 +112,7 @@ def main():
settings['OTHER_TYPE'] = '(u32, )' settings['OTHER_TYPE'] = '(u32, )'
generate_code(template, settings) generate_code(markdown, template, settings)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -0,0 +1,5 @@
{
"TYPE_NAME": "tuple_u64_u32_u8",
"TYPE": "(u64, u32, u8, )",
"VAL0": "(1000000, 1000, 1, )"
}