diff --git a/CHANGELOG.md b/CHANGELOG.md index 50f52fa6..157559eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,19 @@ ## Changelog - ### 0.9.16-dev * Fix infinite loop in `lobster-json` on Windows when given absolute paths. +* The `lobster-python` tool now also supports decorators with `.` in + their name. + +* The `lobster-python` tool now adds any tags on the test class to all + tests in that class (in `--activity` mode only). + +* The `lobster-python` tool now supports lists of tags in decorators. + ### 0.9.15 * The `lobster-html-report` tool now respects line-breaks in item diff --git a/Makefile b/Makefile index ba3ed080..c1b41d7e 100644 --- a/Makefile +++ b/Makefile @@ -45,11 +45,12 @@ integration_tests: packages system_tests: make -B -C test-system/lobster-json + make -B -C test-system/lobster-python unit_tests: python3 -m unittest discover -s test-unit -v -test: integration_tests system_tests +test: integration_tests system_tests unit_tests upload_main: packages python3 -m twine upload --repository pypi packages/*/dist/* diff --git a/lobster/tools/python/python.py b/lobster/tools/python/python.py index 9176ea56..21b21662 100755 --- a/lobster/tools/python/python.py +++ b/lobster/tools/python/python.py @@ -37,6 +37,9 @@ def parse_value(val): if isinstance(val, cst.SimpleString): return val.value[1:-1] + elif isinstance(val, cst.List): + return [parse_value(item.value) + for item in val.elements] else: rv = str(val.value) if rv == "None": @@ -139,8 +142,11 @@ def to_lobster(self, schema, items): node.to_lobster(schema, class_contents) # If we're extracting pyunit items, then we always ignore - # classes. + # classes, but we do add our tags to all the tests. if schema is Activity: + for item in class_contents: + for tag in self.tags: + item.add_tracing_target(tag) items += class_contents return @@ -245,17 +251,28 @@ def __init__(self, file_name, options): self.dec_arg_name = options["dec_arg_name"] self.dec_arg_version = options["dec_arg_version"] + def parse_dotted_name(self, name): + if isinstance(name, cst.Call): + return self.parse_dotted_name(name.func) + elif isinstance(name, cst.Name): + return name.value + elif isinstance(name, cst.Attribute): + # value -- prefix + # attr -- postfix + return "%s.%s" % (self.parse_dotted_name(name.value), + self.parse_dotted_name(name.attr)) + else: + return None + def parse_decorators(self, decorators): for dec in decorators: - if isinstance(dec.decorator, (cst.Name, cst.Attribute)): + dec_name = self.parse_dotted_name(dec.decorator) + if dec_name is None: continue - else: - assert isinstance(dec.decorator, cst.Call) - dec_name = dec.decorator.func.value - if dec_name != self.decorator_name: - continue - dec_args = {arg.keyword.value: parse_value(arg.value) - for arg in dec.decorator.args} + if dec_name != self.decorator_name: + continue + dec_args = {arg.keyword.value: parse_value(arg.value) + for arg in dec.decorator.args} # TODO: Better error messages if these assumptions are # violated @@ -265,11 +282,17 @@ def parse_decorators(self, decorators): tag = Tracing_Tag(self.namespace, dec_args[self.dec_arg_name], dec_args.get(self.dec_arg_version, None)) + self.current_node.register_tag(tag) + + elif isinstance(dec_args[self.dec_arg_name], list): + for item in dec_args[self.dec_arg_name]: + tag = Tracing_Tag(self.namespace, item) + self.current_node.register_tag(tag) + else: tag = Tracing_Tag(self.namespace, dec_args[self.dec_arg_name]) - - self.current_node.register_tag(tag) + self.current_node.register_tag(tag) def visit_ClassDef(self, node): line = self.get_metadata(PositionProvider, node).start.line diff --git a/test-system/lobster-python/Makefile b/test-system/lobster-python/Makefile new file mode 100644 index 00000000..43f9e485 --- /dev/null +++ b/test-system/lobster-python/Makefile @@ -0,0 +1,12 @@ +TOOL=../../lobster-python + +TARGETS=$(addsuffix .output, $(basename $(wildcard *.py))) + +all: $(TARGETS) + +%.output: %.py + @touch $*.lobster + -$(TOOL) $(shell head -1 $< | tail --bytes=+2) $< --out=$*.lobster --single > $@ 2>&1 + @echo "==========" >> $@ + @cat $*.lobster >> $@ + @rm $*.lobster diff --git a/test-system/lobster-python/basic.output b/test-system/lobster-python/basic.output new file mode 100644 index 00000000..e622e4c5 --- /dev/null +++ b/test-system/lobster-python/basic.output @@ -0,0 +1,66 @@ +Written output for 3 items to basic.lobster +========== +{ + "data": [ + { + "tag": "python basic.trlc_reference", + "location": { + "kind": "file", + "file": "basic.py", + "line": 5, + "column": null + }, + "name": "basic.trlc_reference", + "messages": [], + "just_up": [ + "helper function" + ], + "just_down": [], + "just_global": [], + "language": "Python", + "kind": "Function" + }, + { + "tag": "python basic.Example.helper_function", + "location": { + "kind": "file", + "file": "basic.py", + "line": 13, + "column": null + }, + "name": "basic.Example.helper_function", + "messages": [], + "just_up": [], + "just_down": [], + "just_global": [], + "refs": [ + "req example.req_nor" + ], + "language": "Python", + "kind": "Method" + }, + { + "tag": "python basic.Example.nor", + "location": { + "kind": "file", + "file": "basic.py", + "line": 17, + "column": null + }, + "name": "basic.Example.nor", + "messages": [], + "just_up": [], + "just_down": [], + "just_global": [], + "refs": [ + "req example.req_nor" + ], + "language": "Python", + "kind": "Method" + } + ], + "generator": "lobster_python", + "schema": "lobster-imp-trace", + "version": 3 +} + diff --git a/test-system/lobster-python/basic.py b/test-system/lobster-python/basic.py new file mode 100644 index 00000000..c87f1e4f --- /dev/null +++ b/test-system/lobster-python/basic.py @@ -0,0 +1,22 @@ +# --parse-decorator trlc_reference requirement + +import potatolib + +def trlc_reference(requirement): + # lobster-exclude: helper function + def decorator(obj): + return obj + return decorator + +class Example: + @trlc_reference(requirement="example.req_nor") + def helper_function(a, b): + # potato + return a or b + + def nor(a, b): + # lobster-trace: example.req_nor + assert isinstance(a, bool) + assert isinstance(b, bool) + + return not helper_function(a, b) diff --git a/test-system/lobster-python/pytest_mark.output b/test-system/lobster-python/pytest_mark.output new file mode 100644 index 00000000..f0fd12cf --- /dev/null +++ b/test-system/lobster-python/pytest_mark.output @@ -0,0 +1,51 @@ +Written output for 2 items to pytest_mark.lobster +========== +{ + "data": [ + { + "tag": "pyunit pytest_mark.TestFrameServerProcessesStart.test_1", + "location": { + "kind": "file", + "file": "pytest_mark.py", + "line": 21, + "column": null + }, + "name": "pytest_mark.TestFrameServerProcessesStart.test_1", + "messages": [], + "just_up": [], + "just_down": [], + "just_global": [], + "refs": [ + "req 12345" + ], + "framework": "PyUnit", + "kind": "Test", + "status": null + }, + { + "tag": "pyunit pytest_mark.TestFrameServerProcessesStart.test_2", + "location": { + "kind": "file", + "file": "pytest_mark.py", + "line": 24, + "column": null + }, + "name": "pytest_mark.TestFrameServerProcessesStart.test_2", + "messages": [], + "just_up": [], + "just_down": [], + "just_global": [], + "refs": [ + "req 666", + "req 12345" + ], + "framework": "PyUnit", + "kind": "Test", + "status": null + } + ], + "generator": "lobster_python", + "schema": "lobster-act-trace", + "version": 3 +} + diff --git a/test-system/lobster-python/pytest_mark.py b/test-system/lobster-python/pytest_mark.py new file mode 100644 index 00000000..e6e81ed2 --- /dev/null +++ b/test-system/lobster-python/pytest_mark.py @@ -0,0 +1,26 @@ +# --activity --parse-decorator pytest.mark.metadata verifies_tds + +@pytest.mark.metadata( + description="Potato", + verifies=[], + ASIL="QM", + domain="Vegetables", + testType="Requirements-based test", + derivationTechnique="Analysis of requirements", + verifies_tds=[12345], +) +@pytest.mark.supported_potato(["wibble"]) +@pytest.mark.supported_os(["wobble"]) +class TestFrameServerProcessesStart(BaseTestClass): + def setup_test(self): + pass + + def teardown_test(self): + pass + + def test_1(self): + self.assertTrue(1 < 2) + + def test_2(self): + # lobster-trace: 666 + self.assertTrue(1 < 2)