v.io/jiri@v0.0.0-20160715023856-abfb8b131290/project/project_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 project_test 6 7 import ( 8 "bytes" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "reflect" 14 "sort" 15 "strings" 16 "testing" 17 18 "v.io/jiri" 19 "v.io/jiri/gitutil" 20 "v.io/jiri/jiritest" 21 "v.io/jiri/project" 22 ) 23 24 func checkReadme(t *testing.T, jirix *jiri.X, p project.Project, message string) { 25 if _, err := jirix.NewSeq().Stat(p.Path); err != nil { 26 t.Fatalf("%v", err) 27 } 28 readmeFile := filepath.Join(p.Path, "README") 29 data, err := ioutil.ReadFile(readmeFile) 30 if err != nil { 31 t.Fatalf("ReadFile(%v) failed: %v", readmeFile, err) 32 } 33 if got, want := data, []byte(message); bytes.Compare(got, want) != 0 { 34 t.Fatalf("unexpected content in project %v:\ngot\n%s\nwant\n%s\n", p.Name, got, want) 35 } 36 } 37 38 // Checks that /.jiri/ is ignored in a local project checkout 39 func checkMetadataIsIgnored(t *testing.T, jirix *jiri.X, p project.Project) { 40 if _, err := jirix.NewSeq().Stat(p.Path); err != nil { 41 t.Fatalf("%v", err) 42 } 43 gitInfoExcludeFile := filepath.Join(p.Path, ".git", "info", "exclude") 44 data, err := ioutil.ReadFile(gitInfoExcludeFile) 45 if err != nil { 46 t.Fatalf("ReadFile(%v) failed: %v", gitInfoExcludeFile, err) 47 } 48 excludeString := "/.jiri/" 49 if !strings.Contains(string(data), excludeString) { 50 t.Fatalf("Did not find \"%v\" in exclude file", excludeString) 51 } 52 } 53 54 func commitFile(t *testing.T, jirix *jiri.X, dir, file, msg string) { 55 cwd, err := os.Getwd() 56 if err != nil { 57 t.Fatal(err) 58 } 59 defer jirix.NewSeq().Chdir(cwd) 60 if err := jirix.NewSeq().Chdir(dir).Done(); err != nil { 61 t.Fatal(err) 62 } 63 if err := gitutil.New(jirix.NewSeq()).CommitFile(file, msg); err != nil { 64 t.Fatal(err) 65 } 66 } 67 68 func projectName(i int) string { 69 return fmt.Sprintf("project-%d", i) 70 } 71 72 func writeReadme(t *testing.T, jirix *jiri.X, projectDir, message string) { 73 path, perm := filepath.Join(projectDir, "README"), os.FileMode(0644) 74 if err := ioutil.WriteFile(path, []byte(message), perm); err != nil { 75 t.Fatalf("WriteFile(%v, %v) failed: %v", path, perm, err) 76 } 77 commitFile(t, jirix, projectDir, path, "creating README") 78 } 79 80 func checkProjectsMatchPaths(t *testing.T, gotProjects project.Projects, wantProjectPaths []string) { 81 gotProjectPaths := []string{} 82 for _, p := range gotProjects { 83 gotProjectPaths = append(gotProjectPaths, p.Path) 84 } 85 sort.Strings(gotProjectPaths) 86 sort.Strings(wantProjectPaths) 87 if !reflect.DeepEqual(gotProjectPaths, wantProjectPaths) { 88 t.Errorf("project paths got %v, want %v", gotProjectPaths, wantProjectPaths) 89 } 90 } 91 92 // TestLocalProjects tests the behavior of the LocalProjects method with 93 // different ScanModes. 94 func TestLocalProjects(t *testing.T) { 95 jirix, cleanup := jiritest.NewX(t) 96 defer cleanup() 97 98 // Create some projects. 99 numProjects, projectPaths := 3, []string{} 100 for i := 0; i < numProjects; i++ { 101 s := jirix.NewSeq() 102 name := projectName(i) 103 path := filepath.Join(jirix.Root, name) 104 if err := s.MkdirAll(path, 0755).Done(); err != nil { 105 t.Fatal(err) 106 } 107 108 // Initialize empty git repository. The commit is necessary, otherwise 109 // "git rev-parse master" fails. 110 git := gitutil.New(s, gitutil.RootDirOpt(path)) 111 if err := git.Init(path); err != nil { 112 t.Fatal(err) 113 } 114 if err := git.Commit(); err != nil { 115 t.Fatal(err) 116 } 117 118 // Write project metadata. 119 p := project.Project{ 120 Path: path, 121 Name: name, 122 } 123 if err := project.InternalWriteMetadata(jirix, p, path); err != nil { 124 t.Fatalf("writeMetadata %v %v) failed: %v\n", p, path, err) 125 } 126 projectPaths = append(projectPaths, path) 127 } 128 129 // Create a latest update snapshot but only tell it about the first project. 130 manifest := project.Manifest{ 131 Projects: []project.Project{ 132 { 133 Name: projectName(0), 134 Path: projectPaths[0], 135 }, 136 }, 137 } 138 if err := jirix.NewSeq().MkdirAll(jirix.UpdateHistoryDir(), 0755).Done(); err != nil { 139 t.Fatalf("MkdirAll(%v) failed: %v", jirix.UpdateHistoryDir(), err) 140 } 141 if err := manifest.ToFile(jirix, jirix.UpdateHistoryLatestLink()); err != nil { 142 t.Fatalf("manifest.ToFile(%v) failed: %v", jirix.UpdateHistoryLatestLink(), err) 143 } 144 145 // LocalProjects with scanMode = FastScan should only find the first 146 // project. 147 foundProjects, err := project.LocalProjects(jirix, project.FastScan) 148 if err != nil { 149 t.Fatalf("LocalProjects(%v) failed: %v", project.FastScan, err) 150 } 151 checkProjectsMatchPaths(t, foundProjects, projectPaths[:1]) 152 153 // LocalProjects with scanMode = FullScan should find all projects. 154 foundProjects, err = project.LocalProjects(jirix, project.FullScan) 155 if err != nil { 156 t.Fatalf("LocalProjects(%v) failed: %v", project.FastScan, err) 157 } 158 checkProjectsMatchPaths(t, foundProjects, projectPaths[:]) 159 160 // Check that deleting a project forces LocalProjects to run a full scan, 161 // even if FastScan is specified. 162 if err := jirix.NewSeq().RemoveAll(projectPaths[0]).Done(); err != nil { 163 t.Fatalf("RemoveAll(%v) failed: %v", projectPaths[0]) 164 } 165 foundProjects, err = project.LocalProjects(jirix, project.FastScan) 166 if err != nil { 167 t.Fatalf("LocalProjects(%v) failed: %v", project.FastScan, err) 168 } 169 checkProjectsMatchPaths(t, foundProjects, projectPaths[1:]) 170 } 171 172 // setupUniverse creates a fake jiri root with 3 remote projects. Each project 173 // has a README with text "initial readme". 174 func setupUniverse(t *testing.T) ([]project.Project, *jiritest.FakeJiriRoot, func()) { 175 fake, cleanup := jiritest.NewFakeJiriRoot(t) 176 success := false 177 defer func() { 178 if !success { 179 cleanup() 180 } 181 }() 182 183 // Create some projects and add them to the remote manifest. 184 numProjects := 3 185 localProjects := []project.Project{} 186 for i := 0; i < numProjects; i++ { 187 name := projectName(i) 188 path := fmt.Sprintf("path-%d", i) 189 if err := fake.CreateRemoteProject(name); err != nil { 190 t.Fatal(err) 191 } 192 p := project.Project{ 193 Name: name, 194 Path: filepath.Join(fake.X.Root, path), 195 Remote: fake.Projects[name], 196 } 197 localProjects = append(localProjects, p) 198 if err := fake.AddProject(p); err != nil { 199 t.Fatal(err) 200 } 201 } 202 203 // Create initial commit in each repo. 204 for _, remoteProjectDir := range fake.Projects { 205 writeReadme(t, fake.X, remoteProjectDir, "initial readme") 206 } 207 208 success = true 209 return localProjects, fake, cleanup 210 } 211 212 // TestUpdateUniverseSimple tests that UpdateUniverse will pull remote projects 213 // locally, and that jiri metadata is ignored in the repos. 214 func TestUpdateUniverseSimple(t *testing.T) { 215 localProjects, fake, cleanup := setupUniverse(t) 216 defer cleanup() 217 s := fake.X.NewSeq() 218 219 // Check that calling UpdateUniverse() creates local copies of the remote 220 // repositories, and that jiri metadata is ignored by git. 221 if err := fake.UpdateUniverse(false); err != nil { 222 t.Fatal(err) 223 } 224 for _, p := range localProjects { 225 if err := s.AssertDirExists(p.Path).Done(); err != nil { 226 t.Fatalf("expected project to exist at path %q but none found", p.Path) 227 } 228 checkReadme(t, fake.X, p, "initial readme") 229 checkMetadataIsIgnored(t, fake.X, p) 230 } 231 } 232 233 // TestUpdateUniverseWithRevision checks that UpdateUniverse will pull remote 234 // projects at the specified revision. 235 func TestUpdateUniverseWithRevision(t *testing.T) { 236 localProjects, fake, cleanup := setupUniverse(t) 237 defer cleanup() 238 s := fake.X.NewSeq() 239 240 // Set project 1's revision in the manifest to the current revision. 241 git := gitutil.New(s, gitutil.RootDirOpt(fake.Projects[localProjects[1].Name])) 242 rev, err := git.CurrentRevision() 243 if err != nil { 244 t.Fatal(err) 245 } 246 m, err := fake.ReadRemoteManifest() 247 if err != nil { 248 t.Fatal(err) 249 } 250 projects := []project.Project{} 251 for _, p := range m.Projects { 252 if p.Name == localProjects[1].Name { 253 p.Revision = rev 254 } 255 projects = append(projects, p) 256 } 257 m.Projects = projects 258 if err := fake.WriteRemoteManifest(m); err != nil { 259 t.Fatal(err) 260 } 261 // Update README in all projects. 262 for _, remoteProjectDir := range fake.Projects { 263 writeReadme(t, fake.X, remoteProjectDir, "new revision") 264 } 265 // Check that calling UpdateUniverse() updates all projects except for 266 // project 1. 267 if err := fake.UpdateUniverse(false); err != nil { 268 t.Fatal(err) 269 } 270 for i, p := range localProjects { 271 if i == 1 { 272 checkReadme(t, fake.X, p, "initial readme") 273 } else { 274 checkReadme(t, fake.X, p, "new revision") 275 } 276 } 277 } 278 279 // TestUpdateUniverseWithUncommitted checks that uncommitted files are not droped 280 // by UpdateUniverse(). This ensures that the "git reset --hard" mechanism used 281 // for pointing the master branch to a fixed revision does not lose work in 282 // progress. 283 func TestUpdateUniverseWithUncommitted(t *testing.T) { 284 localProjects, fake, cleanup := setupUniverse(t) 285 defer cleanup() 286 if err := fake.UpdateUniverse(false); err != nil { 287 t.Fatal(err) 288 } 289 290 // Create an uncommitted file in project 1. 291 file, perm, want := filepath.Join(localProjects[1].Path, "uncommitted_file"), os.FileMode(0644), []byte("uncommitted work") 292 if err := ioutil.WriteFile(file, want, perm); err != nil { 293 t.Fatalf("WriteFile(%v, %v) failed: %v", file, err, perm) 294 } 295 if err := fake.UpdateUniverse(false); err != nil { 296 t.Fatal(err) 297 } 298 got, err := ioutil.ReadFile(file) 299 if err != nil { 300 t.Fatalf("%v", err) 301 } 302 if bytes.Compare(got, want) != 0 { 303 t.Fatalf("unexpected content %v:\ngot\n%s\nwant\n%s\n", localProjects[1], got, want) 304 } 305 } 306 307 // TestUpdateUniverseMovedProject checks that UpdateUniverse can move a 308 // project. 309 func TestUpdateUniverseMovedProject(t *testing.T) { 310 localProjects, fake, cleanup := setupUniverse(t) 311 defer cleanup() 312 s := fake.X.NewSeq() 313 if err := fake.UpdateUniverse(false); err != nil { 314 t.Fatal(err) 315 } 316 317 // Update the local path at which project 1 is located. 318 m, err := fake.ReadRemoteManifest() 319 if err != nil { 320 t.Fatal(err) 321 } 322 oldProjectPath := localProjects[1].Path 323 localProjects[1].Path = filepath.Join(fake.X.Root, "new-project-path") 324 projects := []project.Project{} 325 for _, p := range m.Projects { 326 if p.Name == localProjects[1].Name { 327 p.Path = localProjects[1].Path 328 } 329 projects = append(projects, p) 330 } 331 m.Projects = projects 332 if err := fake.WriteRemoteManifest(m); err != nil { 333 t.Fatal(err) 334 } 335 // Check that UpdateUniverse() moves the local copy of the project 1. 336 if err := fake.UpdateUniverse(false); err != nil { 337 t.Fatal(err) 338 } 339 if err := s.AssertDirExists(oldProjectPath).Done(); err == nil { 340 t.Fatalf("expected project %q at path %q not to exist but it did", localProjects[1].Name, oldProjectPath) 341 } 342 if err := s.AssertDirExists(localProjects[2].Path).Done(); err != nil { 343 t.Fatalf("expected project %q at path %q to exist but it did not", localProjects[1].Name, localProjects[1].Path) 344 } 345 checkReadme(t, fake.X, localProjects[1], "initial readme") 346 } 347 348 // TestUpdateUniverseDeletedProject checks that UpdateUniverse will delete a 349 // project iff gc=true. 350 func TestUpdateUniverseDeletedProject(t *testing.T) { 351 localProjects, fake, cleanup := setupUniverse(t) 352 defer cleanup() 353 s := fake.X.NewSeq() 354 if err := fake.UpdateUniverse(false); err != nil { 355 t.Fatal(err) 356 } 357 358 // Delete project 1. 359 m, err := fake.ReadRemoteManifest() 360 if err != nil { 361 t.Fatal(err) 362 } 363 projects := []project.Project{} 364 for _, p := range m.Projects { 365 if p.Name == localProjects[1].Name { 366 continue 367 } 368 projects = append(projects, p) 369 } 370 m.Projects = projects 371 if err := fake.WriteRemoteManifest(m); err != nil { 372 t.Fatal(err) 373 } 374 // Check that UpdateUniverse() with gc=false does not delete the local copy 375 // of the project. 376 if err := fake.UpdateUniverse(false); err != nil { 377 t.Fatal(err) 378 } 379 if err := s.AssertDirExists(localProjects[1].Path).Done(); err != nil { 380 t.Fatalf("expected project %q at path %q to exist but it did not", localProjects[1].Name, localProjects[1].Path) 381 } 382 checkReadme(t, fake.X, localProjects[1], "initial readme") 383 // Check that UpdateUniverse() with gc=true does delete the local copy of 384 // the project. 385 if err := fake.UpdateUniverse(true); err != nil { 386 t.Fatal(err) 387 } 388 if err := s.AssertDirExists(localProjects[1].Path).Done(); err == nil { 389 t.Fatalf("expected project %q at path %q not to exist but it did", localProjects[1].Name, localProjects[3].Path) 390 } 391 } 392 393 // TestUpdateUniverseNewProjectSamePath checks that UpdateUniverse can handle a 394 // new project with the same path as a deleted project, but a different path. 395 func TestUpdateUniverseNewProjectSamePath(t *testing.T) { 396 localProjects, fake, cleanup := setupUniverse(t) 397 defer cleanup() 398 if err := fake.UpdateUniverse(false); err != nil { 399 t.Fatal(err) 400 } 401 402 // Delete a project 1 and create a new one with a different name but the 403 // same path. 404 m, err := fake.ReadRemoteManifest() 405 if err != nil { 406 t.Fatal(err) 407 } 408 newProjectName := "new-project-name" 409 projects := []project.Project{} 410 for _, p := range m.Projects { 411 if p.Path == localProjects[1].Path { 412 p.Name = newProjectName 413 } 414 projects = append(projects, p) 415 } 416 localProjects[1].Name = newProjectName 417 m.Projects = projects 418 if err := fake.WriteRemoteManifest(m); err != nil { 419 t.Fatal(err) 420 } 421 // Check that UpdateUniverse() does not fail. 422 if err := fake.UpdateUniverse(true); err != nil { 423 t.Fatal(err) 424 } 425 } 426 427 // TestUpdateUniverseRemoteBranch checks that UpdateUniverse can pull from a 428 // non-master remote branch. 429 func TestUpdateUniverseRemoteBranch(t *testing.T) { 430 localProjects, fake, cleanup := setupUniverse(t) 431 defer cleanup() 432 s := fake.X.NewSeq() 433 if err := fake.UpdateUniverse(false); err != nil { 434 t.Fatal(err) 435 } 436 437 // Commit to master branch of a project 1. 438 writeReadme(t, fake.X, fake.Projects[localProjects[1].Name], "master commit") 439 // Create and checkout a new branch of project 1 and make a new commit. 440 git := gitutil.New(s, gitutil.RootDirOpt(fake.Projects[localProjects[1].Name])) 441 if err := git.CreateAndCheckoutBranch("non-master"); err != nil { 442 t.Fatal(err) 443 } 444 writeReadme(t, fake.X, fake.Projects[localProjects[1].Name], "non-master commit") 445 // Point the manifest to the new non-master branch. 446 m, err := fake.ReadRemoteManifest() 447 if err != nil { 448 t.Fatal(err) 449 } 450 projects := []project.Project{} 451 for _, p := range m.Projects { 452 if p.Name == localProjects[1].Name { 453 p.RemoteBranch = "non-master" 454 } 455 projects = append(projects, p) 456 } 457 m.Projects = projects 458 if err := fake.WriteRemoteManifest(m); err != nil { 459 t.Fatal(err) 460 } 461 // Check that UpdateUniverse pulls the commit from the non-master branch. 462 if err := fake.UpdateUniverse(false); err != nil { 463 t.Fatal(err) 464 } 465 checkReadme(t, fake.X, localProjects[1], "non-master commit") 466 } 467 468 func TestFileImportCycle(t *testing.T) { 469 jirix, cleanup := jiritest.NewX(t) 470 defer cleanup() 471 472 // Set up the cycle .jiri_manifest -> A -> B -> A 473 jiriManifest := project.Manifest{ 474 LocalImports: []project.LocalImport{ 475 {File: "A"}, 476 }, 477 } 478 manifestA := project.Manifest{ 479 LocalImports: []project.LocalImport{ 480 {File: "B"}, 481 }, 482 } 483 manifestB := project.Manifest{ 484 LocalImports: []project.LocalImport{ 485 {File: "A"}, 486 }, 487 } 488 if err := jiriManifest.ToFile(jirix, jirix.JiriManifestFile()); err != nil { 489 t.Fatal(err) 490 } 491 if err := manifestA.ToFile(jirix, filepath.Join(jirix.Root, "A")); err != nil { 492 t.Fatal(err) 493 } 494 if err := manifestB.ToFile(jirix, filepath.Join(jirix.Root, "B")); err != nil { 495 t.Fatal(err) 496 } 497 498 // The update should complain about the cycle. 499 err := project.UpdateUniverse(jirix, false) 500 if got, want := fmt.Sprint(err), "import cycle detected in local manifest files"; !strings.Contains(got, want) { 501 t.Errorf("got error %v, want substr %v", got, want) 502 } 503 } 504 505 func TestRemoteImportCycle(t *testing.T) { 506 fake, cleanup := jiritest.NewFakeJiriRoot(t) 507 defer cleanup() 508 509 // Set up two remote manifest projects, remote1 and remote1. 510 if err := fake.CreateRemoteProject("remote1"); err != nil { 511 t.Fatal(err) 512 } 513 if err := fake.CreateRemoteProject("remote2"); err != nil { 514 t.Fatal(err) 515 } 516 remote1 := fake.Projects["remote1"] 517 remote2 := fake.Projects["remote2"] 518 519 fileA, fileB := filepath.Join(remote1, "A"), filepath.Join(remote2, "B") 520 521 // Set up the cycle .jiri_manifest -> remote1+A -> remote2+B -> remote1+A 522 jiriManifest := project.Manifest{ 523 Imports: []project.Import{ 524 {Manifest: "A", Name: "n1", Remote: remote1}, 525 }, 526 } 527 manifestA := project.Manifest{ 528 Imports: []project.Import{ 529 {Manifest: "B", Name: "n2", Remote: remote2}, 530 }, 531 } 532 manifestB := project.Manifest{ 533 Imports: []project.Import{ 534 {Manifest: "A", Name: "n3", Remote: remote1}, 535 }, 536 } 537 if err := jiriManifest.ToFile(fake.X, fake.X.JiriManifestFile()); err != nil { 538 t.Fatal(err) 539 } 540 if err := manifestA.ToFile(fake.X, fileA); err != nil { 541 t.Fatal(err) 542 } 543 if err := manifestB.ToFile(fake.X, fileB); err != nil { 544 t.Fatal(err) 545 } 546 commitFile(t, fake.X, remote1, fileA, "commit A") 547 commitFile(t, fake.X, remote2, fileB, "commit B") 548 549 // The update should complain about the cycle. 550 err := project.UpdateUniverse(fake.X, false) 551 if got, want := fmt.Sprint(err), "import cycle detected in remote manifest imports"; !strings.Contains(got, want) { 552 t.Errorf("got error %v, want substr %v", got, want) 553 } 554 } 555 556 func TestFileAndRemoteImportCycle(t *testing.T) { 557 fake, cleanup := jiritest.NewFakeJiriRoot(t) 558 defer cleanup() 559 560 // Set up two remote manifest projects, remote1 and remote2. 561 // Set up two remote manifest projects, remote1 and remote1. 562 if err := fake.CreateRemoteProject("remote1"); err != nil { 563 t.Fatal(err) 564 } 565 if err := fake.CreateRemoteProject("remote2"); err != nil { 566 t.Fatal(err) 567 } 568 remote1 := fake.Projects["remote1"] 569 remote2 := fake.Projects["remote2"] 570 fileA, fileD := filepath.Join(remote1, "A"), filepath.Join(remote1, "D") 571 fileB, fileC := filepath.Join(remote2, "B"), filepath.Join(remote2, "C") 572 573 // Set up the cycle .jiri_manifest -> remote1+A -> remote2+B -> C -> remote1+D -> A 574 jiriManifest := project.Manifest{ 575 Imports: []project.Import{ 576 {Manifest: "A", Root: "r1", Name: "n1", Remote: remote1}, 577 }, 578 } 579 manifestA := project.Manifest{ 580 Imports: []project.Import{ 581 {Manifest: "B", Root: "r2", Name: "n2", Remote: remote2}, 582 }, 583 } 584 manifestB := project.Manifest{ 585 LocalImports: []project.LocalImport{ 586 {File: "C"}, 587 }, 588 } 589 manifestC := project.Manifest{ 590 Imports: []project.Import{ 591 {Manifest: "D", Root: "r3", Name: "n3", Remote: remote1}, 592 }, 593 } 594 manifestD := project.Manifest{ 595 LocalImports: []project.LocalImport{ 596 {File: "A"}, 597 }, 598 } 599 if err := jiriManifest.ToFile(fake.X, fake.X.JiriManifestFile()); err != nil { 600 t.Fatal(err) 601 } 602 if err := manifestA.ToFile(fake.X, fileA); err != nil { 603 t.Fatal(err) 604 } 605 if err := manifestB.ToFile(fake.X, fileB); err != nil { 606 t.Fatal(err) 607 } 608 if err := manifestC.ToFile(fake.X, fileC); err != nil { 609 t.Fatal(err) 610 } 611 if err := manifestD.ToFile(fake.X, fileD); err != nil { 612 t.Fatal(err) 613 } 614 commitFile(t, fake.X, remote1, fileA, "commit A") 615 commitFile(t, fake.X, remote2, fileB, "commit B") 616 commitFile(t, fake.X, remote2, fileC, "commit C") 617 commitFile(t, fake.X, remote1, fileD, "commit D") 618 619 // The update should complain about the cycle. 620 err := project.UpdateUniverse(fake.X, false) 621 if got, want := fmt.Sprint(err), "import cycle detected"; !strings.Contains(got, want) { 622 t.Errorf("got error %v, want substr %v", got, want) 623 } 624 } 625 626 // TestUnsupportedProtocolErr checks that calling 627 // UnsupportedPrototoclErr.Error() does not result in an infinite loop. 628 func TestUnsupportedPrototocolErr(t *testing.T) { 629 err := project.UnsupportedProtocolErr("foo") 630 _ = err.Error() 631 } 632 633 type binDirTest struct { 634 Name string 635 Setup func(old, new string) error 636 Teardown func(old, new string) error 637 Error string 638 CheckBackup bool 639 } 640 641 func TestTransitionBinDir(t *testing.T) { 642 tests := []binDirTest{ 643 { 644 "No old dir", 645 func(old, new string) error { return nil }, 646 nil, 647 "", 648 false, 649 }, 650 { 651 "Empty old dir", 652 func(old, new string) error { 653 return os.MkdirAll(old, 0777) 654 }, 655 nil, 656 "", 657 true, 658 }, 659 { 660 "Populated old dir", 661 func(old, new string) error { 662 if err := os.MkdirAll(old, 0777); err != nil { 663 return err 664 } 665 return ioutil.WriteFile(filepath.Join(old, "tool"), []byte("foo"), 0777) 666 }, 667 nil, 668 "", 669 true, 670 }, 671 { 672 "Symlinked old dir", 673 func(old, new string) error { 674 if err := os.MkdirAll(filepath.Dir(old), 0777); err != nil { 675 return err 676 } 677 return os.Symlink(new, old) 678 }, 679 nil, 680 "", 681 false, 682 }, 683 { 684 "Symlinked old dir pointing elsewhere", 685 func(old, new string) error { 686 if err := os.MkdirAll(filepath.Dir(old), 0777); err != nil { 687 return err 688 } 689 return os.Symlink(filepath.Dir(new), old) 690 }, 691 nil, 692 "", 693 true, 694 }, 695 { 696 "Unreadable old dir parent", 697 func(old, new string) error { 698 if err := os.MkdirAll(old, 0777); err != nil { 699 return err 700 } 701 return os.Chmod(filepath.Dir(old), 0222) 702 }, 703 func(old, new string) error { 704 return os.Chmod(filepath.Dir(old), 0777) 705 }, 706 "Failed to stat old bin dir", 707 false, 708 }, 709 { 710 "Unwritable old dir", 711 func(old, new string) error { 712 if err := os.MkdirAll(old, 0777); err != nil { 713 return err 714 } 715 return os.Chmod(old, 0444) 716 }, 717 func(old, new string) error { 718 return os.Chmod(old, 0777) 719 }, 720 "Failed to backup old bin dir", 721 false, 722 }, 723 { 724 "Unreadable backup dir parent", 725 func(old, new string) error { 726 if err := os.MkdirAll(old, 0777); err != nil { 727 return err 728 } 729 return os.Chmod(filepath.Dir(new), 0222) 730 }, 731 func(old, new string) error { 732 return os.Chmod(filepath.Dir(new), 0777) 733 }, 734 "Failed to stat backup bin dir", 735 false, 736 }, 737 { 738 "Existing backup dir", 739 func(old, new string) error { 740 if err := os.MkdirAll(old, 0777); err != nil { 741 return err 742 } 743 return os.MkdirAll(new+".BACKUP", 0777) 744 }, 745 nil, 746 "Backup bin dir", 747 false, 748 }, 749 } 750 for _, test := range tests { 751 jirix, cleanup := jiritest.NewX(t) 752 if err := testTransitionBinDir(jirix, test); err != nil { 753 t.Errorf("%s: %v", test.Name, err) 754 } 755 cleanup() 756 } 757 } 758 759 func testTransitionBinDir(jirix *jiri.X, test binDirTest) (e error) { 760 oldDir, newDir := filepath.Join(jirix.Root, "devtools", "bin"), jirix.BinDir() 761 // The new bin dir always exists. 762 if err := os.MkdirAll(newDir, 0777); err != nil { 763 return fmt.Errorf("make new dir failed: %v", err) 764 } 765 if err := test.Setup(oldDir, newDir); err != nil { 766 return fmt.Errorf("setup failed: %v", err) 767 } 768 if test.Teardown != nil { 769 defer func() { 770 if err := test.Teardown(oldDir, newDir); err != nil && e == nil { 771 e = fmt.Errorf("teardown failed: %v", err) 772 } 773 }() 774 } 775 oldInfo, _ := os.Stat(oldDir) 776 switch err := project.TransitionBinDir(jirix); { 777 case err != nil && test.Error == "": 778 return fmt.Errorf("got error %q, want success", err) 779 case err != nil && !strings.Contains(fmt.Sprint(err), test.Error): 780 return fmt.Errorf("got error %q, want prefix %q", err, test.Error) 781 case err == nil && test.Error != "": 782 return fmt.Errorf("got no error, want %q", test.Error) 783 case err == nil && test.Error == "": 784 // Make sure the symlink exists and is correctly linked. 785 link, err := os.Readlink(oldDir) 786 if err != nil { 787 return fmt.Errorf("old dir isn't a symlink: %v", err) 788 } 789 if got, want := link, newDir; got != want { 790 return fmt.Errorf("old dir symlink got %v, want %v", got, want) 791 } 792 if test.CheckBackup { 793 // Make sure the oldDir was backed up correctly. 794 backupDir := filepath.Join(jirix.RootMetaDir(), "bin.BACKUP") 795 backupInfo, err := os.Stat(backupDir) 796 if err != nil { 797 return fmt.Errorf("stat backup dir failed: %v", err) 798 } 799 if !os.SameFile(oldInfo, backupInfo) { 800 return fmt.Errorf("old dir wasn't backed up correctly") 801 } 802 } 803 } 804 return nil 805 } 806 807 func TestManifestToFromBytes(t *testing.T) { 808 tests := []struct { 809 Manifest project.Manifest 810 XML string 811 }{ 812 { 813 project.Manifest{}, 814 `<manifest> 815 </manifest> 816 `, 817 }, 818 { 819 project.Manifest{ 820 Imports: []project.Import{ 821 { 822 Manifest: "manifest1", 823 Name: "remoteimport1", 824 Protocol: "git", 825 Remote: "remote1", 826 RemoteBranch: "master", 827 }, 828 { 829 Manifest: "manifest2", 830 Name: "remoteimport2", 831 Protocol: "git", 832 Remote: "remote2", 833 RemoteBranch: "branch2", 834 }, 835 }, 836 LocalImports: []project.LocalImport{ 837 {File: "fileimport"}, 838 }, 839 Projects: []project.Project{ 840 { 841 Name: "project1", 842 Path: "path1", 843 Protocol: "git", 844 Remote: "remote1", 845 RemoteBranch: "master", 846 Revision: "HEAD", 847 GerritHost: "https://test-review.googlesource.com", 848 GitHooks: "path/to/githooks", 849 RunHook: "path/to/hook", 850 }, 851 { 852 Name: "project2", 853 Path: "path2", 854 Protocol: "git", 855 Remote: "remote2", 856 RemoteBranch: "branch2", 857 Revision: "rev2", 858 }, 859 }, 860 Tools: []project.Tool{ 861 { 862 Data: "tooldata", 863 Name: "tool", 864 Project: "toolproject", 865 }, 866 }, 867 }, 868 `<manifest> 869 <imports> 870 <import manifest="manifest1" name="remoteimport1" remote="remote1"/> 871 <import manifest="manifest2" name="remoteimport2" remote="remote2" remotebranch="branch2"/> 872 <localimport file="fileimport"/> 873 </imports> 874 <projects> 875 <project name="project1" path="path1" remote="remote1" gerrithost="https://test-review.googlesource.com" githooks="path/to/githooks" runhook="path/to/hook"/> 876 <project name="project2" path="path2" remote="remote2" remotebranch="branch2" revision="rev2"/> 877 </projects> 878 <tools> 879 <tool data="tooldata" name="tool" project="toolproject"/> 880 </tools> 881 </manifest> 882 `, 883 }, 884 } 885 for _, test := range tests { 886 gotBytes, err := test.Manifest.ToBytes() 887 if err != nil { 888 t.Errorf("%+v ToBytes failed: %v", test.Manifest, err) 889 } 890 if got, want := string(gotBytes), test.XML; got != want { 891 t.Errorf("%+v ToBytes GOT\n%v\nWANT\n%v", test.Manifest, got, want) 892 } 893 manifest, err := project.ManifestFromBytes([]byte(test.XML)) 894 if err != nil { 895 t.Errorf("%+v FromBytes failed: %v", test.Manifest, err) 896 } 897 if got, want := manifest, &test.Manifest; !reflect.DeepEqual(got, want) { 898 t.Errorf("%+v FromBytes got %#v, want %#v", test.Manifest, got, want) 899 } 900 } 901 } 902 903 func TestProjectToFromFile(t *testing.T) { 904 jirix, cleanup := jiritest.NewX(t) 905 defer cleanup() 906 907 tests := []struct { 908 Project project.Project 909 XML string 910 }{ 911 { 912 // Default fields are dropped when marshaled, and added when unmarshaled. 913 project.Project{ 914 Name: "project1", 915 Path: filepath.Join(jirix.Root, "path1"), 916 Protocol: "git", 917 Remote: "remote1", 918 RemoteBranch: "master", 919 Revision: "HEAD", 920 }, 921 `<project name="project1" path="path1" remote="remote1"/> 922 `, 923 }, 924 { 925 project.Project{ 926 Name: "project2", 927 Path: filepath.Join(jirix.Root, "path2"), 928 GitHooks: filepath.Join(jirix.Root, "git-hooks"), 929 RunHook: filepath.Join(jirix.Root, "run-hook"), 930 Protocol: "git", 931 Remote: "remote2", 932 RemoteBranch: "branch2", 933 Revision: "rev2", 934 }, 935 `<project name="project2" path="path2" remote="remote2" remotebranch="branch2" revision="rev2" githooks="git-hooks" runhook="run-hook"/> 936 `, 937 }, 938 } 939 for index, test := range tests { 940 filename := filepath.Join(jirix.Root, fmt.Sprintf("test-%d", index)) 941 if err := test.Project.ToFile(jirix, filename); err != nil { 942 t.Errorf("%+v ToFile failed: %v", test.Project, err) 943 } 944 gotBytes, err := jirix.NewSeq().ReadFile(filename) 945 if err != nil { 946 t.Errorf("%+v ReadFile failed: %v", test.Project, err) 947 } 948 if got, want := string(gotBytes), test.XML; got != want { 949 t.Errorf("%+v ToFile GOT\n%v\nWANT\n%v", test.Project, got, want) 950 } 951 project, err := project.ProjectFromFile(jirix, filename) 952 if err != nil { 953 t.Errorf("%+v FromFile failed: %v", test.Project, err) 954 } 955 if got, want := project, &test.Project; !reflect.DeepEqual(got, want) { 956 t.Errorf("%+v FromFile got %#v, want %#v", test.Project, got, want) 957 } 958 } 959 }