github.com/sdboyer/gps@v0.16.3/solve_bimodal_test.go (about) 1 package gps 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "strings" 7 8 "github.com/sdboyer/gps/pkgtree" 9 ) 10 11 // dsp - "depspec with packages" 12 // 13 // Wraps a set of tpkgs onto a depspec, and returns it. 14 func dsp(ds depspec, pkgs ...tpkg) depspec { 15 ds.pkgs = pkgs 16 return ds 17 } 18 19 // pkg makes a tpkg appropriate for use in bimodal testing 20 func pkg(path string, imports ...string) tpkg { 21 return tpkg{ 22 path: path, 23 imports: imports, 24 } 25 } 26 27 func init() { 28 for k, fix := range bimodalFixtures { 29 // Assign the name into the fixture itself 30 fix.n = k 31 bimodalFixtures[k] = fix 32 } 33 } 34 35 // Fixtures that rely on simulated bimodal (project and package-level) 36 // analysis for correct operation. The name given in the map gets assigned into 37 // the fixture itself in init(). 38 var bimodalFixtures = map[string]bimodalFixture{ 39 // Simple case, ensures that we do the very basics of picking up and 40 // including a single, simple import that is not expressed as a constraint 41 "simple bm-add": { 42 ds: []depspec{ 43 dsp(mkDepspec("root 0.0.0"), 44 pkg("root", "a")), 45 dsp(mkDepspec("a 1.0.0"), 46 pkg("a")), 47 }, 48 r: mksolution( 49 "a 1.0.0", 50 ), 51 }, 52 // Ensure it works when the import jump is not from the package with the 53 // same path as root, but from a subpkg 54 "subpkg bm-add": { 55 ds: []depspec{ 56 dsp(mkDepspec("root 0.0.0"), 57 pkg("root", "root/foo"), 58 pkg("root/foo", "a"), 59 ), 60 dsp(mkDepspec("a 1.0.0"), 61 pkg("a"), 62 ), 63 }, 64 r: mksolution( 65 "a 1.0.0", 66 ), 67 }, 68 // The same, but with a jump through two subpkgs 69 "double-subpkg bm-add": { 70 ds: []depspec{ 71 dsp(mkDepspec("root 0.0.0"), 72 pkg("root", "root/foo"), 73 pkg("root/foo", "root/bar"), 74 pkg("root/bar", "a"), 75 ), 76 dsp(mkDepspec("a 1.0.0"), 77 pkg("a"), 78 ), 79 }, 80 r: mksolution( 81 "a 1.0.0", 82 ), 83 }, 84 // Same again, but now nest the subpkgs 85 "double nested subpkg bm-add": { 86 ds: []depspec{ 87 dsp(mkDepspec("root 0.0.0"), 88 pkg("root", "root/foo"), 89 pkg("root/foo", "root/foo/bar"), 90 pkg("root/foo/bar", "a"), 91 ), 92 dsp(mkDepspec("a 1.0.0"), 93 pkg("a"), 94 ), 95 }, 96 r: mksolution( 97 "a 1.0.0", 98 ), 99 }, 100 // Importing package from project with no root package 101 "bm-add on project with no pkg in root dir": { 102 ds: []depspec{ 103 dsp(mkDepspec("root 0.0.0"), 104 pkg("root", "a/foo")), 105 dsp(mkDepspec("a 1.0.0"), 106 pkg("a/foo")), 107 }, 108 r: mksolution( 109 mklp("a 1.0.0", "foo"), 110 ), 111 }, 112 // Import jump is in a dep, and points to a transitive dep 113 "transitive bm-add": { 114 ds: []depspec{ 115 dsp(mkDepspec("root 0.0.0"), 116 pkg("root", "root/foo"), 117 pkg("root/foo", "a"), 118 ), 119 dsp(mkDepspec("a 1.0.0"), 120 pkg("a", "b"), 121 ), 122 dsp(mkDepspec("b 1.0.0"), 123 pkg("b"), 124 ), 125 }, 126 r: mksolution( 127 "a 1.0.0", 128 "b 1.0.0", 129 ), 130 }, 131 // Constraints apply only if the project that declares them has a 132 // reachable import 133 "constraints activated by import": { 134 ds: []depspec{ 135 dsp(mkDepspec("root 0.0.0", "b 1.0.0"), 136 pkg("root", "root/foo"), 137 pkg("root/foo", "a"), 138 ), 139 dsp(mkDepspec("a 1.0.0"), 140 pkg("a", "b"), 141 ), 142 dsp(mkDepspec("b 1.0.0"), 143 pkg("b"), 144 ), 145 dsp(mkDepspec("b 1.1.0"), 146 pkg("b"), 147 ), 148 }, 149 r: mksolution( 150 "a 1.0.0", 151 "b 1.1.0", 152 ), 153 }, 154 // Constraints apply only if the project that declares them has a 155 // reachable import - non-root 156 "constraints activated by import, transitive": { 157 ds: []depspec{ 158 dsp(mkDepspec("root 0.0.0"), 159 pkg("root", "root/foo", "b"), 160 pkg("root/foo", "a"), 161 ), 162 dsp(mkDepspec("a 1.0.0", "b 1.0.0"), 163 pkg("a"), 164 ), 165 dsp(mkDepspec("b 1.0.0"), 166 pkg("b"), 167 ), 168 dsp(mkDepspec("b 1.1.0"), 169 pkg("b"), 170 ), 171 }, 172 r: mksolution( 173 "a 1.0.0", 174 "b 1.1.0", 175 ), 176 }, 177 // Import jump is in a dep, and points to a transitive dep - but only in not 178 // the first version we try 179 "transitive bm-add on older version": { 180 ds: []depspec{ 181 dsp(mkDepspec("root 0.0.0", "a ~1.0.0"), 182 pkg("root", "root/foo"), 183 pkg("root/foo", "a"), 184 ), 185 dsp(mkDepspec("a 1.0.0"), 186 pkg("a", "b"), 187 ), 188 dsp(mkDepspec("a 1.1.0"), 189 pkg("a"), 190 ), 191 dsp(mkDepspec("b 1.0.0"), 192 pkg("b"), 193 ), 194 }, 195 r: mksolution( 196 "a 1.0.0", 197 "b 1.0.0", 198 ), 199 }, 200 // Import jump is in a dep, and points to a transitive dep - but will only 201 // get there via backtracking 202 "backtrack to dep on bm-add": { 203 ds: []depspec{ 204 dsp(mkDepspec("root 0.0.0"), 205 pkg("root", "root/foo"), 206 pkg("root/foo", "a", "b"), 207 ), 208 dsp(mkDepspec("a 1.0.0"), 209 pkg("a", "c"), 210 ), 211 dsp(mkDepspec("a 1.1.0"), 212 pkg("a"), 213 ), 214 // Include two versions of b, otherwise it'll be selected first 215 dsp(mkDepspec("b 0.9.0"), 216 pkg("b", "c"), 217 ), 218 dsp(mkDepspec("b 1.0.0"), 219 pkg("b", "c"), 220 ), 221 dsp(mkDepspec("c 1.0.0", "a 1.0.0"), 222 pkg("c", "a"), 223 ), 224 }, 225 r: mksolution( 226 "a 1.0.0", 227 "b 1.0.0", 228 "c 1.0.0", 229 ), 230 }, 231 // Import jump is in a dep subpkg, and points to a transitive dep 232 "transitive subpkg bm-add": { 233 ds: []depspec{ 234 dsp(mkDepspec("root 0.0.0"), 235 pkg("root", "root/foo"), 236 pkg("root/foo", "a"), 237 ), 238 dsp(mkDepspec("a 1.0.0"), 239 pkg("a", "a/bar"), 240 pkg("a/bar", "b"), 241 ), 242 dsp(mkDepspec("b 1.0.0"), 243 pkg("b"), 244 ), 245 }, 246 r: mksolution( 247 mklp("a 1.0.0", ".", "bar"), 248 "b 1.0.0", 249 ), 250 }, 251 // Import jump is in a dep subpkg, pointing to a transitive dep, but only in 252 // not the first version we try 253 "transitive subpkg bm-add on older version": { 254 ds: []depspec{ 255 dsp(mkDepspec("root 0.0.0", "a ~1.0.0"), 256 pkg("root", "root/foo"), 257 pkg("root/foo", "a"), 258 ), 259 dsp(mkDepspec("a 1.0.0"), 260 pkg("a", "a/bar"), 261 pkg("a/bar", "b"), 262 ), 263 dsp(mkDepspec("a 1.1.0"), 264 pkg("a", "a/bar"), 265 pkg("a/bar"), 266 ), 267 dsp(mkDepspec("b 1.0.0"), 268 pkg("b"), 269 ), 270 }, 271 r: mksolution( 272 mklp("a 1.0.0", ".", "bar"), 273 "b 1.0.0", 274 ), 275 }, 276 "project cycle involving root": { 277 ds: []depspec{ 278 dsp(mkDepspec("root 0.0.0", "a ~1.0.0"), 279 pkg("root", "a"), 280 pkg("root/foo"), 281 ), 282 dsp(mkDepspec("a 1.0.0"), 283 pkg("a", "root/foo"), 284 ), 285 }, 286 r: mksolution( 287 "a 1.0.0", 288 ), 289 }, 290 "project cycle involving root with backtracking": { 291 ds: []depspec{ 292 dsp(mkDepspec("root 0.0.0", "a ~1.0.0"), 293 pkg("root", "a", "b"), 294 pkg("root/foo"), 295 ), 296 dsp(mkDepspec("a 1.0.0"), 297 pkg("a", "root/foo"), 298 ), 299 dsp(mkDepspec("a 1.0.1"), 300 pkg("a", "root/foo"), 301 ), 302 dsp(mkDepspec("b 1.0.0", "a 1.0.0"), 303 pkg("b", "a"), 304 ), 305 dsp(mkDepspec("b 1.0.1", "a 1.0.0"), 306 pkg("b", "a"), 307 ), 308 dsp(mkDepspec("b 1.0.2", "a 1.0.0"), 309 pkg("b", "a"), 310 ), 311 }, 312 r: mksolution( 313 "a 1.0.0", 314 "b 1.0.2", 315 ), 316 }, 317 "project cycle not involving root": { 318 ds: []depspec{ 319 dsp(mkDepspec("root 0.0.0", "a ~1.0.0"), 320 pkg("root", "a"), 321 ), 322 dsp(mkDepspec("a 1.0.0"), 323 pkg("a", "b"), 324 pkg("a/foo"), 325 ), 326 dsp(mkDepspec("b 1.0.0"), 327 pkg("b", "a/foo"), 328 ), 329 }, 330 r: mksolution( 331 mklp("a 1.0.0", ".", "foo"), 332 "b 1.0.0", 333 ), 334 }, 335 "project cycle not involving root with internal paths": { 336 ds: []depspec{ 337 dsp(mkDepspec("root 0.0.0", "a ~1.0.0"), 338 pkg("root", "a"), 339 ), 340 dsp(mkDepspec("a 1.0.0"), 341 pkg("a", "b/baz"), 342 pkg("a/foo", "a/quux", "a/quark"), 343 pkg("a/quux"), 344 pkg("a/quark"), 345 ), 346 dsp(mkDepspec("b 1.0.0"), 347 pkg("b", "a/foo"), 348 pkg("b/baz", "b"), 349 ), 350 }, 351 r: mksolution( 352 mklp("a 1.0.0", ".", "foo", "quark", "quux"), 353 mklp("b 1.0.0", ".", "baz"), 354 ), 355 }, 356 // Ensure that if a constraint is expressed, but no actual import exists, 357 // then the constraint is disregarded - the project named in the constraint 358 // is not part of the solution. 359 "ignore constraint without import": { 360 ds: []depspec{ 361 dsp(mkDepspec("root 0.0.0", "a 1.0.0"), 362 pkg("root", "root/foo"), 363 pkg("root/foo"), 364 ), 365 dsp(mkDepspec("a 1.0.0"), 366 pkg("a"), 367 ), 368 }, 369 r: mksolution(), 370 }, 371 // Transitive deps from one project (a) get incrementally included as other 372 // deps incorporate its various packages. 373 "multi-stage pkg incorporation": { 374 ds: []depspec{ 375 dsp(mkDepspec("root 0.0.0"), 376 pkg("root", "a", "d"), 377 ), 378 dsp(mkDepspec("a 1.0.0"), 379 pkg("a", "b"), 380 pkg("a/second", "c"), 381 ), 382 dsp(mkDepspec("b 2.0.0"), 383 pkg("b"), 384 ), 385 dsp(mkDepspec("c 1.2.0"), 386 pkg("c"), 387 ), 388 dsp(mkDepspec("d 1.0.0"), 389 pkg("d", "a/second"), 390 ), 391 }, 392 r: mksolution( 393 mklp("a 1.0.0", ".", "second"), 394 "b 2.0.0", 395 "c 1.2.0", 396 "d 1.0.0", 397 ), 398 }, 399 // Regression - make sure that the the constraint/import intersector only 400 // accepts a project 'match' if exactly equal, or a separating slash is 401 // present. 402 "radix path separator post-check": { 403 ds: []depspec{ 404 dsp(mkDepspec("root 0.0.0"), 405 pkg("root", "foo", "foobar"), 406 ), 407 dsp(mkDepspec("foo 1.0.0"), 408 pkg("foo"), 409 ), 410 dsp(mkDepspec("foobar 1.0.0"), 411 pkg("foobar"), 412 ), 413 }, 414 r: mksolution( 415 "foo 1.0.0", 416 "foobar 1.0.0", 417 ), 418 }, 419 // Well-formed failure when there's a dependency on a pkg that doesn't exist 420 "fail when imports nonexistent package": { 421 ds: []depspec{ 422 dsp(mkDepspec("root 0.0.0", "a 1.0.0"), 423 pkg("root", "a/foo"), 424 ), 425 dsp(mkDepspec("a 1.0.0"), 426 pkg("a"), 427 ), 428 }, 429 fail: &noVersionError{ 430 pn: mkPI("a"), 431 fails: []failedVersion{ 432 { 433 v: NewVersion("1.0.0"), 434 f: &checkeeHasProblemPackagesFailure{ 435 goal: mkAtom("a 1.0.0"), 436 failpkg: map[string]errDeppers{ 437 "a/foo": errDeppers{ 438 err: nil, // nil indicates package is missing 439 deppers: []atom{ 440 mkAtom("root"), 441 }, 442 }, 443 }, 444 }, 445 }, 446 }, 447 }, 448 }, 449 // Transitive deps from one project (a) get incrementally included as other 450 // deps incorporate its various packages, and fail with proper error when we 451 // discover one incrementally that isn't present 452 "fail multi-stage missing pkg": { 453 ds: []depspec{ 454 dsp(mkDepspec("root 0.0.0"), 455 pkg("root", "a", "d"), 456 ), 457 dsp(mkDepspec("a 1.0.0"), 458 pkg("a", "b"), 459 pkg("a/second", "c"), 460 ), 461 dsp(mkDepspec("b 2.0.0"), 462 pkg("b"), 463 ), 464 dsp(mkDepspec("c 1.2.0"), 465 pkg("c"), 466 ), 467 dsp(mkDepspec("d 1.0.0"), 468 pkg("d", "a/second"), 469 pkg("d", "a/nonexistent"), 470 ), 471 }, 472 fail: &noVersionError{ 473 pn: mkPI("d"), 474 fails: []failedVersion{ 475 { 476 v: NewVersion("1.0.0"), 477 f: &depHasProblemPackagesFailure{ 478 goal: mkADep("d 1.0.0", "a", Any(), "a/nonexistent"), 479 v: NewVersion("1.0.0"), 480 prob: map[string]error{ 481 "a/nonexistent": nil, 482 }, 483 }, 484 }, 485 }, 486 }, 487 }, 488 // Check ignores on the root project 489 "ignore in double-subpkg": { 490 ds: []depspec{ 491 dsp(mkDepspec("root 0.0.0"), 492 pkg("root", "root/foo"), 493 pkg("root/foo", "root/bar", "b"), 494 pkg("root/bar", "a"), 495 ), 496 dsp(mkDepspec("a 1.0.0"), 497 pkg("a"), 498 ), 499 dsp(mkDepspec("b 1.0.0"), 500 pkg("b"), 501 ), 502 }, 503 ignore: []string{"root/bar"}, 504 r: mksolution( 505 "b 1.0.0", 506 ), 507 }, 508 // Ignores on a dep pkg 509 "ignore through dep pkg": { 510 ds: []depspec{ 511 dsp(mkDepspec("root 0.0.0"), 512 pkg("root", "root/foo"), 513 pkg("root/foo", "a"), 514 ), 515 dsp(mkDepspec("a 1.0.0"), 516 pkg("a", "a/bar"), 517 pkg("a/bar", "b"), 518 ), 519 dsp(mkDepspec("b 1.0.0"), 520 pkg("b"), 521 ), 522 }, 523 ignore: []string{"a/bar"}, 524 r: mksolution( 525 "a 1.0.0", 526 ), 527 }, 528 // Preferred version, as derived from a dep's lock, is attempted first 529 "respect prefv, simple case": { 530 ds: []depspec{ 531 dsp(mkDepspec("root 0.0.0"), 532 pkg("root", "a")), 533 dsp(mkDepspec("a 1.0.0"), 534 pkg("a", "b")), 535 dsp(mkDepspec("b 1.0.0 foorev"), 536 pkg("b")), 537 dsp(mkDepspec("b 2.0.0 barrev"), 538 pkg("b")), 539 }, 540 lm: map[string]fixLock{ 541 "a 1.0.0": mklock( 542 "b 1.0.0 foorev", 543 ), 544 }, 545 r: mksolution( 546 "a 1.0.0", 547 "b 1.0.0 foorev", 548 ), 549 }, 550 // Preferred version, as derived from a dep's lock, is attempted first, even 551 // if the root also has a direct dep on it (root doesn't need to use 552 // preferreds, because it has direct control AND because the root lock 553 // already supercedes dep lock "preferences") 554 "respect dep prefv with root import": { 555 ds: []depspec{ 556 dsp(mkDepspec("root 0.0.0"), 557 pkg("root", "a", "b")), 558 dsp(mkDepspec("a 1.0.0"), 559 pkg("a", "b")), 560 //dsp(newDepspec("a 1.0.1"), 561 //pkg("a", "b")), 562 //dsp(newDepspec("a 1.1.0"), 563 //pkg("a", "b")), 564 dsp(mkDepspec("b 1.0.0 foorev"), 565 pkg("b")), 566 dsp(mkDepspec("b 2.0.0 barrev"), 567 pkg("b")), 568 }, 569 lm: map[string]fixLock{ 570 "a 1.0.0": mklock( 571 "b 1.0.0 foorev", 572 ), 573 }, 574 r: mksolution( 575 "a 1.0.0", 576 "b 1.0.0 foorev", 577 ), 578 }, 579 // Preferred versions can only work if the thing offering it has been 580 // selected, or at least marked in the unselected queue 581 "prefv only works if depper is selected": { 582 ds: []depspec{ 583 dsp(mkDepspec("root 0.0.0"), 584 pkg("root", "a", "b")), 585 // Three atoms for a, which will mean it gets visited after b 586 dsp(mkDepspec("a 1.0.0"), 587 pkg("a", "b")), 588 dsp(mkDepspec("a 1.0.1"), 589 pkg("a", "b")), 590 dsp(mkDepspec("a 1.1.0"), 591 pkg("a", "b")), 592 dsp(mkDepspec("b 1.0.0 foorev"), 593 pkg("b")), 594 dsp(mkDepspec("b 2.0.0 barrev"), 595 pkg("b")), 596 }, 597 lm: map[string]fixLock{ 598 "a 1.0.0": mklock( 599 "b 1.0.0 foorev", 600 ), 601 }, 602 r: mksolution( 603 "a 1.1.0", 604 "b 2.0.0 barrev", 605 ), 606 }, 607 "override unconstrained root import": { 608 ds: []depspec{ 609 dsp(mkDepspec("root 0.0.0"), 610 pkg("root", "a")), 611 dsp(mkDepspec("a 1.0.0"), 612 pkg("a")), 613 dsp(mkDepspec("a 2.0.0"), 614 pkg("a")), 615 }, 616 ovr: ProjectConstraints{ 617 ProjectRoot("a"): ProjectProperties{ 618 Constraint: NewVersion("1.0.0"), 619 }, 620 }, 621 r: mksolution( 622 "a 1.0.0", 623 ), 624 }, 625 "alternate net address": { 626 ds: []depspec{ 627 dsp(mkDepspec("root 1.0.0", "foo from bar 2.0.0"), 628 pkg("root", "foo")), 629 dsp(mkDepspec("foo 1.0.0"), 630 pkg("foo")), 631 dsp(mkDepspec("foo 2.0.0"), 632 pkg("foo")), 633 dsp(mkDepspec("bar 1.0.0"), 634 pkg("foo")), 635 dsp(mkDepspec("bar 2.0.0"), 636 pkg("foo")), 637 }, 638 r: mksolution( 639 "foo from bar 2.0.0", 640 ), 641 }, 642 "alternate net address, version only in alt": { 643 ds: []depspec{ 644 dsp(mkDepspec("root 1.0.0", "foo from bar 2.0.0"), 645 pkg("root", "foo")), 646 dsp(mkDepspec("foo 1.0.0"), 647 pkg("foo")), 648 dsp(mkDepspec("bar 1.0.0"), 649 pkg("foo")), 650 dsp(mkDepspec("bar 2.0.0"), 651 pkg("foo")), 652 }, 653 r: mksolution( 654 "foo from bar 2.0.0", 655 ), 656 }, 657 "alternate net address in dep": { 658 ds: []depspec{ 659 dsp(mkDepspec("root 1.0.0", "foo 1.0.0"), 660 pkg("root", "foo")), 661 dsp(mkDepspec("foo 1.0.0", "bar from baz 2.0.0"), 662 pkg("foo", "bar")), 663 dsp(mkDepspec("bar 1.0.0"), 664 pkg("bar")), 665 dsp(mkDepspec("baz 1.0.0"), 666 pkg("bar")), 667 dsp(mkDepspec("baz 2.0.0"), 668 pkg("bar")), 669 }, 670 r: mksolution( 671 "foo 1.0.0", 672 "bar from baz 2.0.0", 673 ), 674 }, 675 // Because NOT specifying an alternate net address for a given import path 676 // is taken as an "eh, whatever", if we see an empty net addr after 677 // something else has already set an alternate one, then the second should 678 // just "go along" with whatever's already been specified. 679 "alternate net address with second depper": { 680 ds: []depspec{ 681 dsp(mkDepspec("root 1.0.0", "foo from bar 2.0.0"), 682 pkg("root", "foo", "baz")), 683 dsp(mkDepspec("foo 1.0.0"), 684 pkg("foo")), 685 dsp(mkDepspec("foo 2.0.0"), 686 pkg("foo")), 687 dsp(mkDepspec("bar 1.0.0"), 688 pkg("foo")), 689 dsp(mkDepspec("bar 2.0.0"), 690 pkg("foo")), 691 dsp(mkDepspec("baz 1.0.0"), 692 pkg("baz", "foo")), 693 }, 694 r: mksolution( 695 "foo from bar 2.0.0", 696 "baz 1.0.0", 697 ), 698 }, 699 // Same as the previous, except the alternate declaration originates in a 700 // dep, not the root. 701 "alternate net addr from dep, with second default depper": { 702 ds: []depspec{ 703 dsp(mkDepspec("root 1.0.0", "foo 1.0.0"), 704 pkg("root", "foo", "bar")), 705 dsp(mkDepspec("foo 1.0.0", "bar 2.0.0"), 706 pkg("foo", "baz")), 707 dsp(mkDepspec("foo 2.0.0", "bar 2.0.0"), 708 pkg("foo", "baz")), 709 dsp(mkDepspec("bar 2.0.0", "baz from quux 1.0.0"), 710 pkg("bar", "baz")), 711 dsp(mkDepspec("baz 1.0.0"), 712 pkg("baz")), 713 dsp(mkDepspec("baz 2.0.0"), 714 pkg("baz")), 715 dsp(mkDepspec("quux 1.0.0"), 716 pkg("baz")), 717 }, 718 r: mksolution( 719 "foo 1.0.0", 720 "bar 2.0.0", 721 "baz from quux 1.0.0", 722 ), 723 }, 724 // When a given project is initially brought in using the default (i.e., 725 // empty) ProjectIdentifier.Source, and a later, presumably 726 // as-yet-undiscovered dependency specifies an alternate net addr for it, we 727 // have to fail - even though, if the deps were visited in the opposite 728 // order (deeper dep w/the alternate location first, default location 729 // second), it would be fine. 730 // 731 // TODO A better solution here would involve restarting the solver w/a 732 // marker to use that alternate, or (ugh) introducing a new failure 733 // path/marker type that changes how backtracking works. (In fact, these 734 // approaches are probably demonstrably equivalent.) 735 "fails with net mismatch when deeper dep specs it": { 736 ds: []depspec{ 737 dsp(mkDepspec("root 1.0.0", "foo 1.0.0"), 738 pkg("root", "foo", "baz")), 739 dsp(mkDepspec("foo 1.0.0", "bar 2.0.0"), 740 pkg("foo", "bar")), 741 dsp(mkDepspec("bar 2.0.0", "baz from quux 1.0.0"), 742 pkg("bar", "baz")), 743 dsp(mkDepspec("baz 1.0.0"), 744 pkg("baz")), 745 dsp(mkDepspec("quux 1.0.0"), 746 pkg("baz")), 747 }, 748 fail: &noVersionError{ 749 pn: mkPI("bar"), 750 fails: []failedVersion{ 751 { 752 v: NewVersion("2.0.0"), 753 f: &sourceMismatchFailure{ 754 shared: ProjectRoot("baz"), 755 current: "baz", 756 mismatch: "quux", 757 prob: mkAtom("bar 2.0.0"), 758 sel: []dependency{mkDep("foo 1.0.0", "bar 2.0.0", "bar")}, 759 }, 760 }, 761 }, 762 }, 763 }, 764 "with mismatched net addrs": { 765 ds: []depspec{ 766 dsp(mkDepspec("root 1.0.0", "foo 1.0.0", "bar 1.0.0"), 767 pkg("root", "foo", "bar")), 768 dsp(mkDepspec("foo 1.0.0", "bar from baz 1.0.0"), 769 pkg("foo", "bar")), 770 dsp(mkDepspec("bar 1.0.0"), 771 pkg("bar")), 772 dsp(mkDepspec("baz 1.0.0"), 773 pkg("bar")), 774 }, 775 fail: &noVersionError{ 776 pn: mkPI("foo"), 777 fails: []failedVersion{ 778 { 779 v: NewVersion("1.0.0"), 780 f: &sourceMismatchFailure{ 781 shared: ProjectRoot("bar"), 782 current: "bar", 783 mismatch: "baz", 784 prob: mkAtom("foo 1.0.0"), 785 sel: []dependency{mkDep("root", "foo 1.0.0", "foo")}, 786 }, 787 }, 788 }, 789 }, 790 }, 791 "overridden mismatched net addrs, alt in dep": { 792 ds: []depspec{ 793 dsp(mkDepspec("root 0.0.0"), 794 pkg("root", "foo")), 795 dsp(mkDepspec("foo 1.0.0", "bar from baz 1.0.0"), 796 pkg("foo", "bar")), 797 dsp(mkDepspec("bar 1.0.0"), 798 pkg("bar")), 799 dsp(mkDepspec("baz 1.0.0"), 800 pkg("bar")), 801 }, 802 ovr: ProjectConstraints{ 803 ProjectRoot("bar"): ProjectProperties{ 804 Source: "baz", 805 }, 806 }, 807 r: mksolution( 808 "foo 1.0.0", 809 "bar from baz 1.0.0", 810 ), 811 }, 812 "overridden mismatched net addrs, alt in root": { 813 ds: []depspec{ 814 dsp(mkDepspec("root 0.0.0", "bar from baz 1.0.0"), 815 pkg("root", "foo")), 816 dsp(mkDepspec("foo 1.0.0"), 817 pkg("foo", "bar")), 818 dsp(mkDepspec("bar 1.0.0"), 819 pkg("bar")), 820 dsp(mkDepspec("baz 1.0.0"), 821 pkg("bar")), 822 }, 823 ovr: ProjectConstraints{ 824 ProjectRoot("bar"): ProjectProperties{ 825 Source: "baz", 826 }, 827 }, 828 r: mksolution( 829 "foo 1.0.0", 830 "bar from baz 1.0.0", 831 ), 832 }, 833 "require package": { 834 ds: []depspec{ 835 dsp(mkDepspec("root 0.0.0", "bar 1.0.0"), 836 pkg("root", "foo")), 837 dsp(mkDepspec("foo 1.0.0"), 838 pkg("foo", "bar")), 839 dsp(mkDepspec("bar 1.0.0"), 840 pkg("bar")), 841 dsp(mkDepspec("baz 1.0.0"), 842 pkg("baz")), 843 }, 844 require: []string{"baz"}, 845 r: mksolution( 846 "foo 1.0.0", 847 "bar 1.0.0", 848 "baz 1.0.0", 849 ), 850 }, 851 "require subpackage": { 852 ds: []depspec{ 853 dsp(mkDepspec("root 0.0.0", "bar 1.0.0"), 854 pkg("root", "foo")), 855 dsp(mkDepspec("foo 1.0.0"), 856 pkg("foo", "bar")), 857 dsp(mkDepspec("bar 1.0.0"), 858 pkg("bar")), 859 dsp(mkDepspec("baz 1.0.0"), 860 pkg("baz", "baz/qux"), 861 pkg("baz/qux")), 862 }, 863 require: []string{"baz/qux"}, 864 r: mksolution( 865 "foo 1.0.0", 866 "bar 1.0.0", 867 mklp("baz 1.0.0", "qux"), 868 ), 869 }, 870 "require impossible subpackage": { 871 ds: []depspec{ 872 dsp(mkDepspec("root 0.0.0", "baz 1.0.0"), 873 pkg("root", "foo")), 874 dsp(mkDepspec("foo 1.0.0"), 875 pkg("foo")), 876 dsp(mkDepspec("baz 1.0.0"), 877 pkg("baz")), 878 dsp(mkDepspec("baz 2.0.0"), 879 pkg("baz", "baz/qux"), 880 pkg("baz/qux")), 881 }, 882 require: []string{"baz/qux"}, 883 fail: &noVersionError{ 884 pn: mkPI("baz"), 885 fails: []failedVersion{ 886 { 887 v: NewVersion("2.0.0"), 888 f: &versionNotAllowedFailure{ 889 goal: mkAtom("baz 2.0.0"), 890 failparent: []dependency{mkDep("root", "baz 1.0.0", "baz/qux")}, 891 c: NewVersion("1.0.0"), 892 }, 893 }, 894 { 895 v: NewVersion("1.0.0"), 896 f: &checkeeHasProblemPackagesFailure{ 897 goal: mkAtom("baz 1.0.0"), 898 failpkg: map[string]errDeppers{ 899 "baz/qux": errDeppers{ 900 err: nil, // nil indicates package is missing 901 deppers: []atom{ 902 mkAtom("root"), 903 }, 904 }, 905 }, 906 }, 907 }, 908 }, 909 }, 910 }, 911 "require subpkg conflicts with other dep constraint": { 912 ds: []depspec{ 913 dsp(mkDepspec("root 0.0.0"), 914 pkg("root", "foo")), 915 dsp(mkDepspec("foo 1.0.0", "baz 1.0.0"), 916 pkg("foo", "baz")), 917 dsp(mkDepspec("baz 1.0.0"), 918 pkg("baz")), 919 dsp(mkDepspec("baz 2.0.0"), 920 pkg("baz", "baz/qux"), 921 pkg("baz/qux")), 922 }, 923 require: []string{"baz/qux"}, 924 fail: &noVersionError{ 925 pn: mkPI("baz"), 926 fails: []failedVersion{ 927 { 928 v: NewVersion("2.0.0"), 929 f: &versionNotAllowedFailure{ 930 goal: mkAtom("baz 2.0.0"), 931 failparent: []dependency{mkDep("foo 1.0.0", "baz 1.0.0", "baz")}, 932 c: NewVersion("1.0.0"), 933 }, 934 }, 935 { 936 v: NewVersion("1.0.0"), 937 f: &checkeeHasProblemPackagesFailure{ 938 goal: mkAtom("baz 1.0.0"), 939 failpkg: map[string]errDeppers{ 940 "baz/qux": errDeppers{ 941 err: nil, // nil indicates package is missing 942 deppers: []atom{ 943 mkAtom("root"), 944 }, 945 }, 946 }, 947 }, 948 }, 949 }, 950 }, 951 }, 952 "require independent subpkg conflicts with other dep constraint": { 953 ds: []depspec{ 954 dsp(mkDepspec("root 0.0.0"), 955 pkg("root", "foo")), 956 dsp(mkDepspec("foo 1.0.0", "baz 1.0.0"), 957 pkg("foo", "baz")), 958 dsp(mkDepspec("baz 1.0.0"), 959 pkg("baz")), 960 dsp(mkDepspec("baz 2.0.0"), 961 pkg("baz"), 962 pkg("baz/qux")), 963 }, 964 require: []string{"baz/qux"}, 965 fail: &noVersionError{ 966 pn: mkPI("baz"), 967 fails: []failedVersion{ 968 { 969 v: NewVersion("2.0.0"), 970 f: &versionNotAllowedFailure{ 971 goal: mkAtom("baz 2.0.0"), 972 failparent: []dependency{mkDep("foo 1.0.0", "baz 1.0.0", "baz")}, 973 c: NewVersion("1.0.0"), 974 }, 975 }, 976 { 977 v: NewVersion("1.0.0"), 978 f: &checkeeHasProblemPackagesFailure{ 979 goal: mkAtom("baz 1.0.0"), 980 failpkg: map[string]errDeppers{ 981 "baz/qux": errDeppers{ 982 err: nil, // nil indicates package is missing 983 deppers: []atom{ 984 mkAtom("root"), 985 }, 986 }, 987 }, 988 }, 989 }, 990 }, 991 }, 992 }, 993 } 994 995 // tpkg is a representation of a single package. It has its own import path, as 996 // well as a list of paths it itself "imports". 997 type tpkg struct { 998 // Full import path of this package 999 path string 1000 // Slice of full paths to its virtual imports 1001 imports []string 1002 } 1003 1004 type bimodalFixture struct { 1005 // name of this fixture datum 1006 n string 1007 // bimodal project; first is always treated as root project 1008 ds []depspec 1009 // results; map of name/version pairs 1010 r map[ProjectIdentifier]LockedProject 1011 // max attempts the solver should need to find solution. 0 means no limit 1012 maxAttempts int 1013 // Use downgrade instead of default upgrade sorter 1014 downgrade bool 1015 // lock file simulator, if one's to be used at all 1016 l fixLock 1017 // map of locks for deps, if any. keys should be of the form: 1018 // "<project> <version>" 1019 lm map[string]fixLock 1020 // solve failure expected, if any 1021 fail error 1022 // overrides, if any 1023 ovr ProjectConstraints 1024 // request up/downgrade to all projects 1025 changeall bool 1026 // pkgs to ignore 1027 ignore []string 1028 // pkgs to require 1029 require []string 1030 } 1031 1032 func (f bimodalFixture) name() string { 1033 return f.n 1034 } 1035 1036 func (f bimodalFixture) specs() []depspec { 1037 return f.ds 1038 } 1039 1040 func (f bimodalFixture) maxTries() int { 1041 return f.maxAttempts 1042 } 1043 1044 func (f bimodalFixture) solution() map[ProjectIdentifier]LockedProject { 1045 return f.r 1046 } 1047 1048 func (f bimodalFixture) rootmanifest() RootManifest { 1049 m := simpleRootManifest{ 1050 c: pcSliceToMap(f.ds[0].deps), 1051 tc: pcSliceToMap(f.ds[0].devdeps), 1052 ovr: f.ovr, 1053 ig: make(map[string]bool), 1054 req: make(map[string]bool), 1055 } 1056 for _, ig := range f.ignore { 1057 m.ig[ig] = true 1058 } 1059 for _, req := range f.require { 1060 m.req[req] = true 1061 } 1062 1063 return m 1064 } 1065 1066 func (f bimodalFixture) rootTree() pkgtree.PackageTree { 1067 pt := pkgtree.PackageTree{ 1068 ImportRoot: string(f.ds[0].n), 1069 Packages: map[string]pkgtree.PackageOrErr{}, 1070 } 1071 1072 for _, pkg := range f.ds[0].pkgs { 1073 elems := strings.Split(pkg.path, "/") 1074 pt.Packages[pkg.path] = pkgtree.PackageOrErr{ 1075 P: pkgtree.Package{ 1076 ImportPath: pkg.path, 1077 Name: elems[len(elems)-1], 1078 // TODO(sdboyer) ugh, tpkg type has no space for supporting test 1079 // imports... 1080 Imports: pkg.imports, 1081 }, 1082 } 1083 } 1084 1085 return pt 1086 } 1087 1088 func (f bimodalFixture) failure() error { 1089 return f.fail 1090 } 1091 1092 // bmSourceManager is an SM specifically for the bimodal fixtures. It composes 1093 // the general depspec SM, and differs from it in how it answers static analysis 1094 // calls, and its support for package ignores and dep lock data. 1095 type bmSourceManager struct { 1096 depspecSourceManager 1097 lm map[string]fixLock 1098 } 1099 1100 var _ SourceManager = &bmSourceManager{} 1101 1102 func newbmSM(bmf bimodalFixture) *bmSourceManager { 1103 sm := &bmSourceManager{ 1104 depspecSourceManager: *newdepspecSM(bmf.ds, bmf.ignore), 1105 } 1106 sm.rm = computeBimodalExternalMap(bmf.ds) 1107 sm.lm = bmf.lm 1108 1109 return sm 1110 } 1111 1112 func (sm *bmSourceManager) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) { 1113 for k, ds := range sm.specs { 1114 // Cheat for root, otherwise we blow up b/c version is empty 1115 if id.normalizedSource() == string(ds.n) && (k == 0 || ds.v.Matches(v)) { 1116 ptree := pkgtree.PackageTree{ 1117 ImportRoot: id.normalizedSource(), 1118 Packages: make(map[string]pkgtree.PackageOrErr), 1119 } 1120 for _, pkg := range ds.pkgs { 1121 ptree.Packages[pkg.path] = pkgtree.PackageOrErr{ 1122 P: pkgtree.Package{ 1123 ImportPath: pkg.path, 1124 Name: filepath.Base(pkg.path), 1125 Imports: pkg.imports, 1126 }, 1127 } 1128 } 1129 1130 return ptree, nil 1131 } 1132 } 1133 1134 return pkgtree.PackageTree{}, fmt.Errorf("Project %s at version %s could not be found", id.errString(), v) 1135 } 1136 1137 func (sm *bmSourceManager) GetManifestAndLock(id ProjectIdentifier, v Version, an ProjectAnalyzer) (Manifest, Lock, error) { 1138 for _, ds := range sm.specs { 1139 if id.normalizedSource() == string(ds.n) && v.Matches(ds.v) { 1140 if l, exists := sm.lm[id.normalizedSource()+" "+v.String()]; exists { 1141 return ds, l, nil 1142 } 1143 return ds, dummyLock{}, nil 1144 } 1145 } 1146 1147 // TODO(sdboyer) proper solver-type errors 1148 return nil, nil, fmt.Errorf("Project %s at version %s could not be found", id.errString(), v) 1149 } 1150 1151 // computeBimodalExternalMap takes a set of depspecs and computes an 1152 // internally-versioned ReachMap that is useful for quickly answering 1153 // ReachMap.Flatten()-type calls. 1154 // 1155 // Note that it does not do things like stripping out stdlib packages - these 1156 // maps are intended for use in SM fixtures, and that's a higher-level 1157 // responsibility within the system. 1158 func computeBimodalExternalMap(specs []depspec) map[pident]map[string][]string { 1159 // map of project name+version -> map of subpkg name -> external pkg list 1160 rm := make(map[pident]map[string][]string) 1161 1162 for _, ds := range specs { 1163 ptree := pkgtree.PackageTree{ 1164 ImportRoot: string(ds.n), 1165 Packages: make(map[string]pkgtree.PackageOrErr), 1166 } 1167 for _, pkg := range ds.pkgs { 1168 ptree.Packages[pkg.path] = pkgtree.PackageOrErr{ 1169 P: pkgtree.Package{ 1170 ImportPath: pkg.path, 1171 Name: filepath.Base(pkg.path), 1172 Imports: pkg.imports, 1173 }, 1174 } 1175 } 1176 reachmap, em := ptree.ToReachMap(false, true, true, nil) 1177 if len(em) > 0 { 1178 panic(fmt.Sprintf("pkgs with errors in reachmap processing: %s", em)) 1179 } 1180 1181 drm := make(map[string][]string) 1182 for ip, ie := range reachmap { 1183 drm[ip] = ie.External 1184 } 1185 rm[pident{n: ds.n, v: ds.v}] = drm 1186 } 1187 1188 return rm 1189 }