def test_simple_values(self): self.walker = BasicValueWalker(FrameVarInfoForTesting()) values = [ # numbers 0, 1, -1234567890412345243, float(4.2), float("inf"), complex(1.3, -1), # strings "", "a", "foo bar", " lots\tof\nspaces\r ", "♫", # other False, True, None, ] for label, value in enumerate(values): with self.patched_logging(): self.walker.walk_value(parent=None, label=str(label), value=value) expected = [(str(_label), repr(x)) for _label, x in enumerate(values)] received = self.walked_values() self.assertListEqual(expected, received)
def assert_walks_contents(self, container, label="xs"): expand_paths = {label} self.values_to_expand = [container] self.walker = BasicValueWalker(FrameVarInfoForTesting(expand_paths)) # Build out list of expected view contents according to container type. expected = [(label, self.value_string(container))] if isinstance(container, PudbMapping): expected.extend([(f"[{repr(key)}]", repr(container[key])) for key in container.keys()] or [self.EMPTY_ITEM]) self.class_counts["mappings"] += 1 elif isinstance(container, PudbSequence): expected.extend([(f"[{repr(index)}]", repr(entry)) for index, entry in enumerate(container)] or [self.EMPTY_ITEM]) self.class_counts["sequences"] += 1 elif isinstance(container, PudbCollection): expected.extend([("[]", repr(entry)) for entry in container] or [self.EMPTY_ITEM]) self.class_counts["collections"] += 1 else: self.class_counts["other"] += 1 expected.extend(self.expected_attrs(container)) with self.patched_logging(): self.walker.walk_value(parent=None, label=label, value=container) received = self.walked_values() self.assertListEqual(expected, received)
class ValueWalkerTest(BaseValueWalkerTestCase): def test_simple_values(self): self.walker = BasicValueWalker(FrameVarInfoForTesting()) values = [ # numbers 0, 1, -1234567890412345243, float(4.2), float("inf"), complex(1.3, -1), # strings "", "a", "foo bar", " lots\tof\nspaces\r ", "♫", # other False, True, None, ] for label, value in enumerate(values): with self.patched_logging(): self.walker.walk_value(parent=None, label=str(label), value=value) expected = [(str(_label), repr(x)) for _label, x in enumerate(values)] received = self.walked_values() self.assertListEqual(expected, received) def test_simple_values_expandable(self): """ Simple values like numbers and strings are now expandable so we can peak under the hood and take a look at their attributes. Make sure that's working properly. """ values = [ # numbers 0, 1, -1234567890412345243, float(4.2), float("inf"), complex(1.3, -1), # strings "", "a", "foo bar", # " lots\tof\nspaces\r ", # long, hits continuation item "♫", # other False, True, None, ] for value in values: self.assert_walks_contents(value) def test_set(self): self.assert_walks_contents( {42, "foo", None, False, (), ("a", "tuple")}) self.assert_class_counts_equal({"collections": 1}) def test_frozenset(self): self.assert_walks_contents( frozenset([42, "foo", None, False, (), ("a", "tuple")])) self.assert_class_counts_equal({"collections": 1}) def test_dict(self): self.assert_walks_contents({ 0: 42, "a": "foo", "": None, True: False, frozenset(range(3)): "abc", (): "empty tuple", (1, 2, "c", ()): "tuple", }) self.assert_class_counts_equal({"mappings": 1}) def test_list(self): self.assert_walks_contents( [42, "foo", None, False, (), ("a", "tuple")]) self.assert_class_counts_equal({"sequences": 1}) def test_tuple(self): self.assert_walks_contents( (42, "foo", None, False, (), ("a", "tuple"))) self.assert_class_counts_equal({"sequences": 1}) def test_containerlike_classes(self): class_count = 0 for cls_idx, containerlike_class in enumerate( generate_containerlike_class()): label = containerlike_class.name() value = containerlike_class( zip(string.ascii_lowercase, range(3, 10))) self.assert_walks_contents(container=value, label=label) class_count = cls_idx + 1 self.assert_class_counts_equal({ "mappings": 256, "sequences": 256, "collections": 256, "other": 1280, }) walked_total = (self.class_counts["mappings"] + self.class_counts["sequences"] + self.class_counts["collections"] + self.class_counts["other"]) # +1 here because enumerate starts from 0, not 1 self.assertEqual(class_count, walked_total) def test_empty_frozenset(self): self.assert_walks_contents(frozenset()) def test_empty_set(self): self.assert_walks_contents(set()) def test_empty_dict(self): self.assert_walks_contents(dict()) def test_empty_list(self): self.assert_walks_contents(list()) def test_reasonable_class(self): """ Are the class objects themselves expandable? """ self.assert_walks_contents(Reasonable, label="Reasonable") self.assert_class_counts_equal({"other": 1}) def test_maybe_unreasonable_classes(self): """ Are class objects, that might look like containers if we're not careful, reasonably expandable? """ for containerlike_class in generate_containerlike_class(): self.assert_walks_contents(container=containerlike_class, label=containerlike_class.name()) # This effectively makes sure that class definitions aren't considered # containers. self.assert_class_counts_equal({"other": 2048})
class BaseValueWalkerTestCase(unittest.TestCase): """ There are no actual tests defined in this class, it provides utilities useful for testing the variable view in various ways. """ EMPTY_ITEM = (ValueWalker.EMPTY_LABEL, None) MOD_STR = " [all+()]" def setUp(self): self.values_to_expand = [] self.class_counts = { "mappings": 0, "sequences": 0, "collections": 0, "other": 0, } def value_string(self, obj, expand=True): if expand and obj in self.values_to_expand: return repr(obj) + self.MOD_STR return repr(obj) def walked_values(self): return [(w.var_label, w.value_str) for w in self.walker.widget_list] def expected_attrs(self, obj): """ `dir()` the object and return (label, value string) pairs for each attribute. Should match the order that these attributes would appear in the var_view. """ return [("." + str(label), self.value_string(getattr(obj, label), expand=False)) for label in sorted(dir(obj))] @contextlib.contextmanager def patched_logging(self): """ Context manager that patches ui_log.exception such that the test will fail if it is called. """ def fake_exception_log(*args, **kwargs): self.fail("ui_log.exception was unexpectedly called") old_logger = ui_log.exception ui_log.exception = fake_exception_log try: yield finally: ui_log.exception = old_logger def assert_walks_contents(self, container, label="xs"): expand_paths = {label} self.values_to_expand = [container] self.walker = BasicValueWalker(FrameVarInfoForTesting(expand_paths)) # Build out list of expected view contents according to container type. expected = [(label, self.value_string(container))] if isinstance(container, PudbMapping): expected.extend([(f"[{repr(key)}]", repr(container[key])) for key in container.keys()] or [self.EMPTY_ITEM]) self.class_counts["mappings"] += 1 elif isinstance(container, PudbSequence): expected.extend([(f"[{repr(index)}]", repr(entry)) for index, entry in enumerate(container)] or [self.EMPTY_ITEM]) self.class_counts["sequences"] += 1 elif isinstance(container, PudbCollection): expected.extend([("[]", repr(entry)) for entry in container] or [self.EMPTY_ITEM]) self.class_counts["collections"] += 1 else: self.class_counts["other"] += 1 expected.extend(self.expected_attrs(container)) with self.patched_logging(): self.walker.walk_value(parent=None, label=label, value=container) received = self.walked_values() self.assertListEqual(expected, received) def assert_class_counts_equal(self, seen=None): """ This is kinda weird since at first it looks like its testing the test code, but really it's testing the `isinstance` checks. But it is also true that it tests the test code, kind of like a sanity check. """ expected = { "mappings": 0, "sequences": 0, "collections": 0, "other": 0, } if seen is not None: expected.update(seen) self.assertDictEqual(expected, self.class_counts)