github.com/ipld/go-ipld-prime@v0.21.0/traversal/selector/spec_test.go (about) 1 package selector_test 2 3 import ( 4 "bytes" 5 "io" 6 "os" 7 "regexp" 8 "testing" 9 10 qt "github.com/frankban/quicktest" 11 "github.com/warpfork/go-testmark" 12 13 "github.com/ipld/go-ipld-prime" 14 "github.com/ipld/go-ipld-prime/codec/dagjson" 15 "github.com/ipld/go-ipld-prime/codec/json" 16 "github.com/ipld/go-ipld-prime/datamodel" 17 "github.com/ipld/go-ipld-prime/fluent/qp" 18 "github.com/ipld/go-ipld-prime/node/basicnode" 19 "github.com/ipld/go-ipld-prime/traversal" 20 selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" 21 ) 22 23 func TestSpecFixtures(t *testing.T) { 24 dir := "../../.ipld/specs/selectors/fixtures/" 25 testOneSpecFixtureFile(t, dir+"selector-fixtures-1.md") 26 testOneSpecFixtureFile(t, dir+"selector-fixtures-recursion.md") 27 } 28 29 func testOneSpecFixtureFile(t *testing.T, filename string) { 30 data, err := os.ReadFile(filename) 31 if os.IsNotExist(err) { 32 t.Skipf("not running spec suite: %s (did you clone the submodule with the data?)", err) 33 } 34 crre := regexp.MustCompile(`\r?\n`) 35 data = []byte(crre.ReplaceAllString(string(data), "\n")) // fix windows carriage-return 36 37 doc, err := testmark.Parse(data) 38 qt.Assert(t, err, qt.IsNil) 39 40 // Data hunk in this spec file are in "directories" of a test scenario each. 41 doc.BuildDirIndex() 42 for _, dir := range doc.DirEnt.ChildrenList { 43 t.Run(dir.Name, func(t *testing.T) { 44 // Each "directory" contains three piece of data: 45 // - `data` -- this is the "block". It's arbitrary example data. They're all in json (or dag-json) format, for simplicity. 46 // - `selector` -- this is the selector. Again, as json. 47 // - `expect-visit` -- these are json lines (one json object on each line) containing description of each node that should be visited, in order. 48 fixtureData := dir.Children["data"].Hunk.Body 49 fixtureSelector := dir.Children["selector"].Hunk.Body 50 fixtureExpect := dir.Children["expect-visit"].Hunk.Body 51 52 // Parse data into DMT form. 53 dataDmt, err := ipld.Decode(fixtureData, dagjson.Decode) 54 qt.Assert(t, err, qt.IsNil) 55 56 // Parse and compile Selector. 57 // (This is already arguably a test event on its own. 58 selector, err := selectorparse.ParseAndCompileJSONSelector(string(fixtureSelector)) 59 qt.Assert(t, err, qt.IsNil) 60 61 // Go! 62 // We'll store the logs of our visit events as... ipld Nodes, actually. 63 // This will make them easy to serialize, which is good for two reasons: 64 // at the end, we're actually going to... do that, and use string diffs for the final assertion 65 // (because string diffing is actually really nice for aggregate feedback in a system like this); 66 // and also that means we're ready to save updated serial data into the fixture files, if we did want to patch them. 67 var visitLogs []datamodel.Node 68 traversal.WalkAdv(dataDmt, selector, func(prog traversal.Progress, n datamodel.Node, reason traversal.VisitReason) error { 69 // Munge info about where we are into DMT shaped like the expectation records in the fixture. 70 visitEventDescr, err := qp.BuildMap(basicnode.Prototype.Any, 3, func(ma datamodel.MapAssembler) { 71 qp.MapEntry(ma, "path", qp.String(prog.Path.String())) 72 qp.MapEntry(ma, "node", qp.Map(1, func(ma datamodel.MapAssembler) { 73 qp.MapEntry(ma, n.Kind().String(), func(na datamodel.NodeAssembler) { 74 switch n.Kind() { 75 case datamodel.Kind_Map, datamodel.Kind_List: 76 na.AssignNull() 77 default: 78 na.AssignNode(n) 79 } 80 }) 81 })) 82 qp.MapEntry(ma, "matched", qp.Bool(reason == traversal.VisitReason_SelectionMatch)) 83 }) 84 if reason == traversal.VisitReason_SelectionMatch && n.Kind() == datamodel.Kind_Bytes { 85 if lbn, ok := n.(datamodel.LargeBytesNode); ok { 86 rdr, err := lbn.AsLargeBytes() 87 if err == nil { 88 io.Copy(io.Discard, rdr) 89 } 90 } 91 _, err := n.AsBytes() 92 if err != nil { 93 panic("insanity at a deeper level than this test's target") 94 } 95 } 96 if err != nil { 97 panic("insanity at a deeper level than this test's target") 98 } 99 visitLogs = append(visitLogs, visitEventDescr) 100 return nil 101 }) 102 103 // Brief detour -- we're going to bounce the fixture data through our own deserialize and serialize. 104 // Just to normalize the heck out of it. I'm not really interested in if the fixture files have non-normative whitespace in them. 105 var fixtureExpectNormBuf bytes.Buffer 106 for _, line := range bytes.Split(fixtureExpect, []byte{'\n'}) { 107 if len(line) == 0 { 108 continue 109 } 110 exp, err := ipld.Decode(line, json.Decode) 111 qt.Assert(t, err, qt.IsNil) 112 qt.Assert(t, ipld.EncodeStreaming(&fixtureExpectNormBuf, exp, json.Encode), qt.IsNil) 113 fixtureExpectNormBuf.WriteByte('\n') 114 } 115 116 // Serialize our own visit logs now too. 117 var visitLogString bytes.Buffer 118 for _, logEnt := range visitLogs { 119 qt.Assert(t, ipld.EncodeStreaming(&visitLogString, logEnt, json.Encode), qt.IsNil) 120 visitLogString.WriteByte('\n') 121 } 122 123 // DIFF TIME. 124 qt.Assert(t, visitLogString.String(), qt.CmpEquals(), fixtureExpectNormBuf.String()) 125 }) 126 } 127 }