package merkletrie_test
import (
"fmt"
"io"
"strings"
"gopkg.in/src-d/go-git.v4/utils/merkletrie"
"gopkg.in/src-d/go-git.v4/utils/merkletrie/internal/fsnoder"
"gopkg.in/src-d/go-git.v4/utils/merkletrie/noder"
. "gopkg.in/check.v1"
)
type IterSuite struct{}
var _ = Suite(&IterSuite{})
// A test is a list of operations we want to perform on an iterator and
// their expected results.
//
// The operations are expressed as a sequence of `n` and `s`,
// representing the amount of next and step operations we want to call
// on the iterator and their order. For example, an operations value of
// "nns" means: call a `n`ext, then another `n`ext and finish with a
// `s`tep.
//
// The expected is the full path of the noders returned by the
// operations, separated by spaces.
//
// For instance:
//
// t := test{
// operations: "ns",
// expected: "a a/b"
// }
//
// means:
//
// - the first iterator operation has to be Next, and it must return a
// node called "a" with no ancestors.
//
// - the second operation has to be Step, and it must return a node
// called "b" with a single ancestor called "a".
type test struct {
operations string
expected string
}
// Runs a test on the provided iterator, checking that the names of the
// returned values are correct. If not, the treeDescription value is
// printed along with information about mismatch.
func (t test) run(c *C, iter *merkletrie.Iter,
treeDescription string, testNumber int) {
expectedChunks := strings.Split(t.expected, " ")
if t.expected == "" {
expectedChunks = []string{}
}
if len(t.operations) < len(expectedChunks) {
c.Fatalf("malformed test %d: not enough operations", testNumber)
return
}
var obtained noder.Path
var err error
for i, b := range t.operations {
comment := Commentf("\ntree: %q\ntest #%d (%q)\noperation #%d (%q)",
treeDescription, testNumber, t.operations, i, t.operations[i])
switch t.operations[i] {
case 'n':
obtained, err = iter.Next()
if err != io.EOF {
c.Assert(err, IsNil)
}
case 's':
obtained, err = iter.Step()
if err != io.EOF {
c.Assert(err, IsNil)
}
default:
c.Fatalf("unknown operation at test %d, operation %d (%c)\n",
testNumber, i, b)
}
if i >= len(expectedChunks) {
c.Assert(err, Equals, io.EOF, comment)
continue
}
c.Assert(err, IsNil, comment)
c.Assert(obtained.String(), Equals, expectedChunks[i], comment)
}
}
// A testsCollection value represents a tree and a collection of tests
// we want to perform on iterators of that tree.
//
// Example:
//
// .
// |
// ---------
// | | |
// a b c
// |
// z
//
// var foo testCollection = {
// tree: "(a<> b(z<>) c<>)"
// tests: []test{
// {operations: "nns", expected: "a b b/z"},
// {operations: "nnn", expected: "a b c"},
// },
// }
//
// A new iterator will be build for each test.
type testsCollection struct {
tree string // a fsnoder description of a tree.
tests []test // the collection of tests we want to run
}
// Executes all the tests in a testsCollection.
func (tc testsCollection) run(c *C) {
root, err := fsnoder.New(tc.tree)
c.Assert(err, IsNil)
for i, t := range tc.tests {
iter, err := merkletrie.NewIter(root)
c.Assert(err, IsNil)
t.run(c, iter, root.String(), i)
}
}
func (s *IterSuite) TestEmptyNamedDir(c *C) {
tc := testsCollection{
tree: "A()",
tests: []test{
{operations: "n", expected: ""},
{operations: "nn", expected: ""},
{operations: "nnn", expected: ""},
{operations: "nnns", expected: ""},
{operations: "nnnssnsnns", expected: ""},
{operations: "s", expected: ""},
{operations: "ss", expected: ""},
{operations: "sss", expected: ""},
{operations: "sssn", expected: ""},
{operations: "sssnnsnssn", expected: ""},
},
}
tc.run(c)
}
func (s *IterSuite) TestEmptyUnnamedDir(c *C) {
tc := testsCollection{
tree: "()",
tests: []test{
{operations: "n", expected: ""},
{operations: "nn", expected: ""},
{operations: "nnn", expected: ""},
{operations: "nnns", expected: ""},
{operations: "nnnssnsnns", expected: ""},
{operations: "s", expected: ""},
{operations: "ss", expected: ""},
{operations: "sss", expected: ""},
{operations: "sssn", expected: ""},
{operations: "sssnnsnssn", expected: ""},
},
}
tc.run(c)
}
func (s *IterSuite) TestOneFile(c *C) {
tc := testsCollection{
tree: "(a<>)",
tests: []test{
{operations: "n", expected: "a"},
{operations: "nn", expected: "a"},
{operations: "nnn", expected: "a"},
{operations: "nnns", expected: "a"},
{operations: "nnnssnsnns", expected: "a"},
{operations: "s", expected: "a"},
{operations: "ss", expected: "a"},
{operations: "sss", expected: "a"},
{operations: "sssn", expected: "a"},
{operations: "sssnnsnssn", expected: "a"},
},
}
tc.run(c)
}
// root
// / \
// a b
func (s *IterSuite) TestTwoFiles(c *C) {
tc := testsCollection{
tree: "(a<> b<>)",
tests: []test{
{operations: "nnn", expected: "a b"},
{operations: "nns", expected: "a b"},
{operations: "nsn", expected: "a b"},
{operations: "nss", expected: "a b"},
{operations: "snn", expected: "a b"},
{operations: "sns", expected: "a b"},
{operations: "ssn", expected: "a b"},
{operations: "sss", expected: "a b"},
},
}
tc.run(c)
}
// root
// |
// a
// |
// b
func (s *IterSuite) TestDirWithFile(c *C) {
tc := testsCollection{
tree: "(a(b<>))",
tests: []test{
{operations: "nnn", expected: "a"},
{operations: "nns", expected: "a"},
{operations: "nsn", expected: "a a/b"},
{operations: "nss", expected: "a a/b"},
{operations: "snn", expected: "a"},
{operations: "sns", expected: "a"},
{operations: "ssn", expected: "a a/b"},
{operations: "sss", expected: "a a/b"},
},
}
tc.run(c)
}
// root
// /|\
// c a b
func (s *IterSuite) TestThreeSiblings(c *C) {
tc := testsCollection{
tree: "(c<> a<> b<>)",
tests: []test{
{operations: "nnnn", expected: "a b c"},
{operations: "nnns", expected: "a b c"},
{operations: "nnsn", expected: "a b c"},
{operations: "nnss", expected: "a b c"},
{operations: "nsnn", expected: "a b c"},
{operations: "nsns", expected: "a b c"},
{operations: "nssn", expected: "a b c"},
{operations: "nsss", expected: "a b c"},
{operations: "snnn", expected: "a b c"},
{operations: "snns", expected: "a b c"},
{operations: "snsn", expected: "a b c"},
{operations: "snss", expected: "a b c"},
{operations: "ssnn", expected: "a b c"},
{operations: "ssns", expected: "a b c"},
{operations: "sssn", expected: "a b c"},
{operations: "ssss", expected: "a b c"},
},
}
tc.run(c)
}
// root
// |
// b
// |
// c
// |
// a
func (s *IterSuite) TestThreeVertical(c *C) {
tc := testsCollection{
tree: "(b(c(a())))",
tests: []test{
{operations: "nnnn", expected: "b"},
{operations: "nnns", expected: "b"},
{operations: "nnsn", expected: "b"},
{operations: "nnss", expected: "b"},
{operations: "nsnn", expected: "b b/c"},
{operations: "nsns", expected: "b b/c"},
{operations: "nssn", expected: "b b/c b/c/a"},
{operations: "nsss", expected: "b b/c b/c/a"},
{operations: "snnn", expected: "b"},
{operations: "snns", expected: "b"},
{operations: "snsn", expected: "b"},
{operations: "snss", expected: "b"},
{operations: "ssnn", expected: "b b/c"},
{operations: "ssns", expected: "b b/c"},
{operations: "sssn", expected: "b b/c b/c/a"},
{operations: "ssss", expected: "b b/c b/c/a"},
},
}
tc.run(c)
}
// root
// / \
// c a
// |
// b
func (s *IterSuite) TestThreeMix1(c *C) {
tc := testsCollection{
tree: "(c(b<>) a<>)",
tests: []test{
{operations: "nnnn", expected: "a c"},
{operations: "nnns", expected: "a c"},
{operations: "nnsn", expected: "a c c/b"},
{operations: "nnss", expected: "a c c/b"},
{operations: "nsnn", expected: "a c"},
{operations: "nsns", expected: "a c"},
{operations: "nssn", expected: "a c c/b"},
{operations: "nsss", expected: "a c c/b"},
{operations: "snnn", expected: "a c"},
{operations: "snns", expected: "a c"},
{operations: "snsn", expected: "a c c/b"},
{operations: "snss", expected: "a c c/b"},
{operations: "ssnn", expected: "a c"},
{operations: "ssns", expected: "a c"},
{operations: "sssn", expected: "a c c/b"},
{operations: "ssss", expected: "a c c/b"},
},
}
tc.run(c)
}
// root
// / \
// b a
// |
// c
func (s *IterSuite) TestThreeMix2(c *C) {
tc := testsCollection{
tree: "(b() a(c<>))",
tests: []test{
{operations: "nnnn", expected: "a b"},
{operations: "nnns", expected: "a b"},
{operations: "nnsn", expected: "a b"},
{operations: "nnss", expected: "a b"},
{operations: "nsnn", expected: "a a/c b"},
{operations: "nsns", expected: "a a/c b"},
{operations: "nssn", expected: "a a/c b"},
{operations: "nsss", expected: "a a/c b"},
{operations: "snnn", expected: "a b"},
{operations: "snns", expected: "a b"},
{operations: "snsn", expected: "a b"},
{operations: "snss", expected: "a b"},
{operations: "ssnn", expected: "a a/c b"},
{operations: "ssns", expected: "a a/c b"},
{operations: "sssn", expected: "a a/c b"},
{operations: "ssss", expected: "a a/c b"},
},
}
tc.run(c)
}
// root
// / | \
// / | ----
// f d h --------
// /\ / \ |
// e a j b/ g
// | / \ |
// l n k icm
// |
// o
// |
// p/
func (s *IterSuite) TestCrazy(c *C) {
tc := testsCollection{
tree: "(f(e(l<>) a(n(o(p())) k<>)) d<> h(j(i<> c<> m<>) b() g<>))",
tests: []test{
{operations: "nnnnn", expected: "d f h"},
{operations: "nnnns", expected: "d f h"},
{operations: "nnnsn", expected: "d f h h/b h/g"},
{operations: "nnnss", expected: "d f h h/b h/g"},
{operations: "nnsnn", expected: "d f f/a f/e h"},
{operations: "nnsns", expected: "d f f/a f/e f/e/l"},
{operations: "nnssn", expected: "d f f/a f/a/k f/a/n"},
{operations: "nnsss", expected: "d f f/a f/a/k f/a/n"},
{operations: "nsnnn", expected: "d f h"},
{operations: "nsnns", expected: "d f h"},
{operations: "nsnsn", expected: "d f h h/b h/g"},
{operations: "nsnss", expected: "d f h h/b h/g"},
{operations: "nssnn", expected: "d f f/a f/e h"},
},
}
tc.run(c)
}
// .
// |
// a
// |
// b
// / \
// z h
// / \
// d e
// |
// f
func (s *IterSuite) TestNewIterFromPath(c *C) {
tree, err := fsnoder.New("(a(b(z(d<> e(f<>)) h<>)))")
c.Assert(err, IsNil)
z := find(c, tree, "z")
iter, err := merkletrie.NewIterFromPath(z)
c.Assert(err, IsNil)
n, err := iter.Next()
c.Assert(err, IsNil)
c.Assert(n.String(), Equals, "a/b/z/d")
n, err = iter.Next()
c.Assert(err, IsNil)
c.Assert(n.String(), Equals, "a/b/z/e")
n, err = iter.Step()
c.Assert(err, IsNil)
c.Assert(n.String(), Equals, "a/b/z/e/f")
_, err = iter.Step()
c.Assert(err, Equals, io.EOF)
}
func find(c *C, tree noder.Noder, name string) noder.Path {
iter, err := merkletrie.NewIter(tree)
c.Assert(err, IsNil)
for {
current, err := iter.Step()
if err != io.EOF {
c.Assert(err, IsNil)
} else {
c.Fatalf("node %s not found in tree %s", name, tree)
}
if current.Name() == name {
return current
}
}
}
type errorNoder struct{ noder.Noder }
func (e *errorNoder) Children() ([]noder.Noder, error) {
return nil, fmt.Errorf("mock error")
}
func (s *IterSuite) TestNewIterNil(c *C) {
i, err := merkletrie.NewIter(nil)
c.Assert(err, IsNil)
_, err = i.Next()
c.Assert(err, Equals, io.EOF)
}
func (s *IterSuite) TestNewIterFailsOnChildrenErrors(c *C) {
_, err := merkletrie.NewIter(&errorNoder{})
c.Assert(err, ErrorMatches, "mock error")
}