Files
dolt/go/datas/commit_test.go
cmasone-attic e954427903 Go: add heuristic merge option to db.Commit() (#2759)
This patch adds an optional MergePolicy field to CommitOptions. It's a
callback. If the caller sets it, then the commit code will look for a
common ancestor between the Dataset HEAD and the provided Commit. If
the caller-provided Commit descends from HEAD, then Commit proceeds as
normal.
If it does not, but there is a common ancestor, the code runs
merge.ThreeWay() on the values of the provided Commit, HEAD, and the
common ancestor, invoking the MergePolicy callback to resolve
conflicts. If merge succeeds, a merge Commit is created that descends
from both HEAD and the caller-provided Commit. This becomes the new
HEAD of the Dataset.

Fixes #2534
2016-10-27 11:52:46 -07:00

197 lines
6.2 KiB
Go

// Copyright 2016 Attic Labs, Inc. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0
package datas
import (
"fmt"
"strings"
"testing"
"github.com/attic-labs/noms/go/chunks"
"github.com/attic-labs/noms/go/types"
"github.com/attic-labs/testify/assert"
)
func TestNewCommit(t *testing.T) {
assert := assert.New(t)
commitFieldNames := []string{MetaField, ParentsField, ValueField}
assertTypeEquals := func(e, a *types.Type) {
assert.True(a.Equals(e), "Actual: %s\nExpected %s", a.Describe(), e.Describe())
}
commit := NewCommit(types.Number(1), types.NewSet(), types.EmptyStruct)
at := commit.Type()
et := types.MakeStructType("Commit", commitFieldNames, []*types.Type{
types.EmptyStructType,
types.MakeSetType(types.MakeRefType(types.MakeCycleType(0))),
types.NumberType,
})
assertTypeEquals(et, at)
// Committing another Number
commit2 := NewCommit(types.Number(2), types.NewSet(types.NewRef(commit)), types.EmptyStruct)
at2 := commit2.Type()
et2 := et
assertTypeEquals(et2, at2)
// Now commit a String
commit3 := NewCommit(types.String("Hi"), types.NewSet(types.NewRef(commit2)), types.EmptyStruct)
at3 := commit3.Type()
et3 := types.MakeStructType("Commit", commitFieldNames, []*types.Type{
types.EmptyStructType,
types.MakeSetType(types.MakeRefType(types.MakeStructType("Commit", commitFieldNames, []*types.Type{
types.EmptyStructType,
types.MakeSetType(types.MakeRefType(types.MakeCycleType(0))),
types.MakeUnionType(types.NumberType, types.StringType),
}))),
types.StringType,
})
assertTypeEquals(et3, at3)
// Now commit a String with MetaInfo
meta := types.NewStruct("Meta", types.StructData{"date": types.String("some date"), "number": types.Number(9)})
metaType := types.MakeStructType("Meta", []string{"date", "number"}, []*types.Type{types.StringType, types.NumberType})
assertTypeEquals(metaType, meta.Type())
commit4 := NewCommit(types.String("Hi"), types.NewSet(types.NewRef(commit2)), meta)
at4 := commit4.Type()
et4 := types.MakeStructType("Commit", commitFieldNames, []*types.Type{
metaType,
types.MakeSetType(types.MakeRefType(types.MakeStructType("Commit", commitFieldNames, []*types.Type{
types.MakeUnionType(types.EmptyStructType, metaType),
types.MakeSetType(types.MakeRefType(types.MakeCycleType(0))),
types.MakeUnionType(types.NumberType, types.StringType),
}))),
types.StringType,
})
assertTypeEquals(et4, at4)
// Merge-commit with different parent types
commit5 := NewCommit(types.String("Hi"), types.NewSet(types.NewRef(commit2), types.NewRef(commit3)), types.EmptyStruct)
at5 := commit5.Type()
et5 := types.MakeStructType("Commit", commitFieldNames, []*types.Type{
types.EmptyStructType,
types.MakeSetType(types.MakeRefType(types.MakeStructType("Commit", commitFieldNames, []*types.Type{
types.EmptyStructType,
types.MakeSetType(types.MakeRefType(types.MakeCycleType(0))),
types.MakeUnionType(types.NumberType, types.StringType),
}))),
types.StringType,
})
assertTypeEquals(et5, at5)
}
func TestCommitWithoutMetaField(t *testing.T) {
assert := assert.New(t)
metaCommit := types.NewStruct("Commit", types.StructData{
"value": types.Number(9),
"parents": types.NewSet(),
"meta": types.EmptyStruct,
})
assert.True(IsCommitType(metaCommit.Type()))
noMetaCommit := types.NewStruct("Commit", types.StructData{
"value": types.Number(9),
"parents": types.NewSet(),
})
assert.False(IsCommitType(noMetaCommit.Type()))
}
// Convert list of Struct's to Set<Ref>
func toRefSet(commits ...types.Struct) types.Set {
set := types.NewSet()
for _, p := range commits {
set = set.Insert(types.NewRef(p))
}
return set
}
// Convert Set<Ref<Struct>> to a string of Struct.Get("value")'s
func toValuesString(refSet types.Set, vr types.ValueReader) string {
values := []string{}
refSet.IterAll(func(v types.Value) {
values = append(values, fmt.Sprintf("%v", v.(types.Ref).TargetValue(vr).(types.Struct).Get("value")))
})
return strings.Join(values, ",")
}
func TestFindCommonAncestor(t *testing.T) {
assert := assert.New(t)
db := NewDatabase(chunks.NewTestStore())
defer db.Close()
// Add a commit and return it
addCommit := func(datasetID string, val string, parents ...types.Struct) types.Struct {
ds := db.GetDataset(datasetID)
var err error
ds, err = db.Commit(ds, types.String(val), CommitOptions{Parents: toRefSet(parents...)})
assert.NoError(err)
return ds.Head()
}
// Assert that c is the common ancestor of a and b
assertCommonAncestor := func(expected, a, b types.Struct) {
if found, ok := FindCommonAncestor(types.NewRef(a), types.NewRef(b), db); assert.True(ok) {
ancestor := found.TargetValue(db).(types.Struct)
assert.True(
expected.Equals(ancestor),
"%s should be common ancestor of %s, %s. Got %s",
expected.Get(ValueField),
a.Get(ValueField),
b.Get(ValueField),
ancestor.Get(ValueField),
)
}
}
// Build commit DAG
//
// ds-a: a1<-a2<-a3<-a4<-a5<-a6
// ^ ^ ^ |
// | \ \----\ /-/
// | \ \V
// ds-b: \ b3<-b4<-b5
// \
// \
// ds-c: c2<-c3
// /
// /
// V
// ds-d: d1<-d2
//
a, b, c, d := "ds-a", "ds-b", "ds-c", "ds-d"
a1 := addCommit(a, "a1")
d1 := addCommit(d, "d1")
a2 := addCommit(a, "a2", a1)
c2 := addCommit(c, "c2", a1)
d2 := addCommit(d, "d2", d1)
a3 := addCommit(a, "a3", a2)
b3 := addCommit(b, "b3", a2)
c3 := addCommit(c, "c3", c2, d2)
a4 := addCommit(a, "a4", a3)
b4 := addCommit(b, "b4", b3)
a5 := addCommit(a, "a5", a4)
b5 := addCommit(b, "b5", b4, a3)
a6 := addCommit(a, "a6", a5, b5)
assertCommonAncestor(a1, a1, a1) // All self
assertCommonAncestor(a1, a1, a2) // One side self
assertCommonAncestor(a2, a3, b3) // Common parent
assertCommonAncestor(a2, a4, b4) // Common grandparent
assertCommonAncestor(a1, a6, c3) // Traversing multiple parents on both sides
// No common ancestor
if found, ok := FindCommonAncestor(types.NewRef(d2), types.NewRef(a6), db); !assert.False(ok) {
assert.Fail(
"Unexpected common ancestor!",
"Should be no common ancestor of %s, %s. Got %s",
d2.Get(ValueField),
a6.Get(ValueField),
found.TargetValue(db).(types.Struct).Get(ValueField),
)
}
}