diff --git a/absl/testing/absltest.py b/absl/testing/absltest.py index 4e3c8e5..70b4065 100644 --- a/absl/testing/absltest.py +++ b/absl/testing/absltest.py @@ -582,6 +582,10 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) # This is to work around missing type stubs in unittest.pyi self._outcome: Optional[Any] = getattr(self, '_outcome') + # A dict[str, Any] of properties for the test itself. + self.__recorded_properties = {} + # A dict of properties dicts per subtest, keyed by the subtest's id(). + self.__subtest_properties = collections.defaultdict(dict) def setUp(self): super().setUp() @@ -604,6 +608,47 @@ def setUpClass(cls): cls._cls_exit_stack = contextlib.ExitStack() cls.addClassCleanup(cls._cls_exit_stack.close) + def getRecordedProperties(self, subtest=None): + """Returns any properties that the user has recorded. + + If called from within a subtest and subtest is None, returns a dict made up + of *both* the subtest's properties *and* the outer test's properties. (The + subtest's properties take precedence in the event of a conflict.) + + Args: + subtest: the subtest whose properties should be returned (default: None) + Unlike the default case, if subtest is set, *only* that subtest's + properties are returned, not those of the enclosing test. + """ + if subtest: + # Return *only* subtest properties + return self.__subtest_properties[subtest.id()].copy() + ret = self.__recorded_properties.copy() + if hasattr(self, '_subtest') and self._subtest: + ret |= self.__subtest_properties[self._subtest.id()] + return ret + + def recordProperty(self, property_name, property_value): + """Record an arbitrary property for later use. + + The property is attributed to the currently active subtest, if any, + or the test itself, otherwise. + + Args: + property_name: str, name of property to record; must be a valid XML + attribute name. + property_value: value of property; must be valid XML attribute value. + """ + # The following avoids breakage due to a two-arg call to + # getRecordedProperties in subclasses of absltest.TestCase that override it + # with the legacy one-arg signature but don't override recordProperty, so + # long as tests based on those subclasses have no subTests. + if hasattr(self, '_subtest') and self._subtest: + recorded_properties = self.__subtest_properties[self._subtest.id()] + else: + recorded_properties = self.__recorded_properties + recorded_properties[property_name] = property_value + def create_tempdir( self, name: Optional[str] = None, diff --git a/absl/testing/tests/absltest_test.py b/absl/testing/tests/absltest_test.py index 20cb54b..946feb0 100644 --- a/absl/testing/tests/absltest_test.py +++ b/absl/testing/tests/absltest_test.py @@ -182,6 +182,50 @@ def test_flags_env_var_flags(self): expect_success=True, ) + def testRecordedProperties(self): + """Tests that a test can record a property and then retrieve it.""" + self.recordProperty('test_property', 'test_value') + self.assertEqual( + self.getRecordedProperties(), {'test_property': 'test_value'} + ) + + def testRecordedPropertiesSubTest(self): + """Tests that a test can record a subtest's property and retrieve it.""" + self.recordProperty('test_property1', 'test_value1') + subtest1 = None + with self.subTest(name='sub1'): + assert hasattr(self, '_subtest') + subtest1 = self._subtest + self.assertIsNotNone(subtest1) + self.recordProperty('subtest_property', 'sub_value1') + self.assertEqual( + self.getRecordedProperties(), + {'test_property1': 'test_value1', 'subtest_property': 'sub_value1'}, + ) + subtest2 = None + with self.subTest(name='sub2'): + assert hasattr(self, '_subtest') + subtest2 = self._subtest + self.assertIsNotNone(subtest2) + self.recordProperty('subtest_property', 'sub_value2') + self.assertEqual( + self.getRecordedProperties(), + {'test_property1': 'test_value1', 'subtest_property': 'sub_value2'}, + ) + self.recordProperty('test_property2', 'test_value2') + self.assertEqual( + self.getRecordedProperties(), + {'test_property1': 'test_value1', 'test_property2': 'test_value2'}, + ) + # Verify that subtest1 and subtest2 have different dict entries and don't + # overwrite each others' values. + self.assertEqual( + self.getRecordedProperties(subtest1), {'subtest_property': 'sub_value1'} + ) + self.assertEqual( + self.getRecordedProperties(subtest2), {'subtest_property': 'sub_value2'} + ) + def test_xml_output_file_from_xml_output_file_env(self): xml_dir = tempfile.mkdtemp(dir=absltest.TEST_TMPDIR.value) xml_output_file_env = os.path.join(xml_dir, 'xml_output_file.xml')