import logging from typing import TYPE_CHECKING, Dict, List from unittest import mock import pytest from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS from pip._internal.commands import create_command from pip._internal.commands.search import highest_version, print_results, transform_hits from tests.lib import PipTestEnvironment if TYPE_CHECKING: from pip._internal.commands.search import TransformedHit def test_version_compare() -> None: """ Test version comparison. """ assert highest_version(["1.0", "2.0", "0.1"]) == "2.0" assert highest_version(["1.0a1", "1.0"]) == "1.0" def test_pypi_xml_transformation() -> None: """ Test transformation of data structures (PyPI xmlrpc to custom list). """ pypi_hits: List[Dict[str, str]] = [ { "name": "foo", "summary": "foo summary", "version": "1.0", }, { "name": "foo", "summary": "foo summary v2", "version": "2.0", }, { "_pypi_ordering": 50, # type: ignore[dict-item] "name": "bar", "summary": "bar summary", "version": "1.0", }, ] expected: List["TransformedHit"] = [ { "versions": ["1.0", "2.0"], "name": "foo", "summary": "foo summary v2", }, { "versions": ["1.0"], "name": "bar", "summary": "bar summary", }, ] assert transform_hits(pypi_hits) == expected @pytest.mark.network @pytest.mark.search def test_basic_search(script: PipTestEnvironment) -> None: """ End to end test of search command. """ output = script.pip("search", "pip") assert "The PyPA recommended tool for installing Python packages." in output.stdout @pytest.mark.network @pytest.mark.skip( reason=( "Warehouse search behavior is different and no longer returns " "multiple results. See " "https://github.com/pypa/warehouse/issues/3717 for more " "information." ), ) @pytest.mark.search def test_multiple_search(script: PipTestEnvironment) -> None: """ Test searching for multiple packages at once. """ output = script.pip("search", "pip", "INITools") assert "The PyPA recommended tool for installing Python packages." in output.stdout assert "Tools for parsing and using INI-style files" in output.stdout @pytest.mark.search def test_search_missing_argument(script: PipTestEnvironment) -> None: """ Test missing required argument for search """ result = script.pip("search", expect_error=True) assert "ERROR: Missing required argument (search query)." in result.stderr @pytest.mark.network @pytest.mark.search def test_run_method_should_return_success_when_find_packages() -> None: """ Test SearchCommand.run for found package """ command = create_command("search") cmdline = "--index=https://pypi.org/pypi pip" with command.main_context(): options, args = command.parse_args(cmdline.split()) status = command.run(options, args) assert status == SUCCESS @pytest.mark.network @pytest.mark.search def test_run_method_should_return_no_matches_found_when_does_not_find_pkgs() -> None: """ Test SearchCommand.run for no matches """ command = create_command("search") cmdline = "--index=https://pypi.org/pypi nonexistentpackage" with command.main_context(): options, args = command.parse_args(cmdline.split()) status = command.run(options, args) assert status == NO_MATCHES_FOUND @pytest.mark.network @pytest.mark.search def test_search_should_exit_status_code_zero_when_find_packages( script: PipTestEnvironment, ) -> None: """ Test search exit status code for package found """ result = script.pip("search", "pip") assert result.returncode == SUCCESS @pytest.mark.network @pytest.mark.search def test_search_exit_status_code_when_finds_no_package( script: PipTestEnvironment, ) -> None: """ Test search exit status code for no matches """ result = script.pip("search", "nonexistentpackage", expect_error=True) assert result.returncode == NO_MATCHES_FOUND, result.returncode @pytest.mark.search def test_latest_prerelease_install_message( caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch ) -> None: """ Test documentation for installing pre-release packages is displayed """ hits: List["TransformedHit"] = [ { "name": "ni", "summary": "For knights who say Ni!", "versions": ["1.0.0", "1.0.1a"], } ] installed_package = mock.Mock(project_name="ni") monkeypatch.setattr("pip._vendor.pkg_resources.working_set", [installed_package]) get_dist = mock.Mock() get_dist.return_value = mock.Mock(version="1.0.0") monkeypatch.setattr("pip._internal.commands.search.get_distribution", get_dist) with caplog.at_level(logging.INFO): print_results(hits) message = caplog.records[-1].getMessage() assert 'pre-release; install with "pip install --pre"' in message assert get_dist.call_args_list == [mock.call("ni")] @pytest.mark.search def test_search_print_results_should_contain_latest_versions( caplog: pytest.LogCaptureFixture, ) -> None: """ Test that printed search results contain the latest package versions """ hits: List["TransformedHit"] = [ { "name": "testlib1", "summary": "Test library 1.", "versions": ["1.0.5", "1.0.3"], }, { "name": "testlib2", "summary": "Test library 1.", "versions": ["2.0.1", "2.0.3"], }, ] with caplog.at_level(logging.INFO): print_results(hits) log_messages = sorted([r.getMessage() for r in caplog.records]) assert log_messages[0].startswith("testlib1 (1.0.5)") assert log_messages[1].startswith("testlib2 (2.0.3)")