github.com/stevenmatthewt/agent@v3.5.4+incompatible/bootstrap/integration/checkout_integration_test.go (about) 1 package integration 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "sync/atomic" 12 "testing" 13 14 "github.com/buildkite/bintest" 15 ) 16 17 func TestCheckingOutLocalGitProject(t *testing.T) { 18 t.Parallel() 19 20 tester, err := NewBootstrapTester() 21 if err != nil { 22 t.Fatal(err) 23 } 24 defer tester.Close() 25 26 env := []string{ 27 "BUILDKITE_GIT_CLONE_FLAGS=-v", 28 "BUILDKITE_GIT_CLEAN_FLAGS=-fdq", 29 } 30 31 // Actually execute git commands, but with expectations 32 git := tester. 33 MustMock(t, "git"). 34 PassthroughToLocalCommand() 35 36 // But assert which ones are called 37 git.ExpectAll([][]interface{}{ 38 {"clone", "-v", "--", tester.Repo.Path, "."}, 39 {"clean", "-fdq"}, 40 {"fetch", "-v", "--prune", "origin", "master"}, 41 {"checkout", "-f", "FETCH_HEAD"}, 42 {"clean", "-fdq"}, 43 {"--no-pager", "show", "HEAD", "-s", "--format=fuller", "--no-color"}, 44 }) 45 46 // Mock out the meta-data calls to the agent after checkout 47 agent := tester.MustMock(t, "buildkite-agent") 48 agent. 49 Expect("meta-data", "exists", "buildkite:git:commit"). 50 AndExitWith(1) 51 agent. 52 Expect("meta-data", "set", "buildkite:git:commit", bintest.MatchAny()). 53 AndExitWith(0) 54 55 tester.RunAndCheck(t, env...) 56 } 57 58 func TestCheckingOutLocalGitProjectWithSubmodules(t *testing.T) { 59 t.Parallel() 60 61 // Git for windows seems to struggle with local submodules in the temp dir 62 if runtime.GOOS == `windows` { 63 t.Skip() 64 } 65 66 tester, err := NewBootstrapTester() 67 if err != nil { 68 t.Fatal(err) 69 } 70 defer tester.Close() 71 72 submoduleRepo, err := createTestGitRespository() 73 if err != nil { 74 t.Fatal(err) 75 } 76 defer submoduleRepo.Close() 77 78 out, err := tester.Repo.Execute("submodule", "add", submoduleRepo.Path) 79 if err != nil { 80 t.Fatalf("Adding submodule failed: %s", out) 81 } 82 83 out, err = tester.Repo.Execute("commit", "-am", "Add example submodule") 84 if err != nil { 85 t.Fatalf("Committing submodule failed: %s", out) 86 } 87 88 env := []string{ 89 "BUILDKITE_GIT_CLONE_FLAGS=-v", 90 "BUILDKITE_GIT_CLEAN_FLAGS=-fdq", 91 } 92 93 // Actually execute git commands, but with expectations 94 git := tester. 95 MustMock(t, "git"). 96 PassthroughToLocalCommand() 97 98 // But assert which ones are called 99 git.ExpectAll([][]interface{}{ 100 {"clone", "-v", "--", tester.Repo.Path, "."}, 101 {"clean", "-fdq"}, 102 {"submodule", "foreach", "--recursive", "git", "clean", "-fdq"}, 103 {"fetch", "-v", "--prune", "origin", "master"}, 104 {"checkout", "-f", "FETCH_HEAD"}, 105 {"submodule", "sync", "--recursive"}, 106 {"submodule", "update", "--init", "--recursive", "--force"}, 107 {"submodule", "foreach", "--recursive", "git", "reset", "--hard"}, 108 {"clean", "-fdq"}, 109 {"submodule", "foreach", "--recursive", "git", "clean", "-fdq"}, 110 {"submodule", "foreach", "--recursive", "git", "ls-remote", "--get-url"}, 111 {"--no-pager", "show", "HEAD", "-s", "--format=fuller", "--no-color"}, 112 }) 113 114 // Mock out the meta-data calls to the agent after checkout 115 agent := tester.MustMock(t, "buildkite-agent") 116 agent. 117 Expect("meta-data", "exists", "buildkite:git:commit"). 118 AndExitWith(1) 119 agent. 120 Expect("meta-data", "set", "buildkite:git:commit", bintest.MatchAny()). 121 AndExitWith(0) 122 123 tester.RunAndCheck(t, env...) 124 } 125 126 func TestCheckingOutSetsCorrectGitMetadataAndSendsItToBuildkite(t *testing.T) { 127 t.Parallel() 128 129 tester, err := NewBootstrapTester() 130 if err != nil { 131 t.Fatal(err) 132 } 133 defer tester.Close() 134 135 agent := tester.MustMock(t, "buildkite-agent") 136 agent. 137 Expect("meta-data", "exists", "buildkite:git:commit"). 138 AndExitWith(1) 139 140 agent. 141 Expect("meta-data", "set", "buildkite:git:commit", 142 bintest.MatchPattern(`^commit`)). 143 AndExitWith(0) 144 145 tester.RunAndCheck(t) 146 } 147 148 func TestCheckingOutWithSSHKeyscan(t *testing.T) { 149 t.Parallel() 150 151 tester, err := NewBootstrapTester() 152 if err != nil { 153 t.Fatal(err) 154 } 155 defer tester.Close() 156 157 tester.MustMock(t, "ssh-keyscan"). 158 Expect("github.com"). 159 AndWriteToStdout("github.com ssh-rsa xxx="). 160 AndExitWith(0) 161 162 git := tester.MustMock(t, "git") 163 git.IgnoreUnexpectedInvocations() 164 165 git.Expect("clone", "-v", "--", "git@github.com:buildkite/agent.git", "."). 166 AndExitWith(0) 167 168 env := []string{ 169 `BUILDKITE_REPO=git@github.com:buildkite/agent.git`, 170 `BUILDKITE_SSH_KEYSCAN=true`, 171 } 172 173 tester.RunAndCheck(t, env...) 174 } 175 176 func TestCheckingOutWithoutSSHKeyscan(t *testing.T) { 177 t.Parallel() 178 179 tester, err := NewBootstrapTester() 180 if err != nil { 181 t.Fatal(err) 182 } 183 defer tester.Close() 184 185 tester.MustMock(t, "ssh-keyscan"). 186 Expect("github.com"). 187 NotCalled() 188 189 env := []string{ 190 `BUILDKITE_REPO=https://github.com/buildkite/bash-example.git`, 191 `BUILDKITE_SSH_KEYSCAN=false`, 192 } 193 194 tester.RunAndCheck(t, env...) 195 } 196 197 func TestCheckingOutWithSSHKeyscanAndUnscannableRepo(t *testing.T) { 198 t.Parallel() 199 200 tester, err := NewBootstrapTester() 201 if err != nil { 202 t.Fatal(err) 203 } 204 defer tester.Close() 205 206 tester.MustMock(t, "ssh-keyscan"). 207 Expect("github.com"). 208 NotCalled() 209 210 git := tester.MustMock(t, "git") 211 git.IgnoreUnexpectedInvocations() 212 213 git.Expect("clone", "-v", "--", "https://github.com/buildkite/bash-example.git", "."). 214 AndExitWith(0) 215 216 env := []string{ 217 `BUILDKITE_REPO=https://github.com/buildkite/bash-example.git`, 218 `BUILDKITE_SSH_KEYSCAN=true`, 219 } 220 221 tester.RunAndCheck(t, env...) 222 } 223 224 func TestCleaningAnExistingCheckout(t *testing.T) { 225 t.Parallel() 226 227 tester, err := NewBootstrapTester() 228 if err != nil { 229 t.Fatal(err) 230 } 231 defer tester.Close() 232 233 // Create an existing checkout 234 out, err := tester.Repo.Execute("clone", "-v", "--", tester.Repo.Path, tester.CheckoutDir()) 235 if err != nil { 236 t.Fatalf("Clone failed with %s", out) 237 } 238 err = ioutil.WriteFile(filepath.Join(tester.CheckoutDir(), "test.txt"), []byte("llamas"), 0700) 239 if err != nil { 240 t.Fatalf("Write failed with %s", out) 241 } 242 243 // Mock out the meta-data calls to the agent after checkout 244 agent := tester.MustMock(t, "buildkite-agent") 245 agent. 246 Expect("meta-data", "exists", "buildkite:git:commit"). 247 AndExitWith(0) 248 249 tester.RunAndCheck(t) 250 251 _, err = os.Stat(filepath.Join(tester.CheckoutDir(), "test.txt")) 252 if os.IsExist(err) { 253 t.Fatalf("test.txt still exitst") 254 } 255 } 256 257 func TestForcingACleanCheckout(t *testing.T) { 258 tester, err := NewBootstrapTester() 259 if err != nil { 260 t.Fatal(err) 261 } 262 defer tester.Close() 263 264 // Mock out the meta-data calls to the agent after checkout 265 agent := tester.MustMock(t, "buildkite-agent") 266 agent. 267 Expect("meta-data", "exists", "buildkite:git:commit"). 268 AndExitWith(0) 269 270 tester.RunAndCheck(t, "BUILDKITE_CLEAN_CHECKOUT=true") 271 272 if !strings.Contains(tester.Output, "Cleaning pipeline checkout") { 273 t.Fatalf("Should have removed checkout dir") 274 } 275 } 276 277 func TestCheckoutOnAnExistingRepositoryWithoutAGitFolder(t *testing.T) { 278 tester, err := NewBootstrapTester() 279 if err != nil { 280 t.Fatal(err) 281 } 282 defer tester.Close() 283 284 // Create an existing checkout 285 out, err := tester.Repo.Execute("clone", "-v", "--", tester.Repo.Path, tester.CheckoutDir()) 286 if err != nil { 287 t.Fatalf("Clone failed with %s", out) 288 } 289 290 if err = os.RemoveAll(filepath.Join(tester.CheckoutDir(), ".git", "refs")); err != nil { 291 t.Fatal(err) 292 } 293 294 agent := tester.MustMock(t, "buildkite-agent") 295 agent. 296 Expect("meta-data", "exists", "buildkite:git:commit"). 297 AndExitWith(0) 298 299 tester.RunAndCheck(t) 300 } 301 302 func TestCheckoutRetriesOnCleanFailure(t *testing.T) { 303 tester, err := NewBootstrapTester() 304 if err != nil { 305 t.Fatal(err) 306 } 307 defer tester.Close() 308 309 var cleanCounter int32 310 311 // Mock out all git commands, passing them through to the real thing unless it's a checkout 312 git := tester.MustMock(t, "git").PassthroughToLocalCommand().Before(func(i bintest.Invocation) error { 313 if i.Args[0] == "clean" { 314 c := atomic.AddInt32(&cleanCounter, 1) 315 316 // NB: clean gets run twice per checkout 317 if c == 1 { 318 return errors.New("Sunspots have caused git clean to fail") 319 } 320 } 321 return nil 322 }) 323 324 git.Expect().AtLeastOnce().WithAnyArguments() 325 tester.RunAndCheck(t) 326 } 327 328 func TestCheckoutRetriesOnCloneFailure(t *testing.T) { 329 tester, err := NewBootstrapTester() 330 if err != nil { 331 t.Fatal(err) 332 } 333 defer tester.Close() 334 335 var cloneCounter int32 336 337 // Mock out all git commands, passing them through to the real thing unless it's a checkout 338 git := tester.MustMock(t, "git").PassthroughToLocalCommand().Before(func(i bintest.Invocation) error { 339 if i.Args[0] == "clone" { 340 c := atomic.AddInt32(&cloneCounter, 1) 341 if c == 1 { 342 return errors.New("Sunspots have caused git clone to fail") 343 } 344 } 345 return nil 346 }) 347 348 git.Expect().AtLeastOnce().WithAnyArguments() 349 tester.RunAndCheck(t) 350 } 351 352 func TestCheckoutDoesNotRetryOnHookFailure(t *testing.T) { 353 tester, err := NewBootstrapTester() 354 if err != nil { 355 t.Fatal(err) 356 } 357 defer tester.Close() 358 359 var checkoutCounter int32 360 361 tester.ExpectGlobalHook("checkout").Once().AndCallFunc(func(c *bintest.Call) { 362 counter := atomic.AddInt32(&checkoutCounter, 1) 363 fmt.Fprintf(c.Stdout, "Checkout invocation %d\n", counter) 364 if counter == 1 { 365 fmt.Fprintf(c.Stdout, "Sunspots have caused checkout to fail\n") 366 c.Exit(1) 367 } else { 368 c.Exit(0) 369 } 370 }) 371 372 if err = tester.Run(t); err == nil { 373 t.Fatal("Expected the bootstrap to fail") 374 } 375 376 tester.CheckMocks(t) 377 }