github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/diff/diff_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 diff
    23  
    24  import (
    25  	"bytes"
    26  	"context"
    27  	"fmt"
    28  	"strings"
    29  	"testing"
    30  
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  
    34  	"github.com/dolthub/dolt/go/store/d"
    35  	"github.com/dolthub/dolt/go/store/types"
    36  	"github.com/dolthub/dolt/go/store/util/test"
    37  	"github.com/dolthub/dolt/go/store/util/writers"
    38  )
    39  
    40  var (
    41  	aa1  types.Map
    42  	aa1x types.Map
    43  
    44  	mm1  types.Map
    45  	mm2  types.Map
    46  	mm3  types.Map
    47  	mm3x types.Map
    48  	mm4  types.Map
    49  )
    50  
    51  func initmaps(vs types.ValueReadWriter) {
    52  	aa1 = createMap(vs, "a1", "a-one", "a2", "a-two", "a3", "a-three", "a4", "a-four")
    53  	aa1x = createMap(vs, "a1", "a-one-diff", "a2", "a-two", "a3", "a-three", "a4", "a-four")
    54  
    55  	mm1 = createMap(vs, "k1", "k-one", "k2", "k-two", "k3", "k-three", "k4", aa1)
    56  	mm2 = createMap(vs, "l1", "l-one", "l2", "l-two", "l3", "l-three", "l4", aa1)
    57  	mm3 = createMap(vs, "m1", "m-one", "v2", "m-two", "m3", "m-three", "m4", aa1)
    58  	mm3x = createMap(vs, "m1", "m-one", "v2", "m-two", "m3", "m-three-diff", "m4", aa1x)
    59  	mm4 = createMap(vs, "n1", "n-one", "n2", "n-two", "n3", "n-three", "n4", aa1)
    60  }
    61  
    62  func valToTypesValue(v interface{}) types.Value {
    63  	var v1 types.Value
    64  	switch t := v.(type) {
    65  	case string:
    66  		v1 = types.String(t)
    67  	case int:
    68  		v1 = types.Float(t)
    69  	case types.Value:
    70  		v1 = t
    71  	}
    72  	return v1
    73  }
    74  
    75  func valsToTypesValues(kv ...interface{}) []types.Value {
    76  	keyValues := []types.Value{}
    77  	for _, e := range kv {
    78  		v := valToTypesValue(e)
    79  		keyValues = append(keyValues, v)
    80  	}
    81  	return keyValues
    82  }
    83  
    84  func createMap(vs types.ValueReadWriter, kv ...interface{}) types.Map {
    85  	keyValues := valsToTypesValues(kv...)
    86  	m, err := types.NewMap(context.Background(), vs, keyValues...)
    87  	d.PanicIfError(err)
    88  	return m
    89  }
    90  
    91  func createSet(vs types.ValueReadWriter, kv ...interface{}) types.Set {
    92  	keyValues := valsToTypesValues(kv...)
    93  	s, err := types.NewSet(context.Background(), vs, keyValues...)
    94  	d.PanicIfError(err)
    95  	return s
    96  }
    97  
    98  func createList(vs types.ValueReadWriter, kv ...interface{}) types.List {
    99  	keyValues := valsToTypesValues(kv...)
   100  	l, err := types.NewList(context.Background(), vs, keyValues...)
   101  	d.PanicIfError(err)
   102  	return l
   103  }
   104  
   105  func createStruct(vs types.ValueReadWriter, name string, kv ...interface{}) types.Struct {
   106  	fields := types.StructData{}
   107  	for i := 0; i < len(kv); i += 2 {
   108  		fields[kv[i].(string)] = valToTypesValue(kv[i+1])
   109  	}
   110  	st, err := types.NewStruct(vs.Format(), name, fields)
   111  	d.PanicIfError(err)
   112  	return st
   113  }
   114  
   115  func pathsFromDiff(v1, v2 types.Value, leftRight bool) ([]string, error) {
   116  	var derr error
   117  	dChan := make(chan Difference)
   118  	go func() {
   119  		defer close(dChan)
   120  		derr = Diff(context.Background(), v1, v2, dChan, leftRight, nil)
   121  	}()
   122  
   123  	var paths []string
   124  	for d := range dChan {
   125  		paths = append(paths, d.Path.String())
   126  	}
   127  
   128  	return paths, derr
   129  }
   130  
   131  func mustParsePath(assert *assert.Assertions, s string) types.Path {
   132  	if s == "" {
   133  		return nil
   134  	}
   135  	p, err := types.ParsePath(s)
   136  	assert.NoError(err)
   137  	return p
   138  }
   139  
   140  func TestNomsDiffPrintMap(t *testing.T) {
   141  	assert := assert.New(t)
   142  	vs := newTestValueStore()
   143  	defer vs.Close()
   144  	initmaps(vs)
   145  
   146  	expected := `["map-3"] {
   147  -   "m3": "m-three"
   148  +   "m3": "m-three-diff"
   149    }
   150  ["map-3"]["m4"] {
   151  -   "a1": "a-one"
   152  +   "a1": "a-one-diff"
   153    }
   154  `
   155  	expectedPaths := []string{
   156  		`["map-3"]["m3"]`,
   157  		`["map-3"]["m4"]["a1"]`,
   158  	}
   159  
   160  	tf := func(leftRight bool) {
   161  		m1 := createMap(vs, "map-1", mm1, "map-2", mm2, "map-3", mm3, "map-4", mm4)
   162  		m2 := createMap(vs, "map-1", mm1, "map-2", mm2, "map-3", mm3x, "map-4", mm4)
   163  		buf := &bytes.Buffer{}
   164  		PrintDiff(context.Background(), buf, m1, m2, leftRight)
   165  		assert.Equal(expected, buf.String())
   166  
   167  		paths, err := pathsFromDiff(m1, m2, leftRight)
   168  		require.NoError(t, err)
   169  		assert.Equal(expectedPaths, paths)
   170  	}
   171  
   172  	tf(true)
   173  	tf(false)
   174  }
   175  
   176  func TestNomsDiffPrintSet(t *testing.T) {
   177  	assert := assert.New(t)
   178  	vs := newTestValueStore()
   179  	defer vs.Close()
   180  
   181  	expected1 := `(root) {
   182  -   "five"
   183  +   "five-diff"
   184    }
   185  `
   186  	expectedPaths1 := []string{
   187  		`["five"]`,
   188  		`["five-diff"]`,
   189  	}
   190  
   191  	expected2 := `(root) {
   192  -   map {  // 4 items
   193  -     "m1": "m-one",
   194  -     "m3": "m-three",
   195  -     "m4": map {  // 4 items
   196  -       "a1": "a-one",
   197  -       "a2": "a-two",
   198  -       "a3": "a-three",
   199  -       "a4": "a-four",
   200  -     },
   201  -     "v2": "m-two",
   202  -   }
   203  +   map {  // 4 items
   204  +     "m1": "m-one",
   205  +     "m3": "m-three-diff",
   206  +     "m4": map {  // 4 items
   207  +       "a1": "a-one-diff",
   208  +       "a2": "a-two",
   209  +       "a3": "a-three",
   210  +       "a4": "a-four",
   211  +     },
   212  +     "v2": "m-two",
   213  +   }
   214    }
   215  `
   216  	h3, err := mm3.Hash(vs.Format())
   217  	require.NoError(t, err)
   218  	h3x, err := mm3x.Hash(vs.Format())
   219  	require.NoError(t, err)
   220  	expectedPaths2 := []string{
   221  		fmt.Sprintf("[#%s]", h3),
   222  		fmt.Sprintf("[#%s]", h3x),
   223  	}
   224  
   225  	s1 := createSet(vs, "one", "three", "five", "seven", "nine")
   226  	s2 := createSet(vs, "one", "three", "five-diff", "seven", "nine")
   227  	s3 := createSet(vs, mm1, mm2, mm3, mm4)
   228  	s4 := createSet(vs, mm1, mm2, mm3x, mm4)
   229  
   230  	tf := func(leftRight bool) {
   231  		buf := &bytes.Buffer{}
   232  		PrintDiff(context.Background(), buf, s1, s2, leftRight)
   233  		assert.Equal(expected1, buf.String())
   234  
   235  		paths, err := pathsFromDiff(s1, s2, leftRight)
   236  		require.NoError(t, err)
   237  		assert.Equal(expectedPaths1, paths)
   238  
   239  		buf = &bytes.Buffer{}
   240  		err = PrintDiff(context.Background(), buf, s3, s4, leftRight)
   241  		require.NoError(t, err)
   242  		assert.Equal(expected2, buf.String())
   243  
   244  		paths, err = pathsFromDiff(s3, s4, leftRight)
   245  		require.NoError(t, err)
   246  		assert.Equal(expectedPaths2, paths)
   247  	}
   248  
   249  	tf(true)
   250  	tf(false)
   251  }
   252  
   253  // This function tests stop functionality in PrintDiff and Diff.
   254  func TestNomsDiffPrintStop(t *testing.T) {
   255  	assert := assert.New(t)
   256  	vs := newTestValueStore()
   257  	defer vs.Close()
   258  	initmaps(vs)
   259  
   260  	expected1 := `(root) {
   261  -   "five"
   262  `
   263  
   264  	expected2 := `(root) {
   265  -   map {  // 4 items
   266  `
   267  
   268  	s1 := createSet(vs, "one", "three", "five", "seven", "nine")
   269  	s2 := createSet(vs, "one", "three", "five-diff", "seven", "nine")
   270  	s3 := createSet(vs, mm1, mm2, mm3, mm4)
   271  	s4 := createSet(vs, mm1, mm2, mm3x, mm4)
   272  
   273  	tf := func(leftRight bool) {
   274  		buf := &bytes.Buffer{}
   275  		mlw := &writers.MaxLineWriter{Dest: buf, MaxLines: 2}
   276  		PrintDiff(context.Background(), mlw, s1, s2, leftRight)
   277  		assert.Equal(expected1, buf.String())
   278  
   279  		buf = &bytes.Buffer{}
   280  		mlw = &writers.MaxLineWriter{Dest: buf, MaxLines: 2}
   281  		PrintDiff(context.Background(), mlw, s3, s4, leftRight)
   282  		assert.Equal(expected2, buf.String())
   283  	}
   284  
   285  	tf(true)
   286  	tf(false)
   287  }
   288  
   289  func TestNomsDiffPrintStruct(t *testing.T) {
   290  	assert := assert.New(t)
   291  	vs := newTestValueStore()
   292  	defer vs.Close()
   293  	initmaps(vs)
   294  
   295  	expected1 := `(root) {
   296  -   "four": "four"
   297  +   "four": "four-diff"
   298    }
   299  ["three"] {
   300  -   field1: "field1-data"
   301  -   field3: "field3-data"
   302  +   field3: "field3-data-diff"
   303  +   field4: "field4-data"
   304    }
   305  `
   306  	expectedPaths1 := []string{
   307  		`["four"]`,
   308  		`["three"].field1`,
   309  		`["three"].field3`,
   310  		`["three"].field4`,
   311  	}
   312  
   313  	expected2 := `(root) {
   314  -   four: "four"
   315  +   four: "four-diff"
   316    }
   317  .three {
   318  -   field1: "field1-data"
   319  -   field3: "field3-data"
   320  +   field3: "field3-data-diff"
   321  +   field4: "field4-data"
   322    }
   323  `
   324  	expectedPaths2 := []string{
   325  		`.four`,
   326  		`.three.field1`,
   327  		`.three.field3`,
   328  		`.three.field4`,
   329  	}
   330  
   331  	s1 := createStruct(vs, "TestData",
   332  		"field1", "field1-data",
   333  		"field2", "field2-data",
   334  		"field3", "field3-data",
   335  	)
   336  	s2 := createStruct(vs, "TestData",
   337  		"field2", "field2-data",
   338  		"field3", "field3-data-diff",
   339  		"field4", "field4-data",
   340  	)
   341  
   342  	m1 := createMap(vs, "one", 1, "two", 2, "three", s1, "four", "four")
   343  	m2 := createMap(vs, "one", 1, "two", 2, "three", s2, "four", "four-diff")
   344  
   345  	s3 := createStruct(vs, "", "one", 1, "two", 2, "three", s1, "four", "four")
   346  	s4 := createStruct(vs, "", "one", 1, "two", 2, "three", s2, "four", "four-diff")
   347  
   348  	tf := func(leftRight bool) {
   349  		buf := &bytes.Buffer{}
   350  		PrintDiff(context.Background(), buf, m1, m2, leftRight)
   351  		assert.Equal(expected1, buf.String())
   352  
   353  		paths, err := pathsFromDiff(m1, m2, leftRight)
   354  		require.NoError(t, err)
   355  		assert.Equal(expectedPaths1, paths)
   356  
   357  		buf = &bytes.Buffer{}
   358  		err = PrintDiff(context.Background(), buf, s3, s4, leftRight)
   359  		require.NoError(t, err)
   360  		assert.Equal(expected2, buf.String())
   361  
   362  		paths, err = pathsFromDiff(s3, s4, leftRight)
   363  		require.NoError(t, err)
   364  		assert.Equal(expectedPaths2, paths)
   365  	}
   366  
   367  	tf(true)
   368  	tf(false)
   369  }
   370  
   371  func TestNomsDiffPrintMapWithStructKeys(t *testing.T) {
   372  	a := assert.New(t)
   373  	vs := newTestValueStore()
   374  	defer vs.Close()
   375  	initmaps(vs)
   376  
   377  	k1 := createStruct(vs, "TestKey", "name", "n1", "label", "l1")
   378  
   379  	expected1 := `(root) {
   380  -   struct TestKey {
   381  -     label: "l1",
   382  -     name: "n1",
   383  -   }: true
   384  +   struct TestKey {
   385  +     label: "l1",
   386  +     name: "n1",
   387  +   }: false
   388    }
   389  `
   390  
   391  	m1, err := types.NewMap(context.Background(), vs, k1, types.Bool(true))
   392  	require.NoError(t, err)
   393  	m2, err := types.NewMap(context.Background(), vs, k1, types.Bool(false))
   394  	require.NoError(t, err)
   395  	tf := func(leftRight bool) {
   396  		buf := &bytes.Buffer{}
   397  		PrintDiff(context.Background(), buf, m1, m2, leftRight)
   398  		a.Equal(expected1, buf.String())
   399  	}
   400  	tf(true)
   401  	tf(false)
   402  }
   403  
   404  func TestNomsDiffPrintList(t *testing.T) {
   405  	assert := assert.New(t)
   406  	vs := newTestValueStore()
   407  	defer vs.Close()
   408  	initmaps(vs)
   409  
   410  	expected1 := `(root) {
   411  -   2
   412  +   22
   413  -   44
   414    }
   415  `
   416  	expectedPaths1 := []string{
   417  		`[1]`,
   418  		`[4]`,
   419  	}
   420  
   421  	l1 := createList(vs, 1, 2, 3, 4, 44, 5, 6)
   422  	l2 := createList(vs, 1, 22, 3, 4, 5, 6)
   423  
   424  	expected2 := `(root) {
   425  +   "seven"
   426    }
   427  `
   428  	expectedPaths2 := []string{
   429  		`[6]`,
   430  	}
   431  
   432  	l3 := createList(vs, "one", "two", "three", "four", "five", "six")
   433  	l4 := createList(vs, "one", "two", "three", "four", "five", "six", "seven")
   434  
   435  	expected3 := `[2] {
   436  -   "m3": "m-three"
   437  +   "m3": "m-three-diff"
   438    }
   439  [2]["m4"] {
   440  -   "a1": "a-one"
   441  +   "a1": "a-one-diff"
   442    }
   443  `
   444  	expectedPaths3 := []string{
   445  		`[2]["m3"]`,
   446  		`[2]["m4"]["a1"]`,
   447  	}
   448  
   449  	l5 := createList(vs, mm1, mm2, mm3, mm4)
   450  	l6 := createList(vs, mm1, mm2, mm3x, mm4)
   451  
   452  	tf := func(leftRight bool) {
   453  		buf := &bytes.Buffer{}
   454  		err := PrintDiff(context.Background(), buf, l1, l2, leftRight)
   455  		require.NoError(t, err)
   456  		assert.Equal(expected1, buf.String())
   457  
   458  		paths, err := pathsFromDiff(l1, l2, leftRight)
   459  		require.NoError(t, err)
   460  		assert.Equal(expectedPaths1, paths)
   461  
   462  		buf = &bytes.Buffer{}
   463  		err = PrintDiff(context.Background(), buf, l3, l4, leftRight)
   464  		require.NoError(t, err)
   465  		assert.Equal(expected2, buf.String())
   466  
   467  		paths, err = pathsFromDiff(l3, l4, leftRight)
   468  		require.NoError(t, err)
   469  		assert.Equal(expectedPaths2, paths)
   470  
   471  		buf = &bytes.Buffer{}
   472  		err = PrintDiff(context.Background(), buf, l5, l6, leftRight)
   473  		require.NoError(t, err)
   474  		assert.Equal(expected3, buf.String())
   475  
   476  		paths, err = pathsFromDiff(l5, l6, leftRight)
   477  		require.NoError(t, err)
   478  		assert.Equal(expectedPaths3, paths)
   479  	}
   480  
   481  	tf(true)
   482  	tf(false)
   483  }
   484  
   485  func TestNomsDiffPrintBlob(t *testing.T) {
   486  	assert := assert.New(t)
   487  	vs := newTestValueStore()
   488  	defer vs.Close()
   489  	initmaps(vs)
   490  
   491  	expected := "-   Blob (2.0 kB)\n+   Blob (11 B)\n"
   492  	expectedPaths1 := []string{``}
   493  	b1, err := types.NewBlob(context.Background(), vs, strings.NewReader(strings.Repeat("x", 2*1024)))
   494  	require.NoError(t, err)
   495  	b2, err := types.NewBlob(context.Background(), vs, strings.NewReader("Hello World"))
   496  	require.NoError(t, err)
   497  
   498  	tf := func(leftRight bool) {
   499  		buf := &bytes.Buffer{}
   500  		err = PrintDiff(context.Background(), buf, b1, b2, leftRight)
   501  		require.NoError(t, err)
   502  		assert.Equal(expected, buf.String())
   503  
   504  		paths, err := pathsFromDiff(b1, b2, leftRight)
   505  		require.NoError(t, err)
   506  		assert.Equal(expectedPaths1, paths)
   507  	}
   508  
   509  	tf(true)
   510  	tf(false)
   511  }
   512  
   513  func TestNomsDiffPrintType(t *testing.T) {
   514  	assert := assert.New(t)
   515  	vs := newTestValueStore()
   516  	defer vs.Close()
   517  	initmaps(vs)
   518  
   519  	expected1 := "-   List<Float>\n+   List<String>\n"
   520  	expectedPaths1 := []string{""}
   521  	t1, err := types.MakeListType(types.PrimitiveTypeMap[types.FloatKind])
   522  	require.NoError(t, err)
   523  	t2, err := types.MakeListType(types.PrimitiveTypeMap[types.StringKind])
   524  	require.NoError(t, err)
   525  
   526  	expected2 := "-   List<Float>\n+   Set<String>\n"
   527  	expectedPaths2 := []string{``}
   528  	t3, err := types.MakeListType(types.PrimitiveTypeMap[types.FloatKind])
   529  	require.NoError(t, err)
   530  	t4, err := types.MakeSetType(types.PrimitiveTypeMap[types.StringKind])
   531  	require.NoError(t, err)
   532  
   533  	tf := func(leftRight bool) {
   534  		buf := &bytes.Buffer{}
   535  		err = PrintDiff(context.Background(), buf, t1, t2, leftRight)
   536  		require.NoError(t, err)
   537  		assert.Equal(expected1, buf.String())
   538  
   539  		paths, err := pathsFromDiff(t1, t2, leftRight)
   540  		require.NoError(t, err)
   541  		assert.Equal(expectedPaths1, paths)
   542  
   543  		buf = &bytes.Buffer{}
   544  		err = PrintDiff(context.Background(), buf, t3, t4, leftRight)
   545  		require.NoError(t, err)
   546  		assert.Equal(expected2, buf.String())
   547  
   548  		paths, err = pathsFromDiff(t3, t4, leftRight)
   549  		require.NoError(t, err)
   550  		assert.Equal(expectedPaths2, paths)
   551  	}
   552  
   553  	tf(true)
   554  	tf(false)
   555  }
   556  
   557  func TestNomsDiffPrintRef(t *testing.T) {
   558  	assert := assert.New(t)
   559  	vs := newTestValueStore()
   560  	defer vs.Close()
   561  	initmaps(vs)
   562  
   563  	expected := "-   #fckcbt7nk5jl4arco2dk7r9nj7abb6ci\n+   #i7d3u5gekm48ot419t2cot6cnl7ltcah\n"
   564  	expectedPaths1 := []string{``}
   565  	l1 := createList(vs, 1)
   566  	l2 := createList(vs, 2)
   567  	r1, err := types.NewRef(l1, vs.Format())
   568  	require.NoError(t, err)
   569  	r2, err := types.NewRef(l2, vs.Format())
   570  	require.NoError(t, err)
   571  
   572  	tf := func(leftRight bool) {
   573  		buf := &bytes.Buffer{}
   574  		err := PrintDiff(context.Background(), buf, r1, r2, leftRight)
   575  		require.NoError(t, err)
   576  		test.EqualsIgnoreHashes(t, expected, buf.String())
   577  
   578  		paths, err := pathsFromDiff(r1, r2, leftRight)
   579  		require.NoError(t, err)
   580  		assert.Equal(expectedPaths1, paths)
   581  	}
   582  
   583  	tf(true)
   584  	tf(false)
   585  }