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  }