| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "os" |
| "path" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "testing" |
| |
| "v.io/jiri/gerrit" |
| "v.io/jiri/gitutil" |
| "v.io/jiri/jiri" |
| "v.io/jiri/jiritest" |
| "v.io/jiri/runutil" |
| ) |
| |
| // assertCommitCount asserts that the commit count between two |
| // branches matches the expectedCount. |
| func assertCommitCount(t *testing.T, jirix *jiri.X, branch, baseBranch string, expectedCount int) { |
| got, err := gitutil.New(jirix.NewSeq()).CountCommits(branch, baseBranch) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if want := 1; got != want { |
| t.Fatalf("unexpected number of commits: got %v, want %v", got, want) |
| } |
| } |
| |
| // assertFileContent asserts that the content of the given file |
| // matches the expected content. |
| func assertFileContent(t *testing.T, jirix *jiri.X, file, want string) { |
| got, err := jirix.NewSeq().ReadFile(file) |
| if err != nil { |
| t.Fatalf("%v\n", err) |
| } |
| if string(got) != want { |
| t.Fatalf("unexpected content of file %v: got %v, want %v", file, got, want) |
| } |
| } |
| |
| // assertFilesExist asserts that the files exist. |
| func assertFilesExist(t *testing.T, jirix *jiri.X, files []string) { |
| s := jirix.NewSeq() |
| for _, file := range files { |
| if _, err := s.Stat(file); err != nil { |
| if runutil.IsNotExist(err) { |
| t.Fatalf("expected file %v to exist but it did not", file) |
| } |
| t.Fatalf("%v", err) |
| } |
| } |
| } |
| |
| // assertFilesDoNotExist asserts that the files do not exist. |
| func assertFilesDoNotExist(t *testing.T, jirix *jiri.X, files []string) { |
| s := jirix.NewSeq() |
| for _, file := range files { |
| if _, err := s.Stat(file); err != nil && !runutil.IsNotExist(err) { |
| t.Fatalf("%v", err) |
| } else if err == nil { |
| t.Fatalf("expected file %v to not exist but it did", file) |
| } |
| } |
| } |
| |
| // assertFilesCommitted asserts that the files exist and are committed |
| // in the current branch. |
| func assertFilesCommitted(t *testing.T, jirix *jiri.X, files []string) { |
| assertFilesExist(t, jirix, files) |
| for _, file := range files { |
| if !gitutil.New(jirix.NewSeq()).IsFileCommitted(file) { |
| t.Fatalf("expected file %v to be committed but it is not", file) |
| } |
| } |
| } |
| |
| // assertFilesNotCommitted asserts that the files exist and are *not* |
| // committed in the current branch. |
| func assertFilesNotCommitted(t *testing.T, jirix *jiri.X, files []string) { |
| assertFilesExist(t, jirix, files) |
| for _, file := range files { |
| if gitutil.New(jirix.NewSeq()).IsFileCommitted(file) { |
| t.Fatalf("expected file %v not to be committed but it is", file) |
| } |
| } |
| } |
| |
| // assertFilesPushedToRef asserts that the given files have been |
| // pushed to the given remote repository reference. |
| func assertFilesPushedToRef(t *testing.T, jirix *jiri.X, repoPath, gerritPath, pushedRef string, files []string) { |
| chdir(t, jirix, gerritPath) |
| assertCommitCount(t, jirix, pushedRef, "master", 1) |
| if err := gitutil.New(jirix.NewSeq()).CheckoutBranch(pushedRef); err != nil { |
| t.Fatalf("%v", err) |
| } |
| assertFilesCommitted(t, jirix, files) |
| chdir(t, jirix, repoPath) |
| } |
| |
| // assertStashSize asserts that the stash size matches the expected |
| // size. |
| func assertStashSize(t *testing.T, jirix *jiri.X, want int) { |
| got, err := gitutil.New(jirix.NewSeq()).StashSize() |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if got != want { |
| t.Fatalf("unxpected stash size: got %v, want %v", got, want) |
| } |
| } |
| |
| // commitFile commits a file with the specified content into a branch |
| func commitFile(t *testing.T, jirix *jiri.X, filename string, content string) { |
| s := jirix.NewSeq() |
| if err := s.WriteFile(filename, []byte(content), 0644).Done(); err != nil { |
| t.Fatalf("%v", err) |
| } |
| commitMessage := "Commit " + filename |
| if err := gitutil.New(jirix.NewSeq()).CommitFile(filename, commitMessage); err != nil { |
| t.Fatalf("%v", err) |
| } |
| } |
| |
| // commitFiles commits the given files into to current branch. |
| func commitFiles(t *testing.T, jirix *jiri.X, filenames []string) { |
| // Create and commit the files one at a time. |
| for _, filename := range filenames { |
| content := "This is file " + filename |
| commitFile(t, jirix, filename, content) |
| } |
| } |
| |
| // createRepo creates a new repository with the given prefix. |
| func createRepo(t *testing.T, jirix *jiri.X, prefix string) string { |
| s := jirix.NewSeq() |
| repoPath, err := s.TempDir(jirix.Root, "repo-"+prefix) |
| if err != nil { |
| t.Fatalf("TempDir() failed: %v", err) |
| } |
| if err := os.Chmod(repoPath, 0777); err != nil { |
| t.Fatalf("Chmod(%v) failed: %v", repoPath, err) |
| } |
| if err := gitutil.New(jirix.NewSeq()).Init(repoPath); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := s.MkdirAll(filepath.Join(repoPath, jiri.ProjectMetaDir), os.FileMode(0755)).Done(); err != nil { |
| t.Fatalf("%v", err) |
| } |
| return repoPath |
| } |
| |
| // Simple commit-msg hook that adds a fake Change Id. |
| var commitMsgHook string = `#!/bin/sh |
| MSG="$1" |
| echo "Change-Id: I0000000000000000000000000000000000000000" >> $MSG |
| ` |
| |
| // installCommitMsgHook links the gerrit commit-msg hook into a different repo. |
| func installCommitMsgHook(t *testing.T, jirix *jiri.X, repoPath string) { |
| hookLocation := path.Join(repoPath, ".git/hooks/commit-msg") |
| if err := jirix.NewSeq().WriteFile(hookLocation, []byte(commitMsgHook), 0755).Done(); err != nil { |
| t.Fatalf("WriteFile(%v) failed: %v", hookLocation, err) |
| } |
| } |
| |
| // chdir changes the runtime working directory and traps any errors. |
| func chdir(t *testing.T, jirix *jiri.X, path string) { |
| if err := jirix.NewSeq().Chdir(path).Done(); err != nil { |
| _, file, line, _ := runtime.Caller(1) |
| t.Fatalf("%s: %d: Chdir(%v) failed: %v", file, line, path, err) |
| } |
| } |
| |
| // createRepoFromOrigin creates a Git repo tracking origin/master. |
| func createRepoFromOrigin(t *testing.T, jirix *jiri.X, subpath string, originPath string) string { |
| repoPath := createRepo(t, jirix, subpath) |
| chdir(t, jirix, repoPath) |
| if err := gitutil.New(jirix.NewSeq()).AddRemote("origin", originPath); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := gitutil.New(jirix.NewSeq()).Pull("origin", "master"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| return repoPath |
| } |
| |
| // createTestRepos sets up three local repositories: origin, gerrit, |
| // and the main test repository which pulls from origin and can push |
| // to gerrit. |
| func createTestRepos(t *testing.T, jirix *jiri.X) (string, string, string) { |
| // Create origin. |
| originPath := createRepo(t, jirix, "origin") |
| chdir(t, jirix, originPath) |
| if err := gitutil.New(jirix.NewSeq()).CommitWithMessage("initial commit"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| // Create test repo. |
| repoPath := createRepoFromOrigin(t, jirix, "test", originPath) |
| // Add Gerrit remote. |
| gerritPath := createRepoFromOrigin(t, jirix, "gerrit", originPath) |
| // Switch back to test repo. |
| chdir(t, jirix, repoPath) |
| return repoPath, originPath, gerritPath |
| } |
| |
| // submit mocks a Gerrit review submit by pushing the Gerrit remote to origin. |
| // Actually origin pulls from Gerrit since origin isn't actually a bare git repo. |
| // Some of our tests actually rely on accessing .git in origin, so it must be non-bare. |
| func submit(t *testing.T, jirix *jiri.X, originPath string, gerritPath string, review *review) { |
| cwd, err := os.Getwd() |
| if err != nil { |
| t.Fatalf("Getwd() failed: %v", err) |
| } |
| chdir(t, jirix, originPath) |
| expectedRef := gerrit.Reference(review.CLOpts) |
| if err := gitutil.New(jirix.NewSeq()).Pull(gerritPath, expectedRef); err != nil { |
| t.Fatalf("Pull gerrit to origin failed: %v", err) |
| } |
| chdir(t, jirix, cwd) |
| } |
| |
| // setupTest creates a setup for testing the review tool. |
| func setupTest(t *testing.T, installHook bool) (fake *jiritest.FakeJiriRoot, repoPath, originPath, gerritPath string, cleanup func()) { |
| oldWD, err := os.Getwd() |
| if err != nil { |
| t.Fatalf("Getwd() failed: %v", err) |
| } |
| var cleanupFake func() |
| if fake, cleanupFake = jiritest.NewFakeJiriRoot(t); err != nil { |
| t.Fatalf("%v", err) |
| } |
| repoPath, originPath, gerritPath = createTestRepos(t, fake.X) |
| if installHook == true { |
| for _, path := range []string{repoPath, originPath, gerritPath} { |
| installCommitMsgHook(t, fake.X, path) |
| } |
| } |
| chdir(t, fake.X, repoPath) |
| cleanup = func() { |
| chdir(t, fake.X, oldWD) |
| cleanupFake() |
| } |
| return |
| } |
| |
| func createCLWithFiles(t *testing.T, jirix *jiri.X, branch string, files ...string) { |
| if err := newCL(jirix, []string{branch}); err != nil { |
| t.Fatalf("%v", err) |
| } |
| commitFiles(t, jirix, files) |
| } |
| |
| // TestCleanupClean checks that cleanup succeeds if the branch to be |
| // cleaned up has been merged with the master. |
| func TestCleanupClean(t *testing.T) { |
| fake, repoPath, originPath, _, cleanup := setupTest(t, true) |
| defer cleanup() |
| branch := "my-branch" |
| if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| commitFiles(t, fake.X, []string{"file1", "file2"}) |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| chdir(t, fake.X, originPath) |
| commitFiles(t, fake.X, []string{"file1", "file2"}) |
| chdir(t, fake.X, repoPath) |
| if err := cleanupCL(fake.X, []string{branch}); err != nil { |
| t.Fatalf("cleanup() failed: %v", err) |
| } |
| if gitutil.New(fake.X.NewSeq()).BranchExists(branch) { |
| t.Fatalf("cleanup failed to remove the feature branch") |
| } |
| } |
| |
| // TestCleanupDirty checks that cleanup is a no-op if the branch to be |
| // cleaned up has unmerged changes. |
| func TestCleanupDirty(t *testing.T) { |
| fake, _, _, _, cleanup := setupTest(t, true) |
| defer cleanup() |
| branch := "my-branch" |
| if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| files := []string{"file1", "file2"} |
| commitFiles(t, fake.X, files) |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := cleanupCL(fake.X, []string{branch}); err == nil { |
| t.Fatalf("cleanup did not fail when it should") |
| } |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| assertFilesCommitted(t, fake.X, files) |
| } |
| |
| // TestCreateReviewBranch checks that the temporary review branch is |
| // created correctly. |
| func TestCreateReviewBranch(t *testing.T) { |
| fake, _, _, _, cleanup := setupTest(t, true) |
| defer cleanup() |
| branch := "my-branch" |
| if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| files := []string{"file1", "file2", "file3"} |
| commitFiles(t, fake.X, files) |
| review, err := newReview(fake.X, gerrit.CLOpts{}) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if expected, got := branch+"-REVIEW", review.reviewBranch; expected != got { |
| t.Fatalf("Unexpected review branch name: expected %v, got %v", expected, got) |
| } |
| commitMessage := "squashed commit" |
| if err := review.createReviewBranch(commitMessage); err != nil { |
| t.Fatalf("%v", err) |
| } |
| // Verify that the branch exists. |
| if !gitutil.New(fake.X.NewSeq()).BranchExists(review.reviewBranch) { |
| t.Fatalf("review branch not found") |
| } |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(review.reviewBranch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| assertCommitCount(t, fake.X, review.reviewBranch, "master", 1) |
| assertFilesCommitted(t, fake.X, files) |
| } |
| |
| // TestCreateReviewBranchWithEmptyChange checks that running |
| // createReviewBranch() on a branch with no changes will result in an |
| // EmptyChangeError. |
| func TestCreateReviewBranchWithEmptyChange(t *testing.T) { |
| fake, _, _, _, cleanup := setupTest(t, true) |
| defer cleanup() |
| branch := "my-branch" |
| if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| review, err := newReview(fake.X, gerrit.CLOpts{Remote: branch}) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| commitMessage := "squashed commit" |
| err = review.createReviewBranch(commitMessage) |
| if err == nil { |
| t.Fatalf("creating a review did not fail when it should") |
| } |
| if _, ok := err.(emptyChangeError); !ok { |
| t.Fatalf("unexpected error type: %v", err) |
| } |
| } |
| |
| // TestSendReview checks the various options for sending a review. |
| func TestSendReview(t *testing.T) { |
| fake, repoPath, _, gerritPath, cleanup := setupTest(t, true) |
| defer cleanup() |
| branch := "my-branch" |
| if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| files := []string{"file1"} |
| commitFiles(t, fake.X, files) |
| { |
| // Test with draft = false, no reviewiers, and no ccs. |
| review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath}) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := review.send(); err != nil { |
| t.Fatalf("failed to send a review: %v", err) |
| } |
| expectedRef := gerrit.Reference(review.CLOpts) |
| assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files) |
| } |
| { |
| // Test with draft = true, no reviewers, and no ccs. |
| review, err := newReview(fake.X, gerrit.CLOpts{ |
| Draft: true, |
| Remote: gerritPath, |
| }) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := review.send(); err != nil { |
| t.Fatalf("failed to send a review: %v", err) |
| } |
| expectedRef := gerrit.Reference(review.CLOpts) |
| assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files) |
| } |
| { |
| // Test with draft = false, reviewers, and no ccs. |
| review, err := newReview(fake.X, gerrit.CLOpts{ |
| Remote: gerritPath, |
| Reviewers: parseEmails("reviewer1,reviewer2@example.org"), |
| }) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := review.send(); err != nil { |
| t.Fatalf("failed to send a review: %v", err) |
| } |
| expectedRef := gerrit.Reference(review.CLOpts) |
| assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files) |
| } |
| { |
| // Test with draft = true, reviewers, and ccs. |
| review, err := newReview(fake.X, gerrit.CLOpts{ |
| Ccs: parseEmails("cc1@example.org,cc2"), |
| Draft: true, |
| Remote: gerritPath, |
| Reviewers: parseEmails("reviewer3@example.org,reviewer4"), |
| }) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := review.send(); err != nil { |
| t.Fatalf("failed to send a review: %v", err) |
| } |
| expectedRef := gerrit.Reference(review.CLOpts) |
| assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files) |
| } |
| } |
| |
| // TestSendReviewNoChangeID checks that review.send() correctly errors when |
| // not run with a commit hook that adds a Change-Id. |
| func TestSendReviewNoChangeID(t *testing.T) { |
| // Pass 'false' to setup so it doesn't install the commit-msg hook. |
| fake, _, _, gerritPath, cleanup := setupTest(t, false) |
| defer cleanup() |
| branch := "my-branch" |
| if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| commitFiles(t, fake.X, []string{"file1"}) |
| review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath}) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| err = review.send() |
| if err == nil { |
| t.Fatalf("sending a review did not fail when it should") |
| } |
| if _, ok := err.(noChangeIDError); !ok { |
| t.Fatalf("unexpected error type: %v", err) |
| } |
| } |
| |
| // TestEndToEnd checks the end-to-end functionality of the review tool. |
| func TestEndToEnd(t *testing.T) { |
| fake, repoPath, _, gerritPath, cleanup := setupTest(t, true) |
| defer cleanup() |
| branch := "my-branch" |
| if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| files := []string{"file1", "file2", "file3"} |
| commitFiles(t, fake.X, files) |
| review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath}) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| setTopicFlag = false |
| if err := review.run(); err != nil { |
| t.Fatalf("run() failed: %v", err) |
| } |
| expectedRef := gerrit.Reference(review.CLOpts) |
| assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files) |
| } |
| |
| // TestLabelsInCommitMessage checks the labels are correctly processed |
| // for the commit message. |
| // |
| // HACK ALERT: This test runs the review.run() function multiple |
| // times. The function ends up pushing a commit to a fake "gerrit" |
| // repository created by the setupTest() function. For the real gerrit |
| // repository, it is possible to push to the refs/for/change reference |
| // multiple times, because it is a special reference that "maps" |
| // incoming commits to CL branches based on the commit message |
| // Change-Id. The fake "gerrit" repository does not implement this |
| // logic and thus the same reference cannot be pushed to multiple |
| // times. To overcome this obstacle, the test takes advantage of the |
| // fact that the reference name is a function of the reviewers and |
| // uses different reviewers for different review runs. |
| func TestLabelsInCommitMessage(t *testing.T) { |
| fake, repoPath, _, gerritPath, cleanup := setupTest(t, true) |
| defer cleanup() |
| s := fake.X.NewSeq() |
| branch := "my-branch" |
| if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| |
| // Test setting -presubmit=none and autosubmit. |
| files := []string{"file1", "file2", "file3"} |
| commitFiles(t, fake.X, files) |
| review, err := newReview(fake.X, gerrit.CLOpts{ |
| Autosubmit: true, |
| Presubmit: gerrit.PresubmitTestTypeNone, |
| Remote: gerritPath, |
| Reviewers: parseEmails("run1"), |
| }) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| setTopicFlag = false |
| if err := review.run(); err != nil { |
| t.Fatalf("%v", err) |
| } |
| expectedRef := gerrit.Reference(review.CLOpts) |
| assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files) |
| // The last three lines of the gerrit commit message file should be: |
| // AutoSubmit |
| // PresubmitTest: none |
| // Change-Id: ... |
| file, err := getCommitMessageFileName(review.jirix, review.CLOpts.Branch) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| bytes, err := s.ReadFile(file) |
| if err != nil { |
| t.Fatalf("%v\n", err) |
| } |
| content := string(bytes) |
| lines := strings.Split(content, "\n") |
| // Make sure the Change-Id line is the last line. |
| if got := lines[len(lines)-1]; !strings.HasPrefix(got, "Change-Id") { |
| t.Fatalf("no Change-Id line found: %s", got) |
| } |
| // Make sure the "AutoSubmit" label exists. |
| if autosubmitLabelRE.FindString(content) == "" { |
| t.Fatalf("AutoSubmit label doesn't exist in the commit message: %s", content) |
| } |
| // Make sure the "PresubmitTest" label exists. |
| if presubmitTestLabelRE.FindString(content) == "" { |
| t.Fatalf("PresubmitTest label doesn't exist in the commit message: %s", content) |
| } |
| |
| // Test setting -presubmit=all but keep autosubmit=true. |
| review, err = newReview(fake.X, gerrit.CLOpts{ |
| Autosubmit: true, |
| Remote: gerritPath, |
| Reviewers: parseEmails("run2"), |
| }) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := review.run(); err != nil { |
| t.Fatalf("%v", err) |
| } |
| expectedRef = gerrit.Reference(review.CLOpts) |
| assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files) |
| bytes, err = s.ReadFile(file) |
| if err != nil { |
| t.Fatalf("%v\n", err) |
| } |
| content = string(bytes) |
| // Make sure there is no PresubmitTest=none any more. |
| match := presubmitTestLabelRE.FindString(content) |
| if match != "" { |
| t.Fatalf("want no presubmit label line, got: %s", match) |
| } |
| // Make sure the "AutoSubmit" label still exists. |
| if autosubmitLabelRE.FindString(content) == "" { |
| t.Fatalf("AutoSubmit label doesn't exist in the commit message: %s", content) |
| } |
| |
| // Test setting autosubmit=false. |
| review, err = newReview(fake.X, gerrit.CLOpts{ |
| Remote: gerritPath, |
| Reviewers: parseEmails("run3"), |
| }) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := review.run(); err != nil { |
| t.Fatalf("%v", err) |
| } |
| expectedRef = gerrit.Reference(review.CLOpts) |
| assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files) |
| bytes, err = s.ReadFile(file) |
| if err != nil { |
| t.Fatalf("%v\n", err) |
| } |
| content = string(bytes) |
| // Make sure there is no AutoSubmit label any more. |
| match = autosubmitLabelRE.FindString(content) |
| if match != "" { |
| t.Fatalf("want no AutoSubmit label line, got: %s", match) |
| } |
| } |
| |
| // TestDirtyBranch checks that the tool correctly handles unstaged and |
| // untracked changes in a working branch with stashed changes. |
| func TestDirtyBranch(t *testing.T) { |
| fake, _, _, gerritPath, cleanup := setupTest(t, true) |
| defer cleanup() |
| s := fake.X.NewSeq() |
| branch := "my-branch" |
| if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| files := []string{"file1", "file2"} |
| commitFiles(t, fake.X, files) |
| assertStashSize(t, fake.X, 0) |
| stashedFile, stashedFileContent := "stashed-file", "stashed-file content" |
| if err := s.WriteFile(stashedFile, []byte(stashedFileContent), 0644).Done(); err != nil { |
| t.Fatalf("WriteFile(%v, %v) failed: %v", stashedFile, stashedFileContent, err) |
| } |
| if err := gitutil.New(fake.X.NewSeq()).Add(stashedFile); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if _, err := gitutil.New(fake.X.NewSeq()).Stash(); err != nil { |
| t.Fatalf("%v", err) |
| } |
| assertStashSize(t, fake.X, 1) |
| modifiedFile, modifiedFileContent := "file1", "modified-file content" |
| if err := s.WriteFile(modifiedFile, []byte(modifiedFileContent), 0644).Done(); err != nil { |
| t.Fatalf("WriteFile(%v, %v) failed: %v", modifiedFile, modifiedFileContent, err) |
| } |
| stagedFile, stagedFileContent := "file2", "staged-file content" |
| if err := s.WriteFile(stagedFile, []byte(stagedFileContent), 0644).Done(); err != nil { |
| t.Fatalf("WriteFile(%v, %v) failed: %v", stagedFile, stagedFileContent, err) |
| } |
| if err := gitutil.New(fake.X.NewSeq()).Add(stagedFile); err != nil { |
| t.Fatalf("%v", err) |
| } |
| untrackedFile, untrackedFileContent := "file3", "untracked-file content" |
| if err := s.WriteFile(untrackedFile, []byte(untrackedFileContent), 0644).Done(); err != nil { |
| t.Fatalf("WriteFile(%v, %v) failed: %v", untrackedFile, untrackedFileContent, err) |
| } |
| review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath}) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| setTopicFlag = false |
| if err := review.run(); err == nil { |
| t.Fatalf("run() didn't fail when it should") |
| } |
| assertFilesNotCommitted(t, fake.X, []string{stagedFile}) |
| assertFilesNotCommitted(t, fake.X, []string{untrackedFile}) |
| assertFileContent(t, fake.X, modifiedFile, modifiedFileContent) |
| assertFileContent(t, fake.X, stagedFile, stagedFileContent) |
| assertFileContent(t, fake.X, untrackedFile, untrackedFileContent) |
| // As of git 2.4.3 "git stash pop" fails if there are uncommitted |
| // changes in the index. So we need to commit them first. |
| if err := gitutil.New(fake.X.NewSeq()).Commit(); err != nil { |
| t.Fatalf("%v", err) |
| } |
| assertStashSize(t, fake.X, 1) |
| if err := gitutil.New(fake.X.NewSeq()).StashPop(); err != nil { |
| t.Fatalf("%v", err) |
| } |
| assertStashSize(t, fake.X, 0) |
| assertFilesNotCommitted(t, fake.X, []string{stashedFile}) |
| assertFileContent(t, fake.X, stashedFile, stashedFileContent) |
| } |
| |
| // TestRunInSubdirectory checks that the command will succeed when run from |
| // within a subdirectory of a branch that does not exist on master branch, and |
| // will return the user to the subdirectory after completion. |
| func TestRunInSubdirectory(t *testing.T) { |
| fake, repoPath, _, gerritPath, cleanup := setupTest(t, true) |
| defer cleanup() |
| s := fake.X.NewSeq() |
| branch := "my-branch" |
| if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| subdir := "sub/directory" |
| subdirPerms := os.FileMode(0744) |
| if err := s.MkdirAll(subdir, subdirPerms).Done(); err != nil { |
| t.Fatalf("MkdirAll(%v, %v) failed: %v", subdir, subdirPerms, err) |
| } |
| files := []string{path.Join(subdir, "file1")} |
| commitFiles(t, fake.X, files) |
| chdir(t, fake.X, subdir) |
| review, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritPath}) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| setTopicFlag = false |
| if err := review.run(); err != nil { |
| t.Fatalf("run() failed: %v", err) |
| } |
| path := path.Join(repoPath, subdir) |
| want, err := filepath.EvalSymlinks(path) |
| if err != nil { |
| t.Fatalf("EvalSymlinks(%v) failed: %v", path, err) |
| } |
| cwd, err := os.Getwd() |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| got, err := filepath.EvalSymlinks(cwd) |
| if err != nil { |
| t.Fatalf("EvalSymlinks(%v) failed: %v", cwd, err) |
| } |
| if got != want { |
| t.Fatalf("unexpected working directory: got %v, want %v", got, want) |
| } |
| expectedRef := gerrit.Reference(review.CLOpts) |
| assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files) |
| } |
| |
| // TestProcessLabels checks that the processLabels function works as expected. |
| func TestProcessLabels(t *testing.T) { |
| fake, _, _, _, cleanup := setupTest(t, true) |
| defer cleanup() |
| testCases := []struct { |
| autosubmit bool |
| presubmitType gerrit.PresubmitTestType |
| originalMessage string |
| expectedMessage string |
| }{ |
| { |
| presubmitType: gerrit.PresubmitTestTypeNone, |
| originalMessage: "", |
| expectedMessage: "PresubmitTest: none\n", |
| }, |
| { |
| autosubmit: true, |
| presubmitType: gerrit.PresubmitTestTypeNone, |
| originalMessage: "", |
| expectedMessage: "AutoSubmit\nPresubmitTest: none\n", |
| }, |
| { |
| presubmitType: gerrit.PresubmitTestTypeNone, |
| originalMessage: "review message\n", |
| expectedMessage: "review message\nPresubmitTest: none\n", |
| }, |
| { |
| autosubmit: true, |
| presubmitType: gerrit.PresubmitTestTypeNone, |
| originalMessage: "review message\n", |
| expectedMessage: "review message\nAutoSubmit\nPresubmitTest: none\n", |
| }, |
| { |
| presubmitType: gerrit.PresubmitTestTypeNone, |
| originalMessage: `review message |
| |
| Change-Id: I0000000000000000000000000000000000000000`, |
| expectedMessage: `review message |
| |
| PresubmitTest: none |
| Change-Id: I0000000000000000000000000000000000000000`, |
| }, |
| { |
| autosubmit: true, |
| presubmitType: gerrit.PresubmitTestTypeNone, |
| originalMessage: `review message |
| |
| Change-Id: I0000000000000000000000000000000000000000`, |
| expectedMessage: `review message |
| |
| AutoSubmit |
| PresubmitTest: none |
| Change-Id: I0000000000000000000000000000000000000000`, |
| }, |
| { |
| presubmitType: gerrit.PresubmitTestTypeAll, |
| originalMessage: "", |
| expectedMessage: "", |
| }, |
| { |
| presubmitType: gerrit.PresubmitTestTypeAll, |
| originalMessage: "review message\n", |
| expectedMessage: "review message\n", |
| }, |
| { |
| presubmitType: gerrit.PresubmitTestTypeAll, |
| originalMessage: `review message |
| |
| Change-Id: I0000000000000000000000000000000000000000`, |
| expectedMessage: `review message |
| |
| Change-Id: I0000000000000000000000000000000000000000`, |
| }, |
| } |
| for _, test := range testCases { |
| review, err := newReview(fake.X, gerrit.CLOpts{ |
| Autosubmit: test.autosubmit, |
| Presubmit: test.presubmitType, |
| }) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if got := review.processLabels(test.originalMessage); got != test.expectedMessage { |
| t.Fatalf("want %s, got %s", test.expectedMessage, got) |
| } |
| } |
| } |
| |
| // TestCLNew checks the operation of the "jiri cl new" command. |
| func TestCLNew(t *testing.T) { |
| fake, _, _, _, cleanup := setupTest(t, true) |
| defer cleanup() |
| |
| // Create some dependent CLs. |
| if err := newCL(fake.X, []string{"feature1"}); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := newCL(fake.X, []string{"feature2"}); err != nil { |
| t.Fatalf("%v", err) |
| } |
| |
| // Check that their dependency paths have been recorded correctly. |
| testCases := []struct { |
| branch string |
| data []byte |
| }{ |
| { |
| branch: "feature1", |
| data: []byte("master"), |
| }, |
| { |
| branch: "feature2", |
| data: []byte("master\nfeature1"), |
| }, |
| } |
| s := fake.X.NewSeq() |
| for _, testCase := range testCases { |
| file, err := getDependencyPathFileName(fake.X, testCase.branch) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| data, err := s.ReadFile(file) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if bytes.Compare(data, testCase.data) != 0 { |
| t.Fatalf("unexpected data:\ngot\n%v\nwant\n%v", string(data), string(testCase.data)) |
| } |
| } |
| } |
| |
| // TestDependentClsWithEditDelete exercises a previously observed failure case |
| // where if a CL edits a file and a dependent CL deletes it, jiri cl mail after |
| // the deletion failed with unrecoverable merge errors. |
| func TestDependentClsWithEditDelete(t *testing.T) { |
| fake, repoPath, originPath, gerritPath, cleanup := setupTest(t, true) |
| defer cleanup() |
| chdir(t, fake.X, originPath) |
| commitFiles(t, fake.X, []string{"A", "B"}) |
| |
| chdir(t, fake.X, repoPath) |
| if err := syncCL(fake.X); err != nil { |
| t.Fatalf("%v", err) |
| } |
| assertFilesExist(t, fake.X, []string{"A", "B"}) |
| |
| createCLWithFiles(t, fake.X, "editme", "C") |
| if err := fake.X.NewSeq().WriteFile("B", []byte("Will I dream?"), 0644).Done(); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := gitutil.New(fake.X.NewSeq()).Add("B"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := gitutil.New(fake.X.NewSeq()).CommitWithMessage("editing stuff"); err != nil { |
| t.Fatalf("git commit failed: %v", err) |
| } |
| review, err := newReview(fake.X, gerrit.CLOpts{ |
| Remote: gerritPath, |
| Reviewers: parseEmails("run1"), // See hack note about TestLabelsInCommitMessage |
| }) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| setTopicFlag = false |
| if err := review.run(); err != nil { |
| t.Fatalf("run() failed: %v", err) |
| } |
| |
| if err := newCL(fake.X, []string{"deleteme"}); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := gitutil.New(fake.X.NewSeq()).Remove("B", "C"); err != nil { |
| t.Fatalf("git rm B C failed: %v", err) |
| } |
| if err := gitutil.New(fake.X.NewSeq()).CommitWithMessage("deleting stuff"); err != nil { |
| t.Fatalf("git commit failed: %v", err) |
| } |
| review, err = newReview(fake.X, gerrit.CLOpts{ |
| Remote: gerritPath, |
| Reviewers: parseEmails("run2"), |
| }) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := review.run(); err != nil { |
| t.Fatalf("run() failed: %v", err) |
| } |
| |
| chdir(t, fake.X, gerritPath) |
| expectedRef := gerrit.Reference(review.CLOpts) |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(expectedRef); err != nil { |
| t.Fatalf("%v", err) |
| } |
| assertFilesExist(t, fake.X, []string{"A"}) |
| assertFilesDoNotExist(t, fake.X, []string{"B", "C"}) |
| } |
| |
| // TestParallelDev checks "jiri cl mail" behavior when parallel development has |
| // been submitted upstream. |
| func TestParallelDev(t *testing.T) { |
| fake, repoPath, originPath, gerritAPath, cleanup := setupTest(t, true) |
| defer cleanup() |
| gerritBPath := createRepoFromOrigin(t, fake.X, "gerritB", originPath) |
| chdir(t, fake.X, repoPath) |
| |
| // Create parallel branches with: |
| // * non-conflicting changes in different files |
| // * conflicting changes in a file |
| createCLWithFiles(t, fake.X, "feature1-A", "A") |
| |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| createCLWithFiles(t, fake.X, "feature1-B", "B") |
| commitFile(t, fake.X, "A", "Don't tread on me.") |
| |
| reviewB, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritBPath}) |
| if err != nil { |
| t.Fatalf("%v", err) |
| } |
| setTopicFlag = false |
| if err := reviewB.run(); err != nil { |
| t.Fatalf("run() failed: %v", err) |
| } |
| |
| // Submit B and verify A doesn't revert it. |
| submit(t, fake.X, originPath, gerritBPath, reviewB) |
| |
| // Assert files pushed to origin. |
| chdir(t, fake.X, originPath) |
| assertFilesExist(t, fake.X, []string{"A", "B"}) |
| chdir(t, fake.X, repoPath) |
| |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("feature1-A"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| |
| reviewA, err := newReview(fake.X, gerrit.CLOpts{Remote: gerritAPath}) |
| if err == nil { |
| t.Fatalf("creating a review did not fail when it should") |
| } |
| // Assert state restored after failed review. |
| assertFileContent(t, fake.X, "A", "This is file A") |
| assertFilesDoNotExist(t, fake.X, []string{"B"}) |
| |
| // Manual conflict resolution. |
| if err := gitutil.New(fake.X.NewSeq()).Merge("master", gitutil.ResetOnFailureOpt(false)); err == nil { |
| t.Fatalf("merge applied cleanly when it shouldn't") |
| } |
| assertFilesNotCommitted(t, fake.X, []string{"A", "B"}) |
| assertFileContent(t, fake.X, "B", "This is file B") |
| |
| if err := fake.X.NewSeq().WriteFile("A", []byte("This is file A. Don't tread on me."), 0644).Done(); err != nil { |
| t.Fatalf("%v", err) |
| } |
| |
| if err := gitutil.New(fake.X.NewSeq()).Add("A"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := gitutil.New(fake.X.NewSeq()).Add("B"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := gitutil.New(fake.X.NewSeq()).CommitWithMessage("Conflict resolution"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| |
| // Retry review. |
| reviewA, err = newReview(fake.X, gerrit.CLOpts{Remote: gerritAPath}) |
| if err != nil { |
| t.Fatalf("review failed: %v", err) |
| } |
| |
| if err := reviewA.run(); err != nil { |
| t.Fatalf("run() failed: %v", err) |
| } |
| |
| chdir(t, fake.X, gerritAPath) |
| expectedRef := gerrit.Reference(reviewA.CLOpts) |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(expectedRef); err != nil { |
| t.Fatalf("%v", err) |
| } |
| assertFilesExist(t, fake.X, []string{"B"}) |
| } |
| |
| // TestCLSync checks the operation of the "jiri cl sync" command. |
| func TestCLSync(t *testing.T) { |
| fake, _, _, _, cleanup := setupTest(t, true) |
| defer cleanup() |
| |
| // Create some dependent CLs. |
| if err := newCL(fake.X, []string{"feature1"}); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := newCL(fake.X, []string{"feature2"}); err != nil { |
| t.Fatalf("%v", err) |
| } |
| |
| // Add the "test" file to the master. |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| commitFiles(t, fake.X, []string{"test"}) |
| |
| // Sync the dependent CLs. |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("feature2"); err != nil { |
| t.Fatalf("%v", err) |
| } |
| if err := syncCL(fake.X); err != nil { |
| t.Fatalf("%v", err) |
| } |
| |
| // Check that the "test" file exists in the dependent CLs. |
| for _, branch := range []string{"feature1", "feature2"} { |
| if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(branch); err != nil { |
| t.Fatalf("%v", err) |
| } |
| assertFilesExist(t, fake.X, []string{"test"}) |
| } |
| } |