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