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  }