v.io/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 }