Skip to content

Commit

Permalink
Merge pull request #231 from ipld/selector-tests-from-specifications
Browse files Browse the repository at this point in the history
selector: add tests which are driven by language-agnostic spec fixtures.
  • Loading branch information
warpfork authored Aug 18, 2021
2 parents 8d16e05 + 628adb7 commit d5b09ad
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule ".ipld"]
path = .ipld
url = https://github.com/ipld/ipld/
1 change: 1 addition & 0 deletions .ipld
Submodule .ipld added at c292cf
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ require (
github.com/multiformats/go-multihash v0.0.15
github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/warpfork/go-testmark v0.3.0
github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/warpfork/go-testmark v0.3.0 h1:Q81c4u7hT+BR5kNfNQhEF0VT2pmL7+Kk0wD+ORYl7iA=
github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0=
github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a h1:G++j5e0OC488te356JvdhaM8YS6nMsjLAYF7JxCv07w=
github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
112 changes: 112 additions & 0 deletions traversal/selector/spec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package selector_test

import (
"bytes"
"os"
"testing"

qt "github.com/frankban/quicktest"
"github.com/warpfork/go-testmark"

"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/codec/json"
"github.com/ipld/go-ipld-prime/fluent/qp"
"github.com/ipld/go-ipld-prime/node/basic"
"github.com/ipld/go-ipld-prime/traversal"
"github.com/ipld/go-ipld-prime/traversal/selector/parse"
)

func TestSpecFixtures(t *testing.T) {
doc, err := testmark.ReadFile("../../.ipld/specs/selectors/fixtures/selector-fixtures-1.md")
if os.IsNotExist(err) {
t.Skipf("not running spec suite: %s (did you clone the submodule with the data?)", err)
}
if err != nil {
t.Fatalf("spec file parse failed?!: %s", err)
}

// Data hunk in this spec file are in "directories" of a test scenario each.
doc.BuildDirIndex()
for _, dir := range doc.DirEnt.ChildrenList {
t.Run(dir.Name, func(t *testing.T) {
// Each "directory" contains three piece of data:
// - `data` -- this is the "block". It's arbitrary example data. They're all in json (or dag-json) format, for simplicity.
// - `selector` -- this is the selector. Again, as json.
// - `expect-visit` -- these are json lines (one json object on each line) containing description of each node that should be visited, in order.
fixtureData := dir.Children["data"].Hunk.Body
fixtureSelector := dir.Children["selector"].Hunk.Body
fixtureExpect := dir.Children["expect-visit"].Hunk.Body

// Parse data into DMT form.
nb := basicnode.Prototype.Any.NewBuilder()
if err := dagjson.Decode(nb, bytes.NewReader(fixtureData)); err != nil {
t.Errorf("failed to parse fixture data: %s", err)
}
dataDmt := nb.Build()

// Parse and compile Selector.
// (This is already arguably a test event on its own.
selector, err := selectorparse.ParseAndCompileJSONSelector(string(fixtureSelector))
if err != nil {
t.Errorf("failed to parse+compile selector: %s", err)
}

// Go!
// We'll store the logs of our visit events as... ipld Nodes, actually.
// This will make them easy to serialize, which is good for two reasons:
// at the end, we're actually going to... do that, and use string diffs for the final assertion
// (because string diffing is actually really nice for aggregate feedback in a system like this);
// and also that means we're ready to save updated serial data into the fixture files, if we did want to patch them.
var visitLogs []ipld.Node
traversal.WalkAdv(dataDmt, selector, func(prog traversal.Progress, n ipld.Node, reason traversal.VisitReason) error {
// Munge info about where we are into DMT shaped like the expectation records in the fixture.
visitEventDescr, err := qp.BuildMap(basicnode.Prototype.Any, 3, func(ma ipld.MapAssembler) {
qp.MapEntry(ma, "path", qp.String(prog.Path.String()))
qp.MapEntry(ma, "node", qp.Map(1, func(ma ipld.MapAssembler) {
qp.MapEntry(ma, n.Kind().String(), func(na ipld.NodeAssembler) {
switch n.Kind() {
case ipld.Kind_Map, ipld.Kind_List:
na.AssignNull()
default:
na.AssignNode(n)
}
})
}))
qp.MapEntry(ma, "matched", qp.Bool(reason == traversal.VisitReason_SelectionMatch))
})
if err != nil {
panic("insanity at a deeper level than this test's target")
}
visitLogs = append(visitLogs, visitEventDescr)
return nil
})

// Brief detour -- we're going to bounce the fixture data through our own deserialize and serialize.
// Just to normalize the heck out of it. I'm not really interested in if the fixture files have non-normative whitespace in them.
var fixtureExpectNormBuf bytes.Buffer
for _, line := range bytes.Split(fixtureExpect, []byte{'\n'}) {
if len(line) == 0 {
continue
}
nb := basicnode.Prototype.Any.NewBuilder()
if err := json.Decode(nb, bytes.NewReader(line)); err != nil {
t.Errorf("failed to parse fixture visit descriptions: %s", err)
}
json.Encode(nb.Build(), &fixtureExpectNormBuf)
fixtureExpectNormBuf.WriteByte('\n')
}

// Serialize our own visit logs now too.
var visitLogString bytes.Buffer
for _, logEnt := range visitLogs {
json.Encode(logEnt, &visitLogString)
visitLogString.WriteByte('\n')
}

// DIFF TIME.
qt.Assert(t, visitLogString.String(), qt.CmpEquals(), fixtureExpectNormBuf.String())
})
}

}

0 comments on commit d5b09ad

Please sign in to comment.