package tree import ( "fmt" "github.com/stretchr/testify/assert" "math/rand" "testing" "time" ) func newChange(id string, snapshotId string, prevIds ...string) *Change { return &Change{ PreviousIds: prevIds, Id: id, SnapshotId: snapshotId, IsSnapshot: false, } } func newSnapshot(id string, snapshotId string, prevIds ...string) *Change { return &Change{ PreviousIds: prevIds, Id: id, SnapshotId: snapshotId, IsSnapshot: true, } } func TestTree_Add(t *testing.T) { t.Run("add first el", func(t *testing.T) { tr := new(Tree) assert.Equal(t, Rebuild, tr.Add(newSnapshot("root", ""))) assert.Equal(t, tr.root.Id, "root") assert.Equal(t, []string{"root"}, tr.Heads()) }) t.Run("linear add", func(t *testing.T) { tr := new(Tree) assert.Equal(t, Rebuild, tr.Add( newSnapshot("root", ""), newChange("one", "root", "root"), newChange("two", "root", "one"), )) assert.Equal(t, []string{"two"}, tr.Heads()) assert.Equal(t, Append, tr.Add(newChange("three", "root", "two"))) el := tr.root var ids []string for el != nil { ids = append(ids, el.Id) if len(el.Next) > 0 { el = el.Next[0] } else { el = nil } } assert.Equal(t, []string{"root", "one", "two", "three"}, ids) assert.Equal(t, []string{"three"}, tr.Heads()) }) t.Run("branch", func(t *testing.T) { tr := new(Tree) assert.Equal(t, Rebuild, tr.Add( newSnapshot("root", ""), newChange("1", "root", "root"), newChange("2", "root", "1"), )) assert.Equal(t, []string{"2"}, tr.Heads()) assert.Equal(t, Rebuild, tr.Add( newChange("1.2", "root", "1.1"), newChange("1.3", "root", "1.2"), newChange("1.1", "root", "1"), )) assert.Len(t, tr.attached["1"].Next, 2) assert.Len(t, tr.unAttached, 0) assert.Len(t, tr.attached, 6) assert.Equal(t, []string{"1.3", "2"}, tr.Heads()) }) t.Run("branch union", func(t *testing.T) { tr := new(Tree) assert.Equal(t, Rebuild, tr.Add( newSnapshot("root", ""), newChange("1", "root", "root"), newChange("2", "root", "1"), newChange("1.2", "root", "1.1"), newChange("1.3", "root", "1.2"), newChange("1.1", "root", "1"), newChange("3", "root", "2", "1.3"), newChange("4", "root", "3"), )) assert.Len(t, tr.unAttached, 0) assert.Len(t, tr.attached, 8) assert.Equal(t, []string{"4"}, tr.Heads()) }) t.Run("big set", func(t *testing.T) { tr := new(Tree) tr.Add(newSnapshot("root", "")) var changes []*Change for i := 0; i < 10000; i++ { if i == 0 { changes = append(changes, newChange(fmt.Sprint(i), "root", "root")) } else { changes = append(changes, newChange(fmt.Sprint(i), "root", fmt.Sprint(i-1))) } } st := time.Now() tr.AddFast(changes...) t.Log(time.Since(st)) assert.Equal(t, []string{"9999"}, tr.Heads()) }) // TODO: add my tests } func TestTree_Hash(t *testing.T) { tr := new(Tree) tr.Add(newSnapshot("root", "")) hash1 := tr.Hash() assert.Equal(t, tr.Hash(), hash1) tr.Add(newChange("1", "root", "root")) assert.NotEqual(t, tr.Hash(), hash1) assert.Equal(t, tr.Hash(), tr.Hash()) } func TestTree_AddFuzzy(t *testing.T) { rand.Seed(time.Now().UnixNano()) getChanges := func() []*Change { changes := []*Change{ newChange("1", "root", "root"), newChange("2", "root", "1"), newChange("1.2", "root", "1.1"), newChange("1.3", "root", "1.2"), newChange("1.1", "root", "1"), newChange("3", "root", "2", "1.3"), } rand.Shuffle(len(changes), func(i, j int) { changes[i], changes[j] = changes[j], changes[i] }) return changes } var phash string for i := 0; i < 100; i++ { tr := new(Tree) tr.Add(newSnapshot("root", "")) tr.Add(getChanges()...) assert.Len(t, tr.unAttached, 0) assert.Len(t, tr.attached, 7) hash := tr.Hash() if phash != "" { assert.Equal(t, phash, hash) } phash = hash assert.Equal(t, []string{"3"}, tr.Heads()) } } func TestTree_Iterate(t *testing.T) { t.Run("complex tree", func(t *testing.T) { tr := new(Tree) tr.Add( newSnapshot("0", ""), newChange("1", "0", "0"), newChange("1.1", "0", "1"), newChange("1.2", "0", "1"), newChange("1.4", "0", "1.2"), newChange("1.3", "0", "1"), newChange("1.3.1", "0", "1.3"), newChange("1.2+3", "0", "1.4", "1.3.1"), newChange("1.2+3.1", "0", "1.2+3"), newChange("10", "0", "1.2+3.1", "1.1"), newChange("last", "0", "10"), ) var res []string tr.Iterate("0", func(c *Change) (isContinue bool) { res = append(res, c.Id) return true }) res = res[:0] tr.Iterate("0", func(c *Change) (isContinue bool) { res = append(res, c.Id) return true }) assert.Equal(t, []string{"0", "1", "1.1", "1.2", "1.4", "1.3", "1.3.1", "1.2+3", "1.2+3.1", "10", "last"}, res) }) } func BenchmarkTree_Add(b *testing.B) { getChanges := func() []*Change { return []*Change{ newChange("1", "root", "root"), newChange("2", "root", "1"), newChange("1.2", "root", "1.1"), newChange("1.3", "root", "1.2"), newChange("1.1", "root", "1"), newChange("3", "root", "2", "1.3"), } } b.Run("by one", func(b *testing.B) { tr := new(Tree) tr.Add(newSnapshot("root", "")) tr.Add(getChanges()...) for i := 0; i < b.N; i++ { tr.Add(newChange(fmt.Sprint(i+4), "root", fmt.Sprint(i+3))) } }) b.Run("add", func(b *testing.B) { for i := 0; i < b.N; i++ { tr := new(Tree) tr.Add(newSnapshot("root", "")) tr.Add(getChanges()...) } }) b.Run("add fast", func(b *testing.B) { for i := 0; i < b.N; i++ { tr := new(Tree) tr.AddFast(newSnapshot("root", "")) tr.AddFast(getChanges()...) } }) // prepare linear tree tr := new(Tree) tr.AddFast(newSnapshot("0", "")) for j := 0; j < 10000; j++ { tr.Add(newChange(fmt.Sprint(j+1), "0", fmt.Sprint(j))) } b.Run("add linear", func(b *testing.B) { for i := 0; i < b.N; i++ { tr.Iterate("0", func(c *Change) (isContinue bool) { return true }) } }) }