Exemple #1
0
    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)
Exemple #2
0
 def setUp(self):
     self.pg = ProductGraph(
         validator=lambda _: True)  # Allow for string nodes for testing.
Exemple #3
0
 def setUp(self):
   self.pg = ProductGraph(validator=lambda _: True)  # Allow for string nodes for testing.
Exemple #4
0
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."""
        for state in states:
            dest = None
            for src in reversed(sequence):
                if state is Waiting:
                    graph.add_dependencies(src, [dest] if dest else [])
                else:
                    graph.complete_node(src, state([dest]))
                dest = src
        return sequence

    def test_disallow_completed_state_change(self):
        self.pg.complete_node('A', Return('done!'))
        with self.assertRaises(CompletedNodeException):
            self.pg.add_dependencies('A', ['B'])

    def test_disallow_completing_with_incomplete_deps(self):
        self.pg.add_dependencies('A', ['B'])
        self.pg.add_dependencies('B', ['C'])
        with self.assertRaises(IncompleteDependencyException):
            self.pg.complete_node('A', Return('done!'))

    def test_dependency_edges(self):
        self.pg.add_dependencies('A', ['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.add_dependencies('A', ['B'])
        self.pg.add_dependencies('B', ['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.add_dependencies('A', ['B'])
        self.pg.add_dependencies('B', ['C'])
        self.pg.add_dependencies('C', ['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.add_dependencies(end, [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))
Exemple #5
0
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."""
    for state in states:
      dest = None
      for src in reversed(sequence):
        graph.update_state(src, state([dest] if dest else []))
        dest = src
    return sequence

  def test_disallow_completed_state_change(self):
    self.pg.update_state('A', Return('done!'))
    with self.assertRaises(CompletedNodeException):
      self.pg.update_state('A', Waiting(['B']))

  def test_disallow_completing_with_incomplete_deps(self):
    self.pg.update_state('A', Waiting(['B']))
    self.pg.update_state('B', Waiting(['C']))
    with self.assertRaises(IncompleteDependencyException):
      self.pg.update_state('A', Return('done!'))

  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)
          )