Fix algorithms and add tests

This commit is contained in:
mcrakhman 2022-09-04 17:05:00 +02:00 committed by Mikhail Iudin
parent d52d539e9d
commit 9882ebc267
No known key found for this signature in database
GPG Key ID: FAAAA8BAABDFF1C0
4 changed files with 53 additions and 26 deletions

View File

@ -25,7 +25,10 @@ type Change struct {
IsSnapshot bool IsSnapshot bool
DecryptedChange []byte // TODO: check if we need it DecryptedChange []byte // TODO: check if we need it
ParsedModel interface{} ParsedModel interface{}
visited bool
// iterator helpers
visited bool
branchesFinished bool
Content *aclpb.Change Content *aclpb.Change
Sign []byte Sign []byte

View File

@ -24,10 +24,11 @@ type Tree struct {
// missed id -> list of dependency ids // missed id -> list of dependency ids
waitList map[string][]string waitList map[string][]string
invalidChanges map[string]struct{} invalidChanges map[string]struct{}
possibleRoots []*Change
// bufs // bufs
iterCompBuf []*Change visitedBuf []*Change
iterQueue []*Change stackBuf []*Change
duplicateEvents int duplicateEvents int
} }
@ -59,14 +60,13 @@ func (t *Tree) AddFast(changes ...*Change) {
func (t *Tree) AddMergedHead(c *Change) error { func (t *Tree) AddMergedHead(c *Change) error {
// check that it was not inserted previously // check that it was not inserted previously
if _, ok := t.attached[c.Id]; ok { if _, ok := t.attached[c.Id]; ok {
return fmt.Errorf("change already exists") return fmt.Errorf("change already exists") // TODO: named error
} else if _, ok := t.unAttached[c.Id]; ok { } else if _, ok := t.unAttached[c.Id]; ok {
return fmt.Errorf("change already exists") return fmt.Errorf("change already exists")
} }
t.add(c)
// check that it was attached after adding // check that it was attached after adding
if _, ok := t.attached[c.Id]; !ok { if !t.add(c) {
return fmt.Errorf("change is not attached") return fmt.Errorf("change is not attached")
} }
@ -172,6 +172,7 @@ func (t *Tree) add(c *Change) (attached bool) {
t.unAttached = make(map[string]*Change) t.unAttached = make(map[string]*Change)
t.waitList = make(map[string][]string) t.waitList = make(map[string][]string)
t.invalidChanges = make(map[string]struct{}) t.invalidChanges = make(map[string]struct{})
t.possibleRoots = make([]*Change, 0, 10)
return true return true
} }
if len(c.PreviousIds) > 1 { if len(c.PreviousIds) > 1 {
@ -216,6 +217,9 @@ func (t *Tree) attach(c *Change, newEl bool) {
if !newEl { if !newEl {
delete(t.unAttached, c.Id) delete(t.unAttached, c.Id)
} }
if c.IsSnapshot {
t.possibleRoots = append(t.possibleRoots, c)
}
// add next to all prev changes // add next to all prev changes
for _, id := range c.PreviousIds { for _, id := range c.PreviousIds {
@ -266,25 +270,31 @@ func (t *Tree) after(id1, id2 string) (found bool) {
return return
} }
func (t *Tree) dfs(startChange string) (uniqMap map[string]*Change) { func (t *Tree) dfsPrev(stack []*Change, visit func(ch *Change), afterVisit func([]*Change)) {
stack := make([]*Change, 0, 10) t.visitedBuf = t.visitedBuf[:0]
stack = append(stack, t.attached[startChange])
uniqMap = map[string]*Change{}
for len(stack) > 0 { for len(stack) > 0 {
ch := stack[len(stack)-1] ch := stack[len(stack)-1]
stack = stack[:len(stack)-1] stack = stack[:len(stack)-1]
if _, exists := uniqMap[ch.Id]; exists { if ch.visited {
continue continue
} }
uniqMap[ch.Id] = ch ch.visited = true
t.visitedBuf = append(t.visitedBuf, ch)
for _, prev := range ch.PreviousIds { for _, prevId := range ch.PreviousIds {
stack = append(stack, t.attached[prev]) prevCh := t.attached[prevId]
if !prevCh.visited {
stack = append(stack, prevCh)
}
} }
visit(ch)
}
afterVisit(t.visitedBuf)
for _, ch := range t.visitedBuf {
ch.visited = false
} }
return uniqMap
} }
func (t *Tree) updateHeads() { func (t *Tree) updateHeads() {

View File

@ -186,22 +186,24 @@ func TestTree_CheckRootReduce(t *testing.T) {
tr.Add( tr.Add(
newSnapshot("0", ""), newSnapshot("0", ""),
newSnapshot("1", "0", "0"), newSnapshot("1", "0", "0"),
newChange("1.1", "0", "1"),
newChange("1.2", "0", "1"), newChange("1.2", "0", "1"),
newChange("1.4", "0", "1.2"),
newChange("1.3", "0", "1"), newChange("1.3", "0", "1"),
newChange("1.3.1", "0", "1.3"), newChange("1.3.1", "0", "1.3"),
newChange("1.2+3", "0", "1.4", "1.3.1"), newSnapshot("1.2+3", "1", "1.2", "1.3.1"),
newChange("1.2+3.1", "0", "1.2+3"), newChange("1.2+3.1", "1", "1.2+3"),
newSnapshot("10", "0", "1.2+3.1", "1.1"), newChange("1.2+3.2", "1", "1.2+3"),
newSnapshot("10", "1.2+3", "1.2+3.1", "1.2+3.2"),
newChange("last", "10", "10"), newChange("last", "10", "10"),
) )
t.Run("check root", func(t *testing.T) { t.Run("check root", func(t *testing.T) {
total := tr.checkRoot(tr.attached["10"]) total := tr.checkRoot(tr.attached["10"])
assert.Equal(t, 1, total) assert.Equal(t, 1, total)
total = tr.checkRoot(tr.attached["1.2+3"])
assert.Equal(t, 4, total)
total = tr.checkRoot(tr.attached["1"]) total = tr.checkRoot(tr.attached["1"])
assert.Equal(t, 9, total) assert.Equal(t, 8, total)
}) })
t.Run("reduce", func(t *testing.T) { t.Run("reduce", func(t *testing.T) {
tr.reduceTree() tr.reduceTree()
@ -304,6 +306,9 @@ func BenchmarkTree_Add(b *testing.B) {
tr.AddFast(getChanges()...) tr.AddFast(getChanges()...)
} }
}) })
}
func BenchmarkTree_IterateLinear(b *testing.B) {
// prepare linear tree // prepare linear tree
tr := new(Tree) tr := new(Tree)
tr.AddFast(newSnapshot("0", "")) tr.AddFast(newSnapshot("0", ""))

View File

@ -46,19 +46,28 @@ func (i *iterator) topSort(start *Change) {
ch := stack[len(stack)-1] ch := stack[len(stack)-1]
stack = stack[:len(stack)-1] stack = stack[:len(stack)-1]
if ch.visited { // this looks a bit clumsy, but the idea is that we will go through the change again as soon as we finished
// going through its branches
if ch.branchesFinished {
i.resBuf = append(i.resBuf, ch) i.resBuf = append(i.resBuf, ch)
ch.branchesFinished = false
continue
}
// in theory, it may be the case that we add the change two times
// but probably due to the way how we build the tree, we won't need it
if ch.visited {
continue continue
} }
ch.visited = true
stack = append(stack, ch) stack = append(stack, ch)
ch.visited = true
ch.branchesFinished = true
for j := 0; j < len(ch.Next); j++ { for j := 0; j < len(ch.Next); j++ {
if ch.Next[j].visited { if !ch.Next[j].visited {
continue stack = append(stack, ch.Next[j])
} }
stack = append(stack, ch.Next[j])
} }
} }
for _, ch := range i.resBuf { for _, ch := range i.resBuf {