github.com/stevenmatthewt/agent@v3.5.4+incompatible/bootstrap/integration/hooks_integration_test.go (about) 1 package integration 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "path/filepath" 7 "runtime" 8 "strings" 9 "sync" 10 "testing" 11 "time" 12 13 "github.com/buildkite/agent/bootstrap/shell" 14 "github.com/buildkite/bintest" 15 ) 16 17 func TestEnvironmentVariablesPassBetweenHooks(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 if runtime.GOOS != "windows" { 27 var script = []string{ 28 "#!/bin/bash", 29 "export LLAMAS_ROCK=absolutely", 30 } 31 32 if err := ioutil.WriteFile(filepath.Join(tester.HooksDir, "environment"), 33 []byte(strings.Join(script, "\n")), 0700); err != nil { 34 t.Fatal(err) 35 } 36 } else { 37 var script = []string{ 38 "@echo off", 39 "set LLAMAS_ROCK=absolutely", 40 } 41 42 if err := ioutil.WriteFile(filepath.Join(tester.HooksDir, "environment.bat"), 43 []byte(strings.Join(script, "\r\n")), 0700); err != nil { 44 t.Fatal(err) 45 } 46 } 47 48 git := tester.MustMock(t, "git").PassthroughToLocalCommand().Before(func(i bintest.Invocation) error { 49 if err := bintest.ExpectEnv(t, i.Env, `MY_CUSTOM_ENV=1`, `LLAMAS_ROCK=absolutely`); err != nil { 50 return err 51 } 52 return nil 53 }) 54 55 git.Expect().AtLeastOnce().WithAnyArguments() 56 57 tester.ExpectGlobalHook("command").Once().AndExitWith(0).AndCallFunc(func(c *bintest.Call) { 58 if err := bintest.ExpectEnv(t, c.Env, `MY_CUSTOM_ENV=1`, `LLAMAS_ROCK=absolutely`); err != nil { 59 fmt.Fprintf(c.Stderr, "%v\n", err) 60 c.Exit(1) 61 } 62 c.Exit(0) 63 }) 64 65 tester.RunAndCheck(t, "MY_CUSTOM_ENV=1") 66 } 67 68 func TestDirectoryPassesBetweenHooks(t *testing.T) { 69 t.Parallel() 70 71 tester, err := NewBootstrapTester() 72 if err != nil { 73 t.Fatal(err) 74 } 75 defer tester.Close() 76 77 if runtime.GOOS == "windows" { 78 t.Skip("Not implemented for windows yet") 79 } 80 81 var script = []string{ 82 "#!/bin/bash", 83 "mkdir -p ./mysubdir", 84 "export MY_CUSTOM_SUBDIR=$(cd mysubdir; pwd)", 85 "cd ./mysubdir", 86 } 87 88 if err := ioutil.WriteFile(filepath.Join(tester.HooksDir, "pre-command"), []byte(strings.Join(script, "\n")), 0700); err != nil { 89 t.Fatal(err) 90 } 91 92 tester.ExpectGlobalHook("command").Once().AndExitWith(0).AndCallFunc(func(c *bintest.Call) { 93 if c.GetEnv("MY_CUSTOM_SUBDIR") != c.Dir { 94 fmt.Fprintf(c.Stderr, "Expected current dir to be %q, got %q\n", c.GetEnv("MY_CUSTOM_SUBDIR"), c.Dir) 95 c.Exit(1) 96 } 97 c.Exit(0) 98 }) 99 100 tester.RunAndCheck(t, "MY_CUSTOM_ENV=1") 101 } 102 103 func TestCheckingOutFiresCorrectHooks(t *testing.T) { 104 t.Parallel() 105 106 tester, err := NewBootstrapTester() 107 if err != nil { 108 t.Fatal(err) 109 } 110 defer tester.Close() 111 112 tester.ExpectGlobalHook("environment").Once() 113 tester.ExpectLocalHook("environment").NotCalled() 114 tester.ExpectGlobalHook("pre-checkout").Once() 115 tester.ExpectLocalHook("pre-checkout").NotCalled() 116 tester.ExpectGlobalHook("post-checkout").Once() 117 tester.ExpectLocalHook("post-checkout").Once() 118 tester.ExpectGlobalHook("pre-command").Once() 119 tester.ExpectLocalHook("pre-command").Once() 120 tester.ExpectGlobalHook("command").Once().AndExitWith(0).AndWriteToStdout("Success!\n") 121 tester.ExpectGlobalHook("post-command").Once() 122 tester.ExpectLocalHook("post-command").Once() 123 tester.ExpectGlobalHook("pre-artifact").NotCalled() 124 tester.ExpectLocalHook("pre-artifact").NotCalled() 125 tester.ExpectGlobalHook("post-artifact").NotCalled() 126 tester.ExpectLocalHook("post-artifact").NotCalled() 127 tester.ExpectGlobalHook("pre-exit").Once() 128 tester.ExpectLocalHook("pre-exit").Once() 129 130 tester.RunAndCheck(t) 131 } 132 133 func TestReplacingCheckoutHook(t *testing.T) { 134 t.Parallel() 135 136 tester, err := NewBootstrapTester() 137 if err != nil { 138 t.Fatal(err) 139 } 140 defer tester.Close() 141 142 // run a checkout in our checkout hook, otherwise we won't have local hooks to run 143 tester.ExpectGlobalHook("checkout").Once().AndCallFunc(func(c *bintest.Call) { 144 out, err := tester.Repo.Execute("clone", "-v", "--", tester.Repo.Path, c.GetEnv(`BUILDKITE_BUILD_CHECKOUT_PATH`)) 145 fmt.Fprint(c.Stderr, out) 146 if err != nil { 147 c.Exit(1) 148 return 149 } 150 c.Exit(0) 151 }) 152 153 tester.ExpectGlobalHook("pre-checkout").Once() 154 tester.ExpectGlobalHook("post-checkout").Once() 155 tester.ExpectLocalHook("post-checkout").Once() 156 tester.ExpectGlobalHook("pre-exit").Once() 157 tester.ExpectLocalHook("pre-exit").Once() 158 159 tester.RunAndCheck(t) 160 } 161 162 func TestReplacingGlobalCommandHook(t *testing.T) { 163 t.Parallel() 164 165 tester, err := NewBootstrapTester() 166 if err != nil { 167 t.Fatal(err) 168 } 169 defer tester.Close() 170 171 tester.ExpectGlobalHook("command").Once().AndExitWith(0) 172 173 tester.ExpectGlobalHook("environment").Once() 174 tester.ExpectGlobalHook("pre-checkout").Once() 175 tester.ExpectGlobalHook("post-checkout").Once() 176 tester.ExpectLocalHook("post-checkout").Once() 177 tester.ExpectGlobalHook("pre-command").Once() 178 tester.ExpectLocalHook("pre-command").Once() 179 tester.ExpectGlobalHook("post-command").Once() 180 tester.ExpectLocalHook("post-command").Once() 181 tester.ExpectGlobalHook("pre-exit").Once() 182 tester.ExpectLocalHook("pre-exit").Once() 183 184 tester.RunAndCheck(t) 185 } 186 187 func TestReplacingLocalCommandHook(t *testing.T) { 188 t.Parallel() 189 190 tester, err := NewBootstrapTester() 191 if err != nil { 192 t.Fatal(err) 193 } 194 defer tester.Close() 195 196 tester.ExpectLocalHook("command").Once().AndExitWith(0) 197 tester.ExpectGlobalHook("command").NotCalled() 198 199 tester.ExpectGlobalHook("environment").Once() 200 tester.ExpectGlobalHook("pre-checkout").Once() 201 tester.ExpectGlobalHook("post-checkout").Once() 202 tester.ExpectLocalHook("post-checkout").Once() 203 tester.ExpectGlobalHook("pre-command").Once() 204 tester.ExpectLocalHook("pre-command").Once() 205 tester.ExpectGlobalHook("post-command").Once() 206 tester.ExpectLocalHook("post-command").Once() 207 tester.ExpectGlobalHook("pre-exit").Once() 208 tester.ExpectLocalHook("pre-exit").Once() 209 210 tester.RunAndCheck(t) 211 } 212 213 func TestPreExitHooksFireAfterCommandFailures(t *testing.T) { 214 t.Parallel() 215 216 tester, err := NewBootstrapTester() 217 if err != nil { 218 t.Fatal(err) 219 } 220 defer tester.Close() 221 222 tester.ExpectGlobalHook("pre-exit").Once() 223 tester.ExpectLocalHook("pre-exit").Once() 224 225 if err = tester.Run(t, "BUILDKITE_COMMAND=false"); err == nil { 226 t.Fatal("Expected the bootstrap to fail") 227 } 228 229 tester.CheckMocks(t) 230 } 231 232 func TestPreExitHooksFireAfterHookFailures(t *testing.T) { 233 t.Parallel() 234 235 var testCases = []struct { 236 failingHook string 237 expectGlobalPreExit bool 238 expectLocalPreExit bool 239 expectCheckout bool 240 expectArtifacts bool 241 }{ 242 {"environment", true, false, false, false}, 243 {"pre-checkout", true, false, false, false}, 244 {"post-checkout", true, true, true, true}, 245 {"checkout", true, false, false, false}, 246 {"pre-command", true, true, true, true}, 247 {"command", true, true, true, true}, 248 {"post-command", true, true, true, true}, 249 {"pre-artifact", true, true, true, false}, 250 {"post-artifact", true, true, true, true}, 251 } 252 253 for _, tc := range testCases { 254 t.Run(tc.failingHook, func(t *testing.T) { 255 t.Parallel() 256 257 tester, err := NewBootstrapTester() 258 if err != nil { 259 t.Fatal(err) 260 } 261 defer tester.Close() 262 263 agent := tester.MustMock(t, "buildkite-agent") 264 265 tester.ExpectGlobalHook(tc.failingHook). 266 Once(). 267 AndWriteToStderr("Blargh\n"). 268 AndExitWith(1) 269 270 if tc.expectCheckout { 271 agent. 272 Expect("meta-data", "exists", "buildkite:git:commit"). 273 Once(). 274 AndExitWith(0) 275 } 276 277 if tc.expectGlobalPreExit { 278 tester.ExpectGlobalHook("pre-exit").Once() 279 } else { 280 tester.ExpectGlobalHook("pre-exit").NotCalled() 281 } 282 283 if tc.expectLocalPreExit { 284 tester.ExpectLocalHook("pre-exit").Once() 285 } else { 286 tester.ExpectGlobalHook("pre-exit").NotCalled() 287 } 288 289 if tc.expectArtifacts { 290 agent. 291 Expect("artifact", "upload", "test.txt"). 292 AndExitWith(0) 293 } 294 295 if err = tester.Run(t, "BUILDKITE_ARTIFACT_PATHS=test.txt"); err == nil { 296 t.Fatal("Expected the bootstrap to fail") 297 } 298 299 tester.CheckMocks(t) 300 }) 301 } 302 } 303 304 func TestNoLocalHooksCalledWhenConfigSet(t *testing.T) { 305 t.Parallel() 306 307 tester, err := NewBootstrapTester() 308 if err != nil { 309 t.Fatal(err) 310 } 311 defer tester.Close() 312 313 tester.Env = append(tester.Env, "BUILDKITE_NO_LOCAL_HOOKS=true") 314 315 tester.ExpectGlobalHook("pre-command").Once() 316 tester.ExpectLocalHook("pre-command").NotCalled() 317 318 if err = tester.Run(t, "BUILDKITE_COMMAND=true"); err == nil { 319 t.Fatal("Expected the bootstrap to fail due to local hook being called") 320 } 321 322 tester.CheckMocks(t) 323 } 324 325 func TestExitCodesPropagateOutFromGlobalHooks(t *testing.T) { 326 t.Parallel() 327 328 for _, hook := range []string{ 329 "environment", 330 "pre-checkout", 331 "post-checkout", 332 "checkout", 333 "pre-command", 334 "command", 335 "post-command", 336 "pre-exit", 337 // "pre-artifact", 338 // "post-artifact", 339 } { 340 t.Run(hook, func(t *testing.T) { 341 tester, err := NewBootstrapTester() 342 if err != nil { 343 t.Fatal(err) 344 } 345 defer tester.Close() 346 347 tester.ExpectGlobalHook(hook).Once().AndExitWith(5) 348 349 err = tester.Run(t) 350 if err == nil { 351 t.Fatalf("Expected the bootstrap to fail because %s hook exits", hook) 352 } 353 354 exitCode := shell.GetExitCode(err) 355 356 if exitCode != 5 { 357 t.Fatalf("Expected an exit code of %d, got %d", 5, exitCode) 358 } 359 360 tester.CheckMocks(t) 361 }) 362 } 363 } 364 365 func TestPreExitHooksFireAfterCancel(t *testing.T) { 366 if runtime.GOOS == "windows" { 367 t.Skip() 368 } 369 370 t.Parallel() 371 372 tester, err := NewBootstrapTester() 373 if err != nil { 374 t.Fatal(err) 375 } 376 defer tester.Close() 377 378 tester.ExpectGlobalHook("pre-exit").Once() 379 tester.ExpectLocalHook("pre-exit").Once() 380 381 var wg sync.WaitGroup 382 wg.Add(1) 383 384 go func() { 385 defer wg.Done() 386 if err = tester.Run(t, "BUILDKITE_COMMAND=sleep 5"); err == nil { 387 t.Errorf("Expected tester to fail with error") 388 } 389 t.Logf("Command finished") 390 }() 391 392 time.Sleep(time.Millisecond * 500) 393 tester.Cancel() 394 395 t.Logf("Waiting for command to finish") 396 wg.Wait() 397 398 tester.CheckMocks(t) 399 }