def testAddNewVertices(self): # Production rhs has no vertices, so nothing done. g = Graph() lhs = Graph() rhs = Graph() p = Production(lhs, rhs) gen = Generator() self.assertEqual(len(g._vertices), 0) gen._addNewVertices(g, p, {}) self.assertEqual(len(g._vertices), 0) # Production rhs has vertices, but they all appear in the LHS. Hence # they aren't new and nothing is done. lhs.addVertex(Vertex('l1', 'A', 1)) rhs.addVertex(Vertex('r1', 'A', 1)) self.assertEqual(len(g._vertices), 0) gen._addNewVertices(g, p, {}) self.assertEqual(len(g._vertices), 0) # rhs has one new vertex not in the lhs. rhsMapping = {} rhs.addVertex(Vertex('r2', 'B', 2)) self.assertEqual(len(g._vertices), 0) gen._addNewVertices(g, p, rhsMapping) self.assertEqual(len(g._vertices), 1) self.assertIn('v0', g._vertices) # new vertex is v0 self.assertEqual(g._vertices['v0'].label, 'B') # with label B self.assertEqual(g._vertices['v0'].number, 2) # with number 2 self.assertIn('r2', rhsMapping) # now appears in rhsMapping self.assertEqual(rhsMapping['r2'], 'v0') # r2 mapped to v0 (the newly added vertex) in graph
def testFindMatchingProductions(self): # Providing no productions should result in no matches. gen = Generator() g = Graph() self.assertEquals( len(gen._findMatchingProductions(g, [])), 0) # We have a production, but the LHS can't be found in the graph. # No solutions. g = Graph() g.addEdge(Vertex('g0', 'A'), Vertex('g1', 'B')) lhs = Graph() lhs.addEdge(Vertex('g0', 'C'), Vertex('g1', 'D')) rhs = Graph() p1 = Production(lhs, rhs) gen = Generator() self.assertEquals( len(gen._findMatchingProductions(g, [p1])), 0) # One matching production, a simple vertex "A". g = Graph() g.addEdge(Vertex('g0', 'A'), Vertex('g1', 'B')) lhs = Graph() lhs.addVertex(Vertex('g0', 'A', '1')) rhs = Graph() p1 = Production(lhs, rhs) self.assertEquals( len(gen._findMatchingProductions(g, [p1])), 1) # Two matching productions. g = Graph() g.addEdge(Vertex('g0', 'A'), Vertex('g1', 'B')) lhs = Graph() lhs.addVertex(Vertex('g0', 'A', '2')) rhs = Graph() p1 = Production(lhs, rhs) p2 = Production(lhs, rhs) self.assertEquals( len(gen._findMatchingProductions(g, [p1, p2])), 2)
def testDeleteMissingVertices(self): # lhs has no vertices(!). Nothing done. g = Graph() lhs = Graph() rhs = Graph() p = Production(lhs, rhs) gen = Generator() gen._deleteMissingVertices(g, p, {}) self.assertEqual(len(g._vertices), 0) # lhs has vertices, but they all appear in the rhs. Nothing done. g.addVertex(Vertex('g0', 'A', 1)) lhs.addVertex(Vertex('l0', 'A', 1)) rhs.addVertex(Vertex('r0', 'A', 1)) p = Production(lhs, rhs) gen._deleteMissingVertices(g, p, {'l0':'g0'}) self.assertEqual(len(g._vertices), 1) # lhs has a vertex (A2) that don't appear in the rhs. It should be # deleted from g. g.addVertex(Vertex('g1', 'A', 2)) lhs.addVertex(Vertex('l1', 'A', 2)) p = Production(lhs, rhs) self.assertEqual(len(g._vertices), 2) gen._deleteMissingVertices(g, p, {'l0':'g0', 'l1':'g1'}) self.assertEqual(len(g._vertices), 1)
def testHasEdgeBetweenVertices(self): self.g.addEdge(Vertex('u0', 'A'), Vertex('u1', 'B')) self.g.addEdge('u1', Vertex('u2', 'C')) self.assertTrue(self.g.hasEdgeBetweenVertices('u0', 'u1')) self.assertFalse(self.g.hasEdgeBetweenVertices('u0', 'u2')) self.assertFalse(self.g.hasEdgeBetweenVertices('XX', 'XX')) self.assertFalse(self.g.hasEdgeBetweenVertices('u0', 'XX'))
def testLabelsProperty(self): self.g.addVertex(Vertex('u1', 'A')) self.g.addVertex(Vertex('u2', 'B')) self.g.addVertex(Vertex('u3', 'C')) labels = self.g.labels self.assertEquals(len(labels), 3) self.assertTrue('A' in labels) self.assertTrue('B' in labels) self.assertTrue('C' in labels)
def testNamesProperty(self): self.g.addVertex(Vertex('u1', 'A', 1)) self.g.addVertex(Vertex('u2', 'B', 2)) self.g.addVertex(Vertex('u3', 'C', 3)) names = self.g.names self.assertEquals(len(names), 3) self.assertTrue('A1' in names) self.assertTrue('B2' in names) self.assertTrue('C3' in names)
def testSearchNoCandidates(self): # If there are no suitable candidate data vertices for every # query vertex, then the returned solutions list should be empty. g = Graph() g.addEdge(Vertex('v1', 'A'), Vertex('v2', 'B')) q = Graph() q.addEdge(Vertex('u1', 'Z'), Vertex('u2', 'Y')) solutions = g.search(q) self.assertEquals(len(solutions), 0)
def testUpdateState(self): matches = {} u = Vertex('u1') v = Vertex('v1') g = Graph() g._updateState(u, v, matches) self.assertEquals(len(g._matchHistory), 1) self.assertEquals(len(matches), 1) self.assertEquals(matches[u.id], 'v1')
def testSubgraphSearchSolutionNoCandidates(self): # Test when an umatched query vertex doesn't have any candidates, we # don't find any solutions. g = Graph() g.addVertex(Vertex('v1', 'A')) q = Graph() q.addVertex(Vertex('u1', 'B')) matches = {} self.assertEqual(len(g._solutions), 0) g._subgraphSearch(matches, q) self.assertEqual(len(g._solutions), 0)
def testEdgesProperty(self): # Build A->B, B->C self.g.addEdge(Vertex('u1', 'A'), Vertex('u2', 'B')) self.g.addEdge('u2', Vertex('u3', 'C')) for e in self.g.edges: if e[0].id == 'u1': self.assertEquals(e[1].id, 'u2') elif e[0].id == 'u2': self.assertEquals(e[1].id, 'u3') else: self.assertFalse(True)
def testSubgraphSearchSimpleSolution(self): # One simple solution. g = Graph() g.addVertex(Vertex('v1', 'A')) v1 = g._vertices['v1'] q = Graph() q.addVertex(Vertex('u1', 'A')) q._vertices['u1'].candidates = [v1] self.assertEqual(len(g._solutions), 0) g._subgraphSearch({}, q) self.assertEqual(len(g._solutions), 1) self.assertIn('u1', g._solutions[0]) self.assertEquals(g._solutions[0]['u1'], 'v1')
def testNumVertices(self): # Empty graph. self.assertEquals(self.g.numVertices, 0) # One vertex. a = self.g.addVertex(Vertex('u1')) self.assertEquals(self.g.numVertices, 1)
def testConstructorLabelNumber(self): """Building a vertex with an id, label, and number should store all.""" v = Vertex('v1', 'label', 1) self.assertEqual(v.id, 'v1') self.assertEqual(v.label, 'label') self.assertEqual(v.number, 1) self.assertEqual(v.name, 'label1')
def testAddVertex(self): # Correct add. v = self.g.addVertex(Vertex('u1')) self.assertTrue('u1' in self.g._vertices) # Adding an existing vertex should return the existing vertex. self.assertEquals(self.g.addVertex(v), v)
def testConstructorNoLabelNoNumber(self): """Building a vertex with only an id should have None for the label, and number.""" v = Vertex('v1') self.assertEqual(v.id, 'v1') self.assertIsNone(v.label) self.assertIsNone(v.number) self.assertEqual(v.name, '')
def testFindMatchedNeighbors(self): q = Graph() # No data should return an empty list. mn = q._findMatchedNeighbors(None, None) self.assertEquals(len(mn), 0) # No matching neighbors should return an empty list. q.addEdge(Vertex('u1', 'A'), Vertex('u2', 'B')) u2 = q._vertices['u2'] mn = q._findMatchedNeighbors(u2, []) self.assertEquals(len(mn), 0) # Make a matching neighbor and make sure it is returned. matches = {'u1': 'v1', 'v1': 'u1'} mn = q._findMatchedNeighbors(u2, matches) self.assertEquals(len(mn), 1) self.assertEquals(mn[0].id, 'u1')
def testSearchTwoSolutions(self): # A1_g0->B_g1->C_g2, A2_g3->B_g1 g = Graph() g.addEdge(Vertex('g0', 'A'), Vertex('g1', 'B')) g.addEdge('g1', Vertex('g2', 'C')) g.addEdge(Vertex('g3', 'A'), 'g1') #g.addEdge('g3', 'g2') # A_g0->B_g1->C_g2 q = Graph() q.addEdge(Vertex('g0', 'A'), Vertex('g1', 'B')) q.addEdge('g1', Vertex('g2', 'C')) solutions = g.search(q) self.assertEquals(len(solutions), 2) # First A->B,C is found. self.assertEquals(solutions[0]['g0'], 'g3') self.assertEquals(solutions[0]['g1'], 'g1') self.assertEquals(solutions[0]['g2'], 'g2') # Second A->B,C is found. self.assertEquals(solutions[1]['g0'], 'g0') self.assertEquals(solutions[1]['g1'], 'g1') self.assertEquals(solutions[1]['g2'], 'g2')
def testApplyProductions(self): # Start graph already has the minimum number of vertices. Nothing done. g = Graph() c = {'min_vertices':0} gen = Generator() gen.applyProductions(g, None, c) self.assertEqual(len(g._vertices), 0) # No matching productions raises an error. c = {'min_vertices':1} self.assertRaises(RuntimeError, gen.applyProductions, g, [], c) # When we're done, g has more at least min_vertices. g.addEdge(Vertex('g0', 'A'), Vertex('g1', 'A')) c = {'min_vertices':10} # Production is A1->A2 ==> A1->A->A2 lhs = Graph() lhs.addEdge(Vertex('l0', 'A', 1), Vertex('l1', 'A', 2)) rhs = Graph() rhs.addEdge(Vertex('r0', 'A', 1), Vertex('r1', 'A')) rhs.addEdge('r1', Vertex('r2', 'A', 2)) p = Production(lhs, rhs) gen.applyProductions(g, [p], c) logging.debug(g) self.assertEqual(len(g._vertices), 10)
def testSubgraphSearchOneCandidateNotJoinable(self): # The query vertex has a candidate data vertex, but they aren't # "joinable" -- no solution. g = Graph() g.addVertex(Vertex('v1', 'A')) g.addVertex(Vertex('v2', 'B')) v1 = g._vertices['v1'] q = Graph() q.addEdge(Vertex('u1', 'A'), Vertex('u2', 'B')) q._vertices['u1'].candidates = [v1] # u2 and v2 are already matched. There's an edge between u1 and # u2, but no edge between v1 and v2 so u1 and v1 cannot be matched. matches = {'u2': 'v2'} self.assertEqual(len(g._solutions), 0) g._subgraphSearch(matches, q) self.assertEqual(len(g._solutions), 0)
def testNextUnmamtchedVertex(self): matches = {} # Build a little graph with three nodes. q = Graph() q.addEdge(Vertex('u1'), Vertex('u2')) q.addEdge('u1', Vertex('u3')) # Call _nextUnmatchedVertex three times. Each time it returns a # vertex, add it to matches. for i in range(3): v = q._nextUnmatchedVertex(matches) self.assertTrue(v.id in ['u1', 'u2', 'u3']) matches[v.id] = 'XYZ' # Now that all of the vertices are labeled, _nextUnmatchedVertex() # should return None. self.assertIsNone(q._nextUnmatchedVertex(matches))
def testSubgraphSearchSolutionFound(self): # Test that when the length of query=>data vertex matches is the # same as the number of query vertices, then the solution is stored. g = Graph() q = Graph() q.addVertex(Vertex('u1', 'A')) # one query vertex matches = {'u1': 'v1'} # one match g._subgraphSearch(matches, q) self.assertEqual(len(g._solutions), 1)
def testApplyProduction_Blackbox2(self): # Another black-box test of _applyProduction this time with # numbered vertices. # Graph is A0->A1,A0->D g = Graph() g.addEdge(Vertex('g0', 'A'), Vertex('g1', 'A')) g.addEdge('g0', Vertex('g2', 'D')) # Production is A1->A2 ==> A1->A->A2. # This production adds a new vertex "A" between the existing As, # leaving the first A still pointing to D. # Resulting graph: A1->A3->A2; A1->D lhs = Graph() lhs.addEdge(Vertex('l0', 'A', 1), Vertex('l1', 'A', 2)) rhs = Graph() rhs.addEdge(Vertex('r0', 'A', 1), Vertex('r1', 'A')) rhs.addEdge('r1', Vertex('r2', 'A', 2)) p = Production(lhs, rhs) gen = Generator() gen._applyProduction(g, p, {'l0':'g0','l1':'g1'}) # Result has 4 vertices. self.assertEqual(len(g._vertices), 4) # <g0,A> is still in the graph. self.assertIn('g0', g._vertices) self.assertEqual(g._vertices['g0'].label, 'A') # <v3,A> has been added. self.assertIn('v3', g._vertices) self.assertEqual(g._vertices['v3'].label, 'A') # g0->v3 self.assertIn(g._vertices['v3'], g._edges['g0']) # <g1,A> is still in the graph. self.assertIn('g1', g._vertices) self.assertEqual(g._vertices['g1'].label, 'A') # <g2,D> is still in the graph. self.assertIn('g2', g._vertices) self.assertEqual(g._vertices['g2'].label, 'D') # <g0,A>-><g2,D> self.assertIn(g._vertices['g2'], g._edges['g0']) # g0->v3 self.assertIn(g._vertices['v3'], g._edges['g0']) # Output looks fine: A1->A->A, A1->D self.assertEqual(str(g), 'digraph {\nA_v3->A_g1;\nA_g0->D_g2;\nA_g0->A_v3;\n\n}')
def testDeleteVertex(self): # Deleting a non-existing vertex raises an exception. self.assertIsNone(self.g.deleteVertex('X')) # Build A->B, B->A self.g.addEdge(Vertex('u1', 'A'), Vertex('u2', 'B')) self.g.addEdge('u2', 'u1') u1 = self.g._vertices['u1'] u2 = self.g._vertices['u2'] self.g.deleteVertex('u1') # u1 shouldn't appear anywhere as an edge or neighbor. self.assertTrue('u1' not in self.g._edges) self.assertTrue('u1' not in self.g._neighbors) self.assertTrue('u1' not in self.g._edges['u2']) self.assertTrue('u1' not in self.g._neighbors['u2']) # u1 isn't a vertex anymore. self.assertTrue('u1' not in self.g._vertices)
def _parseVertexID(self, token, graph): """ Parses the given token (ID) into a text label and optional vertex number (e.g., "A1"). If a vertex with these two data don't exist in the given graph, it is added. Otherwise, the existing vertex from the graph is returned. """ label = re.match('[A-z]+', token.text).group(0) match = re.search('[0-9]+$', token.text) number = match.group(0) if match is not None else None name = Vertex.makeName(label, number) vertex = graph.findVertex(name) if vertex == None: # graph doesn't contain a vertex with the label. vertex = Vertex( 'v%d' % graph.numVertices, # vertex id label, number) graph.addVertex(vertex) return vertex
def testFilterCandidates(self): # Filtering an empty graph should return an empty array. g = Graph() c = g._filterCandidates(None) self.assertEquals(len(c), 0) # Build a little graph with two 'A' labeled vertices. g.addEdge(Vertex('u1', 'A'), Vertex('u2', 'B')) g.addEdge('u1', Vertex('u3', 'A')) # Search the graph for all 'X' vertices (should return an empty array.) u = Vertex('x', 'X') c = g._filterCandidates(u) self.assertTrue(len(c) == 0) # Search for 'A' vertices. Should return two of them. u = Vertex('x', 'A') c = g._filterCandidates(u) self.assertTrue(len(c) == 2) self.assertTrue(c[0].label == 'A') self.assertTrue(c[1].label == 'A')
def testApplyProduction(self): # A basic test that tests all four cases: add and remove vertex, # and add and remove edge. # Graph starts with A->B g = Graph() g.addEdge(Vertex('g0', 'A'), Vertex('g1', 'B')) g1 = g._vertices['g1'] # Production lhs matches A->B lhs = Graph() lhs.addEdge(Vertex('l0', 'A', 1), Vertex('l1', 'B', 1)) # Production rhs transforms that to A->C rhs = Graph() rhs.addEdge(Vertex('r0', 'A', 1), Vertex('r1', 'C')) p = Production(lhs,rhs) gen = Generator() gen._applyProduction(g, p, {'l0':'g0','l1':'g1'}) # g has a new vertex, <v2,C>. self.assertEqual(len(g._vertices), 2) self.assertEqual(g._vertices['v1'].label, 'C') # <g0,A> points to <v2,C> self.assertEqual(len(g._edges['g0']), 1) self.assertEqual(g._edges['g0'][0].id, 'v1') self.assertEqual(g._vertices['v1'].label, 'C') # <g0,A> no longer points to <g1,B> self.assertNotIn(g1, g._edges['g0']) # Vertex <g1,B> has been deleted. self.assertNotIn('g1', g._vertices)
def testFindCandidates(self): # Create empty data and query graphs. g = Graph() q = Graph() # Searching finds no candidates. self.assertFalse(g._findCandidates(q)) # Add some data vertices. g.addEdge(Vertex('u1', 'A'), Vertex('u2', 'B')) # Empty query graph still finds no candidates. self.assertFalse(g._findCandidates(q)) # Add some query vertices. q.addEdge(Vertex('u1', 'A'), Vertex('u2', 'B')) q.addEdge('u1', Vertex('u3', 'C')) # Data graph has no 'C', so still returns false. self.assertFalse(g._findCandidates(q)) # Add a vertex for C, and now the test should succeeed. g.addEdge('u1', Vertex('u3', 'C')) u1 = q._vertices['u1'] self.assertTrue(g._findCandidates(q)) self.assertEquals(len(u1.candidates), 1)
def testRestoreState(self): # Save state with a couple calls to updateState() and then see that # they are undone. g = Graph() matches = {} u1 = Vertex('u1') v1 = Vertex('v1') g._updateState(u1, v1, matches) # u1 matched with v1 u2 = Vertex('u2') v2 = Vertex('v2') g._updateState(u2, v2, matches) # u2 matched with v2 # Now we should get the dictionary with only the original set of # matches. matches = g._restoreState(matches) self.assertEquals(len(matches), 1) self.assertTrue('u1' in matches) self.assertTrue(matches['u1'], 'v1') # Now we should have an empty dictionary. matches = g._restoreState(matches) self.assertEquals(len(matches), 0)
def testMapRHSToGraph(self): # No vertices in rhs. Mapping returned is empty. g = Graph() lhs = Graph() rhs = Graph() p = Production(lhs, rhs) gen = Generator() rhsMapping = gen._mapRHSToGraph(g, p, {}) self.assertEqual(len(rhsMapping), 0) # rhs has vertex r1, but it doesn't appear in the lhs. Mapping returned # is empty. rhs.addVertex(Vertex('r1', 'A', '1')) rhsMapping = gen._mapRHSToGraph(g, p, {}) self.assertEqual(len(rhsMapping), 0) # rhs vertex r1 also appears in lhs as l1, which is mapped to g1. # r1 should appear in rhsMapping mapped to g1. lhs.addVertex(Vertex('l1', 'A', '1')) rhsMapping = gen._mapRHSToGraph(g, p, {'l1':'g1'}) self.assertEqual(len(rhsMapping), 1) self.assertIn('r1', rhsMapping) self.assertEqual(rhsMapping['r1'], 'g1')
def testApplyProduction_Blackbox(self): # Black-box test of _applyProduction. # Graph is A->C,D g = Graph() g.addEdge(Vertex('g0', 'A'), Vertex('g1', 'C')) g.addVertex(Vertex('g2', 'D')) # Production is A->C,D ==> A->B,C. # This adds vertex B, adds edge from A->B, deletes edge from A->C, # and deletes vertex D. lhs = Graph() lhs.addEdge(Vertex('l0', 'A'), Vertex('l1', 'C')) lhs.addVertex(Vertex('l2', 'D')) rhs = Graph() rhs.addEdge(Vertex('r0', 'A'), Vertex('r1', 'B')) rhs.addVertex(Vertex('r2', 'C')) p = Production(lhs, rhs) gen = Generator() gen._applyProduction(g, p, {'l0':'g0','l1':'g1','l2':'g2'}) self.assertEqual(len(g._vertices), 3) # <g0,A> is still in the graph. self.assertIn('g0', g._vertices) self.assertEqual(g._vertices['g0'].label, 'A') # <v3,B> has been added. self.assertIn('v2', g._vertices) self.assertEqual(g._vertices['v2'].label, 'B') # A->B self.assertIn(g._vertices['v2'], g._edges['g0']) # <g1,C> is still in the graph. self.assertIn('g1', g._vertices) self.assertEqual(g._vertices['g1'].label, 'C') # A->C has been removed. self.assertNotIn(g._vertices['g1'], g._edges['g0']) # <g2,D> has been removed. self.assertNotIn('g2', g._vertices) # Output looks fine. self.assertEqual(str(g), 'digraph {\nA_g0->B_v2;\n\n}')