github.com/ipld/go-ipld-prime@v0.21.0/traversal/walk_test.go (about) 1 package traversal_test 2 3 import ( 4 "fmt" 5 "io" 6 "testing" 7 8 qt "github.com/frankban/quicktest" 9 10 "github.com/ipld/go-ipld-prime" 11 _ "github.com/ipld/go-ipld-prime/codec/dagjson" 12 "github.com/ipld/go-ipld-prime/datamodel" 13 "github.com/ipld/go-ipld-prime/fluent" 14 "github.com/ipld/go-ipld-prime/linking" 15 cidlink "github.com/ipld/go-ipld-prime/linking/cid" 16 "github.com/ipld/go-ipld-prime/linking/preload" 17 "github.com/ipld/go-ipld-prime/node/basicnode" 18 nodetests "github.com/ipld/go-ipld-prime/node/tests" 19 "github.com/ipld/go-ipld-prime/storage" 20 "github.com/ipld/go-ipld-prime/traversal" 21 "github.com/ipld/go-ipld-prime/traversal/selector" 22 "github.com/ipld/go-ipld-prime/traversal/selector/builder" 23 selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" 24 ) 25 26 /* Remember, we've got the following fixtures in scope: 27 var ( 28 // baguqeeyexkjwnfy 29 leafAlpha, leafAlphaLnk = encode(basicnode.NewString("alpha")) 30 // baguqeeyeqvc7t3a 31 leafBeta, leafBetaLnk = encode(basicnode.NewString("beta")) 32 // baguqeeyezhlahvq 33 middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype.Map, 3, func(na fluent.MapAssembler) { 34 na.AssembleEntry("foo").AssignBool(true) 35 na.AssembleEntry("bar").AssignBool(false) 36 na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) { 37 na.AssembleEntry("alink").AssignLink(leafAlphaLnk) 38 na.AssembleEntry("nonlink").AssignString("zoo") 39 }) 40 })) 41 // baguqeeyehfkkfwa 42 middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype.List, 4, func(na fluent.ListAssembler) { 43 na.AssembleValue().AssignLink(leafAlphaLnk) 44 na.AssembleValue().AssignLink(leafAlphaLnk) 45 na.AssembleValue().AssignLink(leafBetaLnk) 46 na.AssembleValue().AssignLink(leafAlphaLnk) 47 })) 48 // baguqeeyeie4ajfy 49 rootNode, rootNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype.Map, 4, func(na fluent.MapAssembler) { 50 na.AssembleEntry("plain").AssignString("olde string") 51 na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk) 52 na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk) 53 na.AssembleEntry("linkedList").AssignLink(middleListNodeLnk) 54 }))) 55 */ 56 57 // covers traverse using a variety of selectors. 58 // all cases here use one already-loaded Node; no link-loading exercised. 59 60 func TestWalkMatching(t *testing.T) { 61 ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) 62 t.Run("traverse selecting true should visit the root", func(t *testing.T) { 63 err := traversal.WalkMatching(basicnode.NewString("x"), selector.Matcher{}, func(prog traversal.Progress, n datamodel.Node) error { 64 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("x")) 65 qt.Check(t, prog.Path.String(), qt.Equals, datamodel.Path{}.String()) 66 return nil 67 }) 68 qt.Check(t, err, qt.IsNil) 69 }) 70 t.Run("traverse selecting true should visit only the root and no deeper", func(t *testing.T) { 71 err := traversal.WalkMatching(middleMapNode, selector.Matcher{}, func(prog traversal.Progress, n datamodel.Node) error { 72 qt.Check(t, n, qt.Equals, middleMapNode) 73 qt.Check(t, prog.Path.String(), qt.Equals, datamodel.Path{}.String()) 74 return nil 75 }) 76 qt.Check(t, err, qt.IsNil) 77 }) 78 t.Run("traverse selecting fields should work", func(t *testing.T) { 79 ss := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 80 efsb.Insert("foo", ssb.Matcher()) 81 efsb.Insert("bar", ssb.Matcher()) 82 }) 83 s, err := ss.Selector() 84 qt.Assert(t, err, qt.IsNil) 85 var order int 86 err = traversal.WalkMatching(middleMapNode, s, func(prog traversal.Progress, n datamodel.Node) error { 87 switch order { 88 case 0: 89 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewBool(true)) 90 qt.Check(t, prog.Path.String(), qt.Equals, "foo") 91 case 1: 92 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewBool(false)) 93 qt.Check(t, prog.Path.String(), qt.Equals, "bar") 94 } 95 order++ 96 return nil 97 }) 98 qt.Check(t, err, qt.IsNil) 99 qt.Check(t, order, qt.Equals, 2) 100 }) 101 t.Run("traverse selecting fields recursively should work", func(t *testing.T) { 102 ss := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 103 efsb.Insert("foo", ssb.Matcher()) 104 efsb.Insert("nested", ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 105 efsb.Insert("nonlink", ssb.Matcher()) 106 })) 107 }) 108 s, err := ss.Selector() 109 qt.Assert(t, err, qt.IsNil) 110 var order int 111 err = traversal.WalkMatching(middleMapNode, s, func(prog traversal.Progress, n datamodel.Node) error { 112 switch order { 113 case 0: 114 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewBool(true)) 115 qt.Check(t, prog.Path.String(), qt.Equals, "foo") 116 case 1: 117 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("zoo")) 118 qt.Check(t, prog.Path.String(), qt.Equals, "nested/nonlink") 119 } 120 order++ 121 return nil 122 }) 123 qt.Check(t, err, qt.IsNil) 124 qt.Check(t, order, qt.Equals, 2) 125 }) 126 t.Run("traversing across nodes should work", func(t *testing.T) { 127 ss := ssb.ExploreRecursive(selector.RecursionLimitDepth(3), ssb.ExploreUnion( 128 ssb.Matcher(), 129 ssb.ExploreAll(ssb.ExploreRecursiveEdge()), 130 )) 131 s, err := ss.Selector() 132 qt.Check(t, err, qt.IsNil) 133 var order int 134 lsys := cidlink.DefaultLinkSystem() 135 lsys.SetReadStorage(&store) 136 err = traversal.Progress{ 137 Cfg: &traversal.Config{ 138 LinkSystem: lsys, 139 LinkTargetNodePrototypeChooser: basicnode.Chooser, 140 }, 141 }.WalkMatching(middleMapNode, s, func(prog traversal.Progress, n datamodel.Node) error { 142 switch order { 143 case 0: 144 qt.Check(t, n, qt.Equals, middleMapNode) 145 qt.Check(t, prog.Path.String(), qt.Equals, "") 146 case 1: 147 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewBool(true)) 148 qt.Check(t, prog.Path.String(), qt.Equals, "foo") 149 case 2: 150 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewBool(false)) 151 qt.Check(t, prog.Path.String(), qt.Equals, "bar") 152 case 3: 153 qt.Check(t, n, nodetests.NodeContentEquals, fluent.MustBuildMap(basicnode.Prototype.Map, 2, func(na fluent.MapAssembler) { 154 na.AssembleEntry("alink").AssignLink(leafAlphaLnk) 155 na.AssembleEntry("nonlink").AssignString("zoo") 156 })) 157 qt.Check(t, prog.Path.String(), qt.Equals, "nested") 158 case 4: 159 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 160 qt.Check(t, prog.Path.String(), qt.Equals, "nested/alink") 161 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "nested/alink") 162 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, leafAlphaLnk.String()) 163 164 case 5: 165 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("zoo")) 166 qt.Check(t, prog.Path.String(), qt.Equals, "nested/nonlink") 167 } 168 order++ 169 return nil 170 }) 171 qt.Check(t, err, qt.IsNil) 172 qt.Check(t, order, qt.Equals, 6) 173 }) 174 t.Run("traversing lists should work", func(t *testing.T) { 175 ss := ssb.ExploreRange(0, 3, ssb.Matcher()) 176 s, err := ss.Selector() 177 qt.Check(t, err, qt.IsNil) 178 var order int 179 lsys := cidlink.DefaultLinkSystem() 180 lsys.SetReadStorage(&store) 181 err = traversal.Progress{ 182 Cfg: &traversal.Config{ 183 LinkSystem: lsys, 184 LinkTargetNodePrototypeChooser: basicnode.Chooser, 185 }, 186 }.WalkMatching(middleListNode, s, func(prog traversal.Progress, n datamodel.Node) error { 187 switch order { 188 case 0: 189 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 190 qt.Check(t, prog.Path.String(), qt.Equals, "0") 191 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "0") 192 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, leafAlphaLnk.String()) 193 case 1: 194 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 195 qt.Check(t, prog.Path.String(), qt.Equals, "1") 196 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "1") 197 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, leafAlphaLnk.String()) 198 case 2: 199 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("beta")) 200 qt.Check(t, prog.Path.String(), qt.Equals, "2") 201 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "2") 202 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, leafBetaLnk.String()) 203 } 204 order++ 205 return nil 206 }) 207 qt.Check(t, err, qt.IsNil) 208 qt.Check(t, order, qt.Equals, 3) 209 }) 210 t.Run("multiple layers of link traversal should work", func(t *testing.T) { 211 ss := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 212 efsb.Insert("linkedList", ssb.ExploreAll(ssb.Matcher())) 213 efsb.Insert("linkedMap", ssb.ExploreRecursive(selector.RecursionLimitDepth(3), ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 214 efsb.Insert("foo", ssb.Matcher()) 215 efsb.Insert("nonlink", ssb.Matcher()) 216 efsb.Insert("alink", ssb.Matcher()) 217 efsb.Insert("nested", ssb.ExploreRecursiveEdge()) 218 }))) 219 }) 220 s, err := ss.Selector() 221 qt.Check(t, err, qt.IsNil) 222 var order int 223 lsys := cidlink.DefaultLinkSystem() 224 lsys.SetReadStorage(&store) 225 err = traversal.Progress{ 226 Cfg: &traversal.Config{ 227 LinkSystem: lsys, 228 LinkTargetNodePrototypeChooser: basicnode.Chooser, 229 }, 230 }.WalkMatching(rootNode, s, func(prog traversal.Progress, n datamodel.Node) error { 231 switch order { 232 case 0: 233 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 234 qt.Check(t, prog.Path.String(), qt.Equals, "linkedList/0") 235 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "linkedList/0") 236 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, leafAlphaLnk.String()) 237 case 1: 238 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 239 qt.Check(t, prog.Path.String(), qt.Equals, "linkedList/1") 240 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "linkedList/1") 241 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, leafAlphaLnk.String()) 242 case 2: 243 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("beta")) 244 qt.Check(t, prog.Path.String(), qt.Equals, "linkedList/2") 245 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "linkedList/2") 246 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, leafBetaLnk.String()) 247 case 3: 248 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 249 qt.Check(t, prog.Path.String(), qt.Equals, "linkedList/3") 250 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "linkedList/3") 251 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, leafAlphaLnk.String()) 252 case 4: 253 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewBool(true)) 254 qt.Check(t, prog.Path.String(), qt.Equals, "linkedMap/foo") 255 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "linkedMap") 256 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, middleMapNodeLnk.String()) 257 case 5: 258 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("zoo")) 259 qt.Check(t, prog.Path.String(), qt.Equals, "linkedMap/nested/nonlink") 260 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "linkedMap") 261 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, middleMapNodeLnk.String()) 262 case 6: 263 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 264 qt.Check(t, prog.Path.String(), qt.Equals, "linkedMap/nested/alink") 265 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "linkedMap/nested/alink") 266 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, leafAlphaLnk.String()) 267 } 268 order++ 269 return nil 270 }) 271 qt.Check(t, err, qt.IsNil) 272 qt.Check(t, order, qt.Equals, 7) 273 }) 274 275 t.Run("no visiting of nodes before start path", func(t *testing.T) { 276 ss := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 277 efsb.Insert("linkedList", ssb.ExploreRecursive( 278 selector.RecursionLimitNone(), 279 ssb.ExploreUnion(ssb.Matcher(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())))) 280 efsb.Insert("plain", ssb.ExploreAll(ssb.Matcher())) 281 efsb.Insert("linkedString", ssb.ExploreAll(ssb.Matcher())) 282 efsb.Insert("linkedMap", ssb.ExploreUnion(ssb.Matcher(), 283 ssb.ExploreRecursive(selector.RecursionLimitDepth(3), ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 284 efsb.Insert("foo", ssb.Matcher()) 285 efsb.Insert("nonlink", ssb.Matcher()) 286 efsb.Insert("alink", ssb.Matcher()) 287 efsb.Insert("nested", ssb.ExploreRecursiveEdge()) 288 })))) 289 }) 290 s, err := ss.Selector() 291 qt.Check(t, err, qt.IsNil) 292 var order int 293 lsys := cidlink.DefaultLinkSystem() 294 lsys.SetReadStorage(&store) 295 visitedCids := make([]string, 0) 296 lsys.StorageReadOpener = func(lnkCtx ipld.LinkContext, lnk ipld.Link) (io.Reader, error) { 297 visitedCids = append(visitedCids, lnk.(cidlink.Link).Cid.String()) 298 return store.GetStream(lnkCtx.Ctx, lnk.(cidlink.Link).Cid.KeyString()) 299 } 300 err = traversal.Progress{ 301 Cfg: &traversal.Config{ 302 LinkSystem: lsys, 303 LinkTargetNodePrototypeChooser: basicnode.Chooser, 304 StartAtPath: datamodel.ParsePath("linkedMap/nested/nonlink"), 305 }, 306 }.WalkMatching(rootNode, s, func(prog traversal.Progress, n datamodel.Node) error { 307 switch order { 308 case 0: 309 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("zoo")) 310 qt.Check(t, prog.Path.String(), qt.Equals, "linkedMap/nested/nonlink") 311 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "linkedMap") 312 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, middleMapNodeLnk.String()) 313 case 1: 314 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 315 qt.Check(t, prog.Path.String(), qt.Equals, "linkedMap/nested/alink") 316 qt.Check(t, prog.LastBlock.Path.String(), qt.Equals, "linkedMap/nested/alink") 317 qt.Check(t, prog.LastBlock.Link.String(), qt.Equals, leafAlphaLnk.String()) 318 } 319 order++ 320 return nil 321 }) 322 qt.Check(t, err, qt.IsNil) 323 qt.Check(t, order, qt.Equals, 2) 324 // linkedMap=baguqeeyezhlahvq, alink=baguqeeyexkjwnfy 325 qt.Check(t, visitedCids, qt.DeepEquals, []string{"baguqeeyezhlahvq", "baguqeeyexkjwnfy"}) 326 }) 327 328 t.Run("no loading of unnecessary nodes before start path", func(t *testing.T) { 329 ss := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 330 efsb.Insert("linkedList", ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()))) 331 efsb.Insert("plain", ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()))) 332 efsb.Insert("linkedString", ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()))) 333 efsb.Insert("linkedMap", 334 ssb.ExploreRecursive(selector.RecursionLimitDepth(3), ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 335 efsb.Insert("foo", ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()))) 336 efsb.Insert("nonlink", ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()))) 337 efsb.Insert("alink", ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()))) 338 efsb.Insert("nested", ssb.ExploreRecursiveEdge()) 339 }))) 340 }) 341 s, err := ss.Selector() 342 qt.Check(t, err, qt.IsNil) 343 lsys := cidlink.DefaultLinkSystem() 344 lsys.SetReadStorage(&store) 345 visitedCids := make([]string, 0) 346 lsys.StorageReadOpener = func(lnkCtx ipld.LinkContext, lnk ipld.Link) (io.Reader, error) { 347 visitedCids = append(visitedCids, lnk.(cidlink.Link).Cid.String()) 348 return store.GetStream(lnkCtx.Ctx, lnk.(cidlink.Link).Cid.KeyString()) 349 } 350 err = traversal.Progress{ 351 Cfg: &traversal.Config{ 352 LinkSystem: lsys, 353 LinkTargetNodePrototypeChooser: basicnode.Chooser, 354 StartAtPath: datamodel.ParsePath("linkedMap/nested/nonlink"), 355 }, 356 }.WalkMatching(rootNode, s, func(prog traversal.Progress, n datamodel.Node) error { 357 return nil 358 }) 359 qt.Check(t, err, qt.IsNil) 360 // linkedMap=baguqeeyezhlahvq, alink=baguqeeyexkjwnfy 361 qt.Check(t, visitedCids, qt.DeepEquals, []string{"baguqeeyezhlahvq", "baguqeeyexkjwnfy"}) 362 }) 363 } 364 365 func TestWalkBudgets(t *testing.T) { 366 for _, preloader := range []bool{false, true} { 367 t.Run(fmt.Sprintf("preloader=%v", preloader), func(t *testing.T) { 368 t.Run("node-budget-halts", func(t *testing.T) { 369 ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) 370 ss := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 371 efsb.Insert("foo", ssb.Matcher()) 372 efsb.Insert("bar", ssb.Matcher()) 373 }) 374 s, err := ss.Selector() 375 qt.Assert(t, err, qt.Equals, nil) 376 var order int 377 prog := traversal.Progress{} 378 prog.Budget = &traversal.Budget{ 379 NodeBudget: 2, // should reach root, then "foo", then stop. 380 } 381 preloadLinks := make([]preload.Link, 0) 382 if preloader { 383 // having a preloader shouldn't change budgeting 384 prog.Cfg = &traversal.Config{ 385 Preloader: func(_ preload.PreloadContext, link preload.Link) { 386 preloadLinks = append(preloadLinks, link) 387 }, 388 } 389 } 390 err = prog.WalkMatching(middleMapNode, s, func(prog traversal.Progress, n datamodel.Node) error { 391 switch order { 392 case 0: 393 qt.Assert(t, n, nodetests.NodeContentEquals, basicnode.NewBool(true)) 394 qt.Assert(t, prog.Path.String(), qt.Equals, "foo") 395 } 396 order++ 397 return nil 398 }) 399 if preloader { 400 qt.Assert(t, preloadLinks, qt.HasLen, 0) 401 } 402 qt.Check(t, order, qt.Equals, 1) // because it should've stopped early 403 qt.Assert(t, err, qt.Not(qt.Equals), nil) 404 qt.Check(t, err.Error(), qt.Equals, `traversal budget exceeded: budget for nodes reached zero while on path "bar"`) 405 }) 406 407 t.Run("link-budget-halts", func(t *testing.T) { 408 ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) 409 ss := ssb.ExploreAll(ssb.Matcher()) 410 s, err := ss.Selector() 411 qt.Assert(t, err, qt.Equals, nil) 412 var order int 413 lsys := cidlink.DefaultLinkSystem() 414 lsys.SetReadStorage(&store) 415 prog := traversal.Progress{ 416 Cfg: &traversal.Config{ 417 LinkSystem: lsys, 418 LinkTargetNodePrototypeChooser: basicnode.Chooser, 419 }, 420 Budget: &traversal.Budget{ 421 NodeBudget: 9000, 422 LinkBudget: 3, 423 }, 424 } 425 preloadLinks := make([]preload.Link, 0) 426 if preloader { 427 // having a preloader shouldn't change budgeting 428 prog.Cfg.Preloader = func(_ preload.PreloadContext, link preload.Link) { 429 preloadLinks = append(preloadLinks, link) 430 } 431 } 432 err = prog.WalkMatching(middleListNode, s, func(prog traversal.Progress, n datamodel.Node) error { 433 switch order { 434 case 0: 435 qt.Assert(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 436 qt.Assert(t, prog.Path.String(), qt.Equals, "0") 437 case 1: 438 qt.Assert(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 439 qt.Assert(t, prog.Path.String(), qt.Equals, "1") 440 case 2: 441 qt.Assert(t, n, nodetests.NodeContentEquals, basicnode.NewString("beta")) 442 qt.Assert(t, prog.Path.String(), qt.Equals, "2") 443 } 444 order++ 445 return nil 446 }) 447 qt.Check(t, order, qt.Equals, 3) 448 qt.Assert(t, err, qt.Not(qt.Equals), nil) 449 qt.Check(t, err.Error(), qt.Equals, `traversal budget exceeded: budget for links reached zero while on path "3" (link: "baguqeeyexkjwnfy")`) 450 if preloader { 451 qt.Assert(t, preloadLinks, qt.HasLen, 3) 452 qt.Check(t, preloadLinks[0].Link, qt.Equals, leafAlphaLnk) 453 qt.Check(t, preloadLinks[1].Link, qt.Equals, leafAlphaLnk) 454 qt.Check(t, preloadLinks[2].Link, qt.Equals, leafBetaLnk) 455 } 456 }) 457 }) 458 } 459 } 460 461 func TestWalkBlockLoadOrder(t *testing.T) { 462 // a more nested root that we can use to test SkipMe as well 463 // note that in using `rootNodeLnk` here rather than `rootNode` we're using the 464 // dag-json round-trip version which will have different field ordering 465 newRootNode, newRootLink := encode(fluent.MustBuildList(basicnode.Prototype.List, 6, func(na fluent.ListAssembler) { 466 na.AssembleValue().AssignLink(rootNodeLnk) 467 na.AssembleValue().AssignLink(middleListNodeLnk) 468 na.AssembleValue().AssignLink(rootNodeLnk) 469 na.AssembleValue().AssignLink(middleListNodeLnk) 470 na.AssembleValue().AssignLink(rootNodeLnk) 471 na.AssembleValue().AssignLink(middleListNodeLnk) 472 })) 473 474 linkNames := make(map[datamodel.Link]string) 475 linkNames[newRootLink] = "newRootLink" 476 linkNames[rootNodeLnk] = "rootNodeLnk" 477 linkNames[leafAlphaLnk] = "leafAlphaLnk" 478 linkNames[middleMapNodeLnk] = "middleMapNodeLnk" 479 linkNames[leafAlphaLnk] = "leafAlphaLnk" 480 linkNames[middleListNodeLnk] = "middleListNodeLnk" 481 linkNames[leafAlphaLnk] = "leafAlphaLnk" 482 linkNames[leafBetaLnk] = "leafBetaLnk" 483 /* useful to know CIDs for these when debugging 484 for v, n := range linkNames { 485 t.Logf("n:%v:%v\n", n, v) 486 } 487 */ 488 // the links that we expect from the root node, starting _at_ the root node itself 489 rootNodeExpectedLinks := []datamodel.Link{ 490 rootNodeLnk, 491 middleListNodeLnk, 492 leafAlphaLnk, 493 leafAlphaLnk, 494 leafBetaLnk, 495 leafAlphaLnk, 496 middleMapNodeLnk, 497 leafAlphaLnk, 498 leafAlphaLnk, 499 } 500 // same thing but just for middleListNode 501 middleListNodeLinks := []datamodel.Link{ 502 middleListNodeLnk, 503 leafAlphaLnk, 504 leafAlphaLnk, 505 leafBetaLnk, 506 leafAlphaLnk, 507 } 508 // our newRootNode is a list that contains 3 consecutive links to rootNode 509 expectedAllBlocks := make([]datamodel.Link, 3*(len(rootNodeExpectedLinks)+len(middleListNodeLinks))) 510 for i := 0; i < 3; i++ { 511 copy(expectedAllBlocks[i*len(rootNodeExpectedLinks)+i*len(middleListNodeLinks):], rootNodeExpectedLinks[:]) 512 copy(expectedAllBlocks[(i+1)*len(rootNodeExpectedLinks)+i*len(middleListNodeLinks):], middleListNodeLinks[:]) 513 } 514 515 verifySelectorLoads := func( 516 t *testing.T, 517 rootNode datamodel.Node, 518 expected []datamodel.Link, 519 s datamodel.Node, 520 linkVisitOnce bool, 521 startAtPath datamodel.Path, 522 preloader preload.Loader, 523 readFn func(lc linking.LinkContext, l datamodel.Link) (io.Reader, error)) { 524 525 var count int 526 lsys := cidlink.DefaultLinkSystem() 527 lsys.StorageReadOpener = func(lc linking.LinkContext, l datamodel.Link) (io.Reader, error) { 528 // t.Logf("load %d: %v (%s) <> %v (%s) - %s", count, expected[count].String(), linkNames[expected[count]], l.String(), linkNames[l], lc.LinkPath) 529 // t.Logf("%v (%v) %s<> %v (%v)\n", l, linkNames[l], strings.Repeat(" ", 17-len(linkNames[l])), expected[count], linkNames[expected[count]]) 530 qt.Check(t, l.String(), qt.Equals, expected[count].String()) 531 count++ 532 return readFn(lc, l) 533 } 534 sel, err := selector.CompileSelector(s) 535 qt.Check(t, err, qt.IsNil) 536 err = traversal.Progress{ 537 Cfg: &traversal.Config{ 538 LinkSystem: lsys, 539 LinkTargetNodePrototypeChooser: basicnode.Chooser, 540 LinkVisitOnlyOnce: linkVisitOnce, 541 StartAtPath: startAtPath, 542 Preloader: preloader, 543 }, 544 }.WalkMatching(rootNode, sel, func(prog traversal.Progress, n datamodel.Node) error { 545 return nil 546 }) 547 qt.Check(t, err, qt.IsNil) 548 qt.Check(t, count, qt.Equals, len(expected)) 549 } 550 551 t.Run("CommonSelector_MatchAllRecursively", func(t *testing.T) { 552 s := selectorparse.CommonSelector_MatchAllRecursively 553 verifySelectorLoads(t, newRootNode, expectedAllBlocks, s, false, datamodel.NewPath(nil), nil, func(lctx linking.LinkContext, lnk datamodel.Link) (io.Reader, error) { 554 return storage.GetStream(lctx.Ctx, &store, lnk.Binary()) 555 }) 556 }) 557 558 t.Run("CommonSelector_ExploreAllRecursively", func(t *testing.T) { 559 s := selectorparse.CommonSelector_ExploreAllRecursively 560 verifySelectorLoads(t, newRootNode, expectedAllBlocks, s, false, datamodel.NewPath(nil), nil, func(lctx linking.LinkContext, lnk datamodel.Link) (io.Reader, error) { 561 return storage.GetStream(lctx.Ctx, &store, lnk.Binary()) 562 }) 563 }) 564 565 t.Run("explore all with preload", func(t *testing.T) { 566 s := selectorparse.CommonSelector_ExploreAllRecursively 567 568 newNestedRootNode, _ := encode(fluent.MustBuildList(basicnode.Prototype.List, 2, func(na fluent.ListAssembler) { 569 na.AssembleValue().CreateMap(3, func(ma fluent.MapAssembler) { 570 ma.AssembleEntry("a").AssignLink(rootNodeLnk) 571 ma.AssembleEntry("b").AssignLink(middleListNodeLnk) 572 ma.AssembleEntry("c").AssignLink(rootNodeLnk) 573 }) 574 na.AssembleValue().CreateMap(3, func(ma fluent.MapAssembler) { 575 ma.AssembleEntry("d").AssignLink(middleListNodeLnk) 576 ma.AssembleEntry("e").AssignLink(rootNodeLnk) 577 ma.AssembleEntry("f").AssignLink(middleListNodeLnk) 578 }) 579 })) 580 581 rootNodePreloads := []datamodel.Link{middleListNodeLnk, middleMapNodeLnk, leafAlphaLnk} 582 middleListNodePreloads := []datamodel.Link{leafAlphaLnk, leafAlphaLnk, leafBetaLnk, leafAlphaLnk} 583 middleMapNodePreloads := []datamodel.Link{leafAlphaLnk} 584 rootNodePreloadsRecursive := [][]datamodel.Link{rootNodePreloads, middleListNodePreloads, middleMapNodePreloads} 585 el := [][]datamodel.Link{ 586 {rootNodeLnk, middleListNodeLnk, rootNodeLnk, middleListNodeLnk, rootNodeLnk, middleListNodeLnk}, 587 } 588 for i := 0; i < 3; i++ { 589 el = append(el, rootNodePreloadsRecursive...) 590 el = append(el, middleListNodePreloads) 591 } 592 expectedLinks := make([]datamodel.Link, 0) 593 for _, l := range el { 594 expectedLinks = append(expectedLinks, l...) 595 } 596 preloadIndex := 0 597 preloader := func(_ preload.PreloadContext, link preload.Link) { 598 if preloadIndex >= len(expectedLinks) { 599 t.Fatal("too many preloads") 600 } 601 qt.Check(t, link.Link, qt.Equals, expectedLinks[preloadIndex]) 602 preloadIndex++ 603 } 604 605 verifySelectorLoads(t, newNestedRootNode, expectedAllBlocks, s, false, datamodel.NewPath(nil), preloader, func(lctx linking.LinkContext, lnk datamodel.Link) (io.Reader, error) { 606 return storage.GetStream(lctx.Ctx, &store, lnk.Binary()) 607 }) 608 qt.Check(t, preloadIndex, qt.Equals, len(expectedLinks)) 609 }) 610 611 t.Run("constructed explore-all selector", func(t *testing.T) { 612 // used commonly in Filecoin and other places to "visit all blocks in stable order" 613 ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) 614 s := ssb.ExploreRecursive(selector.RecursionLimitNone(), 615 ssb.ExploreAll(ssb.ExploreRecursiveEdge())). 616 Node() 617 verifySelectorLoads(t, newRootNode, expectedAllBlocks, s, false, datamodel.NewPath(nil), nil, func(lctx linking.LinkContext, lnk datamodel.Link) (io.Reader, error) { 618 return storage.GetStream(lctx.Ctx, &store, lnk.Binary()) 619 }) 620 }) 621 622 t.Run("explore-all with duplicate load skips via SkipMe", func(t *testing.T) { 623 // when we use SkipMe to skip loading of already visited blocks we expect to 624 // see the links show up in Loads but the lack of the links inside rootNode 625 // and middleListNode in this list beyond the first set of loads show that 626 // the block is not traversed when the SkipMe is received 627 expectedSkipMeBlocks := []datamodel.Link{ 628 rootNodeLnk, 629 middleListNodeLnk, 630 leafAlphaLnk, 631 leafAlphaLnk, 632 leafBetaLnk, 633 leafAlphaLnk, 634 middleMapNodeLnk, 635 leafAlphaLnk, 636 leafAlphaLnk, 637 middleListNodeLnk, 638 rootNodeLnk, 639 middleListNodeLnk, 640 rootNodeLnk, 641 middleListNodeLnk, 642 } 643 644 s := selectorparse.CommonSelector_ExploreAllRecursively 645 visited := make(map[datamodel.Link]bool) 646 verifySelectorLoads(t, newRootNode, expectedSkipMeBlocks, s, false, datamodel.NewPath(nil), nil, func(lc linking.LinkContext, l datamodel.Link) (io.Reader, error) { 647 // t.Logf("load %v [%v]\n", l, visited[l]) 648 if visited[l] { 649 return nil, traversal.SkipMe{} 650 } 651 visited[l] = true 652 return storage.GetStream(lc.Ctx, &store, l.Binary()) 653 }) 654 }) 655 656 t.Run("explore-all with duplicate load skips via LinkVisitOnlyOnce:true", func(t *testing.T) { 657 // when using LinkRevisit:false to skip duplicate block loads, our loader 658 // doesn't even get to see the load attempts (unlike SkipMe, where the 659 // loader signals the skips) 660 expectedLinkRevisitBlocks := []datamodel.Link{ 661 rootNodeLnk, 662 middleListNodeLnk, 663 leafAlphaLnk, 664 leafBetaLnk, 665 middleMapNodeLnk, 666 } 667 s := selectorparse.CommonSelector_ExploreAllRecursively 668 verifySelectorLoads(t, newRootNode, expectedLinkRevisitBlocks, s, true, datamodel.NewPath(nil), nil, func(lctx linking.LinkContext, lnk datamodel.Link) (io.Reader, error) { 669 return storage.GetStream(lctx.Ctx, &store, lnk.Binary()) 670 }) 671 }) 672 673 t.Run("explore-all with duplicate load and preloader skips via LinkVisitOnlyOnce:true", func(t *testing.T) { 674 // same as above but make sure the preloader doesn't get in the way 675 expectedLinkRevisitBlocks := []datamodel.Link{ 676 rootNodeLnk, 677 middleListNodeLnk, 678 leafAlphaLnk, 679 leafBetaLnk, 680 middleMapNodeLnk, 681 } 682 s := selectorparse.CommonSelector_ExploreAllRecursively 683 preloadLinks := make(map[datamodel.Link]struct{}) 684 preloader := func(_ preload.PreloadContext, link preload.Link) { 685 preloadLinks[link.Link] = struct{}{} 686 } 687 verifySelectorLoads(t, newRootNode, expectedLinkRevisitBlocks, s, true, datamodel.NewPath(nil), preloader, func(lctx linking.LinkContext, lnk datamodel.Link) (io.Reader, error) { 688 return storage.GetStream(lctx.Ctx, &store, lnk.Binary()) 689 }) 690 for _, l := range expectedLinkRevisitBlocks { 691 qt.Check(t, preloadLinks[l], qt.IsNotNil) 692 } 693 }) 694 695 t.Run("explore-all with duplicate traversal skip via load at path", func(t *testing.T) { 696 // when using LinkRevisit:false to skip duplicate block loads, our loader 697 // doesn't even get to see the load attempts (unlike SkipMe, where the 698 // loader signals the skips) 699 testPathsToBlocksSkipped := []struct { 700 path string 701 expectedLinkVisits []datamodel.Link 702 }{ 703 // 5th node in load sequence for rootNode 704 {"0/linkedList/2", append([]datamodel.Link{rootNodeLnk, middleListNodeLnk}, expectedAllBlocks[4:]...)}, 705 // LinkedMap is 7th no, foo doesn't affect loading 706 {"0/linkedMap/foo", append([]datamodel.Link{rootNodeLnk}, expectedAllBlocks[6:]...)}, 707 // 8th node in load sequence for rootNode 708 {"0/linkedMap/nested/alink", append([]datamodel.Link{rootNodeLnk, middleMapNodeLnk}, expectedAllBlocks[7:]...)}, 709 {"0/linkedString", append([]datamodel.Link{rootNodeLnk}, expectedAllBlocks[8:]...)}, 710 // pash through all nodes first root block, then go load middle list block 711 {"1/2", append([]datamodel.Link{middleListNodeLnk}, expectedAllBlocks[len(rootNodeExpectedLinks)+3:]...)}, 712 {"3/1", append([]datamodel.Link{middleListNodeLnk}, expectedAllBlocks[2*len(rootNodeExpectedLinks)+len(middleListNodeLinks)+2:]...)}, 713 } 714 for _, testCase := range testPathsToBlocksSkipped { 715 t.Run(testCase.path, func(t *testing.T) { 716 startAtPath := datamodel.ParsePath(testCase.path) 717 s := selectorparse.CommonSelector_ExploreAllRecursively 718 verifySelectorLoads(t, newRootNode, testCase.expectedLinkVisits, s, false, startAtPath, nil, func(lctx linking.LinkContext, lnk datamodel.Link) (io.Reader, error) { 719 return storage.GetStream(lctx.Ctx, &store, lnk.Binary()) 720 }) 721 }) 722 } 723 }) 724 } 725 726 func TestWalk_ADLs(t *testing.T) { 727 // we'll make a reifier that when it sees a list returns a custom element instead. 728 customReifier := func(_ linking.LinkContext, n datamodel.Node, _ *linking.LinkSystem) (datamodel.Node, error) { 729 if n.Kind() == datamodel.Kind_List { 730 return leafAlpha, nil 731 } 732 return n, nil 733 } 734 735 ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) 736 ss := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 737 efsb.Insert("linkedList", ssb.ExploreInterpretAs("linkJumper", ssb.Matcher())) 738 }) 739 s, err := ss.Selector() 740 qt.Check(t, err, qt.IsNil) 741 lsys := cidlink.DefaultLinkSystem() 742 lsys.KnownReifiers = map[string]linking.NodeReifier{"linkJumper": customReifier} 743 lsys.SetReadStorage(&store) 744 var order int 745 err = traversal.Progress{ 746 Cfg: &traversal.Config{ 747 LinkSystem: lsys, 748 LinkTargetNodePrototypeChooser: basicnode.Chooser, 749 }, 750 }.WalkMatching(rootNode, s, func(prog traversal.Progress, n datamodel.Node) error { 751 switch order { 752 case 0: 753 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("alpha")) 754 qt.Check(t, prog.Path.String(), qt.Equals, "linkedList") 755 } 756 order++ 757 return nil 758 }) 759 qt.Check(t, err, qt.IsNil) 760 qt.Check(t, order, qt.Equals, 1) 761 } 762 763 func TestWalkTransforming(t *testing.T) { 764 ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) 765 t.Run("transform selecting true should transform the root", func(t *testing.T) { 766 n, err := traversal.WalkTransforming(basicnode.NewString("x"), selector.Matcher{}, func(prog traversal.Progress, n datamodel.Node) (datamodel.Node, error) { 767 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("x")) 768 qt.Check(t, prog.Path.String(), qt.Equals, datamodel.Path{}.String()) 769 return basicnode.NewString("replaced"), nil 770 }) 771 qt.Check(t, err, qt.IsNil) 772 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("replaced")) 773 }) 774 t.Run("transforming selecting fields recursively should work", func(t *testing.T) { 775 ss := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 776 efsb.Insert("foo", ssb.Matcher()) 777 efsb.Insert("nested", ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { 778 efsb.Insert("nonlink", ssb.Matcher()) 779 })) 780 }) 781 s, err := ss.Selector() 782 qt.Assert(t, err, qt.IsNil) 783 var order int 784 n, err := traversal.WalkTransforming(middleMapNode, s, func(prog traversal.Progress, n datamodel.Node) (datamodel.Node, error) { 785 switch order { 786 case 0: 787 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewBool(true)) 788 qt.Check(t, prog.Path.String(), qt.Equals, "foo") 789 case 1: 790 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("zoo")) 791 qt.Check(t, prog.Path.String(), qt.Equals, "nested/nonlink") 792 } 793 order++ 794 return basicnode.NewString("replaced"), nil 795 }) 796 qt.Check(t, err, qt.IsNil) 797 qt.Check(t, order, qt.Equals, 2) 798 qt.Check(t, n, nodetests.NodeContentEquals, fluent.MustBuildMap(basicnode.Prototype.Map, 3, func(na fluent.MapAssembler) { 799 na.AssembleEntry("foo").AssignString("replaced") 800 na.AssembleEntry("bar").AssignBool(false) 801 na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) { 802 na.AssembleEntry("alink").AssignLink(leafAlphaLnk) 803 na.AssembleEntry("nonlink").AssignString("replaced") 804 }) 805 })) 806 }) 807 }