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  }