github.com/ipld/go-ipld-prime@v0.21.0/traversal/walk_with_stop_test.go (about) 1 package traversal_test 2 3 import ( 4 "fmt" 5 "testing" 6 7 qt "github.com/frankban/quicktest" 8 9 _ "github.com/ipld/go-ipld-prime/codec/dagjson" 10 "github.com/ipld/go-ipld-prime/datamodel" 11 "github.com/ipld/go-ipld-prime/fluent" 12 cidlink "github.com/ipld/go-ipld-prime/linking/cid" 13 "github.com/ipld/go-ipld-prime/node/basicnode" 14 nodetests "github.com/ipld/go-ipld-prime/node/tests" 15 "github.com/ipld/go-ipld-prime/traversal" 16 "github.com/ipld/go-ipld-prime/traversal/selector" 17 "github.com/ipld/go-ipld-prime/traversal/selector/builder" 18 ) 19 20 /* Remember, we've got the following fixtures in scope: 21 var ( 22 leafAlpha, leafAlphaLnk = encode(basicnode.NewString("alpha")) 23 leafBeta, leafBetaLnk = encode(basicnode.NewString("beta")) 24 middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 3, func(na fluent.MapAssembler) { 25 na.AssembleEntry("foo").AssignBool(true) 26 na.AssembleEntry("bar").AssignBool(false) 27 na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) { 28 na.AssembleEntry("alink").AssignLink(leafAlphaLnk) 29 na.AssembleEntry("nonlink").AssignString("zoo") 30 }) 31 })) 32 middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { 33 na.AssembleValue().AssignLink(leafAlphaLnk) 34 na.AssembleValue().AssignLink(leafAlphaLnk) 35 na.AssembleValue().AssignLink(leafBetaLnk) 36 na.AssembleValue().AssignLink(leafAlphaLnk) 37 })) 38 rootNode, rootNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { 39 na.AssembleEntry("plain").AssignString("olde string") 40 na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk) 41 na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk) 42 na.AssembleEntry("linkedList").AssignLink(middleListNodeLnk) 43 })) 44 ) 45 */ 46 47 // ExploreRecursiveWithStop builds a recursive selector node with a stop condition 48 func ExploreRecursiveWithStop(limit selector.RecursionLimit, sequence builder.SelectorSpec, stopLnk datamodel.Link) datamodel.Node { 49 np := basicnode.Prototype__Map{} 50 return fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) { 51 // RecursionLimit 52 na.AssembleEntry(selector.SelectorKey_ExploreRecursive).CreateMap(3, func(na fluent.MapAssembler) { 53 na.AssembleEntry(selector.SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) { 54 switch limit.Mode() { 55 case selector.RecursionLimit_Depth: 56 na.AssembleEntry(selector.SelectorKey_LimitDepth).AssignInt(limit.Depth()) 57 case selector.RecursionLimit_None: 58 na.AssembleEntry(selector.SelectorKey_LimitNone).CreateMap(0, func(na fluent.MapAssembler) {}) 59 default: 60 panic("Unsupported recursion limit type") 61 } 62 }) 63 // Sequence 64 na.AssembleEntry(selector.SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) { 65 na.AssembleEntry(selector.SelectorKey_ExploreUnion).CreateList(2, func(na fluent.ListAssembler) { 66 na.AssembleValue().AssignNode(fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) { 67 na.AssembleEntry(selector.SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {}) 68 })) 69 na.AssembleValue().AssignNode(sequence.Node()) 70 }) 71 }) 72 73 // Stop condition 74 if stopLnk != nil { 75 cond := fluent.MustBuildMap(basicnode.Prototype__Map{}, 1, func(na fluent.MapAssembler) { 76 na.AssembleEntry(string(selector.ConditionMode_Link)).AssignLink(stopLnk) 77 }) 78 na.AssembleEntry(selector.SelectorKey_StopAt).AssignNode(cond) 79 } 80 }) 81 }) 82 83 } 84 85 func TestStopAtLink(t *testing.T) { 86 ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype__Any{}) 87 t.Run("test ExploreRecursive stopAt with simple node", func(t *testing.T) { 88 // Selector that passes through the map 89 s, err := selector.CompileSelector(ExploreRecursiveWithStop( 90 selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()), 91 middleMapNodeLnk)) 92 if err != nil { 93 t.Fatal(err) 94 } 95 var order int 96 lsys := cidlink.DefaultLinkSystem() 97 lsys.SetReadStorage(&store) 98 err = traversal.Progress{ 99 Cfg: &traversal.Config{ 100 LinkSystem: lsys, 101 LinkTargetNodePrototypeChooser: basicnode.Chooser, 102 }, 103 }.WalkMatching(rootNode, s, func(prog traversal.Progress, n datamodel.Node) error { 104 // fmt.Println("Order", order, prog.Path.String()) 105 switch order { 106 case 0: 107 // Root 108 qt.Check(t, prog.Path.String(), qt.Equals, "") 109 case 1: 110 qt.Check(t, prog.Path.String(), qt.Equals, "plain") 111 qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("olde string")) 112 case 2: 113 qt.Check(t, prog.Path.String(), qt.Equals, "linkedString") 114 case 3: 115 qt.Check(t, prog.Path.String(), qt.Equals, "linkedList") 116 // We are starting to traverse the linkedList, we passed through the map already 117 case 4: 118 qt.Check(t, prog.Path.String(), qt.Equals, "linkedList/0") 119 } 120 order++ 121 return nil 122 }) 123 qt.Check(t, err, qt.IsNil) 124 qt.Check(t, order, qt.Equals, 8) 125 }) 126 } 127 128 // mkChain creates a DAG that represent a chain of subDAGs. 129 // The stopAt condition is extremely appealing for these use cases, as we can 130 // partially sync a chain using ExploreRecursive without having to sync the 131 // chain from scratch if we are already partially synced 132 func mkChain() (datamodel.Node, []datamodel.Link) { 133 leafAlpha, leafAlphaLnk = encode(basicnode.NewString("alpha")) 134 leafBeta, leafBetaLnk = encode(basicnode.NewString("beta")) 135 middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 3, func(na fluent.MapAssembler) { 136 na.AssembleEntry("foo").AssignBool(true) 137 na.AssembleEntry("bar").AssignBool(false) 138 na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) { 139 na.AssembleEntry("alink").AssignLink(leafAlphaLnk) 140 na.AssembleEntry("nonlink").AssignString("zoo") 141 }) 142 })) 143 middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { 144 na.AssembleValue().AssignLink(leafAlphaLnk) 145 na.AssembleValue().AssignLink(leafAlphaLnk) 146 na.AssembleValue().AssignLink(leafBetaLnk) 147 na.AssembleValue().AssignLink(leafAlphaLnk) 148 })) 149 150 _, ch1Lnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { 151 na.AssembleEntry("linkedList").AssignLink(middleListNodeLnk) 152 })) 153 _, ch2Lnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { 154 na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk) 155 na.AssembleEntry("ch1").AssignLink(ch1Lnk) 156 })) 157 _, ch3Lnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { 158 na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk) 159 na.AssembleEntry("ch2").AssignLink(ch2Lnk) 160 })) 161 162 headNode, headLnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { 163 na.AssembleEntry("plain").AssignString("olde string") 164 na.AssembleEntry("ch3").AssignLink(ch3Lnk) 165 })) 166 return headNode, []datamodel.Link{headLnk, ch3Lnk, ch2Lnk, ch1Lnk} 167 } 168 169 func TestStopInChain(t *testing.T) { 170 chainNode, chainLnks := mkChain() 171 // Stay in head 172 stopAtInChainTest(t, chainNode, chainLnks[1], []string{"", "plain"}) 173 // Get head and following block 174 stopAtInChainTest(t, chainNode, chainLnks[2], []string{"", "plain", "ch3", "ch3/linkedString"}) 175 // One more 176 stopAtInChainTest(t, chainNode, chainLnks[3], []string{ 177 "", 178 "plain", 179 "ch3", 180 "ch3/ch2", 181 "ch3/ch2/linkedMap", 182 "ch3/ch2/linkedMap/bar", 183 "ch3/ch2/linkedMap/foo", 184 "ch3/ch2/linkedMap/nested", 185 "ch3/ch2/linkedMap/nested/alink", 186 "ch3/ch2/linkedMap/nested/nonlink", 187 "ch3/linkedString", 188 }) 189 // Get the full chain 190 stopAtInChainTest(t, chainNode, nil, []string{ 191 "", 192 "plain", 193 "ch3", 194 "ch3/ch2", 195 "ch3/ch2/ch1", 196 "ch3/ch2/ch1/linkedList", 197 "ch3/ch2/ch1/linkedList/0", 198 "ch3/ch2/ch1/linkedList/1", 199 "ch3/ch2/ch1/linkedList/2", 200 "ch3/ch2/ch1/linkedList/3", 201 "ch3/ch2/linkedMap", 202 "ch3/ch2/linkedMap/bar", 203 "ch3/ch2/linkedMap/foo", 204 "ch3/ch2/linkedMap/nested", 205 "ch3/ch2/linkedMap/nested/alink", 206 "ch3/ch2/linkedMap/nested/nonlink", 207 "ch3/linkedString", 208 }) 209 } 210 211 func stopAtInChainTest(t *testing.T, chainNode datamodel.Node, stopLnk datamodel.Link, expectedPaths []string) { 212 ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype__Any{}) 213 t.Run(fmt.Sprintf("test ExploreRecursive stopAt in chain with stoplink: %s", stopLnk), func(t *testing.T) { 214 s, err := selector.CompileSelector(ExploreRecursiveWithStop( 215 selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()), 216 stopLnk)) 217 if err != nil { 218 t.Fatal(err) 219 } 220 221 var order int 222 lsys := cidlink.DefaultLinkSystem() 223 lsys.SetReadStorage(&store) 224 err = traversal.Progress{ 225 Cfg: &traversal.Config{ 226 LinkSystem: lsys, 227 LinkTargetNodePrototypeChooser: basicnode.Chooser, 228 }, 229 }.WalkMatching(chainNode, s, func(prog traversal.Progress, n datamodel.Node) error { 230 //fmt.Println("Order", order, prog.Path.String()) 231 qt.Check(t, order < len(expectedPaths), qt.IsTrue) 232 qt.Check(t, prog.Path.String(), qt.Equals, expectedPaths[order]) 233 order++ 234 return nil 235 }) 236 qt.Check(t, err, qt.IsNil) 237 qt.Check(t, order, qt.Equals, len(expectedPaths)) 238 }) 239 }