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