Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

selector: add tests which are driven by language-agnostic spec fixtures. #231

Merged
merged 2 commits into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
})
}

}