github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/cmd/noms/noms_merge_test.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // This file incorporates work covered by the following copyright and 16 // permission notice: 17 // 18 // Copyright 2016 Attic Labs, Inc. All rights reserved. 19 // Licensed under the Apache License, version 2.0: 20 // http://www.apache.org/licenses/LICENSE-2.0 21 22 package main 23 24 import ( 25 "bytes" 26 "context" 27 "io/ioutil" 28 "os" 29 "testing" 30 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/suite" 33 34 "github.com/dolthub/dolt/go/libraries/utils/osutil" 35 "github.com/dolthub/dolt/go/store/datas" 36 "github.com/dolthub/dolt/go/store/spec" 37 "github.com/dolthub/dolt/go/store/types" 38 "github.com/dolthub/dolt/go/store/util/clienttest" 39 ) 40 41 type nomsMergeTestSuite struct { 42 clienttest.ClientTestSuite 43 } 44 45 func TestNomsMerge(t *testing.T) { 46 suite.Run(t, &nomsMergeTestSuite{}) 47 } 48 49 func (s *nomsMergeTestSuite) TearDownTest() { 50 err := os.RemoveAll(s.DBDir) 51 if !osutil.IsWindows { 52 s.NoError(err) 53 } 54 } 55 56 func (s *nomsMergeTestSuite) TestNomsMerge_Success() { 57 left, right := "left", "right" 58 parentSpec := s.spec("parent") 59 defer parentSpec.Close() 60 leftSpec := s.spec(left) 61 defer leftSpec.Close() 62 rightSpec := s.spec(right) 63 defer rightSpec.Close() 64 65 p := s.setupMergeDataset( 66 parentSpec, 67 types.StructData{ 68 "num": types.Float(42), 69 "str": types.String("foobar"), 70 "lst": mustValue(types.NewList(context.Background(), parentSpec.GetDatabase(context.Background()), types.Float(1), types.String("foo"))), 71 "map": mustValue(types.NewMap(context.Background(), parentSpec.GetDatabase(context.Background()), types.Float(1), types.String("foo"), 72 types.String("foo"), types.Float(1))), 73 }, 74 mustList(types.NewList(context.Background(), parentSpec.GetDatabase(context.Background())))) 75 76 l := s.setupMergeDataset( 77 leftSpec, 78 types.StructData{ 79 "num": types.Float(42), 80 "str": types.String("foobaz"), 81 "lst": mustValue(types.NewList(context.Background(), leftSpec.GetDatabase(context.Background()), types.Float(1), types.String("foo"))), 82 "map": mustValue(types.NewMap(context.Background(), leftSpec.GetDatabase(context.Background()), types.Float(1), types.String("foo"), types.String("foo"), types.Float(1))), 83 }, 84 mustList(types.NewList(context.Background(), leftSpec.GetDatabase(context.Background()), p))) 85 86 r := s.setupMergeDataset( 87 rightSpec, 88 types.StructData{ 89 "num": types.Float(42), 90 "str": types.String("foobar"), 91 "lst": mustValue(types.NewList(context.Background(), rightSpec.GetDatabase(context.Background()), types.Float(1), types.String("foo"))), 92 "map": mustValue(types.NewMap(context.Background(), rightSpec.GetDatabase(context.Background()), types.Float(1), types.String("foo"), types.String("foo"), types.Float(1), types.Float(2), types.String("bar"))), 93 }, 94 mustList(types.NewList(context.Background(), rightSpec.GetDatabase(context.Background()), p))) 95 96 expected := mustValue(types.NewStruct(parentSpec.GetDatabase(context.Background()).Format(), "", types.StructData{ 97 "num": types.Float(42), 98 "str": types.String("foobaz"), 99 "lst": mustValue(types.NewList(context.Background(), parentSpec.GetDatabase(context.Background()), types.Float(1), types.String("foo"))), 100 "map": mustValue(types.NewMap(context.Background(), parentSpec.GetDatabase(context.Background()), types.Float(1), types.String("foo"), types.String("foo"), types.Float(1), types.Float(2), types.String("bar"))), 101 })) 102 103 output := "output" 104 stdout, stderr, err := s.Run(main, []string{"merge", s.DBDir, left, right, output}) 105 if err == nil { 106 s.Equal("", stderr) 107 s.validateDataset(output, expected.(types.Struct), l, r) 108 } else { 109 s.Fail("Run failed", "err: %v\nstdout: %s\nstderr: %s\n", err, stdout, stderr) 110 } 111 } 112 113 func (s *nomsMergeTestSuite) spec(name string) spec.Spec { 114 sp, err := spec.ForDataset(spec.CreateValueSpecString("nbs", s.DBDir, name)) 115 s.NoError(err) 116 return sp 117 } 118 119 func (s *nomsMergeTestSuite) setupMergeDataset(sp spec.Spec, data types.StructData, p types.List) types.Ref { 120 ds := sp.GetDataset(context.Background()) 121 db := sp.GetDatabase(context.Background()) 122 ds, err := db.Commit(context.Background(), ds, mustValue(types.NewStruct(db.Format(), "", data)), datas.CommitOptions{ParentsList: p}) 123 s.NoError(err) 124 return mustHeadRef(ds) 125 } 126 127 func (s *nomsMergeTestSuite) validateDataset(name string, expected types.Struct, parents ...types.Value) { 128 sp, err := spec.ForDataset(spec.CreateValueSpecString("nbs", s.DBDir, name)) 129 db := sp.GetDatabase(context.Background()) 130 if s.NoError(err) { 131 defer sp.Close() 132 commit := mustHead(sp.GetDataset(context.Background())) 133 s.True(mustGetValue(commit.MaybeGet(datas.ParentsField)).Equals(mustSet(types.NewSet(context.Background(), db, parents...)))) 134 merged := mustHeadValue(sp.GetDataset(context.Background())) 135 s.True(expected.Equals(merged), "%s != %s", mustString(types.EncodedValue(context.Background(), expected)), mustString(types.EncodedValue(context.Background(), merged))) 136 } 137 } 138 139 func (s *nomsMergeTestSuite) TestNomsMerge_Left() { 140 left, right := "left", "right" 141 parentSpec := s.spec("parent") 142 defer parentSpec.Close() 143 leftSpec := s.spec(left) 144 defer leftSpec.Close() 145 rightSpec := s.spec(right) 146 defer rightSpec.Close() 147 148 p := s.setupMergeDataset(parentSpec, types.StructData{"num": types.Float(42)}, mustList(types.NewList(context.Background(), parentSpec.GetDatabase(context.Background())))) 149 l := s.setupMergeDataset(leftSpec, types.StructData{"num": types.Float(43)}, mustList(types.NewList(context.Background(), leftSpec.GetDatabase(context.Background()), p))) 150 r := s.setupMergeDataset(rightSpec, types.StructData{"num": types.Float(44)}, mustList(types.NewList(context.Background(), rightSpec.GetDatabase(context.Background()), p))) 151 152 expected := mustValue(types.NewStruct(parentSpec.GetDatabase(context.Background()).Format(), "", types.StructData{"num": types.Float(43)})) 153 154 output := "output" 155 stdout, stderr, err := s.Run(main, []string{"merge", "--policy=l", s.DBDir, left, right, output}) 156 if err == nil { 157 s.Equal("", stderr) 158 s.validateDataset(output, expected.(types.Struct), l, r) 159 } else { 160 s.Fail("Run failed", "err: %v\nstdout: %s\nstderr: %s\n", err, stdout, stderr) 161 } 162 } 163 164 func (s *nomsMergeTestSuite) TestNomsMerge_Right() { 165 left, right := "left", "right" 166 parentSpec := s.spec("parent") 167 defer parentSpec.Close() 168 leftSpec := s.spec(left) 169 defer leftSpec.Close() 170 rightSpec := s.spec(right) 171 defer rightSpec.Close() 172 173 p := s.setupMergeDataset(parentSpec, types.StructData{"num": types.Float(42)}, mustList(types.NewList(context.Background(), parentSpec.GetDatabase(context.Background())))) 174 l := s.setupMergeDataset(leftSpec, types.StructData{"num": types.Float(43)}, mustList(types.NewList(context.Background(), leftSpec.GetDatabase(context.Background()), p))) 175 r := s.setupMergeDataset(rightSpec, types.StructData{"num": types.Float(44)}, mustList(types.NewList(context.Background(), rightSpec.GetDatabase(context.Background()), p))) 176 177 expected := mustValue(types.NewStruct(parentSpec.GetDatabase(context.Background()).Format(), "", types.StructData{"num": types.Float(44)})) 178 179 output := "output" 180 stdout, stderr, err := s.Run(main, []string{"merge", "--policy=r", s.DBDir, left, right, output}) 181 if err == nil { 182 s.Equal("", stderr) 183 s.validateDataset(output, expected.(types.Struct), l, r) 184 } else { 185 s.Fail("Run failed", "err: %v\nstdout: %s\nstderr: %s\n", err, stdout, stderr) 186 } 187 } 188 189 func (s *nomsMergeTestSuite) TestNomsMerge_Conflict() { 190 left, right := "left", "right" 191 parentSpec := s.spec("parent") 192 defer parentSpec.Close() 193 leftSpec := s.spec(left) 194 defer leftSpec.Close() 195 rightSpec := s.spec(right) 196 defer rightSpec.Close() 197 p := s.setupMergeDataset(parentSpec, types.StructData{"num": types.Float(42)}, mustList(types.NewList(context.Background(), parentSpec.GetDatabase(context.Background())))) 198 s.setupMergeDataset(leftSpec, types.StructData{"num": types.Float(43)}, mustList(types.NewList(context.Background(), leftSpec.GetDatabase(context.Background()), p))) 199 s.setupMergeDataset(rightSpec, types.StructData{"num": types.Float(44)}, mustList(types.NewList(context.Background(), rightSpec.GetDatabase(context.Background()), p))) 200 201 s.Panics(func() { s.MustRun(main, []string{"merge", s.DBDir, left, right, "output"}) }) 202 } 203 204 func (s *nomsMergeTestSuite) TestBadInput() { 205 sp, err := spec.ForDatabase(spec.CreateDatabaseSpecString("nbs", s.DBDir)) 206 s.NoError(err) 207 defer sp.Close() 208 209 l, r, o := "left", "right", "output" 210 type c struct { 211 args []string 212 err string 213 } 214 cases := []c{ 215 {[]string{sp.String(), l + "!!", r, o}, "error: Invalid dataset " + l + "!!, must match [a-zA-Z0-9\\-_/]+\n"}, 216 {[]string{sp.String(), l + "2", r, o}, "error: Dataset " + l + "2 has no data\n"}, 217 {[]string{sp.String(), l, r + "2", o}, "error: Dataset " + r + "2 has no data\n"}, 218 {[]string{sp.String(), l, r, "!invalid"}, "error: Invalid dataset !invalid, must match [a-zA-Z0-9\\-_/]+\n"}, 219 } 220 221 db := sp.GetDatabase(context.Background()) 222 223 prep := func(dsName string) { 224 ds, err := db.GetDataset(context.Background(), dsName) 225 s.NoError(err) 226 _, err = db.CommitValue(context.Background(), ds, mustValue(types.NewMap(context.Background(), db, types.String("foo"), types.String("bar")))) 227 s.NoError(err) 228 } 229 prep(l) 230 prep(r) 231 232 for _, c := range cases { 233 stdout, stderr, err := s.Run(main, append([]string{"merge"}, c.args...)) 234 s.Empty(stdout, "Expected empty stdout for case: %#v", c.args) 235 if !s.NotNil(err, "Unexpected success for case: %#v\n", c.args) { 236 continue 237 } 238 if mainErr, ok := err.(clienttest.ExitError); ok { 239 s.Equal(1, mainErr.Code) 240 s.Equal(c.err, stderr, "Unexpected output for case: %#v\n", c.args) 241 } else { 242 s.Fail("Run() recovered non-error panic", "err: %#v\nstdout: %s\nstderr: %s\n", err, stdout, stderr) 243 } 244 } 245 } 246 247 func TestNomsMergeCliResolve(t *testing.T) { 248 type c struct { 249 input string 250 aChange, bChange types.DiffChangeType 251 aVal, bVal types.Value 252 expectedChange types.DiffChangeType 253 expected types.Value 254 success bool 255 } 256 257 cases := []c{ 258 {"l\n", types.DiffChangeAdded, types.DiffChangeAdded, types.String("foo"), types.String("bar"), types.DiffChangeAdded, types.String("foo"), true}, 259 {"r\n", types.DiffChangeAdded, types.DiffChangeAdded, types.String("foo"), types.String("bar"), types.DiffChangeAdded, types.String("bar"), true}, 260 {"l\n", types.DiffChangeAdded, types.DiffChangeAdded, types.Float(7), types.String("bar"), types.DiffChangeAdded, types.Float(7), true}, 261 {"r\n", types.DiffChangeModified, types.DiffChangeModified, types.Float(7), types.String("bar"), types.DiffChangeModified, types.String("bar"), true}, 262 } 263 264 for _, c := range cases { 265 input := bytes.NewBufferString(c.input) 266 267 changeType, newVal, ok := cliResolve(input, ioutil.Discard, c.aChange, c.bChange, c.aVal, c.bVal, types.Path{}) 268 if !c.success { 269 assert.False(t, ok) 270 } else if assert.True(t, ok) { 271 assert.Equal(t, c.expectedChange, changeType) 272 assert.True(t, c.expected.Equals(newVal)) 273 } 274 } 275 }