type5 is much more first principles based, so we get a lot of weird quirks removed: - FromLiteral no longer needs to understand AST - Type unifications works more like Haskell - Function types are just ordinary types, saving a lot of manual busywork and more.
154 lines
4.4 KiB
Python
154 lines
4.4 KiB
Python
import functools
|
|
import json
|
|
import sys
|
|
from typing import Any
|
|
|
|
import marko
|
|
import marko.md_renderer
|
|
|
|
|
|
def get_tests(template):
|
|
test_data = None
|
|
for el in template.children:
|
|
if isinstance(el, marko.block.BlankLine):
|
|
continue
|
|
|
|
if isinstance(el, marko.block.Heading):
|
|
if test_data is not None:
|
|
yield test_data
|
|
|
|
test_data = []
|
|
test_data.append(el)
|
|
continue
|
|
|
|
if test_data is not None:
|
|
test_data.append(el)
|
|
|
|
if test_data is not None:
|
|
yield test_data
|
|
|
|
def apply_settings(settings, txt):
|
|
for k, v in settings.items():
|
|
if k in ('CODE_HEADER', 'PYTHON'):
|
|
continue
|
|
|
|
txt = txt.replace(f'${k}', v)
|
|
return txt
|
|
|
|
def generate_assertion_expect(result, arg, given=None):
|
|
given = given or []
|
|
|
|
result.append('result = Suite(code_py).run_code(' + ', '.join(repr(x) for x in given) + ')')
|
|
result.append(f'assert {repr(arg)} == result.returned_value')
|
|
|
|
def generate_assertion_expect_type_error(result, error_msg):
|
|
result.append(f'with pytest.raises(Type5SolverException, match={error_msg!r}):')
|
|
result.append(' Suite(code_py).run_code()')
|
|
|
|
def json_does_not_support_byte_or_tuple_values_fix(inp: Any):
|
|
if isinstance(inp, (int, float, )):
|
|
return inp
|
|
|
|
if isinstance(inp, str):
|
|
if inp.startswith('bytes:'):
|
|
return inp[6:].encode()
|
|
return inp
|
|
|
|
if isinstance(inp, list):
|
|
return tuple(map(json_does_not_support_byte_or_tuple_values_fix, inp))
|
|
|
|
if isinstance(inp, dict):
|
|
return {
|
|
key: json_does_not_support_byte_or_tuple_values_fix(val)
|
|
for key, val in inp.items()
|
|
}
|
|
|
|
raise NotImplementedError(inp)
|
|
|
|
def generate_assertions(settings, result_code):
|
|
result = []
|
|
|
|
locals_ = {
|
|
'TYPE': settings['TYPE'],
|
|
'TYPE_NAME': settings['TYPE_NAME'],
|
|
'expect': functools.partial(generate_assertion_expect, result),
|
|
'expect_type_error': functools.partial(generate_assertion_expect_type_error, result),
|
|
}
|
|
|
|
if 'PYTHON' in settings:
|
|
locals_.update(json_does_not_support_byte_or_tuple_values_fix(settings['PYTHON']))
|
|
|
|
if 'VAL0' not in locals_:
|
|
locals_['VAL0'] = eval(settings['VAL0'])
|
|
|
|
exec(result_code, {}, locals_)
|
|
|
|
return ' ' + '\n '.join(result) + '\n'
|
|
|
|
def generate_code(markdown, template, settings):
|
|
type_name = settings['TYPE_NAME']
|
|
|
|
print('"""')
|
|
print('AUTO GENERATED')
|
|
print()
|
|
print('TEMPLATE:', sys.argv[1])
|
|
print('SETTINGS:', sys.argv[2])
|
|
print('"""')
|
|
print('import pytest')
|
|
print()
|
|
print('from phasm.type5.solver import Type5SolverException')
|
|
print()
|
|
print('from ..helpers import Suite')
|
|
print()
|
|
print()
|
|
|
|
for test in get_tests(template):
|
|
assert len(test) == 4, test
|
|
heading, paragraph, code_block1, code_block2 = test
|
|
|
|
assert isinstance(heading, marko.block.Heading)
|
|
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)
|
|
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.strip().replace('\n', '\n '))
|
|
print(' """')
|
|
print(' code_py = """')
|
|
if 'CODE_HEADER' in settings:
|
|
for lin in settings['CODE_HEADER']:
|
|
print(lin)
|
|
print()
|
|
print(inp_code.rstrip('\n'))
|
|
print('"""')
|
|
print()
|
|
|
|
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 = markdown.parse(fil.read())
|
|
|
|
with open(sys.argv[2], 'r', encoding='utf-8') as fil:
|
|
settings = json.load(fil)
|
|
|
|
if 'TYPE_NAME' not in settings:
|
|
settings['TYPE_NAME'] = settings['TYPE']
|
|
|
|
generate_code(markdown, template, settings)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|