github.com/golang/dep@v0.5.4/gps/solve_basic_test.go (about) 1 // Copyright 2017 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 "context" 9 "fmt" 10 "net/url" 11 "regexp" 12 "strings" 13 14 "github.com/Masterminds/semver" 15 "github.com/golang/dep/gps/pkgtree" 16 ) 17 18 var regfrom = regexp.MustCompile(`^(\w*) from (\w*) ([0-9\.\*]*)`) 19 20 // nvSplit splits an "info" string on " " into the pair of name and 21 // version/constraint, and returns each individually. 22 // 23 // This is for narrow use - panics if there are less than two resulting items in 24 // the slice. 25 func nvSplit(info string) (id ProjectIdentifier, version string) { 26 if strings.Contains(info, " from ") { 27 parts := regfrom.FindStringSubmatch(info) 28 info = parts[1] + " " + parts[3] 29 id.Source = parts[2] 30 } 31 32 s := strings.SplitN(info, " ", 2) 33 if len(s) < 2 { 34 panic(fmt.Sprintf("Malformed name/version info string '%s'", info)) 35 } 36 37 id.ProjectRoot, version = ProjectRoot(s[0]), s[1] 38 return 39 } 40 41 // nvrSplit splits an "info" string on " " into the triplet of name, 42 // version/constraint, and revision, and returns each individually. 43 // 44 // It will work fine if only name and version/constraint are provided. 45 // 46 // This is for narrow use - panics if there are less than two resulting items in 47 // the slice. 48 func nvrSplit(info string) (id ProjectIdentifier, version string, revision Revision) { 49 if strings.Contains(info, " from ") { 50 parts := regfrom.FindStringSubmatch(info) 51 info = fmt.Sprintf("%s %s", parts[1], parts[3]) 52 id.Source = parts[2] 53 } 54 55 s := strings.SplitN(info, " ", 3) 56 if len(s) < 2 { 57 panic(fmt.Sprintf("Malformed name/version info string '%s'", info)) 58 } 59 60 id.ProjectRoot, version = ProjectRoot(s[0]), s[1] 61 62 if len(s) == 3 { 63 revision = Revision(s[2]) 64 } 65 return 66 } 67 68 // mkAtom splits the input string on a space, and uses the first two elements as 69 // the project identifier and version, respectively. 70 // 71 // The version segment may have a leading character indicating the type of 72 // version to create: 73 // 74 // p: create a "plain" (non-semver) version. 75 // b: create a branch version. 76 // r: create a revision. 77 // 78 // No prefix is assumed to indicate a semver version. 79 // 80 // If a third space-delimited element is provided, it will be interepreted as a 81 // revision, and used as the underlying version in a PairedVersion. No prefix 82 // should be provided in this case. It is an error (and will panic) to try to 83 // pass a revision with an underlying revision. 84 func mkAtom(info string) atom { 85 // if info is "root", special case it to use the root "version" 86 if info == "root" { 87 return atom{ 88 id: ProjectIdentifier{ 89 ProjectRoot: ProjectRoot("root"), 90 }, 91 v: rootRev, 92 } 93 } 94 95 id, ver, rev := nvrSplit(info) 96 97 var v Version 98 switch ver[0] { 99 case 'r': 100 if rev != "" { 101 panic("Cannot pair a revision with a revision") 102 } 103 v = Revision(ver[1:]) 104 case 'p': 105 v = NewVersion(ver[1:]) 106 case 'b': 107 v = NewBranch(ver[1:]) 108 default: 109 _, err := semver.NewVersion(ver) 110 if err != nil { 111 // don't want to allow bad test data at this level, so just panic 112 panic(fmt.Sprintf("Error when converting '%s' into semver: %s", ver, err)) 113 } 114 v = NewVersion(ver) 115 } 116 117 if rev != "" { 118 v = v.(UnpairedVersion).Pair(rev) 119 } 120 121 return atom{ 122 id: id, 123 v: v, 124 } 125 } 126 127 // mkPCstrnt splits the input string on a space, and uses the first two elements 128 // as the project identifier and constraint body, respectively. 129 // 130 // The constraint body may have a leading character indicating the type of 131 // version to create: 132 // 133 // p: create a "plain" (non-semver) version. 134 // b: create a branch version. 135 // r: create a revision. 136 // 137 // If no leading character is used, a semver constraint is assumed. 138 func mkPCstrnt(info string) ProjectConstraint { 139 id, ver, rev := nvrSplit(info) 140 141 var c Constraint 142 switch ver[0] { 143 case 'r': 144 c = Revision(ver[1:]) 145 case 'p': 146 c = NewVersion(ver[1:]) 147 case 'b': 148 c = NewBranch(ver[1:]) 149 default: 150 // Without one of those leading characters, we know it's a proper semver 151 // expression, so use the other parser that doesn't look for a rev 152 rev = "" 153 id, ver = nvSplit(info) 154 var err error 155 c, err = NewSemverConstraint(ver) 156 if err != nil { 157 // don't want bad test data at this level, so just panic 158 panic(fmt.Sprintf("Error when converting '%s' into semver constraint: %s (full info: %s)", ver, err, info)) 159 } 160 } 161 162 // There's no practical reason that a real tool would need to produce a 163 // constraint that's a PairedVersion, but it is a possibility admitted by the 164 // system, so we at least allow for it in our testing harness. 165 if rev != "" { 166 // Of course, this *will* panic if the predicate is a revision or a 167 // semver constraint, neither of which implement UnpairedVersion. This 168 // is as intended, to prevent bad data from entering the system. 169 c = c.(UnpairedVersion).Pair(rev) 170 } 171 172 return ProjectConstraint{ 173 Ident: id, 174 Constraint: c, 175 } 176 } 177 178 // mkCDep composes a completeDep struct from the inputs. 179 // 180 // The only real work here is passing the initial string to mkPDep. All the 181 // other args are taken as package names. 182 func mkCDep(pdep string, pl ...string) completeDep { 183 pc := mkPCstrnt(pdep) 184 return completeDep{ 185 workingConstraint: workingConstraint{ 186 Ident: pc.Ident, 187 Constraint: pc.Constraint, 188 }, 189 pl: pl, 190 } 191 } 192 193 // A depspec is a fixture representing all the information a SourceManager would 194 // ordinarily glean directly from interrogating a repository. 195 type depspec struct { 196 n ProjectRoot 197 v Version 198 deps []ProjectConstraint 199 pkgs []tpkg 200 } 201 202 // mkDepspec creates a depspec by processing a series of strings, each of which 203 // contains an identiifer and version information. 204 // 205 // The first string is broken out into the name and version of the package being 206 // described - see the docs on mkAtom for details. subsequent strings are 207 // interpreted as dep constraints of that dep at that version. See the docs on 208 // mkPDep for details. 209 func mkDepspec(pi string, deps ...string) depspec { 210 pa := mkAtom(pi) 211 if string(pa.id.ProjectRoot) != pa.id.Source && pa.id.Source != "" { 212 panic("alternate source on self makes no sense") 213 } 214 215 ds := depspec{ 216 n: pa.id.ProjectRoot, 217 v: pa.v, 218 } 219 220 for _, dep := range deps { 221 ds.deps = append(ds.deps, mkPCstrnt(dep)) 222 } 223 224 return ds 225 } 226 227 func mkDep(atom, pdep string, pl ...string) dependency { 228 return dependency{ 229 depender: mkAtom(atom), 230 dep: mkCDep(pdep, pl...), 231 } 232 } 233 234 func mkADep(atom, pdep string, c Constraint, pl ...string) dependency { 235 return dependency{ 236 depender: mkAtom(atom), 237 dep: completeDep{ 238 workingConstraint: workingConstraint{ 239 Ident: ProjectIdentifier{ 240 ProjectRoot: ProjectRoot(pdep), 241 }, 242 Constraint: c, 243 }, 244 pl: pl, 245 }, 246 } 247 } 248 249 // mkPI creates a ProjectIdentifier with the ProjectRoot as the provided 250 // string, and the Source unset. 251 // 252 // Call normalize() on the returned value if you need the Source to be be 253 // equal to the ProjectRoot. 254 func mkPI(root string) ProjectIdentifier { 255 return ProjectIdentifier{ 256 ProjectRoot: ProjectRoot(root), 257 } 258 } 259 260 // mkSVC creates a new semver constraint, panicking if an error is returned. 261 func mkSVC(body string) Constraint { 262 c, err := NewSemverConstraint(body) 263 if err != nil { 264 panic(fmt.Sprintf("Error while trying to create semver constraint from %s: %s", body, err.Error())) 265 } 266 return c 267 } 268 269 // mklock makes a fixLock, suitable to act as a lock file 270 func mklock(pairs ...string) fixLock { 271 l := make(fixLock, 0) 272 for _, s := range pairs { 273 pa := mkAtom(s) 274 l = append(l, NewLockedProject(pa.id, pa.v, nil)) 275 } 276 277 return l 278 } 279 280 // mkrevlock makes a fixLock, suitable to act as a lock file, with only a name 281 // and a rev 282 func mkrevlock(pairs ...string) fixLock { 283 l := make(fixLock, 0) 284 for _, s := range pairs { 285 pa := mkAtom(s) 286 l = append(l, NewLockedProject(pa.id, pa.v.(PairedVersion).Revision(), nil)) 287 } 288 289 return l 290 } 291 292 // mksolution creates a map of project identifiers to their LockedProject 293 // result, which is sufficient to act as a solution fixture for the purposes of 294 // most tests. 295 // 296 // Either strings or LockedProjects can be provided. If a string is provided, it 297 // is assumed that we're in the default, "basic" case where there is exactly one 298 // package in a project, and it is the root of the project - meaning that only 299 // the "." package should be listed. If a LockedProject is provided (e.g. as 300 // returned from mklp()), then it's incorporated directly. 301 // 302 // If any other type is provided, the func will panic. 303 func mksolution(inputs ...interface{}) map[ProjectIdentifier]LockedProject { 304 m := make(map[ProjectIdentifier]LockedProject) 305 for _, in := range inputs { 306 switch t := in.(type) { 307 case string: 308 a := mkAtom(t) 309 m[a.id] = NewLockedProject(a.id, a.v, []string{"."}) 310 case LockedProject: 311 m[t.Ident()] = t 312 default: 313 panic(fmt.Sprintf("unexpected input to mksolution: %T %s", in, in)) 314 } 315 } 316 317 return m 318 } 319 320 // mklp creates a LockedProject from string inputs 321 func mklp(pair string, pkgs ...string) LockedProject { 322 a := mkAtom(pair) 323 return NewLockedProject(a.id, a.v, pkgs) 324 } 325 326 // computeBasicReachMap takes a depspec and computes a reach map which is 327 // identical to the explicit depgraph. 328 // 329 // Using a reachMap here is overkill for what the basic fixtures actually need, 330 // but we use it anyway for congruence with the more general cases. 331 func computeBasicReachMap(ds []depspec) reachMap { 332 rm := make(reachMap) 333 334 for k, d := range ds { 335 n := string(d.n) 336 lm := map[string][]string{ 337 n: nil, 338 } 339 v := d.v 340 if k == 0 { 341 // Put the root in with a nil rev, to accommodate the solver 342 v = nil 343 } 344 rm[pident{n: d.n, v: v}] = lm 345 346 for _, dep := range d.deps { 347 lm[n] = append(lm[n], string(dep.Ident.ProjectRoot)) 348 } 349 } 350 351 return rm 352 } 353 354 type pident struct { 355 n ProjectRoot 356 v Version 357 } 358 359 type specfix interface { 360 name() string 361 rootmanifest() RootManifest 362 rootTree() pkgtree.PackageTree 363 specs() []depspec 364 maxTries() int 365 solution() map[ProjectIdentifier]LockedProject 366 failure() error 367 } 368 369 // A basicFixture is a declarative test fixture that can cover a wide variety of 370 // solver cases. All cases, however, maintain one invariant: package == project. 371 // There are no subpackages, and so it is impossible for them to trigger or 372 // require bimodal solving. 373 // 374 // This type is separate from bimodalFixture in part for legacy reasons - many 375 // of these were adapted from similar tests in dart's pub lib, where there is no 376 // such thing as "bimodal solving". 377 // 378 // But it's also useful to keep them separate because bimodal solving involves 379 // considerably more complexity than simple solving, both in terms of fixture 380 // declaration and actual solving mechanics. Thus, we gain a lot of value for 381 // contributors and maintainers by keeping comprehension costs relatively low 382 // while still covering important cases. 383 type basicFixture struct { 384 // name of this fixture datum 385 n string 386 // depspecs. always treat first as root 387 ds []depspec 388 // results; map of name/atom pairs 389 r map[ProjectIdentifier]LockedProject 390 // max attempts the solver should need to find solution. 0 means no limit 391 maxAttempts int 392 // Use downgrade instead of default upgrade sorter 393 downgrade bool 394 // lock file simulator, if one's to be used at all 395 l fixLock 396 // solve failure expected, if any 397 fail error 398 // overrides, if any 399 ovr ProjectConstraints 400 // request up/downgrade to all projects 401 changeall bool 402 // individual projects to change 403 changelist []ProjectRoot 404 // if the fixture is currently broken/expected to fail, this has a message 405 // recording why 406 broken string 407 } 408 409 func (f basicFixture) name() string { 410 return f.n 411 } 412 413 func (f basicFixture) specs() []depspec { 414 return f.ds 415 } 416 417 func (f basicFixture) maxTries() int { 418 return f.maxAttempts 419 } 420 421 func (f basicFixture) solution() map[ProjectIdentifier]LockedProject { 422 return f.r 423 } 424 425 func (f basicFixture) rootmanifest() RootManifest { 426 return simpleRootManifest{ 427 c: pcSliceToMap(f.ds[0].deps), 428 ovr: f.ovr, 429 } 430 } 431 432 func (f basicFixture) rootTree() pkgtree.PackageTree { 433 var imp []string 434 for _, dep := range f.ds[0].deps { 435 imp = append(imp, string(dep.Ident.ProjectRoot)) 436 } 437 438 n := string(f.ds[0].n) 439 pt := pkgtree.PackageTree{ 440 ImportRoot: n, 441 Packages: map[string]pkgtree.PackageOrErr{ 442 string(n): { 443 P: pkgtree.Package{ 444 ImportPath: n, 445 Name: n, 446 Imports: imp, 447 }, 448 }, 449 }, 450 } 451 452 return pt 453 } 454 455 func (f basicFixture) failure() error { 456 return f.fail 457 } 458 459 // A table of basicFixtures, used in the basic solving test set. 460 var basicFixtures = map[string]basicFixture{ 461 // basic fixtures 462 "no dependencies": { 463 ds: []depspec{ 464 mkDepspec("root 0.0.0"), 465 }, 466 r: mksolution(), 467 }, 468 "simple dependency tree": { 469 ds: []depspec{ 470 mkDepspec("root 0.0.0", "a 1.0.0", "b 1.0.0"), 471 mkDepspec("a 1.0.0", "aa 1.0.0", "ab 1.0.0"), 472 mkDepspec("aa 1.0.0"), 473 mkDepspec("ab 1.0.0"), 474 mkDepspec("b 1.0.0", "ba 1.0.0", "bb 1.0.0"), 475 mkDepspec("ba 1.0.0"), 476 mkDepspec("bb 1.0.0"), 477 }, 478 r: mksolution( 479 "a 1.0.0", 480 "aa 1.0.0", 481 "ab 1.0.0", 482 "b 1.0.0", 483 "ba 1.0.0", 484 "bb 1.0.0", 485 ), 486 }, 487 "shared dependency with overlapping constraints": { 488 ds: []depspec{ 489 mkDepspec("root 0.0.0", "a 1.0.0", "b 1.0.0"), 490 mkDepspec("a 1.0.0", "shared >=2.0.0, <4.0.0"), 491 mkDepspec("b 1.0.0", "shared >=3.0.0, <5.0.0"), 492 mkDepspec("shared 2.0.0"), 493 mkDepspec("shared 3.0.0"), 494 mkDepspec("shared 3.6.9"), 495 mkDepspec("shared 4.0.0"), 496 mkDepspec("shared 5.0.0"), 497 }, 498 r: mksolution( 499 "a 1.0.0", 500 "b 1.0.0", 501 "shared 3.6.9", 502 ), 503 }, 504 "downgrade on overlapping constraints": { 505 ds: []depspec{ 506 mkDepspec("root 0.0.0", "a 1.0.0", "b 1.0.0"), 507 mkDepspec("a 1.0.0", "shared >=2.0.0, <=4.0.0"), 508 mkDepspec("b 1.0.0", "shared >=3.0.0, <5.0.0"), 509 mkDepspec("shared 2.0.0"), 510 mkDepspec("shared 3.0.0"), 511 mkDepspec("shared 3.6.9"), 512 mkDepspec("shared 4.0.0"), 513 mkDepspec("shared 5.0.0"), 514 }, 515 r: mksolution( 516 "a 1.0.0", 517 "b 1.0.0", 518 "shared 3.0.0", 519 ), 520 downgrade: true, 521 }, 522 "shared dependency where dependent version in turn affects other dependencies": { 523 ds: []depspec{ 524 mkDepspec("root 0.0.0", "foo <=1.0.2", "bar 1.0.0"), 525 mkDepspec("foo 1.0.0"), 526 mkDepspec("foo 1.0.1", "bang 1.0.0"), 527 mkDepspec("foo 1.0.2", "whoop 1.0.0"), 528 mkDepspec("foo 1.0.3", "zoop 1.0.0"), 529 mkDepspec("bar 1.0.0", "foo <=1.0.1"), 530 mkDepspec("bang 1.0.0"), 531 mkDepspec("whoop 1.0.0"), 532 mkDepspec("zoop 1.0.0"), 533 }, 534 r: mksolution( 535 "foo 1.0.1", 536 "bar 1.0.0", 537 "bang 1.0.0", 538 ), 539 }, 540 "removed dependency": { 541 ds: []depspec{ 542 mkDepspec("root 1.0.0", "foo 1.0.0", "bar *"), 543 mkDepspec("foo 1.0.0"), 544 mkDepspec("foo 2.0.0"), 545 mkDepspec("bar 1.0.0"), 546 mkDepspec("bar 2.0.0", "baz 1.0.0"), 547 mkDepspec("baz 1.0.0", "foo 2.0.0"), 548 }, 549 r: mksolution( 550 "foo 1.0.0", 551 "bar 1.0.0", 552 ), 553 maxAttempts: 2, 554 }, 555 // fixtures with locks 556 "with compatible locked dependency": { 557 ds: []depspec{ 558 mkDepspec("root 0.0.0", "foo *"), 559 mkDepspec("foo 1.0.0", "bar 1.0.0"), 560 mkDepspec("foo 1.0.1", "bar 1.0.1"), 561 mkDepspec("foo 1.0.2", "bar 1.0.2"), 562 mkDepspec("bar 1.0.0"), 563 mkDepspec("bar 1.0.1"), 564 mkDepspec("bar 1.0.2"), 565 }, 566 l: mklock( 567 "foo 1.0.1", 568 ), 569 r: mksolution( 570 "foo 1.0.1", 571 "bar 1.0.1", 572 ), 573 }, 574 "upgrade through lock": { 575 ds: []depspec{ 576 mkDepspec("root 0.0.0", "foo *"), 577 mkDepspec("foo 1.0.0", "bar 1.0.0"), 578 mkDepspec("foo 1.0.1", "bar 1.0.1"), 579 mkDepspec("foo 1.0.2", "bar 1.0.2"), 580 mkDepspec("bar 1.0.0"), 581 mkDepspec("bar 1.0.1"), 582 mkDepspec("bar 1.0.2"), 583 }, 584 l: mklock( 585 "foo 1.0.1", 586 ), 587 r: mksolution( 588 "foo 1.0.2", 589 "bar 1.0.2", 590 ), 591 changeall: true, 592 }, 593 "downgrade through lock": { 594 ds: []depspec{ 595 mkDepspec("root 0.0.0", "foo *"), 596 mkDepspec("foo 1.0.0", "bar 1.0.0"), 597 mkDepspec("foo 1.0.1", "bar 1.0.1"), 598 mkDepspec("foo 1.0.2", "bar 1.0.2"), 599 mkDepspec("bar 1.0.0"), 600 mkDepspec("bar 1.0.1"), 601 mkDepspec("bar 1.0.2"), 602 }, 603 l: mklock( 604 "foo 1.0.1", 605 ), 606 r: mksolution( 607 "foo 1.0.0", 608 "bar 1.0.0", 609 ), 610 changeall: true, 611 downgrade: true, 612 }, 613 "update one with only one": { 614 ds: []depspec{ 615 mkDepspec("root 0.0.0", "foo *"), 616 mkDepspec("foo 1.0.0"), 617 mkDepspec("foo 1.0.1"), 618 mkDepspec("foo 1.0.2"), 619 }, 620 l: mklock( 621 "foo 1.0.1", 622 ), 623 r: mksolution( 624 "foo 1.0.2", 625 ), 626 changelist: []ProjectRoot{"foo"}, 627 }, 628 "update one of multi": { 629 ds: []depspec{ 630 mkDepspec("root 0.0.0", "foo *", "bar *"), 631 mkDepspec("foo 1.0.0"), 632 mkDepspec("foo 1.0.1"), 633 mkDepspec("foo 1.0.2"), 634 mkDepspec("bar 1.0.0"), 635 mkDepspec("bar 1.0.1"), 636 mkDepspec("bar 1.0.2"), 637 }, 638 l: mklock( 639 "foo 1.0.1", 640 "bar 1.0.1", 641 ), 642 r: mksolution( 643 "foo 1.0.2", 644 "bar 1.0.1", 645 ), 646 changelist: []ProjectRoot{"foo"}, 647 }, 648 "update both of multi": { 649 ds: []depspec{ 650 mkDepspec("root 0.0.0", "foo *", "bar *"), 651 mkDepspec("foo 1.0.0"), 652 mkDepspec("foo 1.0.1"), 653 mkDepspec("foo 1.0.2"), 654 mkDepspec("bar 1.0.0"), 655 mkDepspec("bar 1.0.1"), 656 mkDepspec("bar 1.0.2"), 657 }, 658 l: mklock( 659 "foo 1.0.1", 660 "bar 1.0.1", 661 ), 662 r: mksolution( 663 "foo 1.0.2", 664 "bar 1.0.2", 665 ), 666 changelist: []ProjectRoot{"foo", "bar"}, 667 }, 668 "update two of more": { 669 ds: []depspec{ 670 mkDepspec("root 0.0.0", "foo *", "bar *", "baz *"), 671 mkDepspec("foo 1.0.0"), 672 mkDepspec("foo 1.0.1"), 673 mkDepspec("foo 1.0.2"), 674 mkDepspec("bar 1.0.0"), 675 mkDepspec("bar 1.0.1"), 676 mkDepspec("bar 1.0.2"), 677 mkDepspec("baz 1.0.0"), 678 mkDepspec("baz 1.0.1"), 679 mkDepspec("baz 1.0.2"), 680 }, 681 l: mklock( 682 "foo 1.0.1", 683 "bar 1.0.1", 684 "baz 1.0.1", 685 ), 686 r: mksolution( 687 "foo 1.0.2", 688 "bar 1.0.2", 689 "baz 1.0.1", 690 ), 691 changelist: []ProjectRoot{"foo", "bar"}, 692 }, 693 "break other lock with targeted update": { 694 ds: []depspec{ 695 mkDepspec("root 0.0.0", "foo *", "baz *"), 696 mkDepspec("foo 1.0.0", "bar 1.0.0"), 697 mkDepspec("foo 1.0.1", "bar 1.0.1"), 698 mkDepspec("foo 1.0.2", "bar 1.0.2"), 699 mkDepspec("bar 1.0.0"), 700 mkDepspec("bar 1.0.1"), 701 mkDepspec("bar 1.0.2"), 702 mkDepspec("baz 1.0.0"), 703 mkDepspec("baz 1.0.1"), 704 mkDepspec("baz 1.0.2"), 705 }, 706 l: mklock( 707 "foo 1.0.1", 708 "bar 1.0.1", 709 "baz 1.0.1", 710 ), 711 r: mksolution( 712 "foo 1.0.2", 713 "bar 1.0.2", 714 "baz 1.0.1", 715 ), 716 changelist: []ProjectRoot{"foo", "bar"}, 717 }, 718 "with incompatible locked dependency": { 719 ds: []depspec{ 720 mkDepspec("root 0.0.0", "foo >1.0.1"), 721 mkDepspec("foo 1.0.0", "bar 1.0.0"), 722 mkDepspec("foo 1.0.1", "bar 1.0.1"), 723 mkDepspec("foo 1.0.2", "bar 1.0.2"), 724 mkDepspec("bar 1.0.0"), 725 mkDepspec("bar 1.0.1"), 726 mkDepspec("bar 1.0.2"), 727 }, 728 l: mklock( 729 "foo 1.0.1", 730 ), 731 r: mksolution( 732 "foo 1.0.2", 733 "bar 1.0.2", 734 ), 735 }, 736 "with unrelated locked dependency": { 737 ds: []depspec{ 738 mkDepspec("root 0.0.0", "foo *"), 739 mkDepspec("foo 1.0.0", "bar 1.0.0"), 740 mkDepspec("foo 1.0.1", "bar 1.0.1"), 741 mkDepspec("foo 1.0.2", "bar 1.0.2"), 742 mkDepspec("bar 1.0.0"), 743 mkDepspec("bar 1.0.1"), 744 mkDepspec("bar 1.0.2"), 745 mkDepspec("baz 1.0.0 bazrev"), 746 }, 747 l: mklock( 748 "baz 1.0.0 bazrev", 749 ), 750 r: mksolution( 751 "foo 1.0.2", 752 "bar 1.0.2", 753 ), 754 }, 755 "unlocks dependencies if necessary to ensure that a new dependency is satisfied": { 756 ds: []depspec{ 757 mkDepspec("root 0.0.0", "foo *", "newdep *"), 758 mkDepspec("foo 1.0.0 foorev", "bar <2.0.0"), 759 mkDepspec("bar 1.0.0 barrev", "baz <2.0.0"), 760 mkDepspec("baz 1.0.0 bazrev", "qux <2.0.0"), 761 mkDepspec("qux 1.0.0 quxrev"), 762 mkDepspec("foo 2.0.0", "bar <3.0.0"), 763 mkDepspec("bar 2.0.0", "baz <3.0.0"), 764 mkDepspec("baz 2.0.0", "qux <3.0.0"), 765 mkDepspec("qux 2.0.0"), 766 mkDepspec("newdep 2.0.0", "baz >=1.5.0"), 767 }, 768 l: mklock( 769 "foo 1.0.0 foorev", 770 "bar 1.0.0 barrev", 771 "baz 1.0.0 bazrev", 772 "qux 1.0.0 quxrev", 773 ), 774 r: mksolution( 775 "foo 2.0.0", 776 "bar 2.0.0", 777 "baz 2.0.0", 778 "qux 1.0.0 quxrev", 779 "newdep 2.0.0", 780 ), 781 maxAttempts: 4, 782 }, 783 "break lock when only the deps necessitate it": { 784 ds: []depspec{ 785 mkDepspec("root 0.0.0", "foo *", "bar *"), 786 mkDepspec("foo 1.0.0 foorev", "bar <2.0.0"), 787 mkDepspec("foo 2.0.0", "bar <3.0.0"), 788 mkDepspec("bar 2.0.0", "baz <3.0.0"), 789 mkDepspec("baz 2.0.0", "foo >1.0.0"), 790 }, 791 l: mklock( 792 "foo 1.0.0 foorev", 793 ), 794 r: mksolution( 795 "foo 2.0.0", 796 "bar 2.0.0", 797 "baz 2.0.0", 798 ), 799 maxAttempts: 4, 800 }, 801 "locked atoms are matched on both local and net name": { 802 ds: []depspec{ 803 mkDepspec("root 0.0.0", "foo *"), 804 mkDepspec("foo 1.0.0 foorev"), 805 mkDepspec("foo 2.0.0 foorev2"), 806 }, 807 l: mklock( 808 "foo from baz 1.0.0 foorev", 809 ), 810 r: mksolution( 811 "foo 2.0.0 foorev2", 812 ), 813 }, 814 // This fixture describes a situation that should be impossible with a 815 // real-world VCS (contents of dep at same rev are different, as indicated 816 // by different constraints on bar). But, that's not the SUT here, so it's 817 // OK. 818 "pairs bare revs in lock with all versions": { 819 ds: []depspec{ 820 mkDepspec("root 0.0.0", "foo ~1.0.1"), 821 mkDepspec("foo 1.0.0", "bar 1.0.0"), 822 mkDepspec("foo 1.0.1 foorev", "bar 1.0.1"), 823 mkDepspec("foo 1.0.2 foorev", "bar 1.0.2"), 824 mkDepspec("bar 1.0.0"), 825 mkDepspec("bar 1.0.1"), 826 mkDepspec("bar 1.0.2"), 827 }, 828 l: mkrevlock( 829 "foo 1.0.1 foorev", // mkrevlock drops the 1.0.1 830 ), 831 r: mksolution( 832 "foo 1.0.2 foorev", 833 "bar 1.0.2", 834 ), 835 }, 836 "does not pair bare revs in manifest with unpaired lock version": { 837 ds: []depspec{ 838 mkDepspec("root 0.0.0", "foo ~1.0.1"), 839 mkDepspec("foo 1.0.0", "bar 1.0.0"), 840 mkDepspec("foo 1.0.1 foorev", "bar 1.0.1"), 841 mkDepspec("foo 1.0.2", "bar 1.0.2"), 842 mkDepspec("bar 1.0.0"), 843 mkDepspec("bar 1.0.1"), 844 mkDepspec("bar 1.0.2"), 845 }, 846 l: mkrevlock( 847 "foo 1.0.1 foorev", // mkrevlock drops the 1.0.1 848 ), 849 r: mksolution( 850 "foo 1.0.2", 851 "bar 1.0.2", 852 ), 853 }, 854 "lock to branch on old rev keeps old rev": { 855 ds: []depspec{ 856 mkDepspec("root 0.0.0", "foo bmaster"), 857 mkDepspec("foo bmaster newrev"), 858 }, 859 l: mklock( 860 "foo bmaster oldrev", 861 ), 862 r: mksolution( 863 "foo bmaster oldrev", 864 ), 865 }, 866 // Whereas this is a normal situation for a branch, when it occurs for a 867 // tag, it means someone's been naughty upstream. Still, though, the outcome 868 // is the same. 869 // 870 // TODO(sdboyer) this needs to generate a warning, once we start doing that 871 "lock to now-moved tag on old rev keeps old rev": { 872 ds: []depspec{ 873 mkDepspec("root 0.0.0", "foo ptaggerino"), 874 mkDepspec("foo ptaggerino newrev"), 875 }, 876 l: mklock( 877 "foo ptaggerino oldrev", 878 ), 879 r: mksolution( 880 "foo ptaggerino oldrev", 881 ), 882 }, 883 "no version that matches requirement": { 884 ds: []depspec{ 885 mkDepspec("root 0.0.0", "foo ^1.0.0"), 886 mkDepspec("foo 2.0.0"), 887 mkDepspec("foo 2.1.3"), 888 }, 889 fail: &noVersionError{ 890 pn: mkPI("foo"), 891 fails: []failedVersion{ 892 { 893 v: NewVersion("2.1.3"), 894 f: &versionNotAllowedFailure{ 895 goal: mkAtom("foo 2.1.3"), 896 failparent: []dependency{mkDep("root", "foo ^1.0.0", "foo")}, 897 c: mkSVC("^1.0.0"), 898 }, 899 }, 900 { 901 v: NewVersion("2.0.0"), 902 f: &versionNotAllowedFailure{ 903 goal: mkAtom("foo 2.0.0"), 904 failparent: []dependency{mkDep("root", "foo ^1.0.0", "foo")}, 905 c: mkSVC("^1.0.0"), 906 }, 907 }, 908 }, 909 }, 910 }, 911 "no version that matches combined constraint": { 912 ds: []depspec{ 913 mkDepspec("root 0.0.0", "foo 1.0.0", "bar 1.0.0"), 914 mkDepspec("foo 1.0.0", "shared >=2.0.0, <3.0.0"), 915 mkDepspec("bar 1.0.0", "shared >=2.9.0, <4.0.0"), 916 mkDepspec("shared 2.5.0"), 917 mkDepspec("shared 3.5.0"), 918 }, 919 fail: &noVersionError{ 920 pn: mkPI("shared"), 921 fails: []failedVersion{ 922 { 923 v: NewVersion("3.5.0"), 924 f: &versionNotAllowedFailure{ 925 goal: mkAtom("shared 3.5.0"), 926 failparent: []dependency{mkDep("foo 1.0.0", "shared >=2.0.0, <3.0.0", "shared")}, 927 c: mkSVC(">=2.9.0, <3.0.0"), 928 }, 929 }, 930 { 931 v: NewVersion("2.5.0"), 932 f: &versionNotAllowedFailure{ 933 goal: mkAtom("shared 2.5.0"), 934 failparent: []dependency{mkDep("bar 1.0.0", "shared >=2.9.0, <4.0.0", "shared")}, 935 c: mkSVC(">=2.9.0, <3.0.0"), 936 }, 937 }, 938 }, 939 }, 940 }, 941 "disjoint constraints": { 942 ds: []depspec{ 943 mkDepspec("root 0.0.0", "foo 1.0.0", "bar 1.0.0"), 944 mkDepspec("foo 1.0.0", "shared <=2.0.0"), 945 mkDepspec("bar 1.0.0", "shared >3.0.0"), 946 mkDepspec("shared 2.0.0"), 947 mkDepspec("shared 4.0.0"), 948 }, 949 fail: &noVersionError{ 950 pn: mkPI("foo"), 951 fails: []failedVersion{ 952 { 953 v: NewVersion("1.0.0"), 954 f: &disjointConstraintFailure{ 955 goal: mkDep("foo 1.0.0", "shared <=2.0.0", "shared"), 956 failsib: []dependency{mkDep("bar 1.0.0", "shared >3.0.0", "shared")}, 957 nofailsib: nil, 958 c: mkSVC(">3.0.0"), 959 }, 960 }, 961 }, 962 }, 963 }, 964 "no valid solution": { 965 ds: []depspec{ 966 mkDepspec("root 0.0.0", "a *", "b *"), 967 mkDepspec("a 1.0.0", "b 1.0.0"), 968 mkDepspec("a 2.0.0", "b 2.0.0"), 969 mkDepspec("b 1.0.0", "a 2.0.0"), 970 mkDepspec("b 2.0.0", "a 1.0.0"), 971 }, 972 fail: &noVersionError{ 973 pn: mkPI("b"), 974 fails: []failedVersion{ 975 { 976 v: NewVersion("2.0.0"), 977 f: &versionNotAllowedFailure{ 978 goal: mkAtom("b 2.0.0"), 979 failparent: []dependency{mkDep("a 1.0.0", "b 1.0.0", "b")}, 980 c: mkSVC("1.0.0"), 981 }, 982 }, 983 { 984 v: NewVersion("1.0.0"), 985 f: &constraintNotAllowedFailure{ 986 goal: mkDep("b 1.0.0", "a 2.0.0", "a"), 987 v: NewVersion("1.0.0"), 988 }, 989 }, 990 }, 991 }, 992 }, 993 "no version that matches while backtracking": { 994 ds: []depspec{ 995 mkDepspec("root 0.0.0", "a *", "b >1.0.0"), 996 mkDepspec("a 1.0.0"), 997 mkDepspec("b 1.0.0"), 998 }, 999 fail: &noVersionError{ 1000 pn: mkPI("b"), 1001 fails: []failedVersion{ 1002 { 1003 v: NewVersion("1.0.0"), 1004 f: &versionNotAllowedFailure{ 1005 goal: mkAtom("b 1.0.0"), 1006 failparent: []dependency{mkDep("root", "b >1.0.0", "b")}, 1007 c: mkSVC(">1.0.0"), 1008 }, 1009 }, 1010 }, 1011 }, 1012 }, 1013 // The latest versions of a and b disagree on c. An older version of either 1014 // will resolve the problem. This test validates that b, which is farther 1015 // in the dependency graph from myapp is downgraded first. 1016 "rolls back leaf versions first": { 1017 ds: []depspec{ 1018 mkDepspec("root 0.0.0", "a *"), 1019 mkDepspec("a 1.0.0", "b *"), 1020 mkDepspec("a 2.0.0", "b *", "c 2.0.0"), 1021 mkDepspec("b 1.0.0"), 1022 mkDepspec("b 2.0.0", "c 1.0.0"), 1023 mkDepspec("c 1.0.0"), 1024 mkDepspec("c 2.0.0"), 1025 }, 1026 r: mksolution( 1027 "a 2.0.0", 1028 "b 1.0.0", 1029 "c 2.0.0", 1030 ), 1031 maxAttempts: 2, 1032 }, 1033 // Only one version of baz, so foo and bar will have to downgrade until they 1034 // reach it. 1035 "mutual downgrading": { 1036 ds: []depspec{ 1037 mkDepspec("root 0.0.0", "foo *"), 1038 mkDepspec("foo 1.0.0", "bar 1.0.0"), 1039 mkDepspec("foo 2.0.0", "bar 2.0.0"), 1040 mkDepspec("foo 3.0.0", "bar 3.0.0"), 1041 mkDepspec("bar 1.0.0", "baz *"), 1042 mkDepspec("bar 2.0.0", "baz 2.0.0"), 1043 mkDepspec("bar 3.0.0", "baz 3.0.0"), 1044 mkDepspec("baz 1.0.0"), 1045 }, 1046 r: mksolution( 1047 "foo 1.0.0", 1048 "bar 1.0.0", 1049 "baz 1.0.0", 1050 ), 1051 maxAttempts: 3, 1052 }, 1053 // Ensures the solver doesn't exhaustively search all versions of b when 1054 // it's a-2.0.0 whose dependency on c-2.0.0-nonexistent led to the 1055 // problem. We make sure b has more versions than a so that the solver 1056 // tries a first since it sorts sibling dependencies by number of 1057 // versions. 1058 "search real failer": { 1059 ds: []depspec{ 1060 mkDepspec("root 0.0.0", "a *", "b *"), 1061 mkDepspec("a 1.0.0", "c 1.0.0"), 1062 mkDepspec("a 2.0.0", "c 2.0.0"), 1063 mkDepspec("b 1.0.0"), 1064 mkDepspec("b 2.0.0"), 1065 mkDepspec("b 3.0.0"), 1066 mkDepspec("c 1.0.0"), 1067 }, 1068 r: mksolution( 1069 "a 1.0.0", 1070 "b 3.0.0", 1071 "c 1.0.0", 1072 ), 1073 maxAttempts: 2, 1074 }, 1075 // Dependencies are ordered so that packages with fewer versions are tried 1076 // first. Here, there are two valid solutions (either a or b must be 1077 // downgraded once). The chosen one depends on which dep is traversed first. 1078 // Since b has fewer versions, it will be traversed first, which means a 1079 // will come later. Since later selections are revised first, a gets 1080 // downgraded. 1081 "traverse into package with fewer versions first": { 1082 ds: []depspec{ 1083 mkDepspec("root 0.0.0", "a *", "b *"), 1084 mkDepspec("a 1.0.0", "c *"), 1085 mkDepspec("a 2.0.0", "c *"), 1086 mkDepspec("a 3.0.0", "c *"), 1087 mkDepspec("a 4.0.0", "c *"), 1088 mkDepspec("a 5.0.0", "c 1.0.0"), 1089 mkDepspec("b 1.0.0", "c *"), 1090 mkDepspec("b 2.0.0", "c *"), 1091 mkDepspec("b 3.0.0", "c *"), 1092 mkDepspec("b 4.0.0", "c 2.0.0"), 1093 mkDepspec("c 1.0.0"), 1094 mkDepspec("c 2.0.0"), 1095 }, 1096 r: mksolution( 1097 "a 4.0.0", 1098 "b 4.0.0", 1099 "c 2.0.0", 1100 ), 1101 maxAttempts: 2, 1102 }, 1103 // This is similar to the preceding fixture. When getting the number of 1104 // versions of a package to determine which to traverse first, versions that 1105 // are disallowed by the root package's constraints should not be 1106 // considered. Here, foo has more versions than bar in total (4), but fewer 1107 // that meet myapp"s constraints (only 2). There is no solution, but we will 1108 // do less backtracking if foo is tested first. 1109 "root constraints pre-eliminate versions": { 1110 ds: []depspec{ 1111 mkDepspec("root 0.0.0", "foo *", "bar *"), 1112 mkDepspec("foo 1.0.0", "none 2.0.0"), 1113 mkDepspec("foo 2.0.0", "none 2.0.0"), 1114 mkDepspec("foo 3.0.0", "none 2.0.0"), 1115 mkDepspec("foo 4.0.0", "none 2.0.0"), 1116 mkDepspec("bar 1.0.0"), 1117 mkDepspec("bar 2.0.0"), 1118 mkDepspec("bar 3.0.0"), 1119 mkDepspec("none 1.0.0"), 1120 }, 1121 fail: &noVersionError{ 1122 pn: mkPI("none"), 1123 fails: []failedVersion{ 1124 { 1125 v: NewVersion("1.0.0"), 1126 f: &versionNotAllowedFailure{ 1127 goal: mkAtom("none 1.0.0"), 1128 failparent: []dependency{mkDep("foo 1.0.0", "none 2.0.0", "none")}, 1129 c: mkSVC("2.0.0"), 1130 }, 1131 }, 1132 }, 1133 }, 1134 }, 1135 // If there"s a disjoint constraint on a package, then selecting other 1136 // versions of it is a waste of time: no possible versions can match. We 1137 // need to jump past it to the most recent package that affected the 1138 // constraint. 1139 "backjump past failed package on disjoint constraint": { 1140 ds: []depspec{ 1141 mkDepspec("root 0.0.0", "a *", "foo *"), 1142 mkDepspec("a 1.0.0", "foo *"), 1143 mkDepspec("a 2.0.0", "foo <1.0.0"), 1144 mkDepspec("foo 2.0.0"), 1145 mkDepspec("foo 2.0.1"), 1146 mkDepspec("foo 2.0.2"), 1147 mkDepspec("foo 2.0.3"), 1148 mkDepspec("foo 2.0.4"), 1149 mkDepspec("none 1.0.0"), 1150 }, 1151 r: mksolution( 1152 "a 1.0.0", 1153 "foo 2.0.4", 1154 ), 1155 maxAttempts: 2, 1156 }, 1157 // Revision enters vqueue if a dep has a constraint on that revision 1158 "revision injected into vqueue": { 1159 ds: []depspec{ 1160 mkDepspec("root 0.0.0", "foo r123abc"), 1161 mkDepspec("foo r123abc"), 1162 mkDepspec("foo 1.0.0 foorev"), 1163 mkDepspec("foo 2.0.0 foorev2"), 1164 }, 1165 r: mksolution( 1166 "foo r123abc", 1167 ), 1168 }, 1169 // Some basic override checks 1170 "override root's own constraint": { 1171 ds: []depspec{ 1172 mkDepspec("root 0.0.0", "a *", "b *"), 1173 mkDepspec("a 1.0.0", "b 1.0.0"), 1174 mkDepspec("a 2.0.0", "b 1.0.0"), 1175 mkDepspec("b 1.0.0"), 1176 }, 1177 ovr: ProjectConstraints{ 1178 ProjectRoot("a"): ProjectProperties{ 1179 Constraint: NewVersion("1.0.0"), 1180 }, 1181 }, 1182 r: mksolution( 1183 "a 1.0.0", 1184 "b 1.0.0", 1185 ), 1186 }, 1187 "override dep's constraint": { 1188 ds: []depspec{ 1189 mkDepspec("root 0.0.0", "a *"), 1190 mkDepspec("a 1.0.0", "b 1.0.0"), 1191 mkDepspec("a 2.0.0", "b 1.0.0"), 1192 mkDepspec("b 1.0.0"), 1193 mkDepspec("b 2.0.0"), 1194 }, 1195 ovr: ProjectConstraints{ 1196 ProjectRoot("b"): ProjectProperties{ 1197 Constraint: NewVersion("2.0.0"), 1198 }, 1199 }, 1200 r: mksolution( 1201 "a 2.0.0", 1202 "b 2.0.0", 1203 ), 1204 }, 1205 "overridden mismatched net addrs, alt in dep, back to default": { 1206 ds: []depspec{ 1207 mkDepspec("root 1.0.0", "foo 1.0.0", "bar 1.0.0"), 1208 mkDepspec("foo 1.0.0", "bar from baz 1.0.0"), 1209 mkDepspec("bar 1.0.0"), 1210 }, 1211 ovr: ProjectConstraints{ 1212 ProjectRoot("bar"): ProjectProperties{ 1213 Source: "bar", 1214 }, 1215 }, 1216 r: mksolution( 1217 "foo 1.0.0", 1218 "bar from bar 1.0.0", 1219 ), 1220 }, 1221 1222 // TODO(sdboyer) decide how to refactor the solver in order to re-enable these. 1223 // Checking for revision existence is important...but kinda obnoxious. 1224 //{ 1225 //// Solve fails if revision constraint calls for a nonexistent revision 1226 //n: "fail on missing revision", 1227 //ds: []depspec{ 1228 //mkDepspec("root 0.0.0", "bar *"), 1229 //mkDepspec("bar 1.0.0", "foo r123abc"), 1230 //mkDepspec("foo r123nomatch"), 1231 //mkDepspec("foo 1.0.0"), 1232 //mkDepspec("foo 2.0.0"), 1233 //}, 1234 //errp: []string{"bar", "foo", "bar"}, 1235 //}, 1236 //{ 1237 //// Solve fails if revision constraint calls for a nonexistent revision, 1238 //// even if rev constraint is specified by root 1239 //n: "fail on missing revision from root", 1240 //ds: []depspec{ 1241 //mkDepspec("root 0.0.0", "foo r123nomatch"), 1242 //mkDepspec("foo r123abc"), 1243 //mkDepspec("foo 1.0.0"), 1244 //mkDepspec("foo 2.0.0"), 1245 //}, 1246 //errp: []string{"foo", "root", "foo"}, 1247 //}, 1248 1249 // TODO(sdboyer) add fixture that tests proper handling of loops via aliases (where 1250 // a project that wouldn't be a loop is aliased to a project that is a loop) 1251 } 1252 1253 func init() { 1254 // This sets up a hundred versions of foo and bar, 0.0.0 through 9.9.0. Each 1255 // version of foo depends on a baz with the same major version. Each version 1256 // of bar depends on a baz with the same minor version. There is only one 1257 // version of baz, 0.0.0, so only older versions of foo and bar will 1258 // satisfy it. 1259 fix := basicFixture{ 1260 ds: []depspec{ 1261 mkDepspec("root 0.0.0", "foo *", "bar *"), 1262 mkDepspec("baz 0.0.0"), 1263 }, 1264 r: mksolution( 1265 "foo 0.9.0", 1266 "bar 9.0.0", 1267 "baz 0.0.0", 1268 ), 1269 maxAttempts: 10, 1270 } 1271 1272 for i := 0; i < 10; i++ { 1273 for j := 0; j < 10; j++ { 1274 fix.ds = append(fix.ds, mkDepspec(fmt.Sprintf("foo %v.%v.0", i, j), fmt.Sprintf("baz %v.0.0", i))) 1275 fix.ds = append(fix.ds, mkDepspec(fmt.Sprintf("bar %v.%v.0", i, j), fmt.Sprintf("baz 0.%v.0", j))) 1276 } 1277 } 1278 1279 basicFixtures["complex backtrack"] = fix 1280 1281 for k, fix := range basicFixtures { 1282 // Assign the name into the fixture itself 1283 fix.n = k 1284 basicFixtures[k] = fix 1285 } 1286 } 1287 1288 // reachMaps contain externalReach()-type data for a given depspec fixture's 1289 // universe of projects, packages, and versions. 1290 type reachMap map[pident]map[string][]string 1291 1292 type depspecSourceManager struct { 1293 specs []depspec 1294 rm reachMap 1295 ig map[string]bool 1296 } 1297 1298 type fixSM interface { 1299 SourceManager 1300 rootSpec() depspec 1301 allSpecs() []depspec 1302 ignore() map[string]bool 1303 } 1304 1305 var _ fixSM = &depspecSourceManager{} 1306 1307 func newdepspecSM(ds []depspec, ignore []string) *depspecSourceManager { 1308 ig := make(map[string]bool) 1309 if len(ignore) > 0 { 1310 for _, pkg := range ignore { 1311 ig[pkg] = true 1312 } 1313 } 1314 1315 return &depspecSourceManager{ 1316 specs: ds, 1317 rm: computeBasicReachMap(ds), 1318 ig: ig, 1319 } 1320 } 1321 1322 func (sm *depspecSourceManager) GetManifestAndLock(id ProjectIdentifier, v Version, an ProjectAnalyzer) (Manifest, Lock, error) { 1323 // If the input version is a PairedVersion, look only at its top version, 1324 // not the underlying. This is generally consistent with the idea that, for 1325 // this class of lookup, the rev probably DOES exist, but upstream changed 1326 // it (typically a branch). For the purposes of tests, then, that's an OK 1327 // scenario, because otherwise we'd have to enumerate all the revs in the 1328 // fixture declarations, which would screw up other things. 1329 if pv, ok := v.(PairedVersion); ok { 1330 v = pv.Unpair() 1331 } 1332 1333 src := toFold(id.normalizedSource()) 1334 for _, ds := range sm.specs { 1335 if src == string(ds.n) && v.Matches(ds.v) { 1336 return ds, dummyLock{}, nil 1337 } 1338 } 1339 1340 return nil, nil, fmt.Errorf("project %s at version %s could not be found", id, v) 1341 } 1342 1343 func (sm *depspecSourceManager) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) { 1344 pid := pident{n: ProjectRoot(toFold(id.normalizedSource())), v: v} 1345 if pv, ok := v.(PairedVersion); ok && pv.Revision() == "FAKEREV" { 1346 // An empty rev may come in here because that's what we produce in 1347 // ListVersions(). If that's what we see, then just pretend like we have 1348 // an unpaired. 1349 pid.v = pv.Unpair() 1350 } 1351 1352 if r, exists := sm.rm[pid]; exists { 1353 return pkgtree.PackageTree{ 1354 ImportRoot: id.normalizedSource(), 1355 Packages: map[string]pkgtree.PackageOrErr{ 1356 string(pid.n): { 1357 P: pkgtree.Package{ 1358 ImportPath: string(pid.n), 1359 Name: string(pid.n), 1360 Imports: r[string(pid.n)], 1361 }, 1362 }, 1363 }, 1364 }, nil 1365 } 1366 1367 // if incoming version was paired, walk the map and search for a match on 1368 // top-only version 1369 if pv, ok := v.(PairedVersion); ok { 1370 uv := pv.Unpair() 1371 for pid, r := range sm.rm { 1372 if uv.Matches(pid.v) { 1373 return pkgtree.PackageTree{ 1374 ImportRoot: id.normalizedSource(), 1375 Packages: map[string]pkgtree.PackageOrErr{ 1376 string(pid.n): { 1377 P: pkgtree.Package{ 1378 ImportPath: string(pid.n), 1379 Name: string(pid.n), 1380 Imports: r[string(pid.n)], 1381 }, 1382 }, 1383 }, 1384 }, nil 1385 } 1386 } 1387 } 1388 1389 return pkgtree.PackageTree{}, fmt.Errorf("project %s at version %s could not be found", pid.n, v) 1390 } 1391 1392 func (sm *depspecSourceManager) ListVersions(id ProjectIdentifier) ([]PairedVersion, error) { 1393 var pvl []PairedVersion 1394 src := toFold(id.normalizedSource()) 1395 for _, ds := range sm.specs { 1396 if src != string(ds.n) { 1397 continue 1398 } 1399 1400 switch tv := ds.v.(type) { 1401 case Revision: 1402 // To simulate the behavior of the real SourceManager, we do not return 1403 // raw revisions from listVersions(). 1404 case PairedVersion: 1405 pvl = append(pvl, tv) 1406 case UnpairedVersion: 1407 // Dummy revision; if the fixture doesn't provide it, we know 1408 // the test doesn't need revision info, anyway. 1409 pvl = append(pvl, tv.Pair(Revision("FAKEREV"))) 1410 default: 1411 panic(fmt.Sprintf("unreachable: type of version was %#v for spec %s", ds.v, id)) 1412 } 1413 } 1414 1415 if len(pvl) == 0 { 1416 return nil, fmt.Errorf("project %s could not be found", id) 1417 } 1418 return pvl, nil 1419 } 1420 1421 func (sm *depspecSourceManager) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) { 1422 src := toFold(id.normalizedSource()) 1423 for _, ds := range sm.specs { 1424 if src == string(ds.n) && r == ds.v { 1425 return true, nil 1426 } 1427 } 1428 1429 return false, fmt.Errorf("project %s has no revision %s", id, r) 1430 } 1431 1432 func (sm *depspecSourceManager) SourceExists(id ProjectIdentifier) (bool, error) { 1433 src := toFold(id.normalizedSource()) 1434 for _, ds := range sm.specs { 1435 if src == string(ds.n) { 1436 return true, nil 1437 } 1438 } 1439 1440 return false, nil 1441 } 1442 1443 func (sm *depspecSourceManager) SyncSourceFor(id ProjectIdentifier) error { 1444 // Ignore err because it can't happen 1445 if exist, _ := sm.SourceExists(id); !exist { 1446 return fmt.Errorf("source %s does not exist", id) 1447 } 1448 return nil 1449 } 1450 1451 func (sm *depspecSourceManager) Release() {} 1452 1453 func (sm *depspecSourceManager) ExportProject(context.Context, ProjectIdentifier, Version, string) error { 1454 return fmt.Errorf("dummy sm doesn't support exporting") 1455 } 1456 1457 func (sm *depspecSourceManager) ExportPrunedProject(context.Context, LockedProject, PruneOptions, string) error { 1458 return fmt.Errorf("dummy sm doesn't support exporting") 1459 } 1460 1461 func (sm *depspecSourceManager) DeduceProjectRoot(ip string) (ProjectRoot, error) { 1462 fip := toFold(ip) 1463 for _, ds := range sm.allSpecs() { 1464 n := string(ds.n) 1465 if fip == n || strings.HasPrefix(fip, n+"/") { 1466 return ProjectRoot(ip[:len(n)]), nil 1467 } 1468 } 1469 return "", fmt.Errorf("could not find %s, or any parent, in list of known fixtures", ip) 1470 } 1471 1472 func (sm *depspecSourceManager) SourceURLsForPath(ip string) ([]*url.URL, error) { 1473 return nil, fmt.Errorf("dummy sm doesn't implement SourceURLsForPath") 1474 } 1475 1476 func (sm *depspecSourceManager) rootSpec() depspec { 1477 return sm.specs[0] 1478 } 1479 1480 func (sm *depspecSourceManager) allSpecs() []depspec { 1481 return sm.specs 1482 } 1483 1484 func (sm *depspecSourceManager) ignore() map[string]bool { 1485 return sm.ig 1486 } 1487 1488 // InferConstraint tries to puzzle out what kind of version is given in a string - 1489 // semver, a revision, or as a fallback, a plain tag. This current implementation 1490 // is a panic because there's no current circumstance under which the depspecSourceManager 1491 // is useful outside of the gps solving tests, and it shouldn't be used anywhere else without a conscious and intentional 1492 // expansion of its semantics. 1493 func (sm *depspecSourceManager) InferConstraint(s string, pi ProjectIdentifier) (Constraint, error) { 1494 panic("depsecSourceManager is only for gps solving tests") 1495 } 1496 1497 type depspecBridge struct { 1498 *bridge 1499 } 1500 1501 func (b *depspecBridge) listVersions(id ProjectIdentifier) ([]Version, error) { 1502 if vl, exists := b.vlists[id]; exists { 1503 return vl, nil 1504 } 1505 1506 pvl, err := b.sm.ListVersions(id) 1507 if err != nil { 1508 return nil, err 1509 } 1510 1511 // Construct a []Version slice. If any paired versions use the fake rev, 1512 // remove the underlying component. 1513 vl := make([]Version, 0, len(pvl)) 1514 for _, v := range pvl { 1515 if v.Revision() == "FAKEREV" { 1516 vl = append(vl, v.Unpair()) 1517 } else { 1518 vl = append(vl, v) 1519 } 1520 } 1521 1522 if b.down { 1523 SortForDowngrade(vl) 1524 } else { 1525 SortForUpgrade(vl) 1526 } 1527 1528 b.vlists[id] = vl 1529 return vl, nil 1530 } 1531 1532 // override verifyRoot() on bridge to prevent any filesystem interaction 1533 func (b *depspecBridge) verifyRootDir(path string) error { 1534 root := b.sm.(fixSM).rootSpec() 1535 if string(root.n) != path { 1536 return fmt.Errorf("expected only root project %q to verifyRootDir(), got %q", root.n, path) 1537 } 1538 1539 return nil 1540 } 1541 1542 func (b *depspecBridge) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) { 1543 return b.sm.(fixSM).ListPackages(id, v) 1544 } 1545 1546 func (b *depspecBridge) vendorCodeExists(id ProjectIdentifier) (bool, error) { 1547 return false, nil 1548 } 1549 1550 // enforce interfaces 1551 var _ Manifest = depspec{} 1552 var _ Lock = dummyLock{} 1553 var _ Lock = fixLock{} 1554 1555 // impl Spec interface 1556 func (ds depspec) DependencyConstraints() ProjectConstraints { 1557 return pcSliceToMap(ds.deps) 1558 } 1559 1560 type fixLock []LockedProject 1561 1562 // impl Lock interface 1563 func (l fixLock) Projects() []LockedProject { 1564 return l 1565 } 1566 1567 // impl Lock interface 1568 func (fixLock) InputImports() []string { 1569 return nil 1570 } 1571 1572 type dummyLock struct{} 1573 1574 // impl Lock interface 1575 func (dummyLock) Projects() []LockedProject { 1576 return nil 1577 } 1578 1579 // impl Lock interface 1580 func (dummyLock) InputImports() []string { 1581 return nil 1582 }