def test_invalidate_partial(self): comparison_pg = ProductGraph(validator=lambda _: True) chain_a = list("ABCDEF") chain_b = list("GHIJKL") # Add two dependency chains to the primary graph. self._mk_chain(self.pg, chain_a) self._mk_chain(self.pg, chain_b) # Add only the dependency chain we won't invalidate to the comparison graph. self._mk_chain(comparison_pg, chain_b) # Invalidate one of the chains in the primary graph from the right-most node. self.pg.invalidate(lambda node: node == chain_a[-1]) # Ensure the final state of the primary graph matches the comparison graph. self.assertEquals(self.pg.completed_nodes(), comparison_pg.completed_nodes()) self.assertEquals(self.pg.dependents(), comparison_pg.dependents()) self.assertEquals(self.pg.dependencies(), comparison_pg.dependencies()) self.assertEquals(self.pg.cyclic_dependencies(), comparison_pg.cyclic_dependencies())
def test_invalidate_partial(self): comparison_pg = ProductGraph(validator=lambda _: True) chain_a = list('ABCDEF') chain_b = list('GHIJKL') # Add two dependency chains to the primary graph. self._mk_chain(self.pg, chain_a) self._mk_chain(self.pg, chain_b) # Add only the dependency chain we won't invalidate to the comparison graph. self._mk_chain(comparison_pg, chain_b) # Invalidate one of the chains in the primary graph from the right-most node. self.pg.invalidate(lambda node: node == chain_a[-1]) # Ensure the final state of the primary graph matches the comparison graph. self.assertEquals(self.pg.completed_nodes(), comparison_pg.completed_nodes()) self.assertEquals(self.pg.dependents(), comparison_pg.dependents()) self.assertEquals(self.pg.dependencies(), comparison_pg.dependencies()) self.assertEquals(self.pg.cyclic_dependencies(), comparison_pg.cyclic_dependencies())
def setUp(self): self.pg = ProductGraph(validator=lambda _: True) # Allow for string nodes for testing.
class ProductGraphTest(unittest.TestCase): def setUp(self): self.pg = ProductGraph(validator=lambda _: True) # Allow for string nodes for testing. @classmethod def _mk_chain(cls, graph, sequence, states=[Waiting, Return]): """Create a chain of dependencies (e.g. 'A'->'B'->'C'->'D') in the graph from a sequence.""" prior_item = sequence[0] for state in states: for item in sequence: graph.update_state(prior_item, state([item])) prior_item = item return sequence def test_dependency_edges(self): self.pg.update_state("A", Waiting(["B", "C"])) self.assertEquals({"B", "C"}, self.pg.dependencies_of("A")) self.assertEquals({"A"}, self.pg.dependents_of("B")) self.assertEquals({"A"}, self.pg.dependents_of("C")) def test_walk(self): nodes = list("ABCDEF") self._mk_chain(self.pg, nodes) walked_nodes = list((node for (node, _), _ in self.pg.walk(nodes[0]))) self.assertEquals(nodes, walked_nodes) def test_invalidate_all(self): chain_list = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ") invalidators = (self.pg.invalidate, functools.partial(self.pg.invalidate, lambda node: node == "Z")) for invalidator in invalidators: self._mk_chain(self.pg, chain_list) self.assertTrue(self.pg.completed_nodes()) self.assertTrue(self.pg.dependents()) self.assertTrue(self.pg.dependencies()) self.assertTrue(self.pg.cyclic_dependencies()) invalidator() self.assertFalse(self.pg.completed_nodes()) self.assertFalse(self.pg.dependents()) self.assertFalse(self.pg.dependencies()) self.assertFalse(self.pg.cyclic_dependencies()) def test_invalidate_partial(self): comparison_pg = ProductGraph(validator=lambda _: True) chain_a = list("ABCDEF") chain_b = list("GHIJKL") # Add two dependency chains to the primary graph. self._mk_chain(self.pg, chain_a) self._mk_chain(self.pg, chain_b) # Add only the dependency chain we won't invalidate to the comparison graph. self._mk_chain(comparison_pg, chain_b) # Invalidate one of the chains in the primary graph from the right-most node. self.pg.invalidate(lambda node: node == chain_a[-1]) # Ensure the final state of the primary graph matches the comparison graph. self.assertEquals(self.pg.completed_nodes(), comparison_pg.completed_nodes()) self.assertEquals(self.pg.dependents(), comparison_pg.dependents()) self.assertEquals(self.pg.dependencies(), comparison_pg.dependencies()) self.assertEquals(self.pg.cyclic_dependencies(), comparison_pg.cyclic_dependencies()) def test_invalidate_count(self): self._mk_chain(self.pg, list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) invalidated_count = self.pg.invalidate(lambda node: node == "I") self.assertEquals(invalidated_count, 9) def test_invalidate_partial_identity_check(self): # Create a graph with a chain from A..Z. chain = self._mk_chain(self.pg, list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) self.assertTrue(self.pg.completed_nodes()) # Track the pre-invaliation nodes (from A..Q). index_of_q = chain.index("Q") before_nodes = filter(lambda node: node in chain[: index_of_q + 1], self.pg.completed_nodes()) self.assertTrue(before_nodes) # Invalidate all nodes under Q. self.pg.invalidate(lambda node: node == chain[index_of_q]) self.assertTrue(self.pg.completed_nodes()) def _label_tuples(collection, name): for item in collection: yield tuple(list(item) + [name]) # Check that the root node and all fs nodes were removed via a identity checks. chain = itertools.chain( _label_tuples(self.pg.completed_nodes().items(), "completed_nodes"), _label_tuples(self.pg.dependents().items(), "dependents"), _label_tuples(self.pg.dependencies().items(), "dependencies"), _label_tuples(self.pg.cyclic_dependencies().items(), "cyclic_dependencies"), ) for node, associated, collection_name in chain: self.assertFalse( node in before_nodes, "node:\n{}\nwasnt properly removed from {}".format(node, collection_name) ) if isinstance(associated, set): for associated_node in associated: self.assertFalse( associated_node in before_nodes, "node:\n{}\nis still associated with:\n{}\nin {}".format( associated_node, node, collection_name ), )
class ProductGraphTest(unittest.TestCase): def setUp(self): self.pg = ProductGraph(validator=lambda _: True) # Allow for string nodes for testing. @classmethod def _mk_chain(cls, graph, sequence, states=[Waiting, Return]): """Create a chain of dependencies (e.g. 'A'->'B'->'C'->'D') in the graph from a sequence.""" prior_item = sequence[0] for state in states: for item in sequence: graph.update_state(prior_item, state([item])) prior_item = item return sequence def test_dependency_edges(self): self.pg.update_state('A', Waiting(['B', 'C'])) self.assertEquals({'B', 'C'}, set(self.pg.dependencies_of('A'))) self.assertEquals({'A'}, set(self.pg.dependents_of('B'))) self.assertEquals({'A'}, set(self.pg.dependents_of('C'))) def test_cycle_simple(self): self.pg.update_state('A', Waiting(['B'])) self.pg.update_state('B', Waiting(['A'])) # NB: Order matters: the second insertion is the one tracked as a cycle. self.assertEquals({'B'}, set(self.pg.dependencies_of('A'))) self.assertEquals(set(), set(self.pg.dependencies_of('B'))) self.assertEquals(set(), set(self.pg.cyclic_dependencies_of('A'))) self.assertEquals({'A'}, set(self.pg.cyclic_dependencies_of('B'))) def test_cycle_indirect(self): self.pg.update_state('A', Waiting(['B'])) self.pg.update_state('B', Waiting(['C'])) self.pg.update_state('C', Waiting(['A'])) self.assertEquals({'B'}, set(self.pg.dependencies_of('A'))) self.assertEquals({'C'}, set(self.pg.dependencies_of('B'))) self.assertEquals(set(), set(self.pg.dependencies_of('C'))) self.assertEquals(set(), set(self.pg.cyclic_dependencies_of('A'))) self.assertEquals(set(), set(self.pg.cyclic_dependencies_of('B'))) self.assertEquals({'A'}, set(self.pg.cyclic_dependencies_of('C'))) def test_cycle_long(self): # Creating a long chain is allowed. nodes = list(range(0, 100)) self._mk_chain(self.pg, nodes, states=(Waiting,)) walked_nodes = [node for node, _ in self.pg.walk([nodes[0]])] self.assertEquals(nodes, walked_nodes) # Closing the chain is not. begin, end = nodes[0], nodes[-1] self.pg.update_state(end, Waiting([begin])) self.assertEquals(set(), set(self.pg.dependencies_of(end))) self.assertEquals({begin}, set(self.pg.cyclic_dependencies_of(end))) def test_walk(self): nodes = list('ABCDEF') self._mk_chain(self.pg, nodes) walked_nodes = list((node for node, _ in self.pg.walk(nodes[0]))) self.assertEquals(nodes, walked_nodes) def test_invalidate_all(self): chain_list = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') invalidators = ( self.pg.invalidate, functools.partial(self.pg.invalidate, lambda node, _: node == 'Z') ) for invalidator in invalidators: self._mk_chain(self.pg, chain_list) self.assertTrue(self.pg.completed_nodes()) self.assertTrue(self.pg.dependents()) self.assertTrue(self.pg.dependencies()) self.assertTrue(self.pg.cyclic_dependencies()) invalidator() self.assertFalse(self.pg._nodes) def test_invalidate_partial(self): comparison_pg = ProductGraph(validator=lambda _: True) chain_a = list('ABCDEF') chain_b = list('GHIJKL') # Add two dependency chains to the primary graph. self._mk_chain(self.pg, chain_a) self._mk_chain(self.pg, chain_b) # Add only the dependency chain we won't invalidate to the comparison graph. self._mk_chain(comparison_pg, chain_b) # Invalidate one of the chains in the primary graph from the right-most node. self.pg.invalidate(lambda node, _: node == chain_a[-1]) # Ensure the final structure of the primary graph matches the comparison graph. pg_structure = {n: e.structure() for n, e in self.pg._nodes.items()} comparison_structure = {n: e.structure() for n, e in comparison_pg._nodes.items()} self.assertEquals(pg_structure, comparison_structure) def test_invalidate_count(self): self._mk_chain(self.pg, list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')) invalidated_count = self.pg.invalidate(lambda node, _: node == 'I') self.assertEquals(invalidated_count, 9) def test_invalidate_partial_identity_check(self): # Create a graph with a chain from A..Z. chain = self._mk_chain(self.pg, list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')) self.assertTrue(list(self.pg.completed_nodes())) # Track the pre-invaliation nodes (from A..Q). index_of_q = chain.index('Q') before_nodes = [node for node, _ in self.pg.completed_nodes() if node in chain[:index_of_q + 1]] self.assertTrue(before_nodes) # Invalidate all nodes under Q. self.pg.invalidate(lambda node, _: node == chain[index_of_q]) self.assertTrue(list(self.pg.completed_nodes())) # Check that the root node and all fs nodes were removed via a identity checks. for node, entry in self.pg._nodes.items(): self.assertFalse(node in before_nodes, 'node:\n{}\nwasnt properly removed'.format(node)) for associated in (entry.dependencies, entry.dependents, entry.cyclic_dependencies): for associated_entry in associated: self.assertFalse( associated_entry.node in before_nodes, 'node:\n{}\nis still associated with:\n{}\nin {}'.format(node, associated_entry.node, entry) )
def setUp(self): self.pg = ProductGraph( validator=lambda _: True) # Allow for string nodes for testing.
class ProductGraphTest(unittest.TestCase): def setUp(self): self.pg = ProductGraph( validator=lambda _: True) # Allow for string nodes for testing. @classmethod def _mk_chain(cls, graph, sequence, states=[Waiting, Return]): """Create a chain of dependencies (e.g. 'A'->'B'->'C'->'D') in the graph from a sequence.""" prior_item = sequence[0] for state in states: for item in sequence: graph.update_state(prior_item, state([item])) prior_item = item return sequence def test_dependency_edges(self): self.pg.update_state('A', Waiting(['B', 'C'])) self.assertEquals({'B', 'C'}, self.pg.dependencies_of('A')) self.assertEquals({'A'}, self.pg.dependents_of('B')) self.assertEquals({'A'}, self.pg.dependents_of('C')) def test_walk(self): nodes = list('ABCDEF') self._mk_chain(self.pg, nodes) walked_nodes = list((node for (node, _), _ in self.pg.walk(nodes[0]))) self.assertEquals(nodes, walked_nodes) def test_invalidate_all(self): chain_list = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') invalidators = (self.pg.invalidate, functools.partial(self.pg.invalidate, lambda node: node == 'Z')) for invalidator in invalidators: self._mk_chain(self.pg, chain_list) self.assertTrue(self.pg.completed_nodes()) self.assertTrue(self.pg.dependents()) self.assertTrue(self.pg.dependencies()) self.assertTrue(self.pg.cyclic_dependencies()) invalidator() self.assertFalse(self.pg.completed_nodes()) self.assertFalse(self.pg.dependents()) self.assertFalse(self.pg.dependencies()) self.assertFalse(self.pg.cyclic_dependencies()) def test_invalidate_partial(self): comparison_pg = ProductGraph(validator=lambda _: True) chain_a = list('ABCDEF') chain_b = list('GHIJKL') # Add two dependency chains to the primary graph. self._mk_chain(self.pg, chain_a) self._mk_chain(self.pg, chain_b) # Add only the dependency chain we won't invalidate to the comparison graph. self._mk_chain(comparison_pg, chain_b) # Invalidate one of the chains in the primary graph from the right-most node. self.pg.invalidate(lambda node: node == chain_a[-1]) # Ensure the final state of the primary graph matches the comparison graph. self.assertEquals(self.pg.completed_nodes(), comparison_pg.completed_nodes()) self.assertEquals(self.pg.dependents(), comparison_pg.dependents()) self.assertEquals(self.pg.dependencies(), comparison_pg.dependencies()) self.assertEquals(self.pg.cyclic_dependencies(), comparison_pg.cyclic_dependencies()) def test_invalidate_count(self): self._mk_chain(self.pg, list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')) invalidated_count = self.pg.invalidate(lambda node: node == 'I') self.assertEquals(invalidated_count, 9) def test_invalidate_partial_identity_check(self): # Create a graph with a chain from A..Z. chain = self._mk_chain(self.pg, list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')) self.assertTrue(self.pg.completed_nodes()) # Track the pre-invaliation nodes (from A..Q). index_of_q = chain.index('Q') before_nodes = filter(lambda node: node in chain[:index_of_q + 1], self.pg.completed_nodes()) self.assertTrue(before_nodes) # Invalidate all nodes under Q. self.pg.invalidate(lambda node: node == chain[index_of_q]) self.assertTrue(self.pg.completed_nodes()) def _label_tuples(collection, name): for item in collection: yield tuple(list(item) + [name]) # Check that the root node and all fs nodes were removed via a identity checks. chain = itertools.chain( _label_tuples(self.pg.completed_nodes().items(), 'completed_nodes'), _label_tuples(self.pg.dependents().items(), 'dependents'), _label_tuples(self.pg.dependencies().items(), 'dependencies'), _label_tuples(self.pg.cyclic_dependencies().items(), 'cyclic_dependencies')) for node, associated, collection_name in chain: self.assertFalse( node in before_nodes, 'node:\n{}\nwasnt properly removed from {}'.format( node, collection_name)) if isinstance(associated, set): for associated_node in associated: self.assertFalse( associated_node in before_nodes, 'node:\n{}\nis still associated with:\n{}\nin {}'. format(associated_node, node, collection_name))