github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/internal/util/diff/diff_test.go (about)

     1  // Copyright 2019 Google LLC
     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  // These tests depend on `diff` which is not available on Windows
    16  //go:build !windows
    17  
    18  // Package diff_test tests the diff package
    19  package diff_test
    20  
    21  import (
    22  	"bufio"
    23  	"bytes"
    24  	"io"
    25  	"regexp"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/GoogleContainerTools/kpt/internal/printer/fake"
    30  	"github.com/GoogleContainerTools/kpt/internal/testutil"
    31  	"github.com/GoogleContainerTools/kpt/internal/testutil/pkgbuilder"
    32  	. "github.com/GoogleContainerTools/kpt/internal/util/diff"
    33  	"github.com/stretchr/testify/assert"
    34  )
    35  
    36  func TestCommand_Diff(t *testing.T) {
    37  	testCases := map[string]struct {
    38  		reposChanges              map[string][]testutil.Content
    39  		updatedLocal              testutil.Content
    40  		fetchRef                  string
    41  		diffRef                   string
    42  		diffType                  Type
    43  		diffTool                  string
    44  		diffOpts                  string
    45  		expDiff                   string
    46  		hasLocalSubpackageChanges bool
    47  	}{
    48  
    49  		// 1. add data to the upstream master branch
    50  		// 2. commit and tag the upstream master branch
    51  		// 3. add more data to the upstream master branch, commit it
    52  		// 4. create a local clone at the tag
    53  		// 5. add more data to the upstream master branch, commit it
    54  		// 6. Run remote diff between upstream and the local fork.
    55  		"remoteDiff": {
    56  			reposChanges: map[string][]testutil.Content{
    57  				testutil.Upstream: {
    58  					{
    59  						Data:   testutil.Dataset2,
    60  						Branch: "master",
    61  						Tag:    "v2",
    62  					},
    63  					{
    64  						Data: testutil.Dataset3,
    65  					},
    66  				},
    67  			},
    68  			fetchRef: "v2",
    69  			diffRef:  "master",
    70  			diffType: TypeRemote,
    71  			diffTool: "diff",
    72  			diffOpts: "-r -i -w",
    73  			expDiff: `
    74  41c41
    75  <             - containerPort: 80
    76  ---
    77  >             - containerPort: 8081
    78  27,29c27,29
    79  <     - name: "80"
    80  <       port: 80
    81  <       targetPort: 80
    82  ---
    83  >     - name: "8081"
    84  >       port: 8081
    85  >       targetPort: 8081
    86  `,
    87  		},
    88  
    89  		// 1. add data to the upstream master branch
    90  		// 2. commit and tag the upstream master branch
    91  		// 3. add more data to the upstream master branch, commit it
    92  		// 4. create a local clone at the tag
    93  		// 5. add more data to the upstream master branch, commit it
    94  		// 6. Run combined diff between upstream and the local fork
    95  		"combinedDiff": {
    96  			reposChanges: map[string][]testutil.Content{
    97  				testutil.Upstream: {
    98  					{
    99  						Data:   testutil.Dataset2,
   100  						Branch: "master",
   101  						Tag:    "v2",
   102  					},
   103  					{
   104  						Data: testutil.Dataset3,
   105  					},
   106  				},
   107  			},
   108  			fetchRef: "v2",
   109  			diffRef:  "master",
   110  			diffType: TypeCombined,
   111  			diffTool: "diff",
   112  			diffOpts: "-r -i -w",
   113  			expDiff: `
   114  41c41
   115  <             - containerPort: 80
   116  ---
   117  >             - containerPort: 8081
   118  27,29c27,29
   119  <     - name: "80"
   120  <       port: 80
   121  <       targetPort: 80
   122  ---
   123  >     - name: "8081"
   124  >       port: 8081
   125  >       targetPort: 8081
   126  `,
   127  		},
   128  
   129  		// 1. add data to the upstream master branch
   130  		// 2. commit and tag the upstream master branch
   131  		// 3. add more data to the upstream master branch, commit it
   132  		// 4. create a local clone at the tag
   133  		// 5. add more data to the upstream master branch, commit it
   134  		// 6. Update the local fork with dataset3
   135  		// 7. Run remote diff and verify the output
   136  		"localDiff": {
   137  			reposChanges: map[string][]testutil.Content{
   138  				testutil.Upstream: {
   139  					{
   140  						Data:   testutil.Dataset2,
   141  						Branch: "master",
   142  						Tag:    "v2",
   143  					},
   144  				},
   145  			},
   146  			updatedLocal: testutil.Content{
   147  				Data: testutil.Dataset3,
   148  			},
   149  			fetchRef: "v2",
   150  			diffRef:  "master",
   151  			diffType: TypeCombined,
   152  			diffTool: "diff",
   153  			diffOpts: "-r -i -w",
   154  			expDiff: `
   155  41c41
   156  <             - containerPort: 8081
   157  ---
   158  >             - containerPort: 80
   159  27,29c27,29
   160  <     - name: "8081"
   161  <       port: 8081
   162  <       targetPort: 8081
   163  ---
   164  >     - name: "80"
   165  >       port: 80
   166  >       targetPort: 80
   167  `,
   168  		},
   169  		"nested local packages updated in local": {
   170  			reposChanges: map[string][]testutil.Content{
   171  				testutil.Upstream: {
   172  					{
   173  						Pkg: pkgbuilder.NewRootPkg().
   174  							WithResource(pkgbuilder.DeploymentResource),
   175  						Branch: "main",
   176  					},
   177  				},
   178  			},
   179  			updatedLocal: testutil.Content{
   180  				Pkg: pkgbuilder.NewRootPkg().
   181  					WithKptfile(
   182  						pkgbuilder.NewKptfile().
   183  							WithUpstreamRef(testutil.Upstream, "/", "main", "resource-merge").
   184  							WithUpstreamLockRef(testutil.Upstream, "/", "main", 0),
   185  					).
   186  					WithResource(pkgbuilder.DeploymentResource,
   187  						pkgbuilder.SetFieldPath("5", "spec", "replicas")).
   188  					WithSubPackages(
   189  						pkgbuilder.NewSubPkg("foo").
   190  							WithKptfile(pkgbuilder.NewKptfile()).
   191  							WithResource(pkgbuilder.SecretResource).
   192  							WithResource(pkgbuilder.DeploymentResource, pkgbuilder.SetFieldPath("2", "spec", "replicas")),
   193  					),
   194  			},
   195  			fetchRef: "main",
   196  			diffRef:  "main",
   197  			diffType: TypeCombined,
   198  			diffTool: "diff",
   199  			diffOpts: "-r -i -w",
   200  			expDiff: `
   201  9c9
   202  <   replicas: 5
   203  ---
   204  >   replicas: 3
   205  locally changed: foo
   206  			`,
   207  			hasLocalSubpackageChanges: true,
   208  		},
   209  		"nested remote packages updated in local": {
   210  			reposChanges: map[string][]testutil.Content{
   211  				testutil.Upstream: {
   212  					{
   213  						Pkg: pkgbuilder.NewRootPkg().
   214  							WithResource(pkgbuilder.DeploymentResource),
   215  						Branch: "main",
   216  					},
   217  				},
   218  				"foo": {
   219  					{
   220  						Pkg: pkgbuilder.NewRootPkg().
   221  							WithResource(pkgbuilder.SecretResource),
   222  						Branch: "master",
   223  					},
   224  				},
   225  			},
   226  			updatedLocal: testutil.Content{
   227  				Pkg: pkgbuilder.NewRootPkg().
   228  					WithKptfile(
   229  						pkgbuilder.NewKptfile().
   230  							WithUpstreamRef(testutil.Upstream, "/", "main", "resource-merge").
   231  							WithUpstreamLockRef(testutil.Upstream, "/", "main", 0),
   232  					).
   233  					WithResource(pkgbuilder.DeploymentResource,
   234  						pkgbuilder.SetFieldPath("5", "spec", "replicas")).
   235  					WithSubPackages(
   236  						pkgbuilder.NewSubPkg("foo").
   237  							WithKptfile(
   238  								pkgbuilder.NewKptfile().
   239  									WithUpstreamRef("foo", "/", "master", "resource-merge").
   240  									WithUpstreamLockRef("foo", "/", "master", 0),
   241  							).
   242  							WithResource(pkgbuilder.SecretResource).
   243  							WithResource(pkgbuilder.DeploymentResource, pkgbuilder.SetFieldPath("2", "spec", "replicas")),
   244  					),
   245  			},
   246  			fetchRef: "main",
   247  			diffRef:  "main",
   248  			diffType: TypeCombined,
   249  			diffTool: "diff",
   250  			diffOpts: "-r -i -w",
   251  			expDiff: `
   252  9c9
   253  <   replicas: 5
   254  ---
   255  >   replicas: 3
   256  			`,
   257  		},
   258  
   259  		//nolint:gocritic
   260  		// TODO(mortent): Diff functionality must be updated to handle nested packages.
   261  		"nested remote package updated in upstream": {
   262  			reposChanges: map[string][]testutil.Content{
   263  				testutil.Upstream: {
   264  					{
   265  						Pkg: pkgbuilder.NewRootPkg().
   266  							WithResource(pkgbuilder.DeploymentResource),
   267  						Branch: "main",
   268  					},
   269  					{
   270  						Pkg: pkgbuilder.NewRootPkg().
   271  							WithResource(pkgbuilder.DeploymentResource,
   272  								pkgbuilder.SetFieldPath("5", "spec", "replicas")),
   273  					},
   274  				},
   275  				"foo": {
   276  					{
   277  						Pkg: pkgbuilder.NewRootPkg().
   278  							WithResource(pkgbuilder.SecretResource),
   279  						Branch: "master",
   280  					},
   281  				},
   282  			},
   283  			updatedLocal: testutil.Content{
   284  				Pkg: pkgbuilder.NewRootPkg().
   285  					WithKptfile(
   286  						pkgbuilder.NewKptfile().
   287  							WithUpstreamRef(testutil.Upstream, "/", "main", "resource-merge").
   288  							WithUpstreamLockRef(testutil.Upstream, "/", "main", 0),
   289  					).
   290  					WithResource(pkgbuilder.DeploymentResource).
   291  					WithSubPackages(
   292  						pkgbuilder.NewSubPkg("foo").
   293  							WithKptfile(
   294  								pkgbuilder.NewKptfile().
   295  									WithUpstreamRef("foo", "/", "master", "resource-merge").
   296  									WithUpstreamLockRef("foo", "/", "master", 0),
   297  							).
   298  							WithResource(pkgbuilder.SecretResource),
   299  					),
   300  			},
   301  			fetchRef: "main",
   302  			diffRef:  "main",
   303  			diffType: TypeCombined,
   304  			diffTool: "diff",
   305  			diffOpts: "-r -i -w",
   306  			expDiff: `
   307  9c9
   308  <   replicas: 3
   309  ---
   310  >   replicas: 5
   311  		`,
   312  		},
   313  	}
   314  
   315  	for tn, tc := range testCases {
   316  		t.Run(tn, func(t *testing.T) {
   317  			g := &testutil.TestSetupManager{
   318  				T:            t,
   319  				ReposChanges: tc.reposChanges,
   320  				GetRef:       tc.fetchRef,
   321  			}
   322  			defer g.Clean()
   323  
   324  			if tc.updatedLocal.Pkg != nil || len(tc.updatedLocal.Data) > 0 {
   325  				g.LocalChanges = []testutil.Content{
   326  					tc.updatedLocal,
   327  				}
   328  			}
   329  			if !g.Init() {
   330  				return
   331  			}
   332  
   333  			diffOutput := &bytes.Buffer{}
   334  			err := (&Command{
   335  				Path:         g.LocalWorkspace.FullPackagePath(),
   336  				Ref:          tc.diffRef,
   337  				DiffType:     tc.diffType,
   338  				DiffTool:     tc.diffTool,
   339  				DiffToolOpts: tc.diffOpts,
   340  				Output:       diffOutput,
   341  			}).Run(fake.CtxWithDefaultPrinter())
   342  			if !assert.NoError(t, err) {
   343  				t.FailNow()
   344  			}
   345  
   346  			filteredOutput := filterDiffMetadata(diffOutput)
   347  			if tc.hasLocalSubpackageChanges {
   348  				filteredOutput = regexp.MustCompile("Only in /(tmp|var).+:").ReplaceAllString(filteredOutput, "locally changed:")
   349  			}
   350  			assert.Equal(t, strings.TrimSpace(tc.expDiff)+"\n", filteredOutput)
   351  		})
   352  	}
   353  }
   354  
   355  func TestCommand_InvalidRef(t *testing.T) {
   356  	reposChanges := map[string][]testutil.Content{
   357  		testutil.Upstream: {
   358  			{
   359  				Data:   testutil.Dataset2,
   360  				Branch: "master",
   361  				Tag:    "v2",
   362  			},
   363  			{
   364  				Data: testutil.Dataset3,
   365  			},
   366  		},
   367  	}
   368  
   369  	g := &testutil.TestSetupManager{
   370  		T:            t,
   371  		ReposChanges: reposChanges,
   372  		GetRef:       "v2",
   373  	}
   374  	defer g.Clean()
   375  
   376  	if !g.Init() {
   377  		return
   378  	}
   379  
   380  	diffOutput := &bytes.Buffer{}
   381  	err := (&Command{
   382  		Path:         g.LocalWorkspace.FullPackagePath(),
   383  		Ref:          "hurdygurdy", // ref should not exist in upstream
   384  		DiffType:     TypeCombined,
   385  		DiffTool:     "diff",
   386  		DiffToolOpts: "-r -i -w",
   387  		Output:       diffOutput,
   388  	}).Run(fake.CtxWithDefaultPrinter())
   389  	assert.Error(t, err)
   390  
   391  	assert.Contains(t, err.Error(), "unknown revision or path not in the working tree.")
   392  }
   393  
   394  // Validate that all three directories are staged and provided to diff command
   395  func TestCommand_Diff3Parameters(t *testing.T) {
   396  	reposChanges := map[string][]testutil.Content{
   397  		testutil.Upstream: {
   398  			{
   399  				Data:   testutil.Dataset2,
   400  				Branch: "master",
   401  				Tag:    "v2",
   402  			},
   403  			{
   404  				Data: testutil.Dataset3,
   405  			},
   406  		},
   407  	}
   408  
   409  	g := &testutil.TestSetupManager{
   410  		T:            t,
   411  		ReposChanges: reposChanges,
   412  		GetRef:       "v2",
   413  	}
   414  	defer g.Clean()
   415  
   416  	if !g.Init() {
   417  		return
   418  	}
   419  
   420  	diffOutput := &bytes.Buffer{}
   421  	err := (&Command{
   422  		Path:         g.LocalWorkspace.FullPackagePath(),
   423  		Ref:          "master",
   424  		DiffType:     Type3Way,
   425  		DiffTool:     "echo", // this is a proxy for 3 way diffing to validate we pass proper values
   426  		DiffToolOpts: "",
   427  		Output:       diffOutput,
   428  	}).Run(fake.CtxWithDefaultPrinter())
   429  	assert.NoError(t, err)
   430  
   431  	// Expect 3 value to be printed (1 per source)
   432  	results := strings.Split(diffOutput.String(), " ")
   433  	assert.Equal(t, 3, len(results))
   434  	// Validate diff argument ordering
   435  	assert.Contains(t, results[0], LocalPackageSource)
   436  	assert.Contains(t, results[1], RemotePackageSource)
   437  	assert.Contains(t, results[2], TargetRemotePackageSource)
   438  }
   439  
   440  // Tests against directories in different states
   441  func TestCommand_NotAKptDirectory(t *testing.T) {
   442  	// Initial test setup
   443  	dir := t.TempDir()
   444  
   445  	testCases := map[string]struct {
   446  		directory string
   447  	}{
   448  		"Directory Is Not Kpt Package": {directory: dir},
   449  		"Directory Does Not Exist":     {directory: "/not/a/directory"},
   450  	}
   451  
   452  	for tn, tc := range testCases {
   453  		t.Run(tn, func(t *testing.T) {
   454  			diffOutput := &bytes.Buffer{}
   455  			cmdErr := (&Command{
   456  				Path:         tc.directory,
   457  				Ref:          "master",
   458  				DiffType:     TypeCombined,
   459  				DiffTool:     "diff",
   460  				DiffToolOpts: "-r -i -w",
   461  				Output:       diffOutput,
   462  			}).Run(fake.CtxWithDefaultPrinter())
   463  			assert.Error(t, cmdErr)
   464  
   465  			assert.Contains(t, cmdErr.Error(), "no such file or directory")
   466  		})
   467  	}
   468  }
   469  
   470  // filterDiffMetadata removes information from the diff output that is test-run
   471  // specific for ex. removing directory name being used.
   472  func filterDiffMetadata(r io.Reader) string {
   473  	scanner := bufio.NewScanner(r)
   474  	b := &bytes.Buffer{}
   475  
   476  	for scanner.Scan() {
   477  		text := scanner.Text()
   478  		// filter out the diff command that contains directory names
   479  		if strings.HasPrefix(text, "diff ") {
   480  			continue
   481  		}
   482  		b.WriteString(text)
   483  		b.WriteString("\n")
   484  	}
   485  	return b.String()
   486  }
   487  
   488  func TestStagingDirectoryNames(t *testing.T) {
   489  	var tests = []struct {
   490  		source   string
   491  		branch   string
   492  		expected string
   493  	}{
   494  		{"source", "branch", "source-branch"},
   495  		{"source", "refs/tags/version", "source-version"},
   496  	}
   497  
   498  	for i := range tests {
   499  		tt := tests[i]
   500  		t.Run(tt.expected, func(t *testing.T) {
   501  			result := NameStagingDirectory(tt.source, tt.branch)
   502  			assert.Equal(t, tt.expected, result)
   503  		})
   504  	}
   505  }