go.ligato.io/vpp-agent/v3@v3.5.0/plugins/kvscheduler/internal/graph/edge_lookup_test.go (about)

     1  // Copyright (c) 2019 Cisco and/or its affiliates.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at:
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package graph
    16  
    17  import (
    18  	. "github.com/onsi/gomega"
    19  	"testing"
    20  )
    21  
    22  type mockIter struct {
    23  	visitedNodes map[string]struct{}
    24  	visitedEdges map[visitedEdge]struct{}
    25  }
    26  
    27  type visitedEdge struct {
    28  	sourceNode, relation, label string
    29  }
    30  
    31  func newMockIter() *mockIter {
    32  	m := &mockIter{}
    33  	m.reset()
    34  	return m
    35  }
    36  
    37  func (m *mockIter) reset() {
    38  	m.visitedNodes = make(map[string]struct{})
    39  	m.visitedEdges = make(map[visitedEdge]struct{})
    40  }
    41  
    42  func (m *mockIter) visitNode(key string) {
    43  	_, alreadyVisited := m.visitedNodes[key]
    44  	Expect(alreadyVisited).To(BeFalse())
    45  	m.visitedNodes[key] = struct{}{}
    46  }
    47  
    48  func (m *mockIter) visitEdge(sourceNode, relation, label string) {
    49  	edge := visitedEdge{
    50  		sourceNode: sourceNode,
    51  		relation:   relation,
    52  		label:      label,
    53  	}
    54  	_, alreadyVisited := m.visitedEdges[edge]
    55  	Expect(alreadyVisited).To(BeFalse())
    56  	m.visitedEdges[edge] = struct{}{}
    57  }
    58  
    59  func TestLookupOverNodeKeys(t *testing.T) {
    60  	RegisterTestingT(t)
    61  
    62  	mi := newMockIter()
    63  
    64  	el := newEdgeLookup(nil)
    65  	Expect(el).ToNot(BeNil())
    66  
    67  	el.iterTargets("some-key", false, mi.visitNode)
    68  	Expect(mi.visitedNodes).To(BeEmpty())
    69  
    70  	el.addNodeKey("prefix1/node1")
    71  	el.addNodeKey("prefix2/node3")
    72  	el.addNodeKey("prefix1/node2")
    73  
    74  	// static key which exists
    75  	el.iterTargets("prefix1/node1", false, mi.visitNode)
    76  	Expect(mi.visitedNodes).To(HaveLen(1))
    77  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
    78  	mi.reset()
    79  
    80  	// static key which does not exist
    81  	el.iterTargets("prefix2/node1", false, mi.visitNode)
    82  	Expect(mi.visitedNodes).To(BeEmpty())
    83  
    84  	// prefix1
    85  	el.iterTargets("prefix1/", true, mi.visitNode)
    86  	Expect(mi.visitedNodes).To(HaveLen(2))
    87  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
    88  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node2"))
    89  	mi.reset()
    90  
    91  	// prefix2
    92  	el.iterTargets("prefix2/", true, mi.visitNode)
    93  	Expect(mi.visitedNodes).To(HaveLen(1))
    94  	Expect(mi.visitedNodes).To(HaveKey("prefix2/node3"))
    95  	mi.reset()
    96  
    97  	// empty prefix (select all)
    98  	el.iterTargets("", true, mi.visitNode)
    99  	Expect(mi.visitedNodes).To(HaveLen(3))
   100  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   101  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node2"))
   102  	Expect(mi.visitedNodes).To(HaveKey("prefix2/node3"))
   103  	mi.reset()
   104  
   105  	el.delNodeKey("prefix1/node2")
   106  
   107  	// static key which no longer exists
   108  	el.iterTargets("prefix1/node2", false, mi.visitNode)
   109  	Expect(mi.visitedNodes).To(BeEmpty())
   110  
   111  	// prefix1
   112  	el.iterTargets("prefix1/", true, mi.visitNode)
   113  	Expect(mi.visitedNodes).To(HaveLen(1))
   114  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   115  	mi.reset()
   116  
   117  	// remove the rest
   118  	el.delNodeKey("prefix1/node1")
   119  	Expect(el.nodeKeyData).To(HaveLen(1)) // gc-ed
   120  	Expect(el.nodeKeyOffset).To(HaveLen(1)) // gc-ed
   121  	el.delNodeKey("prefix2/node3")
   122  	Expect(el.nodeKeyData).To(HaveLen(0)) // gc-ed
   123  	Expect(el.nodeKeyOffset).To(HaveLen(0)) // gc-ed
   124  
   125  	// empty prefix (select all)
   126  	el.iterTargets("", true, mi.visitNode)
   127  	Expect(mi.visitedNodes).To(HaveLen(0))
   128  	mi.reset()
   129  
   130  	// test "attribute"
   131  	el.addNodeKey("prefix1/node1/attr1")
   132  	el.addNodeKey("prefix1/node1")
   133  	el.addNodeKey("prefix1/node2")
   134  	el.addNodeKey("prefix1/node1/attr2")
   135  
   136  	// prefix1/node1 as key
   137  	el.iterTargets("prefix1/node1", false, mi.visitNode)
   138  	Expect(mi.visitedNodes).To(HaveLen(1))
   139  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   140  	mi.reset()
   141  
   142  	// prefix1/node1 as prefix
   143  	el.iterTargets("prefix1/node1", true, mi.visitNode)
   144  	Expect(mi.visitedNodes).To(HaveLen(3))
   145  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   146  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1/attr1"))
   147  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1/attr2"))
   148  	mi.reset()
   149  }
   150  
   151  func TestLookupOverNodeKeysWithOverlay(t *testing.T) {
   152  	RegisterTestingT(t)
   153  
   154  	mi := newMockIter()
   155  
   156  	el := newEdgeLookup(nil)
   157  	Expect(el).ToNot(BeNil())
   158  
   159  	el.addNodeKey("prefix1/node1")
   160  	el.addNodeKey("prefix2/node3")
   161  
   162  	elOver := el.makeOverlay()
   163  	Expect(elOver).ToNot(BeNil())
   164  	Expect(elOver.underlay).To(Equal(el))
   165  	Expect(el.overlay).To(Equal(elOver))
   166  
   167  	elOver.addNodeKey("prefix1/node2")
   168  
   169  	// check in overlay
   170  	elOver.iterTargets("prefix1/node1", false, mi.visitNode)
   171  	Expect(mi.visitedNodes).To(HaveLen(1))
   172  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   173  	mi.reset()
   174  	elOver.iterTargets("prefix2/node1", false, mi.visitNode)
   175  	Expect(mi.visitedNodes).To(BeEmpty())
   176  	elOver.iterTargets("prefix1/", true, mi.visitNode)
   177  	Expect(mi.visitedNodes).To(HaveLen(2))
   178  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   179  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node2"))
   180  	mi.reset()
   181  	elOver.iterTargets("prefix2/", true, mi.visitNode)
   182  	Expect(mi.visitedNodes).To(HaveLen(1))
   183  	Expect(mi.visitedNodes).To(HaveKey("prefix2/node3"))
   184  	mi.reset()
   185  	elOver.iterTargets("", true, mi.visitNode)
   186  	Expect(mi.visitedNodes).To(HaveLen(3))
   187  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   188  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node2"))
   189  	Expect(mi.visitedNodes).To(HaveKey("prefix2/node3"))
   190  	mi.reset()
   191  
   192  	// check in the underlay before saving
   193  	el.iterTargets("prefix1/node1", false, mi.visitNode)
   194  	Expect(mi.visitedNodes).To(HaveLen(1))
   195  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   196  	mi.reset()
   197  	el.iterTargets("prefix1/node2", false, mi.visitNode)
   198  	Expect(mi.visitedNodes).To(BeEmpty())
   199  	el.iterTargets("prefix1/", true, mi.visitNode)
   200  	Expect(mi.visitedNodes).To(HaveLen(1))
   201  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   202  	mi.reset()
   203  	el.iterTargets("prefix2/", true, mi.visitNode)
   204  	Expect(mi.visitedNodes).To(HaveLen(1))
   205  	Expect(mi.visitedNodes).To(HaveKey("prefix2/node3"))
   206  	mi.reset()
   207  	el.iterTargets("", true, mi.visitNode)
   208  	Expect(mi.visitedNodes).To(HaveLen(2))
   209  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   210  	Expect(mi.visitedNodes).To(HaveKey("prefix2/node3"))
   211  	mi.reset()
   212  
   213  	// save and re-check in the underlay
   214  	elOver.saveOverlay()
   215  	el.iterTargets("prefix1/node1", false, mi.visitNode)
   216  	Expect(mi.visitedNodes).To(HaveLen(1))
   217  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   218  	mi.reset()
   219  	el.iterTargets("prefix1/node2", false, mi.visitNode)
   220  	Expect(mi.visitedNodes).To(HaveLen(1))
   221  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node2"))
   222  	mi.reset()
   223  	el.iterTargets("prefix1/", true, mi.visitNode)
   224  	Expect(mi.visitedNodes).To(HaveLen(2))
   225  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   226  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node2"))
   227  	mi.reset()
   228  	el.iterTargets("prefix2/", true, mi.visitNode)
   229  	Expect(mi.visitedNodes).To(HaveLen(1))
   230  	Expect(mi.visitedNodes).To(HaveKey("prefix2/node3"))
   231  	mi.reset()
   232  	el.iterTargets("", true, mi.visitNode)
   233  	Expect(mi.visitedNodes).To(HaveLen(3))
   234  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   235  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node2"))
   236  	Expect(mi.visitedNodes).To(HaveKey("prefix2/node3"))
   237  	mi.reset()
   238  
   239  	// delete two in overlay
   240  	elOver.delNodeKey("prefix1/node2")
   241  	elOver.delNodeKey("prefix2/node3")
   242  
   243  	// check in overlay:
   244  	elOver.iterTargets("prefix1/node1", false, mi.visitNode)
   245  	Expect(mi.visitedNodes).To(HaveLen(1))
   246  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   247  	mi.reset()
   248  	elOver.iterTargets("prefix1/", true, mi.visitNode)
   249  	Expect(mi.visitedNodes).To(HaveLen(1))
   250  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   251  	mi.reset()
   252  	elOver.iterTargets("prefix2/", true, mi.visitNode)
   253  	Expect(mi.visitedNodes).To(BeEmpty())
   254  	elOver.iterTargets("", true, mi.visitNode)
   255  	Expect(mi.visitedNodes).To(HaveLen(1))
   256  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   257  	mi.reset()
   258  
   259  	// check in underlay before save:
   260  	el.iterTargets("prefix1/node1", false, mi.visitNode)
   261  	Expect(mi.visitedNodes).To(HaveLen(1))
   262  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   263  	mi.reset()
   264  	el.iterTargets("prefix1/node2", false, mi.visitNode)
   265  	Expect(mi.visitedNodes).To(HaveLen(1))
   266  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node2"))
   267  	mi.reset()
   268  	el.iterTargets("prefix1/", true, mi.visitNode)
   269  	Expect(mi.visitedNodes).To(HaveLen(2))
   270  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   271  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node2"))
   272  	mi.reset()
   273  	el.iterTargets("prefix2/", true, mi.visitNode)
   274  	Expect(mi.visitedNodes).To(HaveLen(1))
   275  	Expect(mi.visitedNodes).To(HaveKey("prefix2/node3"))
   276  	mi.reset()
   277  	el.iterTargets("", true, mi.visitNode)
   278  	Expect(mi.visitedNodes).To(HaveLen(3))
   279  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   280  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node2"))
   281  	Expect(mi.visitedNodes).To(HaveKey("prefix2/node3"))
   282  	mi.reset()
   283  
   284  	// save and re-check underlay
   285  	elOver.saveOverlay()
   286  	el.iterTargets("prefix1/node1", false, mi.visitNode)
   287  	Expect(mi.visitedNodes).To(HaveLen(1))
   288  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   289  	mi.reset()
   290  	el.iterTargets("prefix1/", true, mi.visitNode)
   291  	Expect(mi.visitedNodes).To(HaveLen(1))
   292  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   293  	mi.reset()
   294  	el.iterTargets("prefix2/", true, mi.visitNode)
   295  	Expect(mi.visitedNodes).To(BeEmpty())
   296  	el.iterTargets("", true, mi.visitNode)
   297  	Expect(mi.visitedNodes).To(HaveLen(1))
   298  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   299  	mi.reset()
   300  	Expect(elOver.nodeKeyData).To(HaveLen(1))
   301  	Expect(elOver.nodeKeyOffset).To(HaveLen(1))
   302  	Expect(el.nodeKeyData).To(HaveLen(1))
   303  	Expect(el.nodeKeyOffset).To(HaveLen(1))
   304  
   305  	// remove the last key in overlay, but do not save
   306  	Expect(el.makeOverlay()).To(Equal(elOver))
   307  	elOver.delNodeKey("prefix1/node1")
   308  	Expect(elOver.nodeKeyData).To(HaveLen(0))
   309  	Expect(elOver.nodeKeyOffset).To(HaveLen(0))
   310  	Expect(el.nodeKeyData).To(HaveLen(1))
   311  	Expect(el.nodeKeyOffset).To(HaveLen(1))
   312  	elOver.iterTargets("", true, mi.visitNode)
   313  	Expect(mi.visitedNodes).To(BeEmpty())
   314  
   315  	// no changes
   316  	elOver = el.makeOverlay()
   317  	elOver.saveOverlay()
   318  	el.iterTargets("prefix1/node1", false, mi.visitNode)
   319  	Expect(mi.visitedNodes).To(HaveLen(1))
   320  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   321  	mi.reset()
   322  	el.iterTargets("prefix1/", true, mi.visitNode)
   323  	Expect(mi.visitedNodes).To(HaveLen(1))
   324  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   325  	mi.reset()
   326  	el.iterTargets("prefix2/", true, mi.visitNode)
   327  	Expect(mi.visitedNodes).To(BeEmpty())
   328  	el.iterTargets("", true, mi.visitNode)
   329  	Expect(mi.visitedNodes).To(HaveLen(1))
   330  	Expect(mi.visitedNodes).To(HaveKey("prefix1/node1"))
   331  	mi.reset()
   332  	Expect(elOver.nodeKeyData).To(HaveLen(1))
   333  	Expect(elOver.nodeKeyOffset).To(HaveLen(1))
   334  	Expect(el.nodeKeyData).To(HaveLen(1))
   335  	Expect(el.nodeKeyOffset).To(HaveLen(1))
   336  }
   337  
   338  func TestLookupOverEdges(t *testing.T) {
   339  	RegisterTestingT(t)
   340  
   341  	mi := newMockIter()
   342  
   343  	el := newEdgeLookup(nil)
   344  	Expect(el).ToNot(BeNil())
   345  
   346  	el.iterSources("some-key", mi.visitEdge)
   347  	Expect(mi.visitedEdges).To(BeEmpty())
   348  
   349  	el.addEdge(edge{ // "prefix1/node2" -> "prefix1/node1"
   350  		targetKey:  "prefix1/node1",
   351  		isPrefix:   false,
   352  		sourceNode: "prefix1/node2",
   353  		relation:   "depends-on",
   354  		label:      "node1 exists",
   355  	})
   356  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   357  	el.addEdge(edge{ // "prefix1/node2" -> "prefix2/node3"
   358  		targetKey:  "prefix2/node3",
   359  		isPrefix:   false,
   360  		sourceNode: "prefix1/node2",
   361  		relation:   "depends-on",
   362  		label:      "node3 exists",
   363  	})
   364  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   365  
   366  	el.addEdge(edge{ // "prefix2/node3" -> "prefix1/*"
   367  		targetKey:  "prefix1/",
   368  		isPrefix:   true,
   369  		sourceNode: "prefix2/node3",
   370  		relation:   "depends-on",
   371  		label:      "prefix1 non-empty",
   372  	})
   373  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   374  	el.addEdge(edge{ // "prefix2/node3" -> "prefix2/node3"
   375  		targetKey:  "prefix2/node3",
   376  		isPrefix:   false,
   377  		sourceNode: "prefix2/node3",
   378  		relation:   "myself",
   379  		label:      "edge to itself",
   380  	})
   381  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   382  
   383  	el.addEdge(edge{
   384  		targetKey:  "", // "prefix1/node1" -> *
   385  		isPrefix:   true,
   386  		sourceNode: "prefix1/node1",
   387  		relation:   "all",
   388  		label:      "all",
   389  	})
   390  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   391  
   392  	Expect(el.edgeData).To(HaveLen(5))
   393  	Expect(el.edgeOffset).To(HaveLen(5))
   394  
   395  	el.iterSources("prefix1/node1", mi.visitEdge)
   396  	Expect(mi.visitedEdges).To(HaveLen(3))
   397  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   398  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   399  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   400  	mi.reset()
   401  
   402  	el.iterSources("prefix1/node2", mi.visitEdge)
   403  	Expect(mi.visitedEdges).To(HaveLen(2))
   404  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   405  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   406  	mi.reset()
   407  
   408  	el.iterSources("prefix2/node3", mi.visitEdge)
   409  	Expect(mi.visitedEdges).To(HaveLen(3))
   410  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node3 exists"}))
   411  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "myself", "edge to itself"}))
   412  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   413  	mi.reset()
   414  
   415  	// delete 2 edges
   416  	el.delEdge(edge{ // "prefix1/node2" -> "prefix2/node3"
   417  		targetKey:  "prefix2/node3",
   418  		isPrefix:   false,
   419  		sourceNode: "prefix1/node2",
   420  		relation:   "depends-on",
   421  		label:      "node3 exists",
   422  	})
   423  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   424  	el.delEdge(edge{
   425  		targetKey:  "", // "prefix1/node1" -> *
   426  		isPrefix:   true,
   427  		sourceNode: "prefix1/node1",
   428  		relation:   "all",
   429  		label:      "all",
   430  	})
   431  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   432  	Expect(el.edgeData).To(HaveLen(5)) // not gc-ed yet
   433  	Expect(el.edgeOffset).To(HaveLen(5)) // not gc-ed yet
   434  
   435  	el.iterSources("prefix1/node1", mi.visitEdge)
   436  	Expect(mi.visitedEdges).To(HaveLen(2))
   437  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   438  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   439  	mi.reset()
   440  
   441  	el.iterSources("prefix1/node2", mi.visitEdge)
   442  	Expect(mi.visitedEdges).To(HaveLen(1))
   443  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   444  	mi.reset()
   445  
   446  	el.iterSources("prefix2/node3", mi.visitEdge)
   447  	Expect(mi.visitedEdges).To(HaveLen(1))
   448  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "myself", "edge to itself"}))
   449  	mi.reset()
   450  
   451  	// delete another edge
   452  	el.delEdge(edge{ // "prefix2/node3" -> "prefix1/*"
   453  		targetKey:  "prefix1/",
   454  		isPrefix:   true,
   455  		sourceNode: "prefix2/node3",
   456  		relation:   "depends-on",
   457  		label:      "prefix1 non-empty",
   458  	})
   459  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   460  	Expect(el.edgeData).To(HaveLen(2)) // gc-ed
   461  	Expect(el.edgeOffset).To(HaveLen(2)) // gc-ed
   462  
   463  	el.iterSources("prefix1/node1", mi.visitEdge)
   464  	Expect(mi.visitedEdges).To(HaveLen(1))
   465  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   466  	mi.reset()
   467  
   468  	el.iterSources("prefix1/node2", mi.visitEdge)
   469  	Expect(mi.visitedEdges).To(BeEmpty())
   470  	mi.reset()
   471  
   472  	el.iterSources("prefix2/node3", mi.visitEdge)
   473  	Expect(mi.visitedEdges).To(HaveLen(1))
   474  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "myself", "edge to itself"}))
   475  	mi.reset()
   476  
   477  	// delete the remaining edges
   478  	el.delEdge(edge{ // "prefix1/node2" -> "prefix1/node1"
   479  		targetKey:  "prefix1/node1",
   480  		isPrefix:   false,
   481  		sourceNode: "prefix1/node2",
   482  		relation:   "depends-on",
   483  		label:      "node1 exists",
   484  	})
   485  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   486  	el.delEdge(edge{ // "prefix2/node3" -> "prefix2/node3"
   487  		targetKey:  "prefix2/node3",
   488  		isPrefix:   false,
   489  		sourceNode: "prefix2/node3",
   490  		relation:   "myself",
   491  		label:      "edge to itself",
   492  	})
   493  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   494  	Expect(el.edgeData).To(BeEmpty()) // gc-ed
   495  	Expect(el.edgeOffset).To(BeEmpty()) // gc-ed
   496  
   497  	el.iterSources("prefix1/node1", mi.visitEdge)
   498  	Expect(mi.visitedEdges).To(BeEmpty())
   499  
   500  	el.iterSources("prefix1/node2", mi.visitEdge)
   501  	Expect(mi.visitedEdges).To(BeEmpty())
   502  
   503  	el.iterSources("prefix2/node3", mi.visitEdge)
   504  	Expect(mi.visitedEdges).To(BeEmpty())
   505  }
   506  
   507  func TestLookupOverEdgesWithOverlay(t *testing.T) {
   508  	RegisterTestingT(t)
   509  
   510  	mi := newMockIter()
   511  
   512  	el := newEdgeLookup(nil)
   513  	Expect(el).ToNot(BeNil())
   514  
   515  	el.addEdge(edge{ // "prefix1/node2" -> "prefix1/node1"
   516  		targetKey:  "prefix1/node1",
   517  		isPrefix:   false,
   518  		sourceNode: "prefix1/node2",
   519  		relation:   "depends-on",
   520  		label:      "node1 exists",
   521  	})
   522  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   523  	el.addEdge(edge{ // "prefix1/node2" -> "prefix2/node3"
   524  		targetKey:  "prefix2/node3",
   525  		isPrefix:   false,
   526  		sourceNode: "prefix1/node2",
   527  		relation:   "depends-on",
   528  		label:      "node3 exists",
   529  	})
   530  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   531  	el.addEdge(edge{ // "prefix2/node3" -> "prefix1/*"
   532  		targetKey:  "prefix1/",
   533  		isPrefix:   true,
   534  		sourceNode: "prefix2/node3",
   535  		relation:   "depends-on",
   536  		label:      "prefix1 non-empty",
   537  	})
   538  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   539  
   540  	elOver := el.makeOverlay()
   541  	Expect(elOver).ToNot(BeNil())
   542  	Expect(elOver.underlay).To(Equal(el))
   543  	Expect(el.overlay).To(Equal(elOver))
   544  	Expect(elOver.verifyDirDepthBounds()).To(BeNil())
   545  
   546  	elOver.addEdge(edge{ // "prefix2/node3" -> "prefix2/node3"
   547  		targetKey:  "prefix2/node3",
   548  		isPrefix:   false,
   549  		sourceNode: "prefix2/node3",
   550  		relation:   "myself",
   551  		label:      "edge to itself",
   552  	})
   553  	Expect(elOver.verifyDirDepthBounds()).To(BeNil())
   554  
   555  	elOver.addEdge(edge{
   556  		targetKey:  "", // "prefix1/node1" -> *
   557  		isPrefix:   true,
   558  		sourceNode: "prefix1/node1",
   559  		relation:   "all",
   560  		label:      "all",
   561  	})
   562  	Expect(elOver.verifyDirDepthBounds()).To(BeNil())
   563  
   564  	Expect(el.edgeData).To(HaveLen(3))
   565  	Expect(elOver.edgeOffset).To(HaveLen(5))
   566  
   567  	// check overlay
   568  	elOver.iterSources("prefix1/node1", mi.visitEdge)
   569  	Expect(mi.visitedEdges).To(HaveLen(3))
   570  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   571  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   572  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   573  	mi.reset()
   574  	elOver.iterSources("prefix1/node2", mi.visitEdge)
   575  	Expect(mi.visitedEdges).To(HaveLen(2))
   576  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   577  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   578  	mi.reset()
   579  	elOver.iterSources("prefix2/node3", mi.visitEdge)
   580  	Expect(mi.visitedEdges).To(HaveLen(3))
   581  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node3 exists"}))
   582  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "myself", "edge to itself"}))
   583  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   584  	mi.reset()
   585  
   586  	// check underlay
   587  	el.iterSources("prefix1/node1", mi.visitEdge)
   588  	Expect(mi.visitedEdges).To(HaveLen(2))
   589  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   590  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   591  	mi.reset()
   592  	el.iterSources("prefix1/node2", mi.visitEdge)
   593  	Expect(mi.visitedEdges).To(HaveLen(1))
   594  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   595  	mi.reset()
   596  	el.iterSources("prefix2/node3", mi.visitEdge)
   597  	Expect(mi.visitedEdges).To(HaveLen(1))
   598  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node3 exists"}))
   599  	mi.reset()
   600  
   601  	// save and re-check underlay
   602  	elOver.saveOverlay()
   603  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   604  	el.iterSources("prefix1/node1", mi.visitEdge)
   605  	Expect(mi.visitedEdges).To(HaveLen(3))
   606  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   607  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   608  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   609  	mi.reset()
   610  	el.iterSources("prefix1/node2", mi.visitEdge)
   611  	Expect(mi.visitedEdges).To(HaveLen(2))
   612  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   613  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   614  	mi.reset()
   615  	el.iterSources("prefix2/node3", mi.visitEdge)
   616  	Expect(mi.visitedEdges).To(HaveLen(3))
   617  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node3 exists"}))
   618  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "myself", "edge to itself"}))
   619  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   620  	mi.reset()
   621  
   622  	// delete 2 edges in overlay
   623  	elOver.delEdge(edge{ // "prefix1/node2" -> "prefix2/node3"
   624  		targetKey:  "prefix2/node3",
   625  		isPrefix:   false,
   626  		sourceNode: "prefix1/node2",
   627  		relation:   "depends-on",
   628  		label:      "node3 exists",
   629  	})
   630  	Expect(elOver.verifyDirDepthBounds()).To(BeNil())
   631  	elOver.delEdge(edge{
   632  		targetKey:  "", // "prefix1/node1" -> *
   633  		isPrefix:   true,
   634  		sourceNode: "prefix1/node1",
   635  		relation:   "all",
   636  		label:      "all",
   637  	})
   638  	Expect(elOver.verifyDirDepthBounds()).To(BeNil())
   639  	Expect(elOver.edgeData).To(HaveLen(5)) // not gc-ed yet
   640  	Expect(elOver.edgeOffset).To(HaveLen(5)) // not gc-ed yet
   641  
   642  	// check overlay
   643  	elOver.iterSources("prefix1/node1", mi.visitEdge)
   644  	Expect(mi.visitedEdges).To(HaveLen(2))
   645  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   646  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   647  	mi.reset()
   648  	elOver.iterSources("prefix1/node2", mi.visitEdge)
   649  	Expect(mi.visitedEdges).To(HaveLen(1))
   650  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   651  	mi.reset()
   652  	elOver.iterSources("prefix2/node3", mi.visitEdge)
   653  	Expect(mi.visitedEdges).To(HaveLen(1))
   654  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "myself", "edge to itself"}))
   655  	mi.reset()
   656  
   657  	// underlay before save
   658  	el.iterSources("prefix1/node1", mi.visitEdge)
   659  	Expect(mi.visitedEdges).To(HaveLen(3))
   660  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   661  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   662  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   663  	mi.reset()
   664  	el.iterSources("prefix1/node2", mi.visitEdge)
   665  	Expect(mi.visitedEdges).To(HaveLen(2))
   666  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   667  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   668  	mi.reset()
   669  	el.iterSources("prefix2/node3", mi.visitEdge)
   670  	Expect(mi.visitedEdges).To(HaveLen(3))
   671  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node3 exists"}))
   672  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "myself", "edge to itself"}))
   673  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node1", "all", "all"}))
   674  	mi.reset()
   675  
   676  	// save and re-check underlay
   677  	elOver.saveOverlay()
   678  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   679  	el.iterSources("prefix1/node1", mi.visitEdge)
   680  	Expect(mi.visitedEdges).To(HaveLen(2))
   681  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   682  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   683  	mi.reset()
   684  	el.iterSources("prefix1/node2", mi.visitEdge)
   685  	Expect(mi.visitedEdges).To(HaveLen(1))
   686  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   687  	mi.reset()
   688  	el.iterSources("prefix2/node3", mi.visitEdge)
   689  	Expect(mi.visitedEdges).To(HaveLen(1))
   690  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "myself", "edge to itself"}))
   691  	mi.reset()
   692  
   693  	// delete another edge, but do not save
   694  	elOver.delEdge(edge{ // "prefix2/node3" -> "prefix1/*"
   695  		targetKey:  "prefix1/",
   696  		isPrefix:   true,
   697  		sourceNode: "prefix2/node3",
   698  		relation:   "depends-on",
   699  		label:      "prefix1 non-empty",
   700  	})
   701  	Expect(elOver.verifyDirDepthBounds()).To(BeNil())
   702  	Expect(elOver.edgeData).To(HaveLen(2)) // gc-ed
   703  	Expect(elOver.edgeOffset).To(HaveLen(2)) // gc-ed
   704  	Expect(el.edgeOffset).To(HaveLen(5))
   705  	Expect(el.edgeData).To(HaveLen(5))
   706  
   707  	// check overlay
   708  	elOver.iterSources("prefix1/node1", mi.visitEdge)
   709  	Expect(mi.visitedEdges).To(HaveLen(1))
   710  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   711  	mi.reset()
   712  	elOver.iterSources("prefix1/node2", mi.visitEdge)
   713  	Expect(mi.visitedEdges).To(BeEmpty())
   714  	mi.reset()
   715  	elOver.iterSources("prefix2/node3", mi.visitEdge)
   716  	Expect(mi.visitedEdges).To(HaveLen(1))
   717  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "myself", "edge to itself"}))
   718  	mi.reset()
   719  
   720  	// throw away changes
   721  	Expect(el.makeOverlay()).To(Equal(elOver))
   722  	elOver = el.makeOverlay()
   723  	elOver.saveOverlay()
   724  	Expect(el.verifyDirDepthBounds()).To(BeNil())
   725  
   726  	// check underlay
   727  	el.iterSources("prefix1/node1", mi.visitEdge)
   728  	Expect(mi.visitedEdges).To(HaveLen(2))
   729  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix1/node2", "depends-on", "node1 exists"}))
   730  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   731  	mi.reset()
   732  	el.iterSources("prefix1/node2", mi.visitEdge)
   733  	Expect(mi.visitedEdges).To(HaveLen(1))
   734  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "depends-on", "prefix1 non-empty"}))
   735  	mi.reset()
   736  	el.iterSources("prefix2/node3", mi.visitEdge)
   737  	Expect(mi.visitedEdges).To(HaveLen(1))
   738  	Expect(mi.visitedEdges).To(HaveKey(visitedEdge{"prefix2/node3", "myself", "edge to itself"}))
   739  	mi.reset()
   740  }