diff --git a/Makefile b/Makefile index 3fd5cf5..d2dc3a6 100644 --- a/Makefile +++ b/Makefile @@ -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)) 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) lint: venv/.done @@ -39,12 +39,15 @@ venv/.done: requirements.txt venv/bin/python3 -m pip install -r $^ 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 - venv/bin/python3 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_$*.json > $@ clean-examples: 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 .PHONY: examples diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index a5017fb..edfdca6 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -392,10 +392,10 @@ class LiteralFitsConstraint(ConstraintBase): if isinstance(self.type3, types.AppliedType3): if self.type3.base == types.tuple: 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): - return Error('Tuple element count mismatch') + return Error('Tuple element count mismatch', comment=self.comment) res = [] @@ -412,13 +412,13 @@ class LiteralFitsConstraint(ConstraintBase): if self.type3.base == types.static_array: 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 isinstance(self.type3.args[1], types.IntType3) if self.type3.args[1].value != len(self.literal.value): - return Error('Member count mismatch') + return Error('Member count mismatch', comment=self.comment) res = [] diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index 134da41..9f4fe9f 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -26,7 +26,10 @@ def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase def constant(ctx: Context, inp: ourlang.Constant) -> ConstraintGenerator: 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 raise NotImplementedError(constant, inp) diff --git a/tests/integration/test_lang/generator.md b/tests/integration/test_lang/generator.md index 24949a4..43636a0 100644 --- a/tests/integration/test_lang/generator.md +++ b/tests/integration/test_lang/generator.md @@ -1,15 +1,28 @@ # 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 CONSTANT: $TYPE = $VAL0 @exported def testEntry() -> i32: - return 0 + return 9 +``` + +```py +expect(9) ``` # 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 CONSTANT: $OTHER_TYPE = $VAL0 @@ -17,3 +30,16 @@ CONSTANT: $OTHER_TYPE = $VAL0 def testEntry() -> i32: 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', + ) +``` diff --git a/tests/integration/test_lang/generator.py b/tests/integration/test_lang/generator.py index 443a17d..5538fd5 100644 --- a/tests/integration/test_lang/generator.py +++ b/tests/integration/test_lang/generator.py @@ -1,7 +1,9 @@ +import functools import json import sys import marko +import marko.md_renderer def get_tests(template): test_data = None @@ -28,7 +30,30 @@ def apply_settings(settings, txt): txt = txt.replace(f'${k}', v) 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'] print('"""') @@ -39,37 +64,45 @@ def generate_code(template, settings): print('"""') print('import pytest') print() + print('from phasm.type3.entry import Type3Exception') + print() print('from ..helpers import Suite') print() for test in get_tests(template): - assert len(test) == 2, test - heading, code_block = test + assert len(test) == 4, test + heading, paragraph, code_block1, code_block2 = test 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) - code = apply_settings(settings, code_block.children[0].children) - - code = code.rstrip('\n') + user_story = apply_settings(settings, markdown.renderer.render(paragraph)) + inp_code = apply_settings(settings, code_block1.children[0].children) + result_code = markdown.renderer.render_children(code_block2) print('@pytest.mark.integration_test') print(f'def test_{type_name}_{test_id}():') + print(' """') + print(' ' + user_story.replace('\n', '\n ')) + print(' """') print(' code_py = """') - print(code) + print(inp_code.rstrip('\n')) print('"""') print() - print(' result = Suite(code_py).run_code()') - print() - print(' assert 24 == result.returned_value') + print(generate_assertions(settings, result_code)) print() def main(): + markdown = marko.Markdown( + renderer=marko.md_renderer.MarkdownRenderer, + ) 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: settings = json.load(fil) @@ -79,7 +112,7 @@ def main(): settings['OTHER_TYPE'] = '(u32, )' - generate_code(template, settings) + generate_code(markdown, template, settings) if __name__ == '__main__': main() \ No newline at end of file diff --git a/tests/integration/test_lang/generator_tuple_u64_u32_u8.json b/tests/integration/test_lang/generator_tuple_u64_u32_u8.json new file mode 100644 index 0000000..bf404a0 --- /dev/null +++ b/tests/integration/test_lang/generator_tuple_u64_u32_u8.json @@ -0,0 +1,5 @@ +{ + "TYPE_NAME": "tuple_u64_u32_u8", + "TYPE": "(u64, u32, u8, )", + "VAL0": "(1000000, 1000, 1, )" +}