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 }