1import json 2import os 3import sys 4import textwrap 5 6import pytest 7from pip._vendor.packaging.utils import canonicalize_name 8 9from tests.lib import ( 10 create_basic_sdist_for_package, 11 create_basic_wheel_for_package, 12 create_test_package_with_setup, 13) 14from tests.lib.wheel import make_wheel 15 16 17def assert_installed(script, **kwargs): 18 ret = script.pip('list', '--format=json') 19 installed = set( 20 (canonicalize_name(val['name']), val['version']) 21 for val in json.loads(ret.stdout) 22 ) 23 expected = set((canonicalize_name(k), v) for k, v in kwargs.items()) 24 assert expected <= installed, \ 25 "{!r} not all in {!r}".format(expected, installed) 26 27 28def assert_not_installed(script, *args): 29 ret = script.pip("list", "--format=json") 30 installed = set( 31 canonicalize_name(val["name"]) 32 for val in json.loads(ret.stdout) 33 ) 34 # None of the given names should be listed as installed, i.e. their 35 # intersection should be empty. 36 expected = set(canonicalize_name(k) for k in args) 37 assert not (expected & installed), \ 38 "{!r} contained in {!r}".format(expected, installed) 39 40 41def assert_editable(script, *args): 42 # This simply checks whether all of the listed packages have a 43 # corresponding .egg-link file installed. 44 # TODO: Implement a more rigorous way to test for editable installations. 45 egg_links = set("{}.egg-link".format(arg) for arg in args) 46 assert egg_links <= set(os.listdir(script.site_packages_path)), \ 47 "{!r} not all found in {!r}".format(args, script.site_packages_path) 48 49 50def test_new_resolver_can_install(script): 51 create_basic_wheel_for_package( 52 script, 53 "simple", 54 "0.1.0", 55 ) 56 script.pip( 57 "install", 58 "--no-cache-dir", "--no-index", 59 "--find-links", script.scratch_path, 60 "simple" 61 ) 62 assert_installed(script, simple="0.1.0") 63 64 65def test_new_resolver_can_install_with_version(script): 66 create_basic_wheel_for_package( 67 script, 68 "simple", 69 "0.1.0", 70 ) 71 script.pip( 72 "install", 73 "--no-cache-dir", "--no-index", 74 "--find-links", script.scratch_path, 75 "simple==0.1.0" 76 ) 77 assert_installed(script, simple="0.1.0") 78 79 80def test_new_resolver_picks_latest_version(script): 81 create_basic_wheel_for_package( 82 script, 83 "simple", 84 "0.1.0", 85 ) 86 create_basic_wheel_for_package( 87 script, 88 "simple", 89 "0.2.0", 90 ) 91 script.pip( 92 "install", 93 "--no-cache-dir", "--no-index", 94 "--find-links", script.scratch_path, 95 "simple" 96 ) 97 assert_installed(script, simple="0.2.0") 98 99 100def test_new_resolver_picks_installed_version(script): 101 create_basic_wheel_for_package( 102 script, 103 "simple", 104 "0.1.0", 105 ) 106 create_basic_wheel_for_package( 107 script, 108 "simple", 109 "0.2.0", 110 ) 111 script.pip( 112 "install", 113 "--no-cache-dir", "--no-index", 114 "--find-links", script.scratch_path, 115 "simple==0.1.0" 116 ) 117 assert_installed(script, simple="0.1.0") 118 119 result = script.pip( 120 "install", 121 "--no-cache-dir", "--no-index", 122 "--find-links", script.scratch_path, 123 "simple" 124 ) 125 assert "Collecting" not in result.stdout, "Should not fetch new version" 126 assert_installed(script, simple="0.1.0") 127 128 129def test_new_resolver_picks_installed_version_if_no_match_found(script): 130 create_basic_wheel_for_package( 131 script, 132 "simple", 133 "0.1.0", 134 ) 135 create_basic_wheel_for_package( 136 script, 137 "simple", 138 "0.2.0", 139 ) 140 script.pip( 141 "install", 142 "--no-cache-dir", "--no-index", 143 "--find-links", script.scratch_path, 144 "simple==0.1.0" 145 ) 146 assert_installed(script, simple="0.1.0") 147 148 result = script.pip( 149 "install", 150 "--no-cache-dir", "--no-index", 151 "simple" 152 ) 153 assert "Collecting" not in result.stdout, "Should not fetch new version" 154 assert_installed(script, simple="0.1.0") 155 156 157def test_new_resolver_installs_dependencies(script): 158 create_basic_wheel_for_package( 159 script, 160 "base", 161 "0.1.0", 162 depends=["dep"], 163 ) 164 create_basic_wheel_for_package( 165 script, 166 "dep", 167 "0.1.0", 168 ) 169 script.pip( 170 "install", 171 "--no-cache-dir", "--no-index", 172 "--find-links", script.scratch_path, 173 "base" 174 ) 175 assert_installed(script, base="0.1.0", dep="0.1.0") 176 177 178def test_new_resolver_ignore_dependencies(script): 179 create_basic_wheel_for_package( 180 script, 181 "base", 182 "0.1.0", 183 depends=["dep"], 184 ) 185 create_basic_wheel_for_package( 186 script, 187 "dep", 188 "0.1.0", 189 ) 190 script.pip( 191 "install", 192 "--no-cache-dir", "--no-index", "--no-deps", 193 "--find-links", script.scratch_path, 194 "base" 195 ) 196 assert_installed(script, base="0.1.0") 197 assert_not_installed(script, "dep") 198 199 200@pytest.mark.parametrize( 201 "root_dep", 202 [ 203 "base[add]", 204 "base[add] >= 0.1.0", 205 ], 206) 207def test_new_resolver_installs_extras(tmpdir, script, root_dep): 208 req_file = tmpdir.joinpath("requirements.txt") 209 req_file.write_text(root_dep) 210 211 create_basic_wheel_for_package( 212 script, 213 "base", 214 "0.1.0", 215 extras={"add": ["dep"]}, 216 ) 217 create_basic_wheel_for_package( 218 script, 219 "dep", 220 "0.1.0", 221 ) 222 script.pip( 223 "install", 224 "--no-cache-dir", "--no-index", 225 "--find-links", script.scratch_path, 226 "-r", req_file, 227 ) 228 assert_installed(script, base="0.1.0", dep="0.1.0") 229 230 231def test_new_resolver_installs_extras_deprecated(tmpdir, script): 232 req_file = tmpdir.joinpath("requirements.txt") 233 req_file.write_text("base >= 0.1.0[add]") 234 235 create_basic_wheel_for_package( 236 script, 237 "base", 238 "0.1.0", 239 extras={"add": ["dep"]}, 240 ) 241 create_basic_wheel_for_package( 242 script, 243 "dep", 244 "0.1.0", 245 ) 246 result = script.pip( 247 "install", 248 "--no-cache-dir", "--no-index", 249 "--find-links", script.scratch_path, 250 "-r", req_file, 251 expect_stderr=True 252 ) 253 assert "DEPRECATION: Extras after version" in result.stderr 254 assert_installed(script, base="0.1.0", dep="0.1.0") 255 256 257def test_new_resolver_installs_extras_warn_missing(script): 258 create_basic_wheel_for_package( 259 script, 260 "base", 261 "0.1.0", 262 extras={"add": ["dep"]}, 263 ) 264 create_basic_wheel_for_package( 265 script, 266 "dep", 267 "0.1.0", 268 ) 269 result = script.pip( 270 "install", 271 "--no-cache-dir", "--no-index", 272 "--find-links", script.scratch_path, 273 "base[add,missing]", 274 expect_stderr=True, 275 ) 276 assert "does not provide the extra" in result.stderr, str(result) 277 assert "missing" in result.stderr, str(result) 278 assert_installed(script, base="0.1.0", dep="0.1.0") 279 280 281def test_new_resolver_installed_message(script): 282 create_basic_wheel_for_package(script, "A", "1.0") 283 result = script.pip( 284 "install", 285 "--no-cache-dir", "--no-index", 286 "--find-links", script.scratch_path, 287 "A", 288 expect_stderr=False, 289 ) 290 assert "Successfully installed A-1.0" in result.stdout, str(result) 291 292 293def test_new_resolver_no_dist_message(script): 294 create_basic_wheel_for_package(script, "A", "1.0") 295 result = script.pip( 296 "install", 297 "--no-cache-dir", "--no-index", 298 "--find-links", script.scratch_path, 299 "B", 300 expect_error=True, 301 expect_stderr=True, 302 ) 303 304 # Full messages from old resolver: 305 # ERROR: Could not find a version that satisfies the 306 # requirement xxx (from versions: none) 307 # ERROR: No matching distribution found for xxx 308 309 assert "Could not find a version that satisfies the requirement B" \ 310 in result.stderr, str(result) 311 assert "No matching distribution found for B" in result.stderr, str(result) 312 313 314def test_new_resolver_installs_editable(script): 315 create_basic_wheel_for_package( 316 script, 317 "base", 318 "0.1.0", 319 depends=["dep"], 320 ) 321 source_dir = create_test_package_with_setup( 322 script, 323 name="dep", 324 version="0.1.0", 325 ) 326 script.pip( 327 "install", 328 "--no-cache-dir", "--no-index", 329 "--find-links", script.scratch_path, 330 "base", 331 "--editable", source_dir, 332 ) 333 assert_installed(script, base="0.1.0", dep="0.1.0") 334 assert_editable(script, "dep") 335 336 337@pytest.mark.parametrize( 338 "requires_python, ignore_requires_python, dep_version", 339 [ 340 # Something impossible to satisfy. 341 ("<2", False, "0.1.0"), 342 ("<2", True, "0.2.0"), 343 344 # Something guaranteed to satisfy. 345 (">=2", False, "0.2.0"), 346 (">=2", True, "0.2.0"), 347 ], 348) 349def test_new_resolver_requires_python( 350 script, 351 requires_python, 352 ignore_requires_python, 353 dep_version, 354): 355 create_basic_wheel_for_package( 356 script, 357 "base", 358 "0.1.0", 359 depends=["dep"], 360 ) 361 create_basic_wheel_for_package( 362 script, 363 "dep", 364 "0.1.0", 365 ) 366 create_basic_wheel_for_package( 367 script, 368 "dep", 369 "0.2.0", 370 requires_python=requires_python, 371 ) 372 373 args = [ 374 "install", 375 "--no-cache-dir", 376 "--no-index", 377 "--find-links", script.scratch_path, 378 ] 379 if ignore_requires_python: 380 args.append("--ignore-requires-python") 381 args.append("base") 382 383 script.pip(*args) 384 385 assert_installed(script, base="0.1.0", dep=dep_version) 386 387 388def test_new_resolver_requires_python_error(script): 389 create_basic_wheel_for_package( 390 script, 391 "base", 392 "0.1.0", 393 requires_python="<2", 394 ) 395 result = script.pip( 396 "install", 397 "--no-cache-dir", "--no-index", 398 "--find-links", script.scratch_path, 399 "base", 400 expect_error=True, 401 ) 402 403 message = ( 404 "Package 'base' requires a different Python: " 405 "{}.{}.{} not in '<2'".format(*sys.version_info[:3]) 406 ) 407 assert message in result.stderr, str(result) 408 409 410def test_new_resolver_installed(script): 411 create_basic_wheel_for_package( 412 script, 413 "base", 414 "0.1.0", 415 depends=["dep"], 416 ) 417 create_basic_wheel_for_package( 418 script, 419 "dep", 420 "0.1.0", 421 ) 422 423 result = script.pip( 424 "install", 425 "--no-cache-dir", "--no-index", 426 "--find-links", script.scratch_path, 427 "base", 428 ) 429 assert "Requirement already satisfied" not in result.stdout, str(result) 430 431 result = script.pip( 432 "install", 433 "--no-cache-dir", "--no-index", 434 "--find-links", script.scratch_path, 435 "base~=0.1.0", 436 ) 437 assert "Requirement already satisfied: base~=0.1.0" in result.stdout, \ 438 str(result) 439 result.did_not_update( 440 script.site_packages / "base", 441 message="base 0.1.0 reinstalled" 442 ) 443 444 445def test_new_resolver_ignore_installed(script): 446 create_basic_wheel_for_package( 447 script, 448 "base", 449 "0.1.0", 450 ) 451 satisfied_output = "Requirement already satisfied" 452 453 result = script.pip( 454 "install", 455 "--no-cache-dir", "--no-index", 456 "--find-links", script.scratch_path, 457 "base", 458 ) 459 assert satisfied_output not in result.stdout, str(result) 460 461 result = script.pip( 462 "install", 463 "--no-cache-dir", "--no-index", "--ignore-installed", 464 "--find-links", script.scratch_path, 465 "base", 466 ) 467 assert satisfied_output not in result.stdout, str(result) 468 result.did_update( 469 script.site_packages / "base", 470 message="base 0.1.0 not reinstalled" 471 ) 472 473 474def test_new_resolver_only_builds_sdists_when_needed(script): 475 create_basic_wheel_for_package( 476 script, 477 "base", 478 "0.1.0", 479 depends=["dep"], 480 ) 481 create_basic_sdist_for_package( 482 script, 483 "dep", 484 "0.1.0", 485 # Replace setup.py with something that fails 486 extra_files={"setup.py": "assert False"}, 487 ) 488 create_basic_sdist_for_package( 489 script, 490 "dep", 491 "0.2.0", 492 ) 493 # We only ever need to check dep 0.2.0 as it's the latest version 494 script.pip( 495 "install", 496 "--no-cache-dir", "--no-index", 497 "--find-links", script.scratch_path, 498 "base" 499 ) 500 assert_installed(script, base="0.1.0", dep="0.2.0") 501 502 # We merge criteria here, as we have two "dep" requirements 503 script.pip( 504 "install", 505 "--no-cache-dir", "--no-index", 506 "--find-links", script.scratch_path, 507 "base", "dep" 508 ) 509 assert_installed(script, base="0.1.0", dep="0.2.0") 510 511 512def test_new_resolver_install_different_version(script): 513 create_basic_wheel_for_package(script, "base", "0.1.0") 514 create_basic_wheel_for_package(script, "base", "0.2.0") 515 516 script.pip( 517 "install", 518 "--no-cache-dir", "--no-index", 519 "--find-links", script.scratch_path, 520 "base==0.1.0", 521 ) 522 523 # This should trigger an uninstallation of base. 524 result = script.pip( 525 "install", 526 "--no-cache-dir", "--no-index", 527 "--find-links", script.scratch_path, 528 "base==0.2.0", 529 ) 530 531 assert "Uninstalling base-0.1.0" in result.stdout, str(result) 532 assert "Successfully uninstalled base-0.1.0" in result.stdout, str(result) 533 result.did_update( 534 script.site_packages / "base", 535 message="base not upgraded" 536 ) 537 assert_installed(script, base="0.2.0") 538 539 540def test_new_resolver_force_reinstall(script): 541 create_basic_wheel_for_package(script, "base", "0.1.0") 542 543 script.pip( 544 "install", 545 "--no-cache-dir", "--no-index", 546 "--find-links", script.scratch_path, 547 "base==0.1.0", 548 ) 549 550 # This should trigger an uninstallation of base due to --force-reinstall, 551 # even though the installed version matches. 552 result = script.pip( 553 "install", 554 "--no-cache-dir", "--no-index", 555 "--find-links", script.scratch_path, 556 "--force-reinstall", 557 "base==0.1.0", 558 ) 559 560 assert "Uninstalling base-0.1.0" in result.stdout, str(result) 561 assert "Successfully uninstalled base-0.1.0" in result.stdout, str(result) 562 result.did_update( 563 script.site_packages / "base", 564 message="base not reinstalled" 565 ) 566 assert_installed(script, base="0.1.0") 567 568 569@pytest.mark.parametrize( 570 "available_versions, pip_args, expected_version", 571 [ 572 # Choose the latest non-prerelease by default. 573 (["1.0", "2.0a1"], ["pkg"], "1.0"), 574 # Choose the prerelease if the specifier spells out a prerelease. 575 (["1.0", "2.0a1"], ["pkg==2.0a1"], "2.0a1"), 576 # Choose the prerelease if explicitly allowed by the user. 577 (["1.0", "2.0a1"], ["pkg", "--pre"], "2.0a1"), 578 # Choose the prerelease if no stable releases are available. 579 (["2.0a1"], ["pkg"], "2.0a1"), 580 ], 581 ids=["default", "exact-pre", "explicit-pre", "no-stable"], 582) 583def test_new_resolver_handles_prerelease( 584 script, 585 available_versions, 586 pip_args, 587 expected_version, 588): 589 for version in available_versions: 590 create_basic_wheel_for_package(script, "pkg", version) 591 script.pip( 592 "install", 593 "--no-cache-dir", "--no-index", 594 "--find-links", script.scratch_path, 595 *pip_args 596 ) 597 assert_installed(script, pkg=expected_version) 598 599 600@pytest.mark.parametrize( 601 "pkg_deps, root_deps", 602 [ 603 # This tests the marker is picked up from a transitive dependency. 604 (["dep; os_name == 'nonexist_os'"], ["pkg"]), 605 # This tests the marker is picked up from a root dependency. 606 ([], ["pkg", "dep; os_name == 'nonexist_os'"]), 607 ] 608) 609def test_new_reolver_skips_marker(script, pkg_deps, root_deps): 610 create_basic_wheel_for_package(script, "pkg", "1.0", depends=pkg_deps) 611 create_basic_wheel_for_package(script, "dep", "1.0") 612 613 script.pip( 614 "install", 615 "--no-cache-dir", "--no-index", 616 "--find-links", script.scratch_path, 617 *root_deps 618 ) 619 assert_installed(script, pkg="1.0") 620 assert_not_installed(script, "dep") 621 622 623@pytest.mark.parametrize( 624 "constraints", 625 [ 626 ["pkg<2.0", "constraint_only<1.0"], 627 # This also tests the pkg constraint don't get merged with the 628 # requirement prematurely. (pypa/pip#8134) 629 ["pkg<2.0"], 630 ] 631) 632def test_new_resolver_constraints(script, constraints): 633 create_basic_wheel_for_package(script, "pkg", "1.0") 634 create_basic_wheel_for_package(script, "pkg", "2.0") 635 create_basic_wheel_for_package(script, "pkg", "3.0") 636 constraints_file = script.scratch_path / "constraints.txt" 637 constraints_file.write_text("\n".join(constraints)) 638 script.pip( 639 "install", 640 "--no-cache-dir", "--no-index", 641 "--find-links", script.scratch_path, 642 "-c", constraints_file, 643 "pkg" 644 ) 645 assert_installed(script, pkg="1.0") 646 assert_not_installed(script, "constraint_only") 647 648 649def test_new_resolver_constraint_no_specifier(script): 650 "It's allowed (but useless...) for a constraint to have no specifier" 651 create_basic_wheel_for_package(script, "pkg", "1.0") 652 constraints_file = script.scratch_path / "constraints.txt" 653 constraints_file.write_text("pkg") 654 script.pip( 655 "install", 656 "--no-cache-dir", "--no-index", 657 "--find-links", script.scratch_path, 658 "-c", constraints_file, 659 "pkg" 660 ) 661 assert_installed(script, pkg="1.0") 662 663 664@pytest.mark.parametrize( 665 "constraint, error", 666 [ 667 ( 668 "dist.zip", 669 "Unnamed requirements are not allowed as constraints", 670 ), 671 ( 672 "req @ https://example.com/dist.zip", 673 "Links are not allowed as constraints", 674 ), 675 ( 676 "pkg[extra]", 677 "Constraints cannot have extras", 678 ), 679 ], 680) 681def test_new_resolver_constraint_reject_invalid(script, constraint, error): 682 create_basic_wheel_for_package(script, "pkg", "1.0") 683 constraints_file = script.scratch_path / "constraints.txt" 684 constraints_file.write_text(constraint) 685 result = script.pip( 686 "install", 687 "--no-cache-dir", "--no-index", 688 "--find-links", script.scratch_path, 689 "-c", constraints_file, 690 "pkg", 691 expect_error=True, 692 expect_stderr=True, 693 ) 694 assert error in result.stderr, str(result) 695 696 697def test_new_resolver_constraint_on_dependency(script): 698 create_basic_wheel_for_package(script, "base", "1.0", depends=["dep"]) 699 create_basic_wheel_for_package(script, "dep", "1.0") 700 create_basic_wheel_for_package(script, "dep", "2.0") 701 create_basic_wheel_for_package(script, "dep", "3.0") 702 constraints_file = script.scratch_path / "constraints.txt" 703 constraints_file.write_text("dep==2.0") 704 script.pip( 705 "install", 706 "--no-cache-dir", "--no-index", 707 "--find-links", script.scratch_path, 708 "-c", constraints_file, 709 "base" 710 ) 711 assert_installed(script, base="1.0") 712 assert_installed(script, dep="2.0") 713 714 715@pytest.mark.parametrize( 716 "constraint_version, expect_error, message", 717 [ 718 ("1.0", True, "ERROR: No matching distribution found for foo 2.0"), 719 ("2.0", False, "Successfully installed foo-2.0"), 720 ], 721) 722def test_new_resolver_constraint_on_path_empty( 723 script, 724 constraint_version, 725 expect_error, 726 message, 727): 728 """A path requirement can be filtered by a constraint. 729 """ 730 setup_py = script.scratch_path / "setup.py" 731 text = "from setuptools import setup\nsetup(name='foo', version='2.0')" 732 setup_py.write_text(text) 733 734 constraints_txt = script.scratch_path / "constraints.txt" 735 constraints_txt.write_text("foo=={}".format(constraint_version)) 736 737 result = script.pip( 738 "install", 739 "--no-cache-dir", "--no-index", 740 "-c", constraints_txt, 741 str(script.scratch_path), 742 expect_error=expect_error, 743 ) 744 745 if expect_error: 746 assert message in result.stderr, str(result) 747 else: 748 assert message in result.stdout, str(result) 749 750 751def test_new_resolver_constraint_only_marker_match(script): 752 create_basic_wheel_for_package(script, "pkg", "1.0") 753 create_basic_wheel_for_package(script, "pkg", "2.0") 754 create_basic_wheel_for_package(script, "pkg", "3.0") 755 756 constrants_content = textwrap.dedent( 757 """ 758 pkg==1.0; python_version == "{ver[0]}.{ver[1]}" # Always satisfies. 759 pkg==2.0; python_version < "0" # Never satisfies. 760 """ 761 ).format(ver=sys.version_info) 762 constraints_txt = script.scratch_path / "constraints.txt" 763 constraints_txt.write_text(constrants_content) 764 765 script.pip( 766 "install", 767 "--no-cache-dir", "--no-index", 768 "-c", constraints_txt, 769 "--find-links", script.scratch_path, 770 "pkg", 771 ) 772 assert_installed(script, pkg="1.0") 773 774 775def test_new_resolver_upgrade_needs_option(script): 776 # Install pkg 1.0.0 777 create_basic_wheel_for_package(script, "pkg", "1.0.0") 778 script.pip( 779 "install", 780 "--no-cache-dir", "--no-index", 781 "--find-links", script.scratch_path, 782 "pkg", 783 ) 784 785 # Now release a new version 786 create_basic_wheel_for_package(script, "pkg", "2.0.0") 787 788 # This should not upgrade because we don't specify --upgrade 789 result = script.pip( 790 "install", 791 "--no-cache-dir", "--no-index", 792 "--find-links", script.scratch_path, 793 "pkg", 794 ) 795 796 assert "Requirement already satisfied" in result.stdout, str(result) 797 assert_installed(script, pkg="1.0.0") 798 799 # This should upgrade 800 result = script.pip( 801 "install", 802 "--no-cache-dir", "--no-index", 803 "--find-links", script.scratch_path, 804 "--upgrade", 805 "PKG", # Deliberately uppercase to check canonicalization 806 ) 807 808 assert "Uninstalling pkg-1.0.0" in result.stdout, str(result) 809 assert "Successfully uninstalled pkg-1.0.0" in result.stdout, str(result) 810 result.did_update( 811 script.site_packages / "pkg", 812 message="pkg not upgraded" 813 ) 814 assert_installed(script, pkg="2.0.0") 815 816 817def test_new_resolver_upgrade_strategy(script): 818 create_basic_wheel_for_package(script, "base", "1.0.0", depends=["dep"]) 819 create_basic_wheel_for_package(script, "dep", "1.0.0") 820 script.pip( 821 "install", 822 "--no-cache-dir", "--no-index", 823 "--find-links", script.scratch_path, 824 "base", 825 ) 826 827 assert_installed(script, base="1.0.0") 828 assert_installed(script, dep="1.0.0") 829 830 # Now release new versions 831 create_basic_wheel_for_package(script, "base", "2.0.0", depends=["dep"]) 832 create_basic_wheel_for_package(script, "dep", "2.0.0") 833 834 script.pip( 835 "install", 836 "--no-cache-dir", "--no-index", 837 "--find-links", script.scratch_path, 838 "--upgrade", 839 "base", 840 ) 841 842 # With upgrade strategy "only-if-needed" (the default), dep should not 843 # be upgraded. 844 assert_installed(script, base="2.0.0") 845 assert_installed(script, dep="1.0.0") 846 847 create_basic_wheel_for_package(script, "base", "3.0.0", depends=["dep"]) 848 script.pip( 849 "install", 850 "--no-cache-dir", "--no-index", 851 "--find-links", script.scratch_path, 852 "--upgrade", "--upgrade-strategy=eager", 853 "base", 854 ) 855 856 # With upgrade strategy "eager", dep should be upgraded. 857 assert_installed(script, base="3.0.0") 858 assert_installed(script, dep="2.0.0") 859 860 861class TestExtraMerge(object): 862 """ 863 Test installing a package that depends the same package with different 864 extras, one listed as required and the other as in extra. 865 """ 866 867 def _local_with_setup(script, name, version, requires, extras): 868 """Create the package as a local source directory to install from path. 869 """ 870 return create_test_package_with_setup( 871 script, 872 name=name, 873 version=version, 874 install_requires=requires, 875 extras_require=extras, 876 ) 877 878 def _direct_wheel(script, name, version, requires, extras): 879 """Create the package as a wheel to install from path directly. 880 """ 881 return create_basic_wheel_for_package( 882 script, 883 name=name, 884 version=version, 885 depends=requires, 886 extras=extras, 887 ) 888 889 def _wheel_from_index(script, name, version, requires, extras): 890 """Create the package as a wheel to install from index. 891 """ 892 create_basic_wheel_for_package( 893 script, 894 name=name, 895 version=version, 896 depends=requires, 897 extras=extras, 898 ) 899 return name 900 901 @pytest.mark.parametrize( 902 "pkg_builder", 903 [ 904 _local_with_setup, 905 _direct_wheel, 906 _wheel_from_index, 907 ], 908 ) 909 def test_new_resolver_extra_merge_in_package( 910 self, monkeypatch, script, pkg_builder, 911 ): 912 create_basic_wheel_for_package(script, "depdev", "1.0.0") 913 create_basic_wheel_for_package( 914 script, 915 "dep", 916 "1.0.0", 917 extras={"dev": ["depdev"]}, 918 ) 919 requirement = pkg_builder( 920 script, 921 name="pkg", 922 version="1.0.0", 923 requires=["dep"], 924 extras={"dev": ["dep[dev]"]}, 925 ) 926 927 script.pip( 928 "install", 929 "--no-cache-dir", "--no-index", 930 "--find-links", script.scratch_path, 931 requirement + "[dev]", 932 ) 933 assert_installed(script, pkg="1.0.0", dep="1.0.0", depdev="1.0.0") 934 935 936def test_new_resolver_build_directory_error_zazo_19(script): 937 """https://github.com/pradyunsg/zazo/issues/19#issuecomment-631615674 938 939 This will first resolve like this: 940 941 1. Pin pkg-b==2.0.0 (since pkg-b has fewer choices) 942 2. Pin pkg-a==3.0.0 -> Conflict due to dependency pkg-b<2 943 3. Pin pkg-b==1.0.0 944 945 Since pkg-b is only available as sdist, both the first and third steps 946 would trigger building from source. This ensures the preparer can build 947 different versions of a package for the resolver. 948 949 The preparer would fail with the following message if the different 950 versions end up using the same build directory:: 951 952 ERROR: pip can't proceed with requirements 'pkg-b ...' due to a 953 pre-existing build directory (...). This is likely due to a previous 954 installation that failed. pip is being responsible and not assuming it 955 can delete this. Please delete it and try again. 956 """ 957 create_basic_wheel_for_package( 958 script, "pkg_a", "3.0.0", depends=["pkg-b<2"], 959 ) 960 create_basic_wheel_for_package(script, "pkg_a", "2.0.0") 961 create_basic_wheel_for_package(script, "pkg_a", "1.0.0") 962 963 create_basic_sdist_for_package(script, "pkg_b", "2.0.0") 964 create_basic_sdist_for_package(script, "pkg_b", "1.0.0") 965 966 script.pip( 967 "install", 968 "--no-cache-dir", "--no-index", 969 "--find-links", script.scratch_path, 970 "pkg-a", "pkg-b", 971 ) 972 assert_installed(script, pkg_a="3.0.0", pkg_b="1.0.0") 973 974 975def test_new_resolver_upgrade_same_version(script): 976 create_basic_wheel_for_package(script, "pkg", "2") 977 create_basic_wheel_for_package(script, "pkg", "1") 978 979 script.pip( 980 "install", 981 "--no-cache-dir", "--no-index", 982 "--find-links", script.scratch_path, 983 "pkg", 984 ) 985 assert_installed(script, pkg="2") 986 987 script.pip( 988 "install", 989 "--no-cache-dir", "--no-index", 990 "--find-links", script.scratch_path, 991 "--upgrade", 992 "pkg", 993 ) 994 assert_installed(script, pkg="2") 995 996 997def test_new_resolver_local_and_req(script): 998 source_dir = create_test_package_with_setup( 999 script, 1000 name="pkg", 1001 version="0.1.0", 1002 ) 1003 script.pip( 1004 "install", 1005 "--no-cache-dir", "--no-index", 1006 source_dir, "pkg!=0.1.0", 1007 expect_error=True, 1008 ) 1009 1010 1011def test_new_resolver_no_deps_checks_requires_python(script): 1012 create_basic_wheel_for_package( 1013 script, 1014 "base", 1015 "0.1.0", 1016 depends=["dep"], 1017 requires_python="<2", # Something that always fails. 1018 ) 1019 create_basic_wheel_for_package( 1020 script, 1021 "dep", 1022 "0.2.0", 1023 ) 1024 1025 result = script.pip( 1026 "install", 1027 "--no-cache-dir", 1028 "--no-index", 1029 "--no-deps", 1030 "--find-links", script.scratch_path, 1031 "base", 1032 expect_error=True, 1033 ) 1034 1035 message = ( 1036 "Package 'base' requires a different Python: " 1037 "{}.{}.{} not in '<2'".format(*sys.version_info[:3]) 1038 ) 1039 assert message in result.stderr 1040 1041 1042def test_new_resolver_prefers_installed_in_upgrade_if_latest(script): 1043 create_basic_wheel_for_package(script, "pkg", "1") 1044 local_pkg = create_test_package_with_setup(script, name="pkg", version="2") 1045 1046 # Install the version that's not on the index. 1047 script.pip( 1048 "install", 1049 "--no-cache-dir", 1050 "--no-index", 1051 local_pkg, 1052 ) 1053 1054 # Now --upgrade should still pick the local version because it's "better". 1055 script.pip( 1056 "install", 1057 "--no-cache-dir", 1058 "--no-index", 1059 "--find-links", script.scratch_path, 1060 "--upgrade", 1061 "pkg", 1062 ) 1063 assert_installed(script, pkg="2") 1064 1065 1066@pytest.mark.parametrize("N", [2, 10, 20]) 1067def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N): 1068 # Generate a set of wheels that will definitely cause backtracking. 1069 for index in range(1, N+1): 1070 A_version = "{index}.0.0".format(index=index) 1071 B_version = "{index}.0.0".format(index=index) 1072 C_version = "{index_minus_one}.0.0".format(index_minus_one=index - 1) 1073 1074 depends = ["B == " + B_version] 1075 if index != 1: 1076 depends.append("C == " + C_version) 1077 1078 print("A", A_version, "B", B_version, "C", C_version) 1079 create_basic_wheel_for_package(script, "A", A_version, depends=depends) 1080 1081 for index in range(1, N+1): 1082 B_version = "{index}.0.0".format(index=index) 1083 C_version = "{index}.0.0".format(index=index) 1084 depends = ["C == " + C_version] 1085 1086 print("B", B_version, "C", C_version) 1087 create_basic_wheel_for_package(script, "B", B_version, depends=depends) 1088 1089 for index in range(1, N+1): 1090 C_version = "{index}.0.0".format(index=index) 1091 print("C", C_version) 1092 create_basic_wheel_for_package(script, "C", C_version) 1093 1094 # Install A 1095 result = script.pip( 1096 "install", 1097 "--no-cache-dir", 1098 "--no-index", 1099 "--find-links", script.scratch_path, 1100 "A" 1101 ) 1102 1103 assert_installed(script, A="1.0.0", B="1.0.0", C="1.0.0") 1104 # These numbers are hard-coded in the code. 1105 if N >= 1: 1106 assert "This could take a while." in result.stdout 1107 if N >= 8: 1108 assert result.stdout.count("This could take a while.") >= 2 1109 if N >= 13: 1110 assert "press Ctrl + C" in result.stdout 1111 1112 1113@pytest.mark.parametrize( 1114 "metadata_version", 1115 [ 1116 "0.1.0+local.1", # Normalized form. 1117 "0.1.0+local_1", # Non-normalized form containing an underscore. 1118 1119 # Non-normalized form containing a dash. This is allowed, installation 1120 # works correctly, but assert_installed() fails because pkg_resources 1121 # cannot handle it correctly. Nobody is complaining about it right now, 1122 # we're probably dropping it for importlib.metadata soon(tm), so let's 1123 # ignore it for the time being. 1124 pytest.param("0.1.0+local-1", marks=pytest.mark.xfail), 1125 ], 1126 ids=["meta_dot", "meta_underscore", "meta_dash"], 1127) 1128@pytest.mark.parametrize( 1129 "filename_version", 1130 [ 1131 ("0.1.0+local.1"), # Tools are encouraged to use this. 1132 ("0.1.0+local_1"), # But this is allowed (version not normalized). 1133 ], 1134 ids=["file_dot", "file_underscore"], 1135) 1136def test_new_resolver_check_wheel_version_normalized( 1137 script, 1138 metadata_version, 1139 filename_version, 1140): 1141 filename = "simple-{}-py2.py3-none-any.whl".format(filename_version) 1142 1143 wheel_builder = make_wheel(name="simple", version=metadata_version) 1144 wheel_builder.save_to(script.scratch_path / filename) 1145 1146 script.pip( 1147 "install", 1148 "--no-cache-dir", "--no-index", 1149 "--find-links", script.scratch_path, 1150 "simple" 1151 ) 1152 assert_installed(script, simple="0.1.0+local.1") 1153 1154 1155def test_new_resolver_does_reinstall_local_sdists(script): 1156 archive_path = create_basic_sdist_for_package( 1157 script, 1158 "pkg", 1159 "1.0", 1160 ) 1161 script.pip( 1162 "install", "--no-cache-dir", "--no-index", 1163 archive_path, 1164 ) 1165 assert_installed(script, pkg="1.0") 1166 1167 result = script.pip( 1168 "install", "--no-cache-dir", "--no-index", 1169 archive_path, 1170 expect_stderr=True, 1171 ) 1172 assert "Installing collected packages: pkg" in result.stdout, str(result) 1173 assert "DEPRECATION" in result.stderr, str(result) 1174 assert_installed(script, pkg="1.0") 1175 1176 1177def test_new_resolver_does_reinstall_local_paths(script): 1178 pkg = create_test_package_with_setup( 1179 script, 1180 name="pkg", 1181 version="1.0" 1182 ) 1183 script.pip( 1184 "install", "--no-cache-dir", "--no-index", 1185 pkg, 1186 ) 1187 assert_installed(script, pkg="1.0") 1188 1189 result = script.pip( 1190 "install", "--no-cache-dir", "--no-index", 1191 pkg, 1192 ) 1193 assert "Installing collected packages: pkg" in result.stdout, str(result) 1194 assert_installed(script, pkg="1.0") 1195 1196 1197def test_new_resolver_does_not_reinstall_when_from_a_local_index(script): 1198 create_basic_sdist_for_package( 1199 script, 1200 "simple", 1201 "0.1.0", 1202 ) 1203 script.pip( 1204 "install", 1205 "--no-cache-dir", "--no-index", 1206 "--find-links", script.scratch_path, 1207 "simple" 1208 ) 1209 assert_installed(script, simple="0.1.0") 1210 1211 result = script.pip( 1212 "install", 1213 "--no-cache-dir", "--no-index", 1214 "--find-links", script.scratch_path, 1215 "simple" 1216 ) 1217 # Should not reinstall! 1218 assert "Installing collected packages: simple" not in result.stdout, str(result) 1219 assert "Requirement already satisfied: simple" in result.stdout, str(result) 1220 assert_installed(script, simple="0.1.0") 1221 1222 1223def test_new_resolver_skip_inconsistent_metadata(script): 1224 create_basic_wheel_for_package(script, "A", "1") 1225 1226 a_2 = create_basic_wheel_for_package(script, "A", "2") 1227 a_2.rename(a_2.parent.joinpath("a-3-py2.py3-none-any.whl")) 1228 1229 result = script.pip( 1230 "install", 1231 "--no-cache-dir", "--no-index", 1232 "--find-links", script.scratch_path, 1233 "--verbose", 1234 "A", 1235 allow_stderr_warning=True, 1236 ) 1237 1238 assert " different version in metadata: '2'" in result.stderr, str(result) 1239 assert_installed(script, a="1") 1240 1241 1242@pytest.mark.parametrize( 1243 "upgrade", 1244 [True, False], 1245 ids=["upgrade", "no-upgrade"], 1246) 1247def test_new_resolver_lazy_fetch_candidates(script, upgrade): 1248 create_basic_wheel_for_package(script, "myuberpkg", "1") 1249 create_basic_wheel_for_package(script, "myuberpkg", "2") 1250 create_basic_wheel_for_package(script, "myuberpkg", "3") 1251 1252 # Install an old version first. 1253 script.pip( 1254 "install", 1255 "--no-cache-dir", "--no-index", 1256 "--find-links", script.scratch_path, 1257 "myuberpkg==1", 1258 ) 1259 1260 # Now install the same package again, maybe with the upgrade flag. 1261 if upgrade: 1262 pip_upgrade_args = ["--upgrade"] 1263 else: 1264 pip_upgrade_args = [] 1265 result = script.pip( 1266 "install", 1267 "--no-cache-dir", "--no-index", 1268 "--find-links", script.scratch_path, 1269 "myuberpkg", 1270 *pip_upgrade_args # Trailing comma fails on Python 2. 1271 ) 1272 1273 # pip should install the version preferred by the strategy... 1274 if upgrade: 1275 assert_installed(script, myuberpkg="3") 1276 else: 1277 assert_installed(script, myuberpkg="1") 1278 1279 # But should reach there in the best route possible, without trying 1280 # candidates it does not need to. 1281 assert "myuberpkg-2" not in result.stdout, str(result) 1282