github.com/attic-labs/noms@v0.0.0-20210827224422-e5fa29d95e8b/cmd/noms/noms_merge_test.go (about) 1 // Copyright 2016 Attic Labs, Inc. All rights reserved. 2 // Licensed under the Apache License, version 2.0: 3 // http://www.apache.org/licenses/LICENSE-2.0 4 5 package main 6 7 import ( 8 "bytes" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "strings" 13 "testing" 14 15 "github.com/attic-labs/noms/go/datas" 16 "github.com/attic-labs/noms/go/spec" 17 "github.com/attic-labs/noms/go/types" 18 "github.com/attic-labs/noms/go/util/clienttest" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/suite" 21 ) 22 23 type nomsMergeTestSuite struct { 24 clienttest.ClientTestSuite 25 } 26 27 func TestNomsMerge(t *testing.T) { 28 suite.Run(t, &nomsMergeTestSuite{}) 29 } 30 31 func (s *nomsMergeTestSuite) TearDownTest() { 32 s.NoError(os.RemoveAll(s.DBDir)) 33 } 34 35 func (s *nomsMergeTestSuite) TestNomsMerge_Success() { 36 left, right := "left", "right" 37 parentSpec := s.spec("parent") 38 defer parentSpec.Close() 39 leftSpec := s.spec(left) 40 defer leftSpec.Close() 41 rightSpec := s.spec(right) 42 defer rightSpec.Close() 43 44 p := s.setupMergeDataset( 45 parentSpec, 46 types.StructData{ 47 "num": types.Number(42), 48 "str": types.String("foobar"), 49 "lst": types.NewList(parentSpec.GetDatabase(), types.Number(1), types.String("foo")), 50 "map": types.NewMap(parentSpec.GetDatabase(), types.Number(1), types.String("foo"), 51 types.String("foo"), types.Number(1)), 52 }, 53 types.NewSet(parentSpec.GetDatabase())) 54 55 l := s.setupMergeDataset( 56 leftSpec, 57 types.StructData{ 58 "num": types.Number(42), 59 "str": types.String("foobaz"), 60 "lst": types.NewList(leftSpec.GetDatabase(), types.Number(1), types.String("foo")), 61 "map": types.NewMap(leftSpec.GetDatabase(), types.Number(1), types.String("foo"), 62 types.String("foo"), types.Number(1)), 63 }, 64 types.NewSet(leftSpec.GetDatabase(), p)) 65 66 r := s.setupMergeDataset( 67 rightSpec, 68 types.StructData{ 69 "num": types.Number(42), 70 "str": types.String("foobar"), 71 "lst": types.NewList(rightSpec.GetDatabase(), types.Number(1), types.String("foo")), 72 "map": types.NewMap(rightSpec.GetDatabase(), types.Number(1), types.String("foo"), 73 types.String("foo"), types.Number(1), types.Number(2), types.String("bar")), 74 }, 75 types.NewSet(rightSpec.GetDatabase(), p)) 76 77 expected := types.NewStruct("", types.StructData{ 78 "num": types.Number(42), 79 "str": types.String("foobaz"), 80 "lst": types.NewList(parentSpec.GetDatabase(), types.Number(1), types.String("foo")), 81 "map": types.NewMap(parentSpec.GetDatabase(), types.Number(1), types.String("foo"), 82 types.String("foo"), types.Number(1), types.Number(2), types.String("bar")), 83 }) 84 85 stdout, stderr, err := s.Run(main, []string{"merge", s.DBDir, left, right}) 86 if err == nil { 87 s.Equal("", stderr) 88 s.validateOutput(stdout, expected, l, r) 89 } else { 90 s.Fail("Run failed", "err: %v\nstdout: %s\nstderr: %s\n", err, stdout, stderr) 91 } 92 } 93 94 func (s *nomsMergeTestSuite) spec(name string) spec.Spec { 95 sp, err := spec.ForDataset(spec.CreateValueSpecString("nbs", s.DBDir, name)) 96 s.NoError(err) 97 return sp 98 } 99 100 func (s *nomsMergeTestSuite) setupMergeDataset(sp spec.Spec, data types.StructData, p types.Set) types.Ref { 101 ds := sp.GetDataset() 102 ds, err := sp.GetDatabase().Commit(ds, types.NewStruct("", data), datas.CommitOptions{Parents: p}) 103 s.NoError(err) 104 return ds.HeadRef() 105 } 106 107 func (s *nomsMergeTestSuite) validateOutput(outHash string, expected types.Struct, parents ...types.Value) { 108 outHash = strings.TrimSpace(outHash) 109 sp, err := spec.ForPath(spec.CreateValueSpecString("nbs", s.DBDir, fmt.Sprintf("#%s", outHash))) 110 db := sp.GetDatabase() 111 if s.NoError(err) { 112 defer sp.Close() 113 commit := sp.GetValue().(types.Struct) 114 s.True(commit.Get(datas.ParentsField).Equals(types.NewSet(db, parents...))) 115 merged := commit.Get("value") 116 s.True(expected.Equals(merged), "%s != %s", types.EncodedValue(expected), types.EncodedValue(merged)) 117 } 118 } 119 120 func (s *nomsMergeTestSuite) TestNomsMerge_Left() { 121 left, right := "left", "right" 122 parentSpec := s.spec("parent") 123 defer parentSpec.Close() 124 leftSpec := s.spec(left) 125 defer leftSpec.Close() 126 rightSpec := s.spec(right) 127 defer rightSpec.Close() 128 129 p := s.setupMergeDataset(parentSpec, types.StructData{"num": types.Number(42)}, types.NewSet(parentSpec.GetDatabase())) 130 l := s.setupMergeDataset(leftSpec, types.StructData{"num": types.Number(43)}, types.NewSet(leftSpec.GetDatabase(), p)) 131 r := s.setupMergeDataset(rightSpec, types.StructData{"num": types.Number(44)}, types.NewSet(rightSpec.GetDatabase(), p)) 132 133 expected := types.NewStruct("", types.StructData{"num": types.Number(43)}) 134 135 stdout, stderr, err := s.Run(main, []string{"merge", "--policy=l", s.DBDir, left, right}) 136 if err == nil { 137 s.Equal("", stderr) 138 s.validateOutput(stdout, expected, l, r) 139 } else { 140 s.Fail("Run failed", "err: %v\nstdout: %s\nstderr: %s\n", err, stdout, stderr) 141 } 142 } 143 144 func (s *nomsMergeTestSuite) TestNomsMerge_Right() { 145 left, right := "left", "right" 146 parentSpec := s.spec("parent") 147 defer parentSpec.Close() 148 leftSpec := s.spec(left) 149 defer leftSpec.Close() 150 rightSpec := s.spec(right) 151 defer rightSpec.Close() 152 153 p := s.setupMergeDataset(parentSpec, types.StructData{"num": types.Number(42)}, types.NewSet(parentSpec.GetDatabase())) 154 l := s.setupMergeDataset(leftSpec, types.StructData{"num": types.Number(43)}, types.NewSet(leftSpec.GetDatabase(), p)) 155 r := s.setupMergeDataset(rightSpec, types.StructData{"num": types.Number(44)}, types.NewSet(rightSpec.GetDatabase(), p)) 156 157 expected := types.NewStruct("", types.StructData{"num": types.Number(44)}) 158 159 stdout, stderr, err := s.Run(main, []string{"merge", "--policy=r", s.DBDir, left, right}) 160 if err == nil { 161 s.Equal("", stderr) 162 s.validateOutput(stdout, expected, l, r) 163 } else { 164 s.Fail("Run failed", "err: %v\nstdout: %s\nstderr: %s\n", err, stdout, stderr) 165 } 166 } 167 168 func (s *nomsMergeTestSuite) TestNomsMerge_Conflict() { 169 left, right := "left", "right" 170 parentSpec := s.spec("parent") 171 defer parentSpec.Close() 172 leftSpec := s.spec(left) 173 defer leftSpec.Close() 174 rightSpec := s.spec(right) 175 defer rightSpec.Close() 176 p := s.setupMergeDataset(parentSpec, types.StructData{"num": types.Number(42)}, types.NewSet(parentSpec.GetDatabase())) 177 s.setupMergeDataset(leftSpec, types.StructData{"num": types.Number(43)}, types.NewSet(leftSpec.GetDatabase(), p)) 178 s.setupMergeDataset(rightSpec, types.StructData{"num": types.Number(44)}, types.NewSet(rightSpec.GetDatabase(), p)) 179 180 s.Panics(func() { s.MustRun(main, []string{"merge", s.DBDir, left, right}) }) 181 } 182 183 func (s *nomsMergeTestSuite) TestBadInput() { 184 sp, err := spec.ForDatabase(spec.CreateDatabaseSpecString("nbs", s.DBDir)) 185 s.NoError(err) 186 defer sp.Close() 187 188 l, r := "left", "right" 189 type c struct { 190 args []string 191 err string 192 } 193 cases := []c{ 194 {[]string{sp.String(), l + "!!", r}, "error: Invalid dataset " + l + "!!, must match [a-zA-Z0-9\\-_/]+\n"}, 195 {[]string{sp.String(), l + "2", r}, "error: Dataset " + l + "2 has no data\n"}, 196 {[]string{sp.String(), l, r + "2"}, "error: Dataset " + r + "2 has no data\n"}, 197 } 198 199 db := sp.GetDatabase() 200 201 prep := func(dsName string) { 202 ds := db.GetDataset(dsName) 203 db.CommitValue(ds, types.NewMap(db, types.String("foo"), types.String("bar"))) 204 } 205 prep(l) 206 prep(r) 207 208 for _, c := range cases { 209 stdout, stderr, err := s.Run(main, append([]string{"merge"}, c.args...)) 210 s.Empty(stdout, "Expected non-empty stdout for case: %#v", c.args) 211 if !s.NotNil(err, "Unexpected success for case: %#v\n", c.args) { 212 continue 213 } 214 if mainErr, ok := err.(clienttest.ExitError); ok { 215 s.Equal(1, mainErr.Code) 216 s.Equal(c.err, stderr, "Unexpected error output for case: %#v\n", c.args) 217 } else { 218 s.Fail("Run() recovered non-error panic", "err: %#v\nstdout: %s\nstderr: %s\n", err, stdout, stderr) 219 } 220 } 221 } 222 223 func TestNomsMergeCliResolve(t *testing.T) { 224 type c struct { 225 input string 226 aChange, bChange types.DiffChangeType 227 aVal, bVal types.Value 228 expectedChange types.DiffChangeType 229 expected types.Value 230 success bool 231 } 232 233 cases := []c{ 234 {"l\n", types.DiffChangeAdded, types.DiffChangeAdded, types.String("foo"), types.String("bar"), types.DiffChangeAdded, types.String("foo"), true}, 235 {"r\n", types.DiffChangeAdded, types.DiffChangeAdded, types.String("foo"), types.String("bar"), types.DiffChangeAdded, types.String("bar"), true}, 236 {"l\n", types.DiffChangeAdded, types.DiffChangeAdded, types.Number(7), types.String("bar"), types.DiffChangeAdded, types.Number(7), true}, 237 {"r\n", types.DiffChangeModified, types.DiffChangeModified, types.Number(7), types.String("bar"), types.DiffChangeModified, types.String("bar"), true}, 238 } 239 240 for _, c := range cases { 241 input := bytes.NewBufferString(c.input) 242 243 changeType, newVal, ok := cliResolve(input, ioutil.Discard, c.aChange, c.bChange, c.aVal, c.bVal, types.Path{}) 244 if !c.success { 245 assert.False(t, ok) 246 } else if assert.True(t, ok) { 247 assert.Equal(t, c.expectedChange, changeType) 248 assert.True(t, c.expected.Equals(newVal)) 249 } 250 } 251 }