github.com/golang/dep@v0.5.4/gps/manager_test.go (about) 1 // Copyright 2016 The Go 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 gps 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 "io/ioutil" 12 "log" 13 "os" 14 "path" 15 "path/filepath" 16 "runtime" 17 "sort" 18 "sync" 19 "sync/atomic" 20 "testing" 21 "text/tabwriter" 22 "time" 23 24 "github.com/golang/dep/internal/test" 25 ) 26 27 // An analyzer that passes nothing back, but doesn't error. This is the naive 28 // case - no constraints, no lock, and no errors. The SourceManager will 29 // interpret this as open/Any constraints on everything in the import graph. 30 type naiveAnalyzer struct{} 31 32 func (naiveAnalyzer) DeriveManifestAndLock(string, ProjectRoot) (Manifest, Lock, error) { 33 return nil, nil, nil 34 } 35 36 func (a naiveAnalyzer) Info() ProjectAnalyzerInfo { 37 return ProjectAnalyzerInfo{ 38 Name: "naive-analyzer", 39 Version: 1, 40 } 41 } 42 43 func mkNaiveSM(t *testing.T) (*SourceMgr, func()) { 44 cpath, err := ioutil.TempDir("", "smcache") 45 if err != nil { 46 t.Fatalf("Failed to create temp dir: %s", err) 47 } 48 49 sm, err := NewSourceManager(SourceManagerConfig{ 50 Cachedir: cpath, 51 Logger: log.New(test.Writer{TB: t}, "", 0), 52 }) 53 if err != nil { 54 t.Fatalf("Unexpected error on SourceManager creation: %s", err) 55 } 56 57 return sm, func() { 58 sm.Release() 59 err := os.RemoveAll(cpath) 60 if err != nil { 61 t.Errorf("removeAll failed: %s", err) 62 } 63 } 64 } 65 66 func remakeNaiveSM(osm *SourceMgr, t *testing.T) (*SourceMgr, func()) { 67 cpath := osm.cachedir 68 osm.Release() 69 70 sm, err := NewSourceManager(SourceManagerConfig{ 71 Cachedir: cpath, 72 Logger: log.New(test.Writer{TB: t}, "", 0), 73 }) 74 if err != nil { 75 t.Fatalf("unexpected error on SourceManager recreation: %s", err) 76 } 77 78 return sm, func() { 79 sm.Release() 80 err := os.RemoveAll(cpath) 81 if err != nil { 82 t.Errorf("removeAll failed: %s", err) 83 } 84 } 85 } 86 87 func TestSourceManagerInit(t *testing.T) { 88 cpath, err := ioutil.TempDir("", "smcache") 89 if err != nil { 90 t.Errorf("Failed to create temp dir: %s", err) 91 } 92 cfg := SourceManagerConfig{ 93 Cachedir: cpath, 94 Logger: log.New(test.Writer{TB: t}, "", 0), 95 } 96 97 sm, err := NewSourceManager(cfg) 98 99 if err != nil { 100 t.Errorf("Unexpected error on SourceManager creation: %s", err) 101 } 102 103 _, err = NewSourceManager(cfg) 104 if err == nil { 105 t.Errorf("Creating second SourceManager should have failed due to file lock contention") 106 } else if te, ok := err.(CouldNotCreateLockError); !ok { 107 t.Errorf("Should have gotten CouldNotCreateLockError error type, but got %T", te) 108 } 109 110 if _, err = os.Stat(path.Join(cpath, "sm.lock")); err != nil { 111 t.Errorf("Global cache lock file not created correctly") 112 } 113 114 sm.Release() 115 err = os.RemoveAll(cpath) 116 if err != nil { 117 t.Errorf("removeAll failed: %s", err) 118 } 119 120 if _, err = os.Stat(path.Join(cpath, "sm.lock")); !os.IsNotExist(err) { 121 t.Fatalf("Global cache lock file not cleared correctly on Release()") 122 } 123 124 err = os.MkdirAll(cpath, 0777) 125 if err != nil { 126 t.Errorf("Failed to re-create temp dir: %s", err) 127 } 128 defer func() { 129 err = os.RemoveAll(cpath) 130 if err != nil { 131 t.Errorf("removeAll failed: %s", err) 132 } 133 }() 134 // Set another one up at the same spot now, just to be sure 135 sm, err = NewSourceManager(cfg) 136 if err != nil { 137 t.Fatalf("Creating a second SourceManager should have succeeded when the first was released, but failed with err %s", err) 138 } 139 140 sm.Release() 141 } 142 143 func TestSourceInit(t *testing.T) { 144 // This test is a bit slow, skip it on -short 145 if testing.Short() { 146 t.Skip("Skipping project manager init test in short mode") 147 } 148 149 cpath, err := ioutil.TempDir("", "smcache") 150 if err != nil { 151 t.Fatalf("Failed to create temp dir: %s", err) 152 } 153 154 sm, err := NewSourceManager(SourceManagerConfig{ 155 Cachedir: cpath, 156 Logger: log.New(test.Writer{TB: t}, "", 0), 157 }) 158 if err != nil { 159 t.Fatalf("Unexpected error on SourceManager creation: %s", err) 160 } 161 162 defer func() { 163 sm.Release() 164 err := os.RemoveAll(cpath) 165 if err != nil { 166 t.Errorf("removeAll failed: %s", err) 167 } 168 }() 169 170 id := mkPI("github.com/sdboyer/gpkt").normalize() 171 pvl, err := sm.ListVersions(id) 172 if err != nil { 173 t.Errorf("Unexpected error during initial project setup/fetching %s", err) 174 } 175 176 if len(pvl) != 7 { 177 t.Errorf("Expected seven version results from the test repo, got %v", len(pvl)) 178 } else { 179 expected := []PairedVersion{ 180 NewVersion("v2.0.0").Pair(Revision("4a54adf81c75375d26d376459c00d5ff9b703e5e")), 181 NewVersion("v1.1.0").Pair(Revision("b2cb48dda625f6640b34d9ffb664533359ac8b91")), 182 NewVersion("v1.0.0").Pair(Revision("bf85021c0405edbc4f3648b0603818d641674f72")), 183 newDefaultBranch("master").Pair(Revision("bf85021c0405edbc4f3648b0603818d641674f72")), 184 NewBranch("v1").Pair(Revision("e3777f683305eafca223aefe56b4e8ecf103f467")), 185 NewBranch("v1.1").Pair(Revision("f1fbc520489a98306eb28c235204e39fa8a89c84")), 186 NewBranch("v3").Pair(Revision("4a54adf81c75375d26d376459c00d5ff9b703e5e")), 187 } 188 189 // SourceManager itself doesn't guarantee ordering; sort them here so we 190 // can dependably check output 191 SortPairedForUpgrade(pvl) 192 193 for k, e := range expected { 194 if !pvl[k].Matches(e) { 195 t.Errorf("Expected version %s in position %v but got %s", e, k, pvl[k]) 196 } 197 } 198 } 199 200 // Two birds, one stone - make sure the internal ProjectManager vlist cache 201 // works (or at least doesn't not work) by asking for the versions again, 202 // and do it through smcache to ensure its sorting works, as well. 203 smc := &bridge{ 204 sm: sm, 205 vlists: make(map[ProjectIdentifier][]Version), 206 s: &solver{mtr: newMetrics()}, 207 } 208 209 vl, err := smc.listVersions(id) 210 if err != nil { 211 t.Errorf("Unexpected error during initial project setup/fetching %s", err) 212 } 213 214 if len(vl) != 7 { 215 t.Errorf("Expected seven version results from the test repo, got %v", len(vl)) 216 } else { 217 expected := []Version{ 218 NewVersion("v2.0.0").Pair(Revision("4a54adf81c75375d26d376459c00d5ff9b703e5e")), 219 NewVersion("v1.1.0").Pair(Revision("b2cb48dda625f6640b34d9ffb664533359ac8b91")), 220 NewVersion("v1.0.0").Pair(Revision("bf85021c0405edbc4f3648b0603818d641674f72")), 221 newDefaultBranch("master").Pair(Revision("bf85021c0405edbc4f3648b0603818d641674f72")), 222 NewBranch("v1").Pair(Revision("e3777f683305eafca223aefe56b4e8ecf103f467")), 223 NewBranch("v1.1").Pair(Revision("f1fbc520489a98306eb28c235204e39fa8a89c84")), 224 NewBranch("v3").Pair(Revision("4a54adf81c75375d26d376459c00d5ff9b703e5e")), 225 } 226 227 for k, e := range expected { 228 if !vl[k].Matches(e) { 229 t.Errorf("Expected version %s in position %v but got %s", e, k, vl[k]) 230 } 231 } 232 233 if !vl[3].(versionPair).v.(branchVersion).isDefault { 234 t.Error("Expected master branch version to have isDefault flag, but it did not") 235 } 236 if vl[4].(versionPair).v.(branchVersion).isDefault { 237 t.Error("Expected v1 branch version not to have isDefault flag, but it did") 238 } 239 if vl[5].(versionPair).v.(branchVersion).isDefault { 240 t.Error("Expected v1.1 branch version not to have isDefault flag, but it did") 241 } 242 if vl[6].(versionPair).v.(branchVersion).isDefault { 243 t.Error("Expected v3 branch version not to have isDefault flag, but it did") 244 } 245 } 246 247 present, err := smc.RevisionPresentIn(id, Revision("4a54adf81c75375d26d376459c00d5ff9b703e5e")) 248 if err != nil { 249 t.Errorf("Should have found revision in source, but got err: %s", err) 250 } else if !present { 251 t.Errorf("Should have found revision in source, but did not") 252 } 253 254 // SyncSourceFor will ensure we have everything 255 err = smc.SyncSourceFor(id) 256 if err != nil { 257 t.Errorf("SyncSourceFor failed with unexpected error: %s", err) 258 } 259 260 // Ensure that the appropriate cache dirs and files exist 261 _, err = os.Stat(filepath.Join(cpath, "sources", "https---github.com-sdboyer-gpkt", ".git")) 262 if err != nil { 263 t.Error("Cache repo does not exist in expected location") 264 } 265 266 os.Stat(filepath.Join(cpath, "metadata", "github.com", "sdboyer", "gpkt", "cache.json")) 267 268 // Ensure source existence values are what we expect 269 var exists bool 270 exists, err = sm.SourceExists(id) 271 if err != nil { 272 t.Errorf("Error on checking SourceExists: %s", err) 273 } 274 if !exists { 275 t.Error("Source should exist after non-erroring call to ListVersions") 276 } 277 } 278 279 func TestDefaultBranchAssignment(t *testing.T) { 280 if testing.Short() { 281 t.Skip("Skipping default branch assignment test in short mode") 282 } 283 284 sm, clean := mkNaiveSM(t) 285 defer clean() 286 287 id := mkPI("github.com/sdboyer/test-multibranch") 288 v, err := sm.ListVersions(id) 289 if err != nil { 290 t.Errorf("Unexpected error during initial project setup/fetching %s", err) 291 } 292 293 if len(v) != 3 { 294 t.Errorf("Expected three version results from the test repo, got %v", len(v)) 295 } else { 296 brev := Revision("fda020843ac81352004b9dca3fcccdd517600149") 297 mrev := Revision("9f9c3a591773d9b28128309ac7a9a72abcab267d") 298 expected := []PairedVersion{ 299 NewBranch("branchone").Pair(brev), 300 NewBranch("otherbranch").Pair(brev), 301 NewBranch("master").Pair(mrev), 302 } 303 304 SortPairedForUpgrade(v) 305 306 for k, e := range expected { 307 if !v[k].Matches(e) { 308 t.Errorf("Expected version %s in position %v but got %s", e, k, v[k]) 309 } 310 } 311 312 if !v[0].(versionPair).v.(branchVersion).isDefault { 313 t.Error("Expected branchone branch version to have isDefault flag, but it did not") 314 } 315 if !v[0].(versionPair).v.(branchVersion).isDefault { 316 t.Error("Expected otherbranch branch version to have isDefault flag, but it did not") 317 } 318 if v[2].(versionPair).v.(branchVersion).isDefault { 319 t.Error("Expected master branch version not to have isDefault flag, but it did") 320 } 321 } 322 } 323 324 func TestMgrMethodsFailWithBadPath(t *testing.T) { 325 // a symbol will always bork it up 326 bad := mkPI("foo/##&^").normalize() 327 sm, clean := mkNaiveSM(t) 328 defer clean() 329 330 var err error 331 if _, err = sm.SourceExists(bad); err == nil { 332 t.Error("SourceExists() did not error on bad input") 333 } 334 if err = sm.SyncSourceFor(bad); err == nil { 335 t.Error("SyncSourceFor() did not error on bad input") 336 } 337 if _, err = sm.ListVersions(bad); err == nil { 338 t.Error("ListVersions() did not error on bad input") 339 } 340 if _, err = sm.RevisionPresentIn(bad, Revision("")); err == nil { 341 t.Error("RevisionPresentIn() did not error on bad input") 342 } 343 if _, err = sm.ListPackages(bad, nil); err == nil { 344 t.Error("ListPackages() did not error on bad input") 345 } 346 if _, _, err = sm.GetManifestAndLock(bad, nil, naiveAnalyzer{}); err == nil { 347 t.Error("GetManifestAndLock() did not error on bad input") 348 } 349 if err = sm.ExportProject(context.Background(), bad, nil, ""); err == nil { 350 t.Error("ExportProject() did not error on bad input") 351 } 352 } 353 354 type sourceCreationTestFixture struct { 355 roots []ProjectIdentifier 356 namecount, srccount int 357 } 358 359 func (f sourceCreationTestFixture) run(t *testing.T) { 360 t.Parallel() 361 sm, clean := mkNaiveSM(t) 362 defer clean() 363 364 for _, pi := range f.roots { 365 _, err := sm.SourceExists(pi) 366 if err != nil { 367 t.Fatal(err) 368 } 369 } 370 371 if len(sm.srcCoord.nameToURL) != f.namecount { 372 t.Errorf("want %v names in the name->url map, but got %v. contents: \n%v", f.namecount, len(sm.srcCoord.nameToURL), sm.srcCoord.nameToURL) 373 } 374 375 if len(sm.srcCoord.srcs) != f.srccount { 376 t.Errorf("want %v gateways in the sources map, but got %v", f.srccount, len(sm.srcCoord.srcs)) 377 } 378 379 if t.Failed() { 380 var keys []string 381 for k := range sm.srcCoord.nameToURL { 382 keys = append(keys, k) 383 } 384 sort.Strings(keys) 385 386 var buf bytes.Buffer 387 w := tabwriter.NewWriter(&buf, 0, 4, 2, ' ', 0) 388 fmt.Fprint(w, "NAME\tMAPPED URL\n") 389 for _, r := range keys { 390 fmt.Fprintf(w, "%s\t%s\n", r, sm.srcCoord.nameToURL[r]) 391 } 392 w.Flush() 393 t.Log("\n", buf.String()) 394 395 t.Log("SRC KEYS") 396 for k := range sm.srcCoord.srcs { 397 t.Log(k) 398 } 399 } 400 } 401 402 // This test is primarily about making sure that the logic around folding 403 // together different ways of referencing the same underlying resource - whether 404 // that be intentionally folding them, or intentionally keeping them separate - 405 // work as intended. 406 func TestSourceCreationCounts(t *testing.T) { 407 if testing.Short() { 408 t.Skip("Skipping slow test in short mode") 409 } 410 411 fixtures := map[string]sourceCreationTestFixture{ 412 "gopkgin uniqueness": { 413 roots: []ProjectIdentifier{ 414 mkPI("gopkg.in/sdboyer/gpkt.v1"), 415 mkPI("gopkg.in/sdboyer/gpkt.v2"), 416 mkPI("gopkg.in/sdboyer/gpkt.v3"), 417 }, 418 namecount: 6, 419 srccount: 3, 420 }, 421 "gopkgin separation from github": { 422 roots: []ProjectIdentifier{ 423 mkPI("gopkg.in/sdboyer/gpkt.v1"), 424 mkPI("github.com/sdboyer/gpkt"), 425 mkPI("http://github.com/sdboyer/gpkt"), 426 mkPI("https://github.com/sdboyer/gpkt"), 427 }, 428 namecount: 5, 429 srccount: 3, 430 }, 431 "case variance across path and URL-based access": { 432 roots: []ProjectIdentifier{ 433 {ProjectRoot: ProjectRoot("github.com/sdboyer/gpkt"), Source: "https://github.com/Sdboyer/gpkt"}, 434 {ProjectRoot: ProjectRoot("github.com/sdboyer/gpkt"), Source: "https://github.com/SdbOyer/gpkt"}, 435 mkPI("github.com/sdboyer/gpkt"), 436 {ProjectRoot: ProjectRoot("github.com/sdboyer/gpkt"), Source: "https://github.com/sdboyeR/gpkt"}, 437 mkPI("github.com/sdboyeR/gpkt"), 438 }, 439 namecount: 6, 440 srccount: 1, 441 }, 442 } 443 444 for name, fix := range fixtures { 445 t.Run(name, fix.run) 446 } 447 } 448 449 func TestGetSources(t *testing.T) { 450 // This test is a tad slow, skip it on -short 451 if testing.Short() { 452 t.Skip("Skipping source setup test in short mode") 453 } 454 requiresBins(t, "git", "hg", "bzr") 455 456 sm, clean := mkNaiveSM(t) 457 458 pil := []ProjectIdentifier{ 459 mkPI("github.com/Masterminds/VCSTestRepo").normalize(), 460 mkPI("bitbucket.org/mattfarina/testhgrepo").normalize(), 461 mkPI("launchpad.net/govcstestbzrrepo").normalize(), 462 } 463 464 ctx := context.Background() 465 // protects against premature release of sm 466 t.Run("inner", func(t *testing.T) { 467 for _, pi := range pil { 468 lpi := pi 469 t.Run(lpi.normalizedSource(), func(t *testing.T) { 470 t.Parallel() 471 472 srcg, err := sm.srcCoord.getSourceGatewayFor(ctx, lpi) 473 if err != nil { 474 t.Errorf("unexpected error setting up source: %s", err) 475 return 476 } 477 478 // Re-get the same, make sure they are the same 479 srcg2, err := sm.srcCoord.getSourceGatewayFor(ctx, lpi) 480 if err != nil { 481 t.Errorf("unexpected error re-getting source: %s", err) 482 } else if srcg != srcg2 { 483 t.Error("first and second sources are not eq") 484 } 485 486 // All of them _should_ select https, so this should work 487 lpi.Source = "https://" + lpi.Source 488 srcg3, err := sm.srcCoord.getSourceGatewayFor(ctx, lpi) 489 if err != nil { 490 t.Errorf("unexpected error getting explicit https source: %s", err) 491 } else if srcg != srcg3 { 492 t.Error("explicit https source should reuse autodetected https source") 493 } 494 495 // Now put in http, and they should differ 496 lpi.Source = "http://" + string(lpi.ProjectRoot) 497 srcg4, err := sm.srcCoord.getSourceGatewayFor(ctx, lpi) 498 if err != nil { 499 t.Errorf("unexpected error getting explicit http source: %s", err) 500 } else if srcg == srcg4 { 501 t.Error("explicit http source should create a new src") 502 } 503 }) 504 } 505 }) 506 507 // nine entries (of which three are dupes): for each vcs, raw import path, 508 // the https url, and the http url. also three more from case folding of 509 // github.com/Masterminds/VCSTestRepo -> github.com/masterminds/vcstestrepo 510 if len(sm.srcCoord.nameToURL) != 12 { 511 t.Errorf("Should have twelve discrete entries in the nameToURL map, got %v", len(sm.srcCoord.nameToURL)) 512 } 513 clean() 514 } 515 516 func TestFSCaseSensitivityConvergesSources(t *testing.T) { 517 if testing.Short() { 518 t.Skip("Skipping slow test in short mode") 519 } 520 521 f := func(name string, pi1, pi2 ProjectIdentifier) { 522 t.Run(name, func(t *testing.T) { 523 t.Parallel() 524 sm, clean := mkNaiveSM(t) 525 defer clean() 526 527 sm.SyncSourceFor(pi1) 528 sg1, err := sm.srcCoord.getSourceGatewayFor(context.Background(), pi1) 529 if err != nil { 530 t.Fatal(err) 531 } 532 533 sm.SyncSourceFor(pi2) 534 sg2, err := sm.srcCoord.getSourceGatewayFor(context.Background(), pi2) 535 if err != nil { 536 t.Fatal(err) 537 } 538 539 path1 := sg1.src.(*gitSource).repo.LocalPath() 540 stat1, err := os.Stat(path1) 541 if err != nil { 542 t.Fatal("path1:", path1, err) 543 } 544 path2 := sg2.src.(*gitSource).repo.LocalPath() 545 stat2, err := os.Stat(path2) 546 if err != nil { 547 t.Fatal("path2:", path2, err) 548 } 549 550 same, count := os.SameFile(stat1, stat2), len(sm.srcCoord.srcs) 551 if same && count != 1 { 552 t.Log("are same, count", count) 553 t.Fatal("on case-insensitive filesystem, case-varying sources should have been folded together but were not") 554 } 555 if !same && count != 2 { 556 t.Log("not same, count", count) 557 t.Fatal("on case-sensitive filesystem, case-varying sources should not have been folded together, but were") 558 } 559 }) 560 } 561 562 folded := mkPI("github.com/sdboyer/deptest").normalize() 563 casevar1 := mkPI("github.com/Sdboyer/deptest").normalize() 564 casevar2 := mkPI("github.com/SdboyeR/deptest").normalize() 565 f("folded first", folded, casevar1) 566 f("folded second", casevar1, folded) 567 f("both unfolded", casevar1, casevar2) 568 } 569 570 // Regression test for #32 571 func TestGetInfoListVersionsOrdering(t *testing.T) { 572 // This test is quite slow, skip it on -short 573 if testing.Short() { 574 t.Skip("Skipping slow test in short mode") 575 } 576 577 sm, clean := mkNaiveSM(t) 578 defer clean() 579 580 // setup done, now do the test 581 582 id := mkPI("github.com/sdboyer/gpkt").normalize() 583 584 _, _, err := sm.GetManifestAndLock(id, NewVersion("v1.0.0"), naiveAnalyzer{}) 585 if err != nil { 586 t.Errorf("Unexpected error from GetInfoAt %s", err) 587 } 588 589 v, err := sm.ListVersions(id) 590 if err != nil { 591 t.Errorf("Unexpected error from ListVersions %s", err) 592 } 593 594 if len(v) != 7 { 595 t.Errorf("Expected seven results from ListVersions, got %v", len(v)) 596 } 597 } 598 599 func TestDeduceProjectRoot(t *testing.T) { 600 sm, clean := mkNaiveSM(t) 601 defer clean() 602 603 in := "github.com/sdboyer/gps" 604 pr, err := sm.DeduceProjectRoot(in) 605 if err != nil { 606 t.Errorf("Problem while detecting root of %q %s", in, err) 607 } 608 if string(pr) != in { 609 t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in) 610 } 611 if sm.deduceCoord.rootxt.Len() != 1 { 612 t.Errorf("Root path trie should have one element after one deduction, has %v", sm.deduceCoord.rootxt.Len()) 613 } 614 615 pr, err = sm.DeduceProjectRoot(in) 616 if err != nil { 617 t.Errorf("Problem while detecting root of %q %s", in, err) 618 } else if string(pr) != in { 619 t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in) 620 } 621 if sm.deduceCoord.rootxt.Len() != 1 { 622 t.Errorf("Root path trie should still have one element after performing the same deduction twice; has %v", sm.deduceCoord.rootxt.Len()) 623 } 624 625 // Now do a subpath 626 sub := path.Join(in, "foo") 627 pr, err = sm.DeduceProjectRoot(sub) 628 if err != nil { 629 t.Errorf("Problem while detecting root of %q %s", sub, err) 630 } else if string(pr) != in { 631 t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in) 632 } 633 if sm.deduceCoord.rootxt.Len() != 1 { 634 t.Errorf("Root path trie should still have one element, as still only one unique root has gone in; has %v", sm.deduceCoord.rootxt.Len()) 635 } 636 637 // Now do a fully different root, but still on github 638 in2 := "github.com/bagel/lox" 639 sub2 := path.Join(in2, "cheese") 640 pr, err = sm.DeduceProjectRoot(sub2) 641 if err != nil { 642 t.Errorf("Problem while detecting root of %q %s", sub2, err) 643 } else if string(pr) != in2 { 644 t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in) 645 } 646 if sm.deduceCoord.rootxt.Len() != 2 { 647 t.Errorf("Root path trie should have two elements, one for each unique root; has %v", sm.deduceCoord.rootxt.Len()) 648 } 649 650 // Ensure that our prefixes are bounded by path separators 651 in4 := "github.com/bagel/loxx" 652 pr, err = sm.DeduceProjectRoot(in4) 653 if err != nil { 654 t.Errorf("Problem while detecting root of %q %s", in4, err) 655 } else if string(pr) != in4 { 656 t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in) 657 } 658 if sm.deduceCoord.rootxt.Len() != 3 { 659 t.Errorf("Root path trie should have three elements, one for each unique root; has %v", sm.deduceCoord.rootxt.Len()) 660 } 661 662 // Ensure that vcs extension-based matching comes through 663 in5 := "ffffrrrraaaaaapppppdoesnotresolve.com/baz.git" 664 pr, err = sm.DeduceProjectRoot(in5) 665 if err != nil { 666 t.Errorf("Problem while detecting root of %q %s", in5, err) 667 } else if string(pr) != in5 { 668 t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in) 669 } 670 if sm.deduceCoord.rootxt.Len() != 4 { 671 t.Errorf("Root path trie should have four elements, one for each unique root; has %v", sm.deduceCoord.rootxt.Len()) 672 } 673 } 674 675 func TestMultiFetchThreadsafe(t *testing.T) { 676 // This test is quite slow, skip it on -short 677 if testing.Short() { 678 t.Skip("Skipping slow test in short mode") 679 } 680 681 projects := []ProjectIdentifier{ 682 mkPI("github.com/sdboyer/gps"), 683 mkPI("github.com/sdboyer/gpkt"), 684 { 685 ProjectRoot: ProjectRoot("github.com/sdboyer/gpkt"), 686 Source: "https://github.com/sdboyer/gpkt", 687 }, 688 mkPI("github.com/sdboyer/gogl"), 689 mkPI("github.com/sdboyer/gliph"), 690 mkPI("github.com/sdboyer/frozone"), 691 mkPI("gopkg.in/sdboyer/gpkt.v1"), 692 mkPI("gopkg.in/sdboyer/gpkt.v2"), 693 mkPI("github.com/Masterminds/VCSTestRepo"), 694 mkPI("github.com/go-yaml/yaml"), 695 mkPI("github.com/sirupsen/logrus"), 696 mkPI("github.com/Masterminds/semver"), 697 mkPI("github.com/Masterminds/vcs"), 698 //mkPI("bitbucket.org/sdboyer/withbm"), 699 //mkPI("bitbucket.org/sdboyer/nobm"), 700 } 701 702 do := func(name string, sm SourceManager) { 703 t.Run(name, func(t *testing.T) { 704 // This gives us ten calls per op, per project, which should be(?) 705 // decently likely to reveal underlying concurrency problems 706 ops := 4 707 cnum := len(projects) * ops * 10 708 709 for i := 0; i < cnum; i++ { 710 // Trigger all four ops on each project, then move on to the next 711 // project. 712 id, op := projects[(i/ops)%len(projects)], i%ops 713 // The count of times this op has been been invoked on this project 714 // (after the upcoming invocation) 715 opcount := i/(ops*len(projects)) + 1 716 717 switch op { 718 case 0: 719 t.Run(fmt.Sprintf("deduce:%v:%s", opcount, id), func(t *testing.T) { 720 t.Parallel() 721 if _, err := sm.DeduceProjectRoot(string(id.ProjectRoot)); err != nil { 722 t.Error(err) 723 } 724 }) 725 case 1: 726 t.Run(fmt.Sprintf("sync:%v:%s", opcount, id), func(t *testing.T) { 727 t.Parallel() 728 err := sm.SyncSourceFor(id) 729 if err != nil { 730 t.Error(err) 731 } 732 }) 733 case 2: 734 t.Run(fmt.Sprintf("listVersions:%v:%s", opcount, id), func(t *testing.T) { 735 t.Parallel() 736 vl, err := sm.ListVersions(id) 737 if err != nil { 738 t.Fatal(err) 739 } 740 if len(vl) == 0 { 741 t.Error("no versions returned") 742 } 743 }) 744 case 3: 745 t.Run(fmt.Sprintf("exists:%v:%s", opcount, id), func(t *testing.T) { 746 t.Parallel() 747 y, err := sm.SourceExists(id) 748 if err != nil { 749 t.Fatal(err) 750 } 751 if !y { 752 t.Error("said source does not exist") 753 } 754 }) 755 default: 756 panic(fmt.Sprintf("wtf, %s %v", id, op)) 757 } 758 } 759 }) 760 } 761 762 sm, _ := mkNaiveSM(t) 763 do("first", sm) 764 765 // Run the thing twice with a remade sm so that we cover both the cases of 766 // pre-existing and new clones. 767 // 768 // This triggers a release of the first sm, which is much of what we're 769 // testing here - that the release is complete and clean, and can be 770 // immediately followed by a new sm coming in. 771 sm2, clean := remakeNaiveSM(sm, t) 772 do("second", sm2) 773 clean() 774 } 775 776 // Ensure that we don't see concurrent map writes when calling ListVersions. 777 // Regression test for https://github.com/sdboyer/gps/issues/156. 778 // 779 // Ideally this would be caught by TestMultiFetchThreadsafe, but perhaps the 780 // high degree of parallelism pretty much eliminates that as a realistic 781 // possibility? 782 func TestListVersionsRacey(t *testing.T) { 783 // This test is quite slow, skip it on -short 784 if testing.Short() { 785 t.Skip("Skipping slow test in short mode") 786 } 787 788 sm, clean := mkNaiveSM(t) 789 defer clean() 790 791 wg := &sync.WaitGroup{} 792 id := mkPI("github.com/sdboyer/gps") 793 for i := 0; i < 20; i++ { 794 wg.Add(1) 795 go func() { 796 _, err := sm.ListVersions(id) 797 if err != nil { 798 t.Errorf("listing versions failed with err %s", err.Error()) 799 } 800 wg.Done() 801 }() 802 } 803 804 wg.Wait() 805 } 806 807 func TestErrAfterRelease(t *testing.T) { 808 sm, clean := mkNaiveSM(t) 809 clean() 810 id := ProjectIdentifier{} 811 812 _, err := sm.SourceExists(id) 813 if err == nil { 814 t.Errorf("SourceExists did not error after calling Release()") 815 } else if err != ErrSourceManagerIsReleased { 816 t.Errorf("SourceExists errored after Release(), but with unexpected error: %T %s", err, err.Error()) 817 } 818 819 err = sm.SyncSourceFor(id) 820 if err == nil { 821 t.Errorf("SyncSourceFor did not error after calling Release()") 822 } else if err != ErrSourceManagerIsReleased { 823 t.Errorf("SyncSourceFor errored after Release(), but with unexpected error: %T %s", err, err.Error()) 824 } 825 826 _, err = sm.ListVersions(id) 827 if err == nil { 828 t.Errorf("ListVersions did not error after calling Release()") 829 } else if err != ErrSourceManagerIsReleased { 830 t.Errorf("ListVersions errored after Release(), but with unexpected error: %T %s", err, err.Error()) 831 } 832 833 _, err = sm.RevisionPresentIn(id, "") 834 if err == nil { 835 t.Errorf("RevisionPresentIn did not error after calling Release()") 836 } else if err != ErrSourceManagerIsReleased { 837 t.Errorf("RevisionPresentIn errored after Release(), but with unexpected error: %T %s", err, err.Error()) 838 } 839 840 _, err = sm.ListPackages(id, nil) 841 if err == nil { 842 t.Errorf("ListPackages did not error after calling Release()") 843 } else if err != ErrSourceManagerIsReleased { 844 t.Errorf("ListPackages errored after Release(), but with unexpected error: %T %s", err, err.Error()) 845 } 846 847 _, _, err = sm.GetManifestAndLock(id, nil, naiveAnalyzer{}) 848 if err == nil { 849 t.Errorf("GetManifestAndLock did not error after calling Release()") 850 } else if err != ErrSourceManagerIsReleased { 851 t.Errorf("GetManifestAndLock errored after Release(), but with unexpected error: %T %s", err, err.Error()) 852 } 853 854 err = sm.ExportProject(context.Background(), id, nil, "") 855 if err == nil { 856 t.Errorf("ExportProject did not error after calling Release()") 857 } else if err != ErrSourceManagerIsReleased { 858 t.Errorf("ExportProject errored after Release(), but with unexpected error: %T %s", err, err.Error()) 859 } 860 861 _, err = sm.DeduceProjectRoot("") 862 if err == nil { 863 t.Errorf("DeduceProjectRoot did not error after calling Release()") 864 } else if err != ErrSourceManagerIsReleased { 865 t.Errorf("DeduceProjectRoot errored after Release(), but with unexpected error: %T %s", err, err.Error()) 866 } 867 } 868 869 func TestSignalHandling(t *testing.T) { 870 if testing.Short() { 871 t.Skip("Skipping slow test in short mode") 872 } 873 874 sm, clean := mkNaiveSM(t) 875 876 sigch := make(chan os.Signal) 877 sm.HandleSignals(sigch) 878 879 sigch <- os.Interrupt 880 <-time.After(10 * time.Millisecond) 881 882 if atomic.LoadInt32(&sm.releasing) != 1 { 883 t.Error("Releasing flag did not get set") 884 } 885 886 clean() 887 888 // Test again, this time with a running call 889 sm, clean = mkNaiveSM(t) 890 sm.HandleSignals(sigch) 891 892 errchan := make(chan error) 893 go func() { 894 _, callerr := sm.DeduceProjectRoot("k8s.io/kubernetes") 895 errchan <- callerr 896 }() 897 go func() { sigch <- os.Interrupt }() 898 runtime.Gosched() 899 900 callerr := <-errchan 901 if callerr == nil { 902 t.Error("network call could not have completed before cancellation, should have gotten an error") 903 } 904 if atomic.LoadInt32(&sm.releasing) != 1 { 905 t.Error("Releasing flag did not get set") 906 } 907 clean() 908 909 sm, clean = mkNaiveSM(t) 910 // Ensure that handling also works after stopping and restarting itself, 911 // and that Release happens only once. 912 sm.UseDefaultSignalHandling() 913 sm.StopSignalHandling() 914 sm.HandleSignals(sigch) 915 916 go func() { 917 _, callerr := sm.DeduceProjectRoot("k8s.io/kubernetes") 918 errchan <- callerr 919 }() 920 go func() { 921 sigch <- os.Interrupt 922 sm.Release() 923 }() 924 runtime.Gosched() 925 926 after := time.After(2 * time.Second) 927 select { 928 case <-sm.qch: 929 case <-after: 930 t.Error("did not shut down in reasonable time") 931 } 932 933 clean() 934 } 935 936 func TestUnreachableSource(t *testing.T) { 937 // If a git remote is unreachable (maybe the server is only accessible behind a VPN, or 938 // something), we should return a clear error, not a panic. 939 if testing.Short() { 940 t.Skip("Skipping slow test in short mode") 941 } 942 943 sm, clean := mkNaiveSM(t) 944 defer clean() 945 946 id := mkPI("github.com/golang/notexist").normalize() 947 err := sm.SyncSourceFor(id) 948 if err == nil { 949 t.Error("expected err when listing versions of a bogus source, but got nil") 950 } 951 } 952 953 func TestSupervisor(t *testing.T) { 954 bgc := context.Background() 955 ctx, cancelFunc := context.WithCancel(bgc) 956 superv := newSupervisor(ctx) 957 958 ci := callInfo{ 959 name: "foo", 960 typ: 0, 961 } 962 963 _, err := superv.start(ci) 964 if err != nil { 965 t.Fatal("unexpected err on setUpCall:", err) 966 } 967 968 tc, exists := superv.running[ci] 969 if !exists { 970 t.Fatal("running call not recorded in map") 971 } 972 973 if tc.count != 1 { 974 t.Fatalf("wrong count of running ci: wanted 1 got %v", tc.count) 975 } 976 977 // run another, but via do 978 block, wait := make(chan struct{}), make(chan struct{}) 979 errchan := make(chan error) 980 go func() { 981 wait <- struct{}{} 982 err := superv.do(bgc, "foo", 0, func(ctx context.Context) error { 983 <-block 984 return nil 985 }) 986 errchan <- err 987 //if err != nil { 988 // t.Fatal("unexpected err on do() completion:", err) 989 //} 990 close(wait) 991 }() 992 <-wait 993 994 superv.mu.Lock() 995 tc, exists = superv.running[ci] 996 if !exists { 997 t.Fatal("running call not recorded in map") 998 } 999 1000 // TODO (kris-nova) We need to disable this bypass here, and in the .travis.yml 1001 // as soon as dep#501 is fixed 1002 bypass := os.Getenv("DEPTESTBYPASS501") 1003 if bypass != "" { 1004 t.Log("bypassing tc.count check for running ci") 1005 } else if tc.count != 2 { 1006 t.Fatalf("wrong count of running ci: wanted 2 got %v", tc.count) 1007 } 1008 superv.mu.Unlock() 1009 1010 close(block) 1011 1012 possibleConcurrentError := <-errchan 1013 if possibleConcurrentError != nil { 1014 t.Fatal("unexpected err on do() completion:", err) 1015 } 1016 1017 <-wait 1018 superv.mu.Lock() 1019 if len(superv.ran) != 0 { 1020 t.Fatal("should not record metrics until last one drops") 1021 } 1022 1023 tc, exists = superv.running[ci] 1024 if !exists { 1025 t.Fatal("running call not recorded in map") 1026 } 1027 1028 if tc.count != 1 { 1029 t.Fatalf("wrong count of running ci: wanted 1 got %v", tc.count) 1030 } 1031 superv.mu.Unlock() 1032 1033 superv.done(ci) 1034 superv.mu.Lock() 1035 ran, exists := superv.ran[0] 1036 if !exists { 1037 t.Fatal("should have metrics after closing last of a ci, but did not") 1038 } 1039 1040 if ran.count != 1 { 1041 t.Fatalf("wrong count of serial runs of a call: wanted 1 got %v", ran.count) 1042 } 1043 superv.mu.Unlock() 1044 1045 cancelFunc() 1046 _, err = superv.start(ci) 1047 if err == nil { 1048 t.Fatal("should have errored on cm.run() after canceling cm's input context") 1049 } 1050 1051 superv.do(bgc, "foo", 0, func(ctx context.Context) error { 1052 t.Fatal("calls should not be initiated by do() after main context is cancelled") 1053 return nil 1054 }) 1055 }