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  }