github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/internal/gitutil/gitutil_test.go (about)

     1  // Copyright 2021 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  package gitutil_test
    16  
    17  import (
    18  	"os"
    19  	"path/filepath"
    20  	"sort"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/GoogleContainerTools/kpt/internal/errors"
    25  	. "github.com/GoogleContainerTools/kpt/internal/gitutil"
    26  	"github.com/GoogleContainerTools/kpt/internal/printer/fake"
    27  	"github.com/GoogleContainerTools/kpt/internal/testutil"
    28  	"github.com/GoogleContainerTools/kpt/internal/testutil/pkgbuilder"
    29  	"github.com/stretchr/testify/assert"
    30  )
    31  
    32  func TestMain(m *testing.M) {
    33  	os.Exit(testutil.ConfigureTestKptCache(m))
    34  }
    35  
    36  func TestLocalGitRunner(t *testing.T) {
    37  	testCases := map[string]struct {
    38  		command        string
    39  		args           []string
    40  		expectedStdout string
    41  		expectedErr    *GitExecError
    42  	}{
    43  		"successful command with output to stdout": {
    44  			command:        "branch",
    45  			args:           []string{"--show-current"},
    46  			expectedStdout: "main",
    47  		},
    48  		"failed command with output to stderr": {
    49  			command: "checkout",
    50  			args:    []string{"does-not-exist"},
    51  			expectedErr: &GitExecError{
    52  				StdOut: "",
    53  				StdErr: "error: pathspec 'does-not-exist' did not match any file(s) known to git",
    54  			},
    55  		},
    56  	}
    57  
    58  	for tn, tc := range testCases {
    59  		t.Run(tn, func(t *testing.T) {
    60  			dir := t.TempDir()
    61  
    62  			runner, err := NewLocalGitRunner(dir)
    63  			if !assert.NoError(t, err) {
    64  				t.FailNow()
    65  			}
    66  			_, err = runner.Run(fake.CtxWithDefaultPrinter(), "init", "--initial-branch=main")
    67  			if !assert.NoError(t, err) {
    68  				t.FailNow()
    69  			}
    70  
    71  			rr, err := runner.Run(fake.CtxWithDefaultPrinter(), tc.command, tc.args...)
    72  			if tc.expectedErr != nil {
    73  				var gitExecError *GitExecError
    74  				if !errors.As(err, &gitExecError) {
    75  					t.Error("expected error of type *GitExecError")
    76  					t.FailNow()
    77  				}
    78  				assert.Equal(t, tc.expectedErr.StdOut, strings.TrimSpace(gitExecError.StdOut))
    79  				assert.Equal(t, tc.expectedErr.StdErr, strings.TrimSpace(gitExecError.StdErr))
    80  				return
    81  			}
    82  
    83  			if !assert.NoError(t, err) {
    84  				t.FailNow()
    85  			}
    86  
    87  			assert.Equal(t, tc.expectedStdout, strings.TrimSpace(rr.Stdout))
    88  		})
    89  	}
    90  }
    91  
    92  func TestNewGitUpstreamRepo_noRepo(t *testing.T) {
    93  	dir := t.TempDir()
    94  
    95  	_, err := NewGitUpstreamRepo(fake.CtxWithDefaultPrinter(), dir)
    96  	if !assert.Error(t, err) {
    97  		t.FailNow()
    98  	}
    99  	assert.Contains(t, err.Error(), "does not appear to be a git repository")
   100  }
   101  
   102  func TestNewGitUpstreamRepo_noRefs(t *testing.T) {
   103  	dir := t.TempDir()
   104  
   105  	runner, err := NewLocalGitRunner(dir)
   106  	if !assert.NoError(t, err) {
   107  		t.FailNow()
   108  	}
   109  	_, err = runner.Run(fake.CtxWithDefaultPrinter(), "init", "--bare")
   110  	if !assert.NoError(t, err) {
   111  		t.FailNow()
   112  	}
   113  
   114  	gur, err := NewGitUpstreamRepo(fake.CtxWithDefaultPrinter(), dir)
   115  	if !assert.NoError(t, err) {
   116  		t.FailNow()
   117  	}
   118  	assert.Equal(t, 0, len(gur.Heads))
   119  	assert.Equal(t, 0, len(gur.Tags))
   120  }
   121  
   122  func TestNewGitUpstreamRepo(t *testing.T) {
   123  	testCases := map[string]struct {
   124  		repoContent   []testutil.Content
   125  		expectedHeads []string
   126  		expectedTags  []string
   127  	}{
   128  		"single branch, no tags": {
   129  			repoContent: []testutil.Content{
   130  				{
   131  					Pkg: pkgbuilder.NewRootPkg().
   132  						WithResource(pkgbuilder.DeploymentResource),
   133  					Branch: "master",
   134  				},
   135  			},
   136  			expectedHeads: []string{"master"},
   137  			expectedTags:  []string{},
   138  		},
   139  		"multiple tags and branches": {
   140  			repoContent: []testutil.Content{
   141  				{
   142  					Pkg: pkgbuilder.NewRootPkg().
   143  						WithResource(pkgbuilder.DeploymentResource),
   144  					Branch: "master",
   145  					Tag:    "v1",
   146  				},
   147  				{
   148  					Pkg: pkgbuilder.NewRootPkg().
   149  						WithResource(pkgbuilder.DeploymentResource),
   150  					Branch:       "main",
   151  					CreateBranch: true,
   152  					Tag:          "v2",
   153  				},
   154  			},
   155  			expectedHeads: []string{"main", "master"},
   156  			expectedTags:  []string{"v1", "v2"},
   157  		},
   158  	}
   159  
   160  	for tn, tc := range testCases {
   161  		t.Run(tn, func(t *testing.T) {
   162  			repoContent := map[string][]testutil.Content{
   163  				testutil.Upstream: tc.repoContent,
   164  			}
   165  			g, _, clean := testutil.SetupReposAndWorkspace(t, repoContent)
   166  			defer clean()
   167  			if !assert.NoError(t, testutil.UpdateRepos(t, g, repoContent)) {
   168  				t.FailNow()
   169  			}
   170  
   171  			gur, err := NewGitUpstreamRepo(fake.CtxWithDefaultPrinter(), g[testutil.Upstream].RepoDirectory)
   172  			if !assert.NoError(t, err) {
   173  				t.FailNow()
   174  			}
   175  			assert.EqualValues(t, tc.expectedHeads, toKeys(gur.Heads))
   176  			assert.EqualValues(t, tc.expectedTags, toKeys(gur.Tags))
   177  		})
   178  	}
   179  }
   180  
   181  func TestGitUpstreamRepo_GetDefaultBranch_noRefs(t *testing.T) {
   182  	dir := t.TempDir()
   183  
   184  	runner, err := NewLocalGitRunner(dir)
   185  	if !assert.NoError(t, err) {
   186  		t.FailNow()
   187  	}
   188  	_, err = runner.Run(fake.CtxWithDefaultPrinter(), "init", "--bare")
   189  	if !assert.NoError(t, err) {
   190  		t.FailNow()
   191  	}
   192  
   193  	gur, err := NewGitUpstreamRepo(fake.CtxWithDefaultPrinter(), dir)
   194  	if !assert.NoError(t, err) {
   195  		t.FailNow()
   196  	}
   197  	_, err = gur.GetDefaultBranch(fake.CtxWithDefaultPrinter())
   198  	if !assert.Error(t, err) {
   199  		t.FailNow()
   200  	}
   201  	assert.Contains(t, err.Error(), "unable to detect default branch in repo")
   202  }
   203  
   204  func TestGitUpstreamRepo_GetDefaultBranch(t *testing.T) {
   205  	testCases := map[string]struct {
   206  		repoContent []testutil.Content
   207  		expectedRef string
   208  	}{
   209  		"selects the default branch if it is the only one available": {
   210  			repoContent: []testutil.Content{
   211  				{
   212  					Data:   testutil.Dataset1,
   213  					Branch: "main",
   214  				},
   215  			},
   216  			expectedRef: "main",
   217  		},
   218  		"selects the default branch if there are multiple branches": {
   219  			repoContent: []testutil.Content{
   220  				{
   221  					Data:   testutil.Dataset1,
   222  					Branch: "foo",
   223  				},
   224  				{
   225  					Data:   testutil.Dataset2,
   226  					Branch: "main",
   227  				},
   228  				{
   229  					Data:   testutil.Dataset3,
   230  					Branch: "master",
   231  				},
   232  			},
   233  			expectedRef: "foo",
   234  		},
   235  	}
   236  
   237  	for tn, tc := range testCases {
   238  		t.Run(tn, func(t *testing.T) {
   239  			g, _, clean := testutil.SetupReposAndWorkspace(t, map[string][]testutil.Content{
   240  				testutil.Upstream: tc.repoContent,
   241  			})
   242  			defer clean()
   243  
   244  			gur, err := NewGitUpstreamRepo(fake.CtxWithDefaultPrinter(), g[testutil.Upstream].RepoDirectory)
   245  			if !assert.NoError(t, err) {
   246  				t.FailNow()
   247  			}
   248  
   249  			defaultRef, err := gur.GetDefaultBranch(fake.CtxWithDefaultPrinter())
   250  			if !assert.NoError(t, err) {
   251  				t.FailNow()
   252  			}
   253  			if !assert.Equal(t, tc.expectedRef, defaultRef) {
   254  				t.FailNow()
   255  			}
   256  		})
   257  	}
   258  }
   259  
   260  func TestGitUpstreamRepo_GetRepo(t *testing.T) {
   261  	testCases := map[string]struct {
   262  		repoContent []testutil.Content
   263  		refsFunc    func(*testing.T, string) []string
   264  	}{
   265  		"get branch": {
   266  			repoContent: []testutil.Content{
   267  				{
   268  					Pkg: pkgbuilder.NewRootPkg().
   269  						WithResource(pkgbuilder.DeploymentResource),
   270  					Branch: "foo",
   271  				},
   272  			},
   273  			refsFunc: func(*testing.T, string) []string {
   274  				return []string{"foo"}
   275  			},
   276  		},
   277  		// TODO: We should test both lightweight tags and annotated tags.
   278  		"get tag": {
   279  			repoContent: []testutil.Content{
   280  				{
   281  					Pkg: pkgbuilder.NewRootPkg().
   282  						WithResource(pkgbuilder.DeploymentResource),
   283  					Branch: "foo",
   284  					Tag:    "abc/123",
   285  				},
   286  			},
   287  			refsFunc: func(*testing.T, string) []string {
   288  				return []string{"abc/123"}
   289  			},
   290  		},
   291  		"get commit": {
   292  			repoContent: []testutil.Content{
   293  				{
   294  					Pkg: pkgbuilder.NewRootPkg().
   295  						WithResource(pkgbuilder.DeploymentResource),
   296  					Branch: "foo",
   297  					Tag:    "abc/123",
   298  				},
   299  			},
   300  			refsFunc: func(t *testing.T, upstreamPath string) []string {
   301  				runner, err := NewLocalGitRunner(upstreamPath)
   302  				if !assert.NoError(t, err) {
   303  					t.FailNow()
   304  				}
   305  				rr, err := runner.Run(fake.CtxWithDefaultPrinter(), "show-ref", "-s", "abc/123")
   306  				if !assert.NoError(t, err) {
   307  					t.FailNow()
   308  				}
   309  				return []string{strings.TrimSpace(rr.Stdout)}
   310  			},
   311  		},
   312  		"get short commit": {
   313  			repoContent: []testutil.Content{
   314  				{
   315  					Pkg: pkgbuilder.NewRootPkg().
   316  						WithResource(pkgbuilder.DeploymentResource),
   317  					Branch: "foo",
   318  					Tag:    "abc/123",
   319  				},
   320  			},
   321  			refsFunc: func(t *testing.T, upstreamPath string) []string {
   322  				runner, err := NewLocalGitRunner(upstreamPath)
   323  				if !assert.NoError(t, err) {
   324  					t.FailNow()
   325  				}
   326  				rr, err := runner.Run(fake.CtxWithDefaultPrinter(), "show-ref", "-s", "abc/123")
   327  				if !assert.NoError(t, err) {
   328  					t.FailNow()
   329  				}
   330  				sha := strings.TrimSpace(rr.Stdout)
   331  				rr, err = runner.Run(fake.CtxWithDefaultPrinter(), "rev-parse", "--short", sha)
   332  				if !assert.NoError(t, err) {
   333  					t.FailNow()
   334  				}
   335  				return []string{strings.TrimSpace(rr.Stdout)}
   336  			},
   337  		},
   338  	}
   339  
   340  	for tn, tc := range testCases {
   341  		t.Run(tn, func(t *testing.T) {
   342  			g, _, clean := testutil.SetupReposAndWorkspace(t, map[string][]testutil.Content{
   343  				testutil.Upstream: tc.repoContent,
   344  			})
   345  			defer clean()
   346  
   347  			gur, err := NewGitUpstreamRepo(fake.CtxWithDefaultPrinter(), g[testutil.Upstream].RepoDirectory)
   348  			if !assert.NoError(t, err) {
   349  				t.FailNow()
   350  			}
   351  			refs := tc.refsFunc(t, g[testutil.Upstream].RepoDirectory)
   352  			dir, err := gur.GetRepo(fake.CtxWithDefaultPrinter(), refs)
   353  			if !assert.NoError(t, err) {
   354  				t.FailNow()
   355  			}
   356  
   357  			runner, err := NewLocalGitRunner(dir)
   358  			if !assert.NoError(t, err) {
   359  				t.FailNow()
   360  			}
   361  			for _, r := range refs {
   362  				sha, found := gur.ResolveRef(r)
   363  				if !found {
   364  					// Assume the ref is a commit...
   365  					sha = r
   366  				}
   367  				_, err := runner.Run(fake.CtxWithDefaultPrinter(), "reset", "--hard", sha)
   368  				assert.NoError(t, err)
   369  			}
   370  		})
   371  	}
   372  }
   373  
   374  // Verify that we can fetch two different version of the same ref into the
   375  // same cached repo.
   376  func TestGitUpstreamRepo_GetRepo_multipleUpdates(t *testing.T) {
   377  	branchName := "kpt-test"
   378  	repoContent := map[string][]testutil.Content{
   379  		testutil.Upstream: {
   380  			{
   381  				Pkg: pkgbuilder.NewRootPkg().
   382  					WithResource(pkgbuilder.DeploymentResource),
   383  				Branch: branchName,
   384  			},
   385  			{
   386  				Pkg: pkgbuilder.NewRootPkg().
   387  					WithResource(pkgbuilder.ConfigMapResource),
   388  				Branch: branchName,
   389  			},
   390  		},
   391  	}
   392  	g, _, clean := testutil.SetupReposAndWorkspace(t, repoContent)
   393  	defer clean()
   394  
   395  	firstRepoDir := getRepoAndVerify(t, g[testutil.Upstream].RepoDirectory, branchName)
   396  	_, err := os.Stat(filepath.Join(firstRepoDir, "deployment.yaml"))
   397  	if !assert.NoError(t, err) {
   398  		t.FailNow()
   399  	}
   400  
   401  	if !assert.NoError(t, testutil.UpdateRepos(t, g, repoContent)) {
   402  		t.FailNow()
   403  	}
   404  
   405  	secondRepoDir := getRepoAndVerify(t, g[testutil.Upstream].RepoDirectory, branchName)
   406  	_, err = os.Stat(filepath.Join(secondRepoDir, "configmap.yaml"))
   407  	if !assert.NoError(t, err) {
   408  		t.FailNow()
   409  	}
   410  
   411  	assert.Equal(t, firstRepoDir, secondRepoDir)
   412  }
   413  
   414  func getRepoAndVerify(t *testing.T, repo, branchName string) string {
   415  	gur, err := NewGitUpstreamRepo(fake.CtxWithDefaultPrinter(), repo)
   416  	if !assert.NoError(t, err) {
   417  		t.FailNow()
   418  	}
   419  	dir, err := gur.GetRepo(fake.CtxWithDefaultPrinter(), []string{branchName})
   420  	if !assert.NoError(t, err) {
   421  		t.FailNow()
   422  	}
   423  
   424  	runner, err := NewLocalGitRunner(dir)
   425  	if !assert.NoError(t, err) {
   426  		t.FailNow()
   427  	}
   428  
   429  	sha, _ := gur.ResolveBranch(branchName)
   430  	_, err = runner.Run(fake.CtxWithDefaultPrinter(), "reset", "--hard", sha)
   431  	assert.NoError(t, err)
   432  
   433  	return dir
   434  }
   435  
   436  func toKeys(m map[string]string) []string {
   437  	keys := make([]string, 0)
   438  	for k := range m {
   439  		keys = append(keys, k)
   440  	}
   441  	sort.Strings(keys)
   442  	return keys
   443  }