diff --git a/src/oxygen/oxygen_handler_result.py b/src/oxygen/oxygen_handler_result.py index 1702e32..97aa84b 100644 --- a/src/oxygen/oxygen_handler_result.py +++ b/src/oxygen/oxygen_handler_result.py @@ -12,7 +12,7 @@ import functools -from typing import List +from typing import List, Dict # TODO FIXME: Python 3.10 requires these to be imported from here # Python 3.10 EOL is in 2026 from typing_extensions import TypedDict, Required @@ -45,6 +45,7 @@ class OxygenSuiteDict(TypedDict, total=False): tags: List[str] setup: OxygenKeywordDict teardown: OxygenKeywordDict + metadata: Dict[str, str] suites: List['OxygenSuiteDict'] tests: List[OxygenTestCaseDict] diff --git a/src/oxygen/robot3_interface.py b/src/oxygen/robot3_interface.py index 023abf9..ea36d8c 100644 --- a/src/oxygen/robot3_interface.py +++ b/src/oxygen/robot3_interface.py @@ -44,6 +44,7 @@ def build_suite(self, starting_time, suite): teardown_keyword = suite.get('teardown') or None child_suites = suite.get('suites') or [] tests = suite.get('tests') or [] + metadata = suite.get('metadata') or {} updated_time, robot_setup = self.build_keyword(updated_time, setup_keyword, setup=True) updated_time, robot_teardown = self.build_keyword(updated_time, teardown_keyword, teardown=True) @@ -57,7 +58,8 @@ def build_suite(self, starting_time, suite): robot_setup, robot_teardown, robot_suites, - robot_tests) + robot_tests, + metadata) return updated_time, robot_suite @@ -70,13 +72,15 @@ def spawn_robot_suite(self, setup_keyword, teardown_keyword, suites, - tests): + tests, + metadata): start_timestamp = self.ms_to_timestamp(start_time) end_timestamp = self.ms_to_timestamp(end_time) robot_suite = RobotResultSuite(name, starttime=start_timestamp, - endtime=end_timestamp) + endtime=end_timestamp, + metadata=metadata) robot_suite.set_tags(add=tags, persist=True) if setup_keyword: @@ -342,9 +346,15 @@ def create_wrapper_keyword(self, class RobotRunningInterface(object): def build_suite(self, parsed_results): - robot_root_suite = RobotRunningSuite(parsed_results['name']) + robot_root_suite = RobotRunningSuite( + parsed_results['name'], + metadata=parsed_results.get('metadata', {}) + ) for parsed_suite in parsed_results.get('suites', []): - robot_suite = robot_root_suite.suites.create(parsed_suite['name']) + robot_suite = robot_root_suite.suites.create( + parsed_suite['name'], + metadata=parsed_results.get('metadata', {}) + ) for subsuite in parsed_suite.get('suites', []): robot_subsuite = self.build_suite(subsuite) robot_suite.suites.append(robot_subsuite) diff --git a/src/oxygen/robot4_interface.py b/src/oxygen/robot4_interface.py index c405d5b..d0dcd34 100644 --- a/src/oxygen/robot4_interface.py +++ b/src/oxygen/robot4_interface.py @@ -43,6 +43,7 @@ def build_suite(self, starting_time, suite): teardown_keyword = suite.get('teardown') or None child_suites = suite.get('suites') or [] tests = suite.get('tests') or [] + metadata = suite.get('metadata') or {} updated_time, robot_setup = self.build_keyword(updated_time, setup_keyword, setup=True) updated_time, robot_teardown = self.build_keyword(updated_time, teardown_keyword, teardown=True) @@ -56,7 +57,8 @@ def build_suite(self, starting_time, suite): robot_setup, robot_teardown, robot_suites, - robot_tests) + robot_tests, + metadata) return updated_time, robot_suite @@ -69,13 +71,15 @@ def spawn_robot_suite(self, setup_keyword, teardown_keyword, suites, - tests): + tests, + metadata): start_timestamp = self.ms_to_timestamp(start_time) end_timestamp = self.ms_to_timestamp(end_time) robot_suite = RobotResultSuite(name, starttime=start_timestamp, - endtime=end_timestamp) + endtime=end_timestamp, + metadata=metadata) robot_suite.set_tags(add=tags, persist=True) if setup_keyword: @@ -342,9 +346,15 @@ def create_wrapper_keyword(self, class RobotRunningInterface(object): def build_suite(self, parsed_results): - robot_root_suite = RobotRunningSuite(parsed_results['name']) + robot_root_suite = RobotRunningSuite( + parsed_results['name'], + metadata=parsed_results.get('metadata', {}) + ) for parsed_suite in parsed_results.get('suites', []): - robot_suite = robot_root_suite.suites.create(parsed_suite['name']) + robot_suite = robot_root_suite.suites.create( + parsed_suite['name'], + metadata=parsed_suite.get('metadata', {}) + ) for subsuite in parsed_suite.get('suites', []): robot_subsuite = self.build_suite(subsuite) robot_suite.suites.append(robot_subsuite) diff --git a/tests/utest/helpers.py b/tests/utest/helpers.py index 0646a41..ec3afcc 100644 --- a/tests/utest/helpers.py +++ b/tests/utest/helpers.py @@ -79,6 +79,9 @@ class _TCSubclass(OxygenTestCaseDict): '''Used in test cases''' pass +class _StrSubclass(str): + '''Used in test cases''' + pass GATLING_EXPECTED_OUTPUT = {'name': 'Gatling Scenario', 'tags': ['GATLING'], diff --git a/tests/utest/oxygen_handler_result/shared_tests.py b/tests/utest/oxygen_handler_result/shared_tests.py index f4c1e8e..06e30a0 100644 --- a/tests/utest/oxygen_handler_result/shared_tests.py +++ b/tests/utest/oxygen_handler_result/shared_tests.py @@ -1,13 +1,12 @@ from ..helpers import (MINIMAL_KEYWORD_DICT, _ListSubclass, + _StrSubclass, _KwSubclass) class SharedTestsForName(object): def shared_test_for_name(self): - class StrSubclass(str): - pass - valid_inherited = StrSubclass('someKeyword') - this_is_not_None = StrSubclass(None) + valid_inherited = _StrSubclass('someKeyword') + this_is_not_None = _StrSubclass(None) self.valid_inputs_for('name', '', diff --git a/tests/utest/oxygen_handler_result/test_OxygenSuiteDict.py b/tests/utest/oxygen_handler_result/test_OxygenSuiteDict.py index 8237cd7..f1af4be 100644 --- a/tests/utest/oxygen_handler_result/test_OxygenSuiteDict.py +++ b/tests/utest/oxygen_handler_result/test_OxygenSuiteDict.py @@ -6,6 +6,7 @@ from ..helpers import (MINIMAL_TC_DICT, MINIMAL_SUITE_DICT, _ListSubclass, + _StrSubclass, _TCSubclass) from .shared_tests import (SharedTestsForName, SharedTestsForKeywordField, @@ -79,3 +80,22 @@ def test_validate_oxygen_suite_validates_tests(self): self.invalid_inputs_for('tests', None, [ {} ]) + + def test_validate_oxygen_suite_validates_metadata(self): + class DictSubclass(dict): + pass + inherited_key = _StrSubclass('key') + + this_is_not_None = _StrSubclass(None) + + self.valid_inputs_for('metadata', + {}, + {'': ''}, + {'key': 'value'}, + {_StrSubclass('key'): _StrSubclass('value')}, + DictSubclass(inherited_key=_StrSubclass('value')), + {this_is_not_None: 'value'}) + + self.invalid_inputs_for('metadata', + {'key': None}, + {'key': 1234},) diff --git a/tests/utest/robot_interface/test_robot_interface_basic_usage.py b/tests/utest/robot_interface/test_robot_interface_basic_usage.py index b98a7ce..e63ffd7 100644 --- a/tests/utest/robot_interface/test_robot_interface_basic_usage.py +++ b/tests/utest/robot_interface/test_robot_interface_basic_usage.py @@ -13,6 +13,7 @@ EXAMPLE_SUITES = [{ 'name': 'suite1', 'setup': [], + 'metadata': {'metadata-key': 'metadata-value'}, 'suites': [{'name': 'suite2', 'setup': {'elapsed': 0.0, 'keywords': [], @@ -152,9 +153,12 @@ class RobotInterfaceBasicTests(TestCase): def setUp(self): self.iface = RobotInterface() + def now(self): + return int(round(time() * 1000)) + def test_result_build_suites(self): - now = int(round(time() * 1000)) - _, converted = self.iface.result.build_suites(now, *EXAMPLE_SUITES) + _, converted = self.iface.result.build_suites(self.now(), + *EXAMPLE_SUITES) self.assertIsInstance(converted, list) self.assertEqual(len(converted), 2) @@ -199,6 +203,14 @@ def test_result_create_wrapper_keyword_for_setup(self): from robot.model import BodyItem self.assertEqual(ret.type, BodyItem.SETUP) + def validate_metadata(self, actual): + self.assertEqual(actual.name, EXAMPLE_SUITES[0]['name']) + self.assertEqual(dict(actual.metadata), EXAMPLE_SUITES[0]['metadata']) + + def test_result_build_suite_with_metadata(self): + _, ret = self.iface.result.build_suite(self.now(), EXAMPLE_SUITES[0]) + self.validate_metadata(ret) + def test_result_create_wrapper_keyword_for_teardown(self): ret = self.iface.result.create_wrapper_keyword('My Wrapper', '20200507 13:42:50.001', @@ -219,3 +231,7 @@ def test_running_build_suite(self): self.assertIsInstance(ret, RobotRunningSuite) self.assertEqual(ret.name, EXAMPLE_SUITES[1]['name']) + + def test_running_build_suite_with_metadata(self): + ret = self.iface.running.build_suite(EXAMPLE_SUITES[0]) + self.validate_metadata(ret)