github.com/vanadium-archive/go.jiri@v0.0.0-20160715023856-abfb8b131290/cmd/jiri/cl_test.go (about)

     1  // Copyright 2015 The Vanadium Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"reflect"
    15  	"runtime"
    16  	"strings"
    17  	"testing"
    18  
    19  	"v.io/jiri"
    20  	"v.io/jiri/gerrit"
    21  	"v.io/jiri/gitutil"
    22  	"v.io/jiri/jiritest"
    23  	"v.io/jiri/project"
    24  	"v.io/jiri/runutil"
    25  )
    26  
    27  // assertCommitCount asserts that the commit count between two
    28  // branches matches the expectedCount.
    29  func assertCommitCount(t *testing.T, jirix *jiri.X, branch, baseBranch string, expectedCount int) {
    30  	got, err := gitutil.New(jirix.NewSeq()).CountCommits(branch, baseBranch)
    31  	if err != nil {
    32  		t.Fatalf("%v", err)
    33  	}
    34  	if want := 1; got != want {
    35  		t.Fatalf("unexpected number of commits: got %v, want %v", got, want)
    36  	}
    37  }
    38  
    39  // assertFileContent asserts that the content of the given file
    40  // matches the expected content.
    41  func assertFileContent(t *testing.T, jirix *jiri.X, file, want string) {
    42  	got, err := jirix.NewSeq().ReadFile(file)
    43  	if err != nil {
    44  		t.Fatalf("%v\n", err)
    45  	}
    46  	if string(got) != want {
    47  		t.Fatalf("unexpected content of file %v: got %v, want %v", file, got, want)
    48  	}
    49  }
    50  
    51  // assertFilesExist asserts that the files exist.
    52  func assertFilesExist(t *testing.T, jirix *jiri.X, files []string) {
    53  	s := jirix.NewSeq()
    54  	for _, file := range files {
    55  		if _, err := s.Stat(file); err != nil {
    56  			if runutil.IsNotExist(err) {
    57  				t.Fatalf("expected file %v to exist but it did not", file)
    58  			}
    59  			t.Fatalf("%v", err)
    60  		}
    61  	}
    62  }
    63  
    64  // assertFilesDoNotExist asserts that the files do not exist.
    65  func assertFilesDoNotExist(t *testing.T, jirix *jiri.X, files []string) {
    66  	s := jirix.NewSeq()
    67  	for _, file := range files {
    68  		if _, err := s.Stat(file); err != nil && !runutil.IsNotExist(err) {
    69  			t.Fatalf("%v", err)
    70  		} else if err == nil {
    71  			t.Fatalf("expected file %v to not exist but it did", file)
    72  		}
    73  	}
    74  }
    75  
    76  // assertFilesCommitted asserts that the files exist and are committed
    77  // in the current branch.
    78  func assertFilesCommitted(t *testing.T, jirix *jiri.X, files []string) {
    79  	assertFilesExist(t, jirix, files)
    80  	for _, file := range files {
    81  		if !gitutil.New(jirix.NewSeq()).IsFileCommitted(file) {
    82  			t.Fatalf("expected file %v to be committed but it is not", file)
    83  		}
    84  	}
    85  }
    86  
    87  // assertFilesNotCommitted asserts that the files exist and are *not*
    88  // committed in the current branch.
    89  func assertFilesNotCommitted(t *testing.T, jirix *jiri.X, files []string) {
    90  	assertFilesExist(t, jirix, files)
    91  	for _, file := range files {
    92  		if gitutil.New(jirix.NewSeq()).IsFileCommitted(file) {
    93  			t.Fatalf("expected file %v not to be committed but it is", file)
    94  		}
    95  	}
    96  }
    97  
    98  // assertFilesPushedToRef asserts that the given files have been
    99  // pushed to the given remote repository reference.
   100  func assertFilesPushedToRef(t *testing.T, jirix *jiri.X, repoPath, gerritPath, pushedRef string, files []string) {
   101  	chdir(t, jirix, gerritPath)
   102  	assertCommitCount(t, jirix, pushedRef, "master", 1)
   103  	if err := gitutil.New(jirix.NewSeq()).CheckoutBranch(pushedRef); err != nil {
   104  		t.Fatalf("%v", err)
   105  	}
   106  	assertFilesCommitted(t, jirix, files)
   107  	chdir(t, jirix, repoPath)
   108  }
   109  
   110  // assertStashSize asserts that the stash size matches the expected
   111  // size.
   112  func assertStashSize(t *testing.T, jirix *jiri.X, want int) {
   113  	got, err := gitutil.New(jirix.NewSeq()).StashSize()
   114  	if err != nil {
   115  		t.Fatalf("%v", err)
   116  	}
   117  	if got != want {
   118  		t.Fatalf("unxpected stash size: got %v, want %v", got, want)
   119  	}
   120  }
   121  
   122  // commitFile commits a file with the specified content into a branch
   123  func commitFile(t *testing.T, jirix *jiri.X, filename string, content string) {
   124  	s := jirix.NewSeq()
   125  	if err := s.WriteFile(filename, []byte(content), 0644).Done(); err != nil {
   126  		t.Fatalf("%v", err)
   127  	}
   128  	commitMessage := "Commit " + filename
   129  	if err := gitutil.New(jirix.NewSeq()).CommitFile(filename, commitMessage); err != nil {
   130  		t.Fatalf("%v", err)
   131  	}
   132  }
   133  
   134  // commitFiles commits the given files into to current branch.
   135  func commitFiles(t *testing.T, jirix *jiri.X, filenames []string) {
   136  	// Create and commit the files one at a time.
   137  	for _, filename := range filenames {
   138  		content := "This is file " + filename
   139  		commitFile(t, jirix, filename, content)
   140  	}
   141  }
   142  
   143  // createRepo creates a new repository with the given prefix.
   144  func createRepo(t *testing.T, jirix *jiri.X, prefix string) string {
   145  	s := jirix.NewSeq()
   146  	repoPath, err := s.TempDir(jirix.Root, "repo-"+prefix)
   147  	if err != nil {
   148  		t.Fatalf("TempDir() failed: %v", err)
   149  	}
   150  	if err := os.Chmod(repoPath, 0777); err != nil {
   151  		t.Fatalf("Chmod(%v) failed: %v", repoPath, err)
   152  	}
   153  	if err := gitutil.New(jirix.NewSeq()).Init(repoPath); err != nil {
   154  		t.Fatalf("%v", err)
   155  	}
   156  	if err := s.MkdirAll(filepath.Join(repoPath, jiri.ProjectMetaDir), os.FileMode(0755)).Done(); err != nil {
   157  		t.Fatalf("%v", err)
   158  	}
   159  	return repoPath
   160  }
   161  
   162  // Simple commit-msg hook that removes any existing Change-Id and adds a
   163  // fake one.
   164  var commitMsgHook string = `#!/bin/sh
   165  MSG="$1"
   166  cat $MSG | sed -e "/Change-Id/d" > $MSG.tmp
   167  echo "Change-Id: I0000000000000000000000000000000000000000" >> $MSG.tmp
   168  mv $MSG.tmp $MSG
   169  `
   170  
   171  // installCommitMsgHook links the gerrit commit-msg hook into a different repo.
   172  func installCommitMsgHook(t *testing.T, jirix *jiri.X, repoPath string) {
   173  	hookLocation := path.Join(repoPath, ".git/hooks/commit-msg")
   174  	if err := jirix.NewSeq().WriteFile(hookLocation, []byte(commitMsgHook), 0755).Done(); err != nil {
   175  		t.Fatalf("WriteFile(%v) failed: %v", hookLocation, err)
   176  	}
   177  }
   178  
   179  // chdir changes the runtime working directory and traps any errors.
   180  func chdir(t *testing.T, jirix *jiri.X, path string) {
   181  	if err := jirix.NewSeq().Chdir(path).Done(); err != nil {
   182  		_, file, line, _ := runtime.Caller(1)
   183  		t.Fatalf("%s: %d: Chdir(%v) failed: %v", file, line, path, err)
   184  	}
   185  }
   186  
   187  // createRepoFromOrigin creates a Git repo tracking origin/master.
   188  func createRepoFromOrigin(t *testing.T, jirix *jiri.X, subpath string, originPath string) string {
   189  	repoPath := createRepo(t, jirix, subpath)
   190  	chdir(t, jirix, repoPath)
   191  	if err := gitutil.New(jirix.NewSeq()).AddRemote("origin", originPath); err != nil {
   192  		t.Fatalf("%v", err)
   193  	}
   194  	if err := gitutil.New(jirix.NewSeq()).Pull("origin", "master"); err != nil {
   195  		t.Fatalf("%v", err)
   196  	}
   197  	return repoPath
   198  }
   199  
   200  // createTestRepos sets up three local repositories: origin, gerrit,
   201  // and the main test repository which pulls from origin and can push
   202  // to gerrit.
   203  func createTestRepos(t *testing.T, jirix *jiri.X) (string, string, string) {
   204  	// Create origin.
   205  	originPath := createRepo(t, jirix, "origin")
   206  	chdir(t, jirix, originPath)
   207  	if err := gitutil.New(jirix.NewSeq()).CommitWithMessage("initial commit"); err != nil {
   208  		t.Fatalf("%v", err)
   209  	}
   210  	// Create test repo.
   211  	repoPath := createRepoFromOrigin(t, jirix, "test", originPath)
   212  	// Add Gerrit remote.
   213  	gerritPath := createRepoFromOrigin(t, jirix, "gerrit", originPath)
   214  	// Switch back to test repo.
   215  	chdir(t, jirix, repoPath)
   216  	return repoPath, originPath, gerritPath
   217  }
   218  
   219  // submit mocks a Gerrit review submit by pushing the Gerrit remote to origin.
   220  // Actually origin pulls from Gerrit since origin isn't actually a bare git repo.
   221  // Some of our tests actually rely on accessing .git in origin, so it must be non-bare.
   222  func submit(t *testing.T, jirix *jiri.X, originPath string, gerritPath string, review *review) {
   223  	cwd, err := os.Getwd()
   224  	if err != nil {
   225  		t.Fatalf("Getwd() failed: %v", err)
   226  	}
   227  	chdir(t, jirix, originPath)
   228  	expectedRef := gerrit.Reference(review.CLOpts)
   229  	if err := gitutil.New(jirix.NewSeq()).Pull(gerritPath, expectedRef); err != nil {
   230  		t.Fatalf("Pull gerrit to origin failed: %v", err)
   231  	}
   232  	chdir(t, jirix, cwd)
   233  }
   234  
   235  // setupTest creates a setup for testing the review tool.
   236  func setupTest(t *testing.T, installHook bool) (fake *jiritest.FakeJiriRoot, repoPath, originPath, gerritPath string, cleanup func()) {
   237  	oldWD, err := os.Getwd()
   238  	if err != nil {
   239  		t.Fatalf("Getwd() failed: %v", err)
   240  	}
   241  	var cleanupFake func()
   242  	if fake, cleanupFake = jiritest.NewFakeJiriRoot(t); err != nil {
   243  		t.Fatalf("%v", err)
   244  	}
   245  	repoPath, originPath, gerritPath = createTestRepos(t, fake.X)
   246  	if installHook == true {
   247  		for _, path := range []string{repoPath, originPath, gerritPath} {
   248  			installCommitMsgHook(t, fake.X, path)
   249  		}
   250  	}
   251  	chdir(t, fake.X, repoPath)
   252  	cleanup = func() {
   253  		chdir(t, fake.X, oldWD)
   254  		cleanupFake()
   255  	}
   256  	return
   257  }
   258  
   259  func createCLWithFiles(t *testing.T, jirix *jiri.X, branch string, files ...string) {
   260  	if err := newCL(jirix, []string{branch}); err != nil {
   261  		t.Fatalf("%v", err)
   262  	}
   263  	commitFiles(t, jirix, files)
   264  }
   265  
   266  // TestCleanupClean checks that cleanup succeeds if the branch to be
   267  // cleaned up has been merged with the master.
   268  func TestCleanupClean(t *testing.T) {
   269  	fake, repoPath, originPath, _, cleanup := setupTest(t, true)
   270  	defer cleanup()
   271  	branch := "my-branch"
   272  	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
   273  		t.Fatalf("%v", err)
   274  	}
   275  	commitFiles(t, fake.X, []string{"file1", "file2"})
   276  	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil {
   277  		t.Fatalf("%v", err)
   278  	}
   279  	chdir(t, fake.X, originPath)
   280  	commitFiles(t, fake.X, []string{"file1", "file2"})
   281  	chdir(t, fake.X, repoPath)
   282  	if err := cleanupCL(fake.X, []string{branch}); err != nil {
   283  		t.Fatalf("cleanup() failed: %v", err)
   284  	}
   285  	if gitutil.New(fake.X.NewSeq()).BranchExists(branch) {
   286  		t.Fatalf("cleanup failed to remove the feature branch")
   287  	}
   288  }
   289  
   290  // TestCleanupDirty checks that cleanup is a no-op if the branch to be
   291  // cleaned up has unmerged changes.
   292  func TestCleanupDirty(t *testing.T) {
   293  	fake, _, _, _, cleanup := setupTest(t, true)
   294  	defer cleanup()
   295  	branch := "my-branch"
   296  	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
   297  		t.Fatalf("%v", err)
   298  	}
   299  	files := []string{"file1", "file2"}
   300  	commitFiles(t, fake.X, files)
   301  	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil {
   302  		t.Fatalf("%v", err)
   303  	}
   304  	if err := cleanupCL(fake.X, []string{branch}); err == nil {
   305  		t.Fatalf("cleanup did not fail when it should")
   306  	}
   307  	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(branch); err != nil {
   308  		t.Fatalf("%v", err)
   309  	}
   310  	assertFilesCommitted(t, fake.X, files)
   311  }
   312  
   313  // TestCreateReviewBranch checks that the temporary review branch is
   314  // created correctly.
   315  func TestCreateReviewBranch(t *testing.T) {
   316  	fake, _, _, _, cleanup := setupTest(t, true)
   317  	defer cleanup()
   318  	branch := "my-branch"
   319  	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
   320  		t.Fatalf("%v", err)
   321  	}
   322  	files := []string{"file1", "file2", "file3"}
   323  	commitFiles(t, fake.X, files)
   324  	review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{})
   325  	if err != nil {
   326  		t.Fatalf("%v", err)
   327  	}
   328  	if expected, got := branch+"-REVIEW", review.reviewBranch; expected != got {
   329  		t.Fatalf("Unexpected review branch name: expected %v, got %v", expected, got)
   330  	}
   331  	commitMessage := "squashed commit"
   332  	if err := review.createReviewBranch(commitMessage); err != nil {
   333  		t.Fatalf("%v", err)
   334  	}
   335  	// Verify that the branch exists.
   336  	if !gitutil.New(fake.X.NewSeq()).BranchExists(review.reviewBranch) {
   337  		t.Fatalf("review branch not found")
   338  	}
   339  	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(review.reviewBranch); err != nil {
   340  		t.Fatalf("%v", err)
   341  	}
   342  	assertCommitCount(t, fake.X, review.reviewBranch, "master", 1)
   343  	assertFilesCommitted(t, fake.X, files)
   344  }
   345  
   346  // TestCreateReviewBranchWithEmptyChange checks that running
   347  // createReviewBranch() on a branch with no changes will result in an
   348  // EmptyChangeError.
   349  func TestCreateReviewBranchWithEmptyChange(t *testing.T) {
   350  	fake, _, _, _, cleanup := setupTest(t, true)
   351  	defer cleanup()
   352  	branch := "my-branch"
   353  	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
   354  		t.Fatalf("%v", err)
   355  	}
   356  	review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{Remote: branch})
   357  	if err != nil {
   358  		t.Fatalf("%v", err)
   359  	}
   360  	commitMessage := "squashed commit"
   361  	err = review.createReviewBranch(commitMessage)
   362  	if err == nil {
   363  		t.Fatalf("creating a review did not fail when it should")
   364  	}
   365  	if _, ok := err.(emptyChangeError); !ok {
   366  		t.Fatalf("unexpected error type: %v", err)
   367  	}
   368  }
   369  
   370  // TestSendReview checks the various options for sending a review.
   371  func TestSendReview(t *testing.T) {
   372  	fake, repoPath, _, gerritPath, cleanup := setupTest(t, true)
   373  	defer cleanup()
   374  	branch := "my-branch"
   375  	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
   376  		t.Fatalf("%v", err)
   377  	}
   378  	files := []string{"file1"}
   379  	commitFiles(t, fake.X, files)
   380  	{
   381  		// Test with draft = false, no reviewiers, and no ccs.
   382  		review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{Remote: gerritPath})
   383  		if err != nil {
   384  			t.Fatalf("%v", err)
   385  		}
   386  		if err := review.send(); err != nil {
   387  			t.Fatalf("failed to send a review: %v", err)
   388  		}
   389  		expectedRef := gerrit.Reference(review.CLOpts)
   390  		assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
   391  	}
   392  	{
   393  		// Test with draft = true, no reviewers, and no ccs.
   394  		review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{
   395  			Draft:  true,
   396  			Remote: gerritPath,
   397  		})
   398  		if err != nil {
   399  			t.Fatalf("%v", err)
   400  		}
   401  		if err := review.send(); err != nil {
   402  			t.Fatalf("failed to send a review: %v", err)
   403  		}
   404  		expectedRef := gerrit.Reference(review.CLOpts)
   405  		assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
   406  	}
   407  	{
   408  		// Test with draft = false, reviewers, and no ccs.
   409  		review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{
   410  			Remote:    gerritPath,
   411  			Reviewers: parseEmails("reviewer1,reviewer2@example.org"),
   412  		})
   413  		if err != nil {
   414  			t.Fatalf("%v", err)
   415  		}
   416  		if err := review.send(); err != nil {
   417  			t.Fatalf("failed to send a review: %v", err)
   418  		}
   419  		expectedRef := gerrit.Reference(review.CLOpts)
   420  		assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
   421  	}
   422  	{
   423  		// Test with draft = true, reviewers, and ccs.
   424  		review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{
   425  			Ccs:       parseEmails("cc1@example.org,cc2"),
   426  			Draft:     true,
   427  			Remote:    gerritPath,
   428  			Reviewers: parseEmails("reviewer3@example.org,reviewer4"),
   429  		})
   430  		if err != nil {
   431  			t.Fatalf("%v", err)
   432  		}
   433  		if err := review.send(); err != nil {
   434  			t.Fatalf("failed to send a review: %v", err)
   435  		}
   436  		expectedRef := gerrit.Reference(review.CLOpts)
   437  		assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
   438  	}
   439  }
   440  
   441  // TestSendReviewNoChangeID checks that review.send() correctly errors when
   442  // not run with a commit hook that adds a Change-Id.
   443  func TestSendReviewNoChangeID(t *testing.T) {
   444  	// Pass 'false' to setup so it doesn't install the commit-msg hook.
   445  	fake, _, _, gerritPath, cleanup := setupTest(t, false)
   446  	defer cleanup()
   447  	branch := "my-branch"
   448  	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
   449  		t.Fatalf("%v", err)
   450  	}
   451  	commitFiles(t, fake.X, []string{"file1"})
   452  	review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{Remote: gerritPath})
   453  	if err != nil {
   454  		t.Fatalf("%v", err)
   455  	}
   456  	err = review.send()
   457  	if err == nil {
   458  		t.Fatalf("sending a review did not fail when it should")
   459  	}
   460  	if _, ok := err.(noChangeIDError); !ok {
   461  		t.Fatalf("unexpected error type: %v", err)
   462  	}
   463  }
   464  
   465  // TestEndToEnd checks the end-to-end functionality of the review tool.
   466  func TestEndToEnd(t *testing.T) {
   467  	fake, repoPath, _, gerritPath, cleanup := setupTest(t, true)
   468  	defer cleanup()
   469  	branch := "my-branch"
   470  	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
   471  		t.Fatalf("%v", err)
   472  	}
   473  	files := []string{"file1", "file2", "file3"}
   474  	commitFiles(t, fake.X, files)
   475  	review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{Remote: gerritPath})
   476  	if err != nil {
   477  		t.Fatalf("%v", err)
   478  	}
   479  	setTopicFlag = false
   480  	if err := review.run(); err != nil {
   481  		t.Fatalf("run() failed: %v", err)
   482  	}
   483  	expectedRef := gerrit.Reference(review.CLOpts)
   484  	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
   485  }
   486  
   487  // TestLabelsInCommitMessage checks the labels are correctly processed
   488  // for the commit message.
   489  //
   490  // HACK ALERT: This test runs the review.run() function multiple
   491  // times. The function ends up pushing a commit to a fake "gerrit"
   492  // repository created by the setupTest() function. For the real gerrit
   493  // repository, it is possible to push to the refs/for/change reference
   494  // multiple times, because it is a special reference that "maps"
   495  // incoming commits to CL branches based on the commit message
   496  // Change-Id. The fake "gerrit" repository does not implement this
   497  // logic and thus the same reference cannot be pushed to multiple
   498  // times. To overcome this obstacle, the test takes advantage of the
   499  // fact that the reference name is a function of the reviewers and
   500  // uses different reviewers for different review runs.
   501  func TestLabelsInCommitMessage(t *testing.T) {
   502  	fake, repoPath, _, gerritPath, cleanup := setupTest(t, true)
   503  	defer cleanup()
   504  	s := fake.X.NewSeq()
   505  	branch := "my-branch"
   506  	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
   507  		t.Fatalf("%v", err)
   508  	}
   509  
   510  	// Test setting -presubmit=none and autosubmit.
   511  	files := []string{"file1", "file2", "file3"}
   512  	commitFiles(t, fake.X, files)
   513  	review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{
   514  		Autosubmit: true,
   515  		Presubmit:  gerrit.PresubmitTestTypeNone,
   516  		Remote:     gerritPath,
   517  		Reviewers:  parseEmails("run1"),
   518  	})
   519  	if err != nil {
   520  		t.Fatalf("%v", err)
   521  	}
   522  	setTopicFlag = false
   523  	if err := review.run(); err != nil {
   524  		t.Fatalf("%v", err)
   525  	}
   526  	expectedRef := gerrit.Reference(review.CLOpts)
   527  	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
   528  	// The last three lines of the gerrit commit message file should be:
   529  	// AutoSubmit
   530  	// PresubmitTest: none
   531  	// Change-Id: ...
   532  	file, err := getCommitMessageFileName(review.jirix, review.CLOpts.Branch)
   533  	if err != nil {
   534  		t.Fatalf("%v", err)
   535  	}
   536  	bytes, err := s.ReadFile(file)
   537  	if err != nil {
   538  		t.Fatalf("%v\n", err)
   539  	}
   540  	content := string(bytes)
   541  	lines := strings.Split(content, "\n")
   542  	// Make sure the Change-Id line is the last line.
   543  	if got := lines[len(lines)-1]; !strings.HasPrefix(got, "Change-Id") {
   544  		t.Fatalf("no Change-Id line found: %s", got)
   545  	}
   546  	// Make sure the "AutoSubmit" label exists.
   547  	if autosubmitLabelRE.FindString(content) == "" {
   548  		t.Fatalf("AutoSubmit label doesn't exist in the commit message: %s", content)
   549  	}
   550  	// Make sure the "PresubmitTest" label exists.
   551  	if presubmitTestLabelRE.FindString(content) == "" {
   552  		t.Fatalf("PresubmitTest label doesn't exist in the commit message: %s", content)
   553  	}
   554  
   555  	// Test setting -presubmit=all but keep autosubmit=true.
   556  	review, err = newReview(fake.X, project.Project{}, gerrit.CLOpts{
   557  		Autosubmit: true,
   558  		Remote:     gerritPath,
   559  		Reviewers:  parseEmails("run2"),
   560  	})
   561  	if err != nil {
   562  		t.Fatalf("%v", err)
   563  	}
   564  	if err := review.run(); err != nil {
   565  		t.Fatalf("%v", err)
   566  	}
   567  	expectedRef = gerrit.Reference(review.CLOpts)
   568  	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
   569  	bytes, err = s.ReadFile(file)
   570  	if err != nil {
   571  		t.Fatalf("%v\n", err)
   572  	}
   573  	content = string(bytes)
   574  	// Make sure there is no PresubmitTest=none any more.
   575  	match := presubmitTestLabelRE.FindString(content)
   576  	if match != "" {
   577  		t.Fatalf("want no presubmit label line, got: %s", match)
   578  	}
   579  	// Make sure the "AutoSubmit" label still exists.
   580  	if autosubmitLabelRE.FindString(content) == "" {
   581  		t.Fatalf("AutoSubmit label doesn't exist in the commit message: %s", content)
   582  	}
   583  
   584  	// Test setting autosubmit=false.
   585  	review, err = newReview(fake.X, project.Project{}, gerrit.CLOpts{
   586  		Remote:    gerritPath,
   587  		Reviewers: parseEmails("run3"),
   588  	})
   589  	if err != nil {
   590  		t.Fatalf("%v", err)
   591  	}
   592  	if err := review.run(); err != nil {
   593  		t.Fatalf("%v", err)
   594  	}
   595  	expectedRef = gerrit.Reference(review.CLOpts)
   596  	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
   597  	bytes, err = s.ReadFile(file)
   598  	if err != nil {
   599  		t.Fatalf("%v\n", err)
   600  	}
   601  	content = string(bytes)
   602  	// Make sure there is no AutoSubmit label any more.
   603  	match = autosubmitLabelRE.FindString(content)
   604  	if match != "" {
   605  		t.Fatalf("want no AutoSubmit label line, got: %s", match)
   606  	}
   607  }
   608  
   609  // TestDirtyBranch checks that the tool correctly handles unstaged and
   610  // untracked changes in a working branch with stashed changes.
   611  func TestDirtyBranch(t *testing.T) {
   612  	fake, _, _, gerritPath, cleanup := setupTest(t, true)
   613  	defer cleanup()
   614  	s := fake.X.NewSeq()
   615  	branch := "my-branch"
   616  	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
   617  		t.Fatalf("%v", err)
   618  	}
   619  	files := []string{"file1", "file2"}
   620  	commitFiles(t, fake.X, files)
   621  	assertStashSize(t, fake.X, 0)
   622  	stashedFile, stashedFileContent := "stashed-file", "stashed-file content"
   623  	if err := s.WriteFile(stashedFile, []byte(stashedFileContent), 0644).Done(); err != nil {
   624  		t.Fatalf("WriteFile(%v, %v) failed: %v", stashedFile, stashedFileContent, err)
   625  	}
   626  	if err := gitutil.New(fake.X.NewSeq()).Add(stashedFile); err != nil {
   627  		t.Fatalf("%v", err)
   628  	}
   629  	if _, err := gitutil.New(fake.X.NewSeq()).Stash(); err != nil {
   630  		t.Fatalf("%v", err)
   631  	}
   632  	assertStashSize(t, fake.X, 1)
   633  	modifiedFile, modifiedFileContent := "file1", "modified-file content"
   634  	if err := s.WriteFile(modifiedFile, []byte(modifiedFileContent), 0644).Done(); err != nil {
   635  		t.Fatalf("WriteFile(%v, %v) failed: %v", modifiedFile, modifiedFileContent, err)
   636  	}
   637  	stagedFile, stagedFileContent := "file2", "staged-file content"
   638  	if err := s.WriteFile(stagedFile, []byte(stagedFileContent), 0644).Done(); err != nil {
   639  		t.Fatalf("WriteFile(%v, %v) failed: %v", stagedFile, stagedFileContent, err)
   640  	}
   641  	if err := gitutil.New(fake.X.NewSeq()).Add(stagedFile); err != nil {
   642  		t.Fatalf("%v", err)
   643  	}
   644  	untrackedFile, untrackedFileContent := "file3", "untracked-file content"
   645  	if err := s.WriteFile(untrackedFile, []byte(untrackedFileContent), 0644).Done(); err != nil {
   646  		t.Fatalf("WriteFile(%v, %v) failed: %v", untrackedFile, untrackedFileContent, err)
   647  	}
   648  	review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{Remote: gerritPath})
   649  	if err != nil {
   650  		t.Fatalf("%v", err)
   651  	}
   652  	setTopicFlag = false
   653  	if err := review.run(); err == nil {
   654  		t.Fatalf("run() didn't fail when it should")
   655  	}
   656  	assertFilesNotCommitted(t, fake.X, []string{stagedFile})
   657  	assertFilesNotCommitted(t, fake.X, []string{untrackedFile})
   658  	assertFileContent(t, fake.X, modifiedFile, modifiedFileContent)
   659  	assertFileContent(t, fake.X, stagedFile, stagedFileContent)
   660  	assertFileContent(t, fake.X, untrackedFile, untrackedFileContent)
   661  	// As of git 2.4.3 "git stash pop" fails if there are uncommitted
   662  	// changes in the index. So we need to commit them first.
   663  	if err := gitutil.New(fake.X.NewSeq()).Commit(); err != nil {
   664  		t.Fatalf("%v", err)
   665  	}
   666  	assertStashSize(t, fake.X, 1)
   667  	if err := gitutil.New(fake.X.NewSeq()).StashPop(); err != nil {
   668  		t.Fatalf("%v", err)
   669  	}
   670  	assertStashSize(t, fake.X, 0)
   671  	assertFilesNotCommitted(t, fake.X, []string{stashedFile})
   672  	assertFileContent(t, fake.X, stashedFile, stashedFileContent)
   673  }
   674  
   675  // TestRunInSubdirectory checks that the command will succeed when run from
   676  // within a subdirectory of a branch that does not exist on master branch, and
   677  // will return the user to the subdirectory after completion.
   678  func TestRunInSubdirectory(t *testing.T) {
   679  	fake, repoPath, _, gerritPath, cleanup := setupTest(t, true)
   680  	defer cleanup()
   681  	s := fake.X.NewSeq()
   682  	branch := "my-branch"
   683  	if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil {
   684  		t.Fatalf("%v", err)
   685  	}
   686  	subdir := "sub/directory"
   687  	subdirPerms := os.FileMode(0744)
   688  	if err := s.MkdirAll(subdir, subdirPerms).Done(); err != nil {
   689  		t.Fatalf("MkdirAll(%v, %v) failed: %v", subdir, subdirPerms, err)
   690  	}
   691  	files := []string{path.Join(subdir, "file1")}
   692  	commitFiles(t, fake.X, files)
   693  	chdir(t, fake.X, subdir)
   694  	review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{Remote: gerritPath})
   695  	if err != nil {
   696  		t.Fatalf("%v", err)
   697  	}
   698  	setTopicFlag = false
   699  	if err := review.run(); err != nil {
   700  		t.Fatalf("run() failed: %v", err)
   701  	}
   702  	path := path.Join(repoPath, subdir)
   703  	want, err := filepath.EvalSymlinks(path)
   704  	if err != nil {
   705  		t.Fatalf("EvalSymlinks(%v) failed: %v", path, err)
   706  	}
   707  	cwd, err := os.Getwd()
   708  	if err != nil {
   709  		t.Fatalf("%v", err)
   710  	}
   711  	got, err := filepath.EvalSymlinks(cwd)
   712  	if err != nil {
   713  		t.Fatalf("EvalSymlinks(%v) failed: %v", cwd, err)
   714  	}
   715  	if got != want {
   716  		t.Fatalf("unexpected working directory: got %v, want %v", got, want)
   717  	}
   718  	expectedRef := gerrit.Reference(review.CLOpts)
   719  	assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files)
   720  }
   721  
   722  // TestProcessLabels checks that the processLabels function works as expected.
   723  func TestProcessLabels(t *testing.T) {
   724  	fake, _, _, _, cleanup := setupTest(t, true)
   725  	defer cleanup()
   726  	testCases := []struct {
   727  		autosubmit      bool
   728  		presubmitType   gerrit.PresubmitTestType
   729  		originalMessage string
   730  		expectedMessage string
   731  	}{
   732  		{
   733  			presubmitType:   gerrit.PresubmitTestTypeNone,
   734  			originalMessage: "",
   735  			expectedMessage: "PresubmitTest: none\n",
   736  		},
   737  		{
   738  			autosubmit:      true,
   739  			presubmitType:   gerrit.PresubmitTestTypeNone,
   740  			originalMessage: "",
   741  			expectedMessage: "AutoSubmit\nPresubmitTest: none\n",
   742  		},
   743  		{
   744  			presubmitType:   gerrit.PresubmitTestTypeNone,
   745  			originalMessage: "review message\n",
   746  			expectedMessage: "review message\nPresubmitTest: none\n",
   747  		},
   748  		{
   749  			autosubmit:      true,
   750  			presubmitType:   gerrit.PresubmitTestTypeNone,
   751  			originalMessage: "review message\n",
   752  			expectedMessage: "review message\nAutoSubmit\nPresubmitTest: none\n",
   753  		},
   754  		{
   755  			presubmitType: gerrit.PresubmitTestTypeNone,
   756  			originalMessage: `review message
   757  
   758  Change-Id: I0000000000000000000000000000000000000000`,
   759  			expectedMessage: `review message
   760  
   761  PresubmitTest: none
   762  Change-Id: I0000000000000000000000000000000000000000`,
   763  		},
   764  		{
   765  			autosubmit:    true,
   766  			presubmitType: gerrit.PresubmitTestTypeNone,
   767  			originalMessage: `review message
   768  
   769  Change-Id: I0000000000000000000000000000000000000000`,
   770  			expectedMessage: `review message
   771  
   772  AutoSubmit
   773  PresubmitTest: none
   774  Change-Id: I0000000000000000000000000000000000000000`,
   775  		},
   776  		{
   777  			presubmitType:   gerrit.PresubmitTestTypeAll,
   778  			originalMessage: "",
   779  			expectedMessage: "",
   780  		},
   781  		{
   782  			presubmitType:   gerrit.PresubmitTestTypeAll,
   783  			originalMessage: "review message\n",
   784  			expectedMessage: "review message\n",
   785  		},
   786  		{
   787  			presubmitType: gerrit.PresubmitTestTypeAll,
   788  			originalMessage: `review message
   789  
   790  Change-Id: I0000000000000000000000000000000000000000`,
   791  			expectedMessage: `review message
   792  
   793  Change-Id: I0000000000000000000000000000000000000000`,
   794  		},
   795  	}
   796  	for _, test := range testCases {
   797  		review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{
   798  			Autosubmit: test.autosubmit,
   799  			Presubmit:  test.presubmitType,
   800  		})
   801  		if err != nil {
   802  			t.Fatalf("%v", err)
   803  		}
   804  		if got := review.processLabelsAndCommitFile(test.originalMessage); got != test.expectedMessage {
   805  			t.Fatalf("want %s, got %s", test.expectedMessage, got)
   806  		}
   807  	}
   808  }
   809  
   810  // TestCLNew checks the operation of the "jiri cl new" command.
   811  func TestCLNew(t *testing.T) {
   812  	fake, _, _, _, cleanup := setupTest(t, true)
   813  	defer cleanup()
   814  
   815  	// Create some dependent CLs.
   816  	if err := newCL(fake.X, []string{"feature1"}); err != nil {
   817  		t.Fatalf("%v", err)
   818  	}
   819  	if err := newCL(fake.X, []string{"feature2"}); err != nil {
   820  		t.Fatalf("%v", err)
   821  	}
   822  
   823  	// Check that their dependency paths have been recorded correctly.
   824  	testCases := []struct {
   825  		branch string
   826  		data   []byte
   827  	}{
   828  		{
   829  			branch: "feature1",
   830  			data:   []byte("master"),
   831  		},
   832  		{
   833  			branch: "feature2",
   834  			data:   []byte("master\nfeature1"),
   835  		},
   836  	}
   837  	s := fake.X.NewSeq()
   838  	for _, testCase := range testCases {
   839  		file, err := getDependencyPathFileName(fake.X, testCase.branch)
   840  		if err != nil {
   841  			t.Fatalf("%v", err)
   842  		}
   843  		data, err := s.ReadFile(file)
   844  		if err != nil {
   845  			t.Fatalf("%v", err)
   846  		}
   847  		if bytes.Compare(data, testCase.data) != 0 {
   848  			t.Fatalf("unexpected data:\ngot\n%v\nwant\n%v", string(data), string(testCase.data))
   849  		}
   850  	}
   851  }
   852  
   853  // TestDependentClsWithEditDelete exercises a previously observed failure case
   854  // where if a CL edits a file and a dependent CL deletes it, jiri cl mail after
   855  // the deletion failed with unrecoverable merge errors.
   856  func TestDependentClsWithEditDelete(t *testing.T) {
   857  	fake, repoPath, originPath, gerritPath, cleanup := setupTest(t, true)
   858  	defer cleanup()
   859  	chdir(t, fake.X, originPath)
   860  	commitFiles(t, fake.X, []string{"A", "B"})
   861  
   862  	chdir(t, fake.X, repoPath)
   863  	if err := syncCL(fake.X); err != nil {
   864  		t.Fatalf("%v", err)
   865  	}
   866  	assertFilesExist(t, fake.X, []string{"A", "B"})
   867  
   868  	createCLWithFiles(t, fake.X, "editme", "C")
   869  	if err := fake.X.NewSeq().WriteFile("B", []byte("Will I dream?"), 0644).Done(); err != nil {
   870  		t.Fatalf("%v", err)
   871  	}
   872  	if err := gitutil.New(fake.X.NewSeq()).Add("B"); err != nil {
   873  		t.Fatalf("%v", err)
   874  	}
   875  	if err := gitutil.New(fake.X.NewSeq()).CommitWithMessage("editing stuff"); err != nil {
   876  		t.Fatalf("git commit failed: %v", err)
   877  	}
   878  	review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{
   879  		Remote:    gerritPath,
   880  		Reviewers: parseEmails("run1"), // See hack note about TestLabelsInCommitMessage
   881  	})
   882  	if err != nil {
   883  		t.Fatalf("%v", err)
   884  	}
   885  	setTopicFlag = false
   886  	if err := review.run(); err != nil {
   887  		t.Fatalf("run() failed: %v", err)
   888  	}
   889  
   890  	if err := newCL(fake.X, []string{"deleteme"}); err != nil {
   891  		t.Fatalf("%v", err)
   892  	}
   893  	if err := gitutil.New(fake.X.NewSeq()).Remove("B", "C"); err != nil {
   894  		t.Fatalf("git rm B C failed: %v", err)
   895  	}
   896  	if err := gitutil.New(fake.X.NewSeq()).CommitWithMessage("deleting stuff"); err != nil {
   897  		t.Fatalf("git commit failed: %v", err)
   898  	}
   899  	review, err = newReview(fake.X, project.Project{}, gerrit.CLOpts{
   900  		Remote:    gerritPath,
   901  		Reviewers: parseEmails("run2"),
   902  	})
   903  	if err != nil {
   904  		t.Fatalf("%v", err)
   905  	}
   906  	if err := review.run(); err != nil {
   907  		t.Fatalf("run() failed: %v", err)
   908  	}
   909  
   910  	chdir(t, fake.X, gerritPath)
   911  	expectedRef := gerrit.Reference(review.CLOpts)
   912  	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(expectedRef); err != nil {
   913  		t.Fatalf("%v", err)
   914  	}
   915  	assertFilesExist(t, fake.X, []string{"A"})
   916  	assertFilesDoNotExist(t, fake.X, []string{"B", "C"})
   917  }
   918  
   919  // TestParallelDev checks "jiri cl mail" behavior when parallel development has
   920  // been submitted upstream.
   921  func TestParallelDev(t *testing.T) {
   922  	fake, repoPath, originPath, gerritAPath, cleanup := setupTest(t, true)
   923  	defer cleanup()
   924  	gerritBPath := createRepoFromOrigin(t, fake.X, "gerritB", originPath)
   925  	chdir(t, fake.X, repoPath)
   926  
   927  	// Create parallel branches with:
   928  	// * non-conflicting changes in different files
   929  	// * conflicting changes in a file
   930  	createCLWithFiles(t, fake.X, "feature1-A", "A")
   931  
   932  	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil {
   933  		t.Fatalf("%v", err)
   934  	}
   935  	createCLWithFiles(t, fake.X, "feature1-B", "B")
   936  	commitFile(t, fake.X, "A", "Don't tread on me.")
   937  
   938  	reviewB, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{Remote: gerritBPath})
   939  	if err != nil {
   940  		t.Fatalf("%v", err)
   941  	}
   942  	setTopicFlag = false
   943  	if err := reviewB.run(); err != nil {
   944  		t.Fatalf("run() failed: %v", err)
   945  	}
   946  
   947  	// Submit B and verify A doesn't revert it.
   948  	submit(t, fake.X, originPath, gerritBPath, reviewB)
   949  
   950  	// Assert files pushed to origin.
   951  	chdir(t, fake.X, originPath)
   952  	assertFilesExist(t, fake.X, []string{"A", "B"})
   953  	chdir(t, fake.X, repoPath)
   954  
   955  	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("feature1-A"); err != nil {
   956  		t.Fatalf("%v", err)
   957  	}
   958  
   959  	reviewA, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{Remote: gerritAPath})
   960  	if err == nil {
   961  		t.Fatalf("creating a review did not fail when it should")
   962  	}
   963  	// Assert state restored after failed review.
   964  	assertFileContent(t, fake.X, "A", "This is file A")
   965  	assertFilesDoNotExist(t, fake.X, []string{"B"})
   966  
   967  	// Manual conflict resolution.
   968  	if err := gitutil.New(fake.X.NewSeq()).Merge("master", gitutil.ResetOnFailureOpt(false)); err == nil {
   969  		t.Fatalf("merge applied cleanly when it shouldn't")
   970  	}
   971  	assertFilesNotCommitted(t, fake.X, []string{"A", "B"})
   972  	assertFileContent(t, fake.X, "B", "This is file B")
   973  
   974  	if err := fake.X.NewSeq().WriteFile("A", []byte("This is file A. Don't tread on me."), 0644).Done(); err != nil {
   975  		t.Fatalf("%v", err)
   976  	}
   977  
   978  	if err := gitutil.New(fake.X.NewSeq()).Add("A"); err != nil {
   979  		t.Fatalf("%v", err)
   980  	}
   981  	if err := gitutil.New(fake.X.NewSeq()).Add("B"); err != nil {
   982  		t.Fatalf("%v", err)
   983  	}
   984  	if err := gitutil.New(fake.X.NewSeq()).CommitWithMessage("Conflict resolution"); err != nil {
   985  		t.Fatalf("%v", err)
   986  	}
   987  
   988  	// Retry review.
   989  	reviewA, err = newReview(fake.X, project.Project{}, gerrit.CLOpts{Remote: gerritAPath})
   990  	if err != nil {
   991  		t.Fatalf("review failed: %v", err)
   992  	}
   993  
   994  	if err := reviewA.run(); err != nil {
   995  		t.Fatalf("run() failed: %v", err)
   996  	}
   997  
   998  	chdir(t, fake.X, gerritAPath)
   999  	expectedRef := gerrit.Reference(reviewA.CLOpts)
  1000  	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(expectedRef); err != nil {
  1001  		t.Fatalf("%v", err)
  1002  	}
  1003  	assertFilesExist(t, fake.X, []string{"B"})
  1004  }
  1005  
  1006  // TestCLSync checks the operation of the "jiri cl sync" command.
  1007  func TestCLSync(t *testing.T) {
  1008  	fake, _, _, _, cleanup := setupTest(t, true)
  1009  	defer cleanup()
  1010  
  1011  	// Create some dependent CLs.
  1012  	if err := newCL(fake.X, []string{"feature1"}); err != nil {
  1013  		t.Fatalf("%v", err)
  1014  	}
  1015  	if err := newCL(fake.X, []string{"feature2"}); err != nil {
  1016  		t.Fatalf("%v", err)
  1017  	}
  1018  
  1019  	// Add the "test" file to the master.
  1020  	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil {
  1021  		t.Fatalf("%v", err)
  1022  	}
  1023  	commitFiles(t, fake.X, []string{"test"})
  1024  
  1025  	// Sync the dependent CLs.
  1026  	if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("feature2"); err != nil {
  1027  		t.Fatalf("%v", err)
  1028  	}
  1029  	if err := syncCL(fake.X); err != nil {
  1030  		t.Fatalf("%v", err)
  1031  	}
  1032  
  1033  	// Check that the "test" file exists in the dependent CLs.
  1034  	for _, branch := range []string{"feature1", "feature2"} {
  1035  		if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(branch); err != nil {
  1036  			t.Fatalf("%v", err)
  1037  		}
  1038  		assertFilesExist(t, fake.X, []string{"test"})
  1039  	}
  1040  }
  1041  
  1042  func TestMultiPart(t *testing.T) {
  1043  	fake, cleanup := jiritest.NewFakeJiriRoot(t)
  1044  	defer cleanup()
  1045  	projects := addProjects(t, fake)
  1046  
  1047  	origCleanupFlag, origCurrentProjectFlag := cleanupMultiPartFlag, currentProjectFlag
  1048  	defer func() {
  1049  		cleanupMultiPartFlag, currentProjectFlag = origCleanupFlag, origCurrentProjectFlag
  1050  	}()
  1051  	cleanupMultiPartFlag, currentProjectFlag = false, false
  1052  
  1053  	name, err := gitutil.New(fake.X.NewSeq()).CurrentBranchName()
  1054  	if err != nil {
  1055  		t.Fatal(err)
  1056  	}
  1057  	if name == "master" {
  1058  		// The test cases below assume that they are run on a feature-branch,
  1059  		// but this is not necessarily always the case when running under
  1060  		// jenkins, so if it's run on a master branch it will create
  1061  		// a feature branch.
  1062  		if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch("feature-branch"); err != nil {
  1063  			t.Fatal(err)
  1064  		}
  1065  		defer func() {
  1066  			git := gitutil.New(fake.X.NewSeq())
  1067  			git.CheckoutBranch("master", gitutil.ForceOpt(true))
  1068  			git.DeleteBranch("feature-branch", gitutil.ForceOpt(true))
  1069  		}()
  1070  	}
  1071  
  1072  	cwd, err := os.Getwd()
  1073  	if err != nil {
  1074  		t.Fatal(err)
  1075  	}
  1076  	defer os.Chdir(cwd)
  1077  
  1078  	relchdir := func(dir string) {
  1079  		chdir(t, fake.X, dir)
  1080  	}
  1081  
  1082  	initMP := func() *multiPart {
  1083  		mp, err := initForMultiPart(fake.X)
  1084  		if err != nil {
  1085  			_, file, line, _ := runtime.Caller(1)
  1086  			t.Fatalf("%s:%d: %v", filepath.Base(file), line, err)
  1087  		}
  1088  		return mp
  1089  	}
  1090  
  1091  	wr := func(mp *multiPart) *multiPart {
  1092  		return mp
  1093  	}
  1094  
  1095  	git := func(dir string) *gitutil.Git {
  1096  		return gitutil.New(fake.X.NewSeq(), gitutil.RootDirOpt(dir))
  1097  	}
  1098  
  1099  	cleanupMultiPartFlag = true
  1100  	if got, want := initMP(), wr(&multiPart{clean: true}); !reflect.DeepEqual(got, want) {
  1101  		t.Errorf("got %#v, want %#v", got, want)
  1102  	}
  1103  
  1104  	currentProjectFlag = true
  1105  	if got, want := initMP(), wr(&multiPart{clean: true, current: true}); !reflect.DeepEqual(got, want) {
  1106  		t.Errorf("got %#v, want %#v", got, want)
  1107  	}
  1108  	cleanupMultiPartFlag, currentProjectFlag = false, false
  1109  
  1110  	// Test metadata generation.
  1111  	ra := projects[0].Path
  1112  	rb := projects[1].Path
  1113  	rc := projects[2].Path
  1114  	t1 := projects[3].Path
  1115  	git(ra).CreateAndCheckoutBranch("a1")
  1116  	relchdir(ra)
  1117  
  1118  	if got, want := initMP(), wr(&multiPart{current: true, currentKey: projects[0].Key(), currentBranch: "a1"}); !reflect.DeepEqual(got, want) {
  1119  		t.Errorf("got %#v, want %#v", got, want)
  1120  	}
  1121  
  1122  	git(rb).CreateAndCheckoutBranch("a1")
  1123  	mp := initMP()
  1124  	if mp.current != false || mp.clean != false {
  1125  		t.Errorf("current or clean not false: %v, %v", mp.current, mp.clean)
  1126  	}
  1127  	if got, want := len(mp.keys), 2; got != want {
  1128  		t.Errorf("got %v, want %v", got, want)
  1129  	}
  1130  	tmp := &multiPart{
  1131  		keys: project.ProjectKeys{projects[0].Key(), projects[1].Key()},
  1132  	}
  1133  	for i, k := range mp.keys {
  1134  		if got, want := k, tmp.keys[i]; got != want {
  1135  			t.Errorf("got %v, want %v", got, want)
  1136  		}
  1137  	}
  1138  	if got, want := len(mp.states), 2; got != want {
  1139  		t.Errorf("got %v, want %v", got, want)
  1140  	}
  1141  
  1142  	git(rc).CreateAndCheckoutBranch("a1")
  1143  	git(t1).CreateAndCheckoutBranch("a2")
  1144  	mp = initMP()
  1145  	if got, want := len(mp.keys), 3; got != want {
  1146  		t.Errorf("got %v, want %v", got, want)
  1147  	}
  1148  	if err := mp.writeMultiPartMetadata(fake.X); err != nil {
  1149  		t.Fatal(err)
  1150  	}
  1151  
  1152  	hasMetaData := func(total int, branch string, projectPaths ...string) {
  1153  		_, file, line, _ := runtime.Caller(1)
  1154  		loc := fmt.Sprintf("%s:%d", filepath.Base(file), line)
  1155  		for i, dir := range projectPaths {
  1156  			filename := filepath.Join(dir, jiri.ProjectMetaDir, branch, multiPartMetaDataFileName)
  1157  			msg, err := ioutil.ReadFile(filename)
  1158  			if err != nil {
  1159  				t.Fatalf("%s: %v", loc, err)
  1160  			}
  1161  			if got, want := string(msg), fmt.Sprintf("MultiPart: %d/%d\n", i+1, total); got != want {
  1162  				t.Errorf("%v: got %v, want %v", dir, got, want)
  1163  			}
  1164  		}
  1165  	}
  1166  
  1167  	hasNoMetaData := func(branch string, projectPaths ...string) {
  1168  		_, file, line, _ := runtime.Caller(1)
  1169  		loc := fmt.Sprintf("%s:%d", filepath.Base(file), line)
  1170  		for _, dir := range projectPaths {
  1171  			filename := filepath.Join(fake.X.Root, dir, jiri.ProjectMetaDir, branch, multiPartMetaDataFileName)
  1172  			_, err := os.Stat(filename)
  1173  			if !os.IsNotExist(err) {
  1174  				t.Fatalf("%s: %s should not exist", loc, filename)
  1175  			}
  1176  		}
  1177  	}
  1178  
  1179  	newFile := func(dir, file string) {
  1180  		testfile := filepath.Join(dir, file)
  1181  		_, err := fake.X.NewSeq().Create(testfile)
  1182  		if err != nil {
  1183  			t.Errorf("failed to create %s: %v", testfile, err)
  1184  		}
  1185  	}
  1186  
  1187  	hasMetaData(len(mp.keys), "a1", ra, rb, rc)
  1188  	hasNoMetaData(t1, "a2")
  1189  	if err := mp.cleanMultiPartMetadata(fake.X); err != nil {
  1190  		t.Fatal(err)
  1191  	}
  1192  	hasNoMetaData(ra, "a1", rb, rc, t1)
  1193  
  1194  	// Test CL messages.
  1195  
  1196  	for _, p := range projects {
  1197  		// Install commit hook so that Change-Id is written.
  1198  		installCommitMsgHook(t, fake.X, p.Path)
  1199  
  1200  	}
  1201  
  1202  	// Create a fake jiri root for the fake gerrit repos.
  1203  	gerritFake, gerritCleanup := jiritest.NewFakeJiriRoot(t)
  1204  	defer gerritCleanup()
  1205  
  1206  	relchdir(ra)
  1207  
  1208  	if err := mp.writeMultiPartMetadata(fake.X); err != nil {
  1209  		t.Fatal(err)
  1210  	}
  1211  	hasMetaData(len(mp.keys), "a1", ra, rb, rc)
  1212  
  1213  	gitAddFiles := func(name string, repos ...string) {
  1214  		for _, dir := range repos {
  1215  			newFile(dir, name)
  1216  			if err := git(dir).Add(name); err != nil {
  1217  				t.Error(err)
  1218  			}
  1219  		}
  1220  	}
  1221  
  1222  	gitCommit := func(msg string, repos ...string) {
  1223  		for _, dir := range repos {
  1224  			committer := git(dir).NewCommitter(false)
  1225  			if err := committer.Commit(msg); err != nil {
  1226  				t.Error(err)
  1227  			}
  1228  		}
  1229  	}
  1230  
  1231  	gitAddFiles("new-file", ra, rb, rc)
  1232  	_, err = initForMultiPart(fake.X)
  1233  	if err == nil || !strings.Contains(err.Error(), "uncommitted changes:") {
  1234  		t.Fatalf("expected an error about uncommitted changes: got %v", err)
  1235  	}
  1236  
  1237  	gitCommit("oh multipart test\n", ra, rb, rc)
  1238  	bodyMessage := "xyz\n\na simple message\n"
  1239  	messageFile := filepath.Join(fake.X.Root, jiri.RootMetaDir, "message-body")
  1240  	if err := ioutil.WriteFile(messageFile, []byte(bodyMessage), 0666); err != nil {
  1241  		t.Fatal(err)
  1242  	}
  1243  
  1244  	mp = initMP()
  1245  	setTopicFlag = false
  1246  	commitMessageBodyFlag = messageFile
  1247  
  1248  	testCommitMsgs := func(branch string, cls ...*project.Project) {
  1249  		_, file, line, _ := runtime.Caller(1)
  1250  		loc := fmt.Sprintf("%s:%d", filepath.Base(file), line)
  1251  
  1252  		total := len(cls)
  1253  		for index, p := range cls {
  1254  			// Create a new gerrit repo each time we commit, since we can't
  1255  			// push more than once to the fake gerrit repo without actually
  1256  			// running gerrit.
  1257  			gp := createRepoFromOrigin(t, gerritFake.X, "gerrit", p.Remote)
  1258  			defer os.Remove(gp)
  1259  			relchdir(p.Path)
  1260  			review, err := newReview(fake.X, *p, gerrit.CLOpts{
  1261  				Presubmit: gerrit.PresubmitTestTypeNone,
  1262  				Remote:    gp,
  1263  			})
  1264  			if err != nil {
  1265  				t.Fatalf("%v: %v: %v", loc, p.Path, err)
  1266  			}
  1267  			// use the default commit message
  1268  			if err := review.run(); err != nil {
  1269  				t.Fatalf("%v: %v, %v", loc, p.Path, err)
  1270  			}
  1271  			filename, err := getCommitMessageFileName(fake.X, branch)
  1272  			if err != nil {
  1273  				t.Fatalf("%v: %v", loc, err)
  1274  			}
  1275  			msg, err := ioutil.ReadFile(filename)
  1276  			if err != nil {
  1277  				t.Fatalf("%v: %v", loc, err)
  1278  			}
  1279  			if total < 2 {
  1280  				if strings.Contains(string(msg), "MultiPart") {
  1281  					t.Errorf("%v: commit message contains MultiPart when it should not: %v", loc, string(msg))
  1282  				}
  1283  				continue
  1284  			}
  1285  			expected := fmt.Sprintf("\nMultiPart: %d/%d\n", index+1, total)
  1286  			if !strings.Contains(string(msg), expected) {
  1287  				t.Errorf("%v: commit message for %v does not contain %v: %v", loc, p.Path, expected, string(msg))
  1288  			}
  1289  			if got, want := string(msg), bodyMessage+"PresubmitTest: none"+expected+"Change-Id: I0000000000000000000000000000000000000000"; got != want {
  1290  				t.Errorf("got %v, want %v", got, want)
  1291  			}
  1292  		}
  1293  	}
  1294  
  1295  	testCommitMsgs("a1", projects[0], projects[1], projects[2])
  1296  
  1297  	cl := mp.commandline("", []string{"-r=alice"})
  1298  	expected := []string{
  1299  		"runp",
  1300  		"--interactive",
  1301  		"--projects=" + string(projects[0].Key()) + "," + string(projects[1].Key()) + "," + string(projects[2].Key()),
  1302  		"jiri",
  1303  		"cl",
  1304  		"mail",
  1305  		"--current-project-only=true",
  1306  		"-r=alice",
  1307  	}
  1308  	if got, want := strings.Join(cl, " "), strings.Join(expected, " "); got != want {
  1309  		t.Errorf("got %v, want %v", got, want)
  1310  	}
  1311  	cl = mp.commandline(projects[0].Key(), []string{"-r=bob"})
  1312  	expected[2] = "--projects=" + string(projects[1].Key()) + "," + string(projects[2].Key())
  1313  	expected[len(expected)-1] = "-r=bob"
  1314  	if got, want := strings.Join(cl, " "), strings.Join(expected, " "); got != want {
  1315  		t.Errorf("got %v, want %v", got, want)
  1316  	}
  1317  
  1318  	git(rb).CreateAndCheckoutBranch("a2")
  1319  	gitAddFiles("new-file1", ra, rc)
  1320  	gitCommit("oh multipart test: 2\n", ra, rc)
  1321  
  1322  	mp = initMP()
  1323  	if err := mp.writeMultiPartMetadata(fake.X); err != nil {
  1324  		t.Fatal(err)
  1325  	}
  1326  	hasMetaData(len(mp.keys), "a1", ra, rc)
  1327  	testCommitMsgs("a1", projects[0], projects[2])
  1328  
  1329  	git(ra).CreateAndCheckoutBranch("a2")
  1330  
  1331  	mp = initMP()
  1332  	if err := mp.writeMultiPartMetadata(fake.X); err != nil {
  1333  		t.Fatal(err)
  1334  	}
  1335  	hasNoMetaData(rc)
  1336  	testCommitMsgs("a1", projects[2])
  1337  }