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  }