diff options
Diffstat (limited to 'tests/functional/test_completion.py')
-rw-r--r-- | tests/functional/test_completion.py | 373 |
1 files changed, 232 insertions, 141 deletions
diff --git a/tests/functional/test_completion.py b/tests/functional/test_completion.py index 8a7464982..b02cd4fa3 100644 --- a/tests/functional/test_completion.py +++ b/tests/functional/test_completion.py @@ -1,20 +1,36 @@ import os import sys +from pathlib import Path +from typing import TYPE_CHECKING, Tuple, Union import pytest -from tests.lib.path import Path +from tests.conftest import ScriptFactory +from tests.lib import PipTestEnvironment, TestData, TestPipResult + +if TYPE_CHECKING: + from typing import Protocol +else: + # TODO: Protocol was introduced in Python 3.8. Remove this branch when + # dropping support for Python 3.7. + Protocol = object + COMPLETION_FOR_SUPPORTED_SHELLS_TESTS = ( - ('bash', """\ + ( + "bash", + """\ _pip_completion() { COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\ COMP_CWORD=$COMP_CWORD \\ PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) ) } -complete -o default -F _pip_completion pip"""), - ('fish', """\ +complete -o default -F _pip_completion pip""", + ), + ( + "fish", + """\ function __fish_complete_pip set -lx COMP_WORDS (commandline -o) "" set -lx COMP_CWORD ( \\ @@ -23,8 +39,11 @@ function __fish_complete_pip set -lx PIP_AUTO_COMPLETE 1 string split \\ -- (eval $COMP_WORDS[1]) end -complete -fa "(__fish_complete_pip)" -c pip"""), - ('zsh', """\ +complete -fa "(__fish_complete_pip)" -c pip""", + ), + ( + "zsh", + """\ function _pip_completion { local words cword read -Ac words @@ -33,56 +52,101 @@ function _pip_completion { COMP_CWORD=$(( cword-1 )) \\ PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null )) } -compctl -K _pip_completion pip"""), +compctl -K _pip_completion pip""", + ), + ( + "powershell", + """\ +if ((Test-Path Function:\\TabExpansion) -and -not ` + (Test-Path Function:\\_pip_completeBackup)) { + Rename-Item Function:\\TabExpansion _pip_completeBackup +} +function TabExpansion($line, $lastWord) { + $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart() + if ($lastBlock.StartsWith("pip ")) { + $Env:COMP_WORDS=$lastBlock + $Env:COMP_CWORD=$lastBlock.Split().Length - 1 + $Env:PIP_AUTO_COMPLETE=1 + (& pip).Split() + Remove-Item Env:COMP_WORDS + Remove-Item Env:COMP_CWORD + Remove-Item Env:PIP_AUTO_COMPLETE + } + elseif (Test-Path Function:\\_pip_completeBackup) { + # Fall back on existing tab expansion + _pip_completeBackup $line $lastWord + } +}""", + ), ) @pytest.fixture(scope="session") def script_with_launchers( - tmpdir_factory, script_factory, common_wheels, pip_src -): - tmpdir = Path(str(tmpdir_factory.mktemp("script_with_launchers"))) + tmpdir_factory: pytest.TempPathFactory, + script_factory: ScriptFactory, + common_wheels: Path, + pip_src: Path, +) -> PipTestEnvironment: + tmpdir = tmpdir_factory.mktemp("script_with_launchers") script = script_factory(tmpdir.joinpath("workspace")) # Re-install pip so we get the launchers. - script.pip_install_local('-f', common_wheels, pip_src) + script.pip_install_local("-f", common_wheels, pip_src) return script @pytest.mark.parametrize( - 'shell, completion', + "shell, completion", COMPLETION_FOR_SUPPORTED_SHELLS_TESTS, ids=[t[0] for t in COMPLETION_FOR_SUPPORTED_SHELLS_TESTS], ) def test_completion_for_supported_shells( - script_with_launchers, shell, completion -): + script_with_launchers: PipTestEnvironment, shell: str, completion: str +) -> None: """ Test getting completion for bash shell """ - result = script_with_launchers.pip( - 'completion', '--' + shell, use_module=False - ) - assert completion in result.stdout, str(result.stdout) + result = script_with_launchers.pip("completion", "--" + shell, use_module=False) + actual = str(result.stdout) + if script_with_launchers.zipapp: + # The zipapp reports its name as "pip.pyz", but the expected + # output assumes "pip" + actual = actual.replace("pip.pyz", "pip") + assert completion in actual, actual @pytest.fixture(scope="session") -def autocomplete_script(tmpdir_factory, script_factory): - tmpdir = Path(str(tmpdir_factory.mktemp("autocomplete_script"))) +def autocomplete_script( + tmpdir_factory: pytest.TempPathFactory, script_factory: ScriptFactory +) -> PipTestEnvironment: + tmpdir = tmpdir_factory.mktemp("autocomplete_script") return script_factory(tmpdir.joinpath("workspace")) -@pytest.fixture -def autocomplete(autocomplete_script, monkeypatch): - monkeypatch.setattr(autocomplete_script, 'environ', os.environ.copy()) - autocomplete_script.environ['PIP_AUTO_COMPLETE'] = '1' +class DoAutocomplete(Protocol): + def __call__( + self, words: str, cword: str, cwd: Union[Path, str, None] = None + ) -> Tuple[TestPipResult, PipTestEnvironment]: + ... + - def do_autocomplete(words, cword, cwd=None): - autocomplete_script.environ['COMP_WORDS'] = words - autocomplete_script.environ['COMP_CWORD'] = cword +@pytest.fixture +def autocomplete( + autocomplete_script: PipTestEnvironment, monkeypatch: pytest.MonkeyPatch +) -> DoAutocomplete: + monkeypatch.setattr(autocomplete_script, "environ", os.environ.copy()) + autocomplete_script.environ["PIP_AUTO_COMPLETE"] = "1" + + def do_autocomplete( + words: str, cword: str, cwd: Union[Path, str, None] = None + ) -> Tuple[TestPipResult, PipTestEnvironment]: + autocomplete_script.environ["COMP_WORDS"] = words + autocomplete_script.environ["COMP_CWORD"] = cword result = autocomplete_script.run( - 'python', '-c', - 'from pip._internal.cli.autocompletion import autocomplete;' - 'autocomplete()', + "python", + "-c", + "from pip._internal.cli.autocompletion import autocomplete;" + "autocomplete()", expect_error=True, cwd=cwd, ) @@ -92,225 +156,252 @@ def autocomplete(autocomplete_script, monkeypatch): return do_autocomplete -def test_completion_for_unknown_shell(autocomplete_script): +def test_completion_for_unknown_shell(autocomplete_script: PipTestEnvironment) -> None: """ Test getting completion for an unknown shell """ - error_msg = 'no such option: --myfooshell' - result = autocomplete_script.pip( - 'completion', '--myfooshell', expect_error=True - ) - assert error_msg in result.stderr, 'tests for an unknown shell failed' + error_msg = "no such option: --myfooshell" + result = autocomplete_script.pip("completion", "--myfooshell", expect_error=True) + assert error_msg in result.stderr, "tests for an unknown shell failed" -def test_completion_alone(autocomplete_script): +def test_completion_alone(autocomplete_script: PipTestEnvironment) -> None: """ Test getting completion for none shell, just pip completion """ - result = autocomplete_script.pip('completion', allow_stderr_error=True) - assert 'ERROR: You must pass --bash or --fish or --zsh' in result.stderr, \ - 'completion alone failed -- ' + result.stderr + result = autocomplete_script.pip("completion", allow_stderr_error=True) + assert ( + "ERROR: You must pass --bash or --fish or --powershell or --zsh" + in result.stderr + ), ("completion alone failed -- " + result.stderr) -def test_completion_for_un_snippet(autocomplete): +def test_completion_for_un_snippet(autocomplete: DoAutocomplete) -> None: """ Test getting completion for ``un`` should return uninstall """ - res, env = autocomplete('pip un', '1') - assert res.stdout.strip().split() == ['uninstall'], res.stdout + res, env = autocomplete("pip un", "1") + assert res.stdout.strip().split() == ["uninstall"], res.stdout -def test_completion_for_default_parameters(autocomplete): +def test_completion_for_default_parameters(autocomplete: DoAutocomplete) -> None: """ Test getting completion for ``--`` should contain --help """ - res, env = autocomplete('pip --', '1') - assert '--help' in res.stdout,\ - "autocomplete function could not complete ``--``" + res, env = autocomplete("pip --", "1") + assert "--help" in res.stdout, "autocomplete function could not complete ``--``" -def test_completion_option_for_command(autocomplete): +def test_completion_option_for_command(autocomplete: DoAutocomplete) -> None: """ Test getting completion for ``--`` in command (e.g. ``pip search --``) """ - res, env = autocomplete('pip search --', '2') - assert '--help' in res.stdout,\ - "autocomplete function could not complete ``--``" + res, env = autocomplete("pip search --", "2") + assert "--help" in res.stdout, "autocomplete function could not complete ``--``" -def test_completion_short_option(autocomplete): +def test_completion_short_option(autocomplete: DoAutocomplete) -> None: """ Test getting completion for short options after ``-`` (eg. pip -) """ - res, env = autocomplete('pip -', '1') + res, env = autocomplete("pip -", "1") - assert '-h' in res.stdout.split(),\ - "autocomplete function could not complete short options after ``-``" + assert ( + "-h" in res.stdout.split() + ), "autocomplete function could not complete short options after ``-``" -def test_completion_short_option_for_command(autocomplete): +def test_completion_short_option_for_command(autocomplete: DoAutocomplete) -> None: """ Test getting completion for short options after ``-`` in command (eg. pip search -) """ - res, env = autocomplete('pip search -', '2') + res, env = autocomplete("pip search -", "2") - assert '-h' in res.stdout.split(),\ - "autocomplete function could not complete short options after ``-``" + assert ( + "-h" in res.stdout.split() + ), "autocomplete function could not complete short options after ``-``" -def test_completion_files_after_option(autocomplete, data): +def test_completion_files_after_option( + autocomplete: DoAutocomplete, data: TestData +) -> None: """ Test getting completion for <file> or <dir> after options in command (e.g. ``pip install -r``) """ res, env = autocomplete( - words=('pip install -r r'), - cword='3', + words=("pip install -r r"), + cword="3", cwd=data.completion_paths, ) - assert 'requirements.txt' in res.stdout, ( - "autocomplete function could not complete <file> " - "after options in command" - ) - assert os.path.join('resources', '') in res.stdout, ( - "autocomplete function could not complete <dir> " - "after options in command" - ) - assert not any(out in res.stdout for out in - (os.path.join('REPLAY', ''), 'README.txt')), ( + assert ( + "requirements.txt" in res.stdout + ), "autocomplete function could not complete <file> after options in command" + assert ( + os.path.join("resources", "") in res.stdout + ), "autocomplete function could not complete <dir> after options in command" + assert not any( + out in res.stdout for out in (os.path.join("REPLAY", ""), "README.txt") + ), ( "autocomplete function completed <file> or <dir> that " "should not be completed" ) - if sys.platform != 'win32': + if sys.platform != "win32": return - assert 'readme.txt' in res.stdout, ( - "autocomplete function could not complete <file> " - "after options in command" - ) - assert os.path.join('replay', '') in res.stdout, ( - "autocomplete function could not complete <dir> " - "after options in command" - ) + assert ( + "readme.txt" in res.stdout + ), "autocomplete function could not complete <file> after options in command" + assert ( + os.path.join("replay", "") in res.stdout + ), "autocomplete function could not complete <dir> after options in command" -def test_completion_not_files_after_option(autocomplete, data): +def test_completion_not_files_after_option( + autocomplete: DoAutocomplete, data: TestData +) -> None: """ Test not getting completion files after options which not applicable - (e.g. ``pip install``) + (e.g. ``pip wheel``) """ res, env = autocomplete( - words=('pip install r'), - cword='2', + words=("pip wheel r"), + cword="2", cwd=data.completion_paths, ) - assert not any(out in res.stdout for out in - ('requirements.txt', 'readme.txt',)), ( - "autocomplete function completed <file> when " - "it should not complete" - ) - assert not any(os.path.join(out, '') in res.stdout - for out in ('replay', 'resources')), ( - "autocomplete function completed <dir> when " - "it should not complete" + assert not any( + out in res.stdout + for out in ( + "requirements.txt", + "readme.txt", + ) + ), "autocomplete function completed <file> when it should not complete" + assert not any( + os.path.join(out, "") in res.stdout for out in ("replay", "resources") + ), "autocomplete function completed <dir> when it should not complete" + + +def test_pip_install_complete_files( + autocomplete: DoAutocomplete, data: TestData +) -> None: + """``pip install`` autocompletes wheel and sdist files.""" + res, env = autocomplete( + words=("pip install r"), + cword="2", + cwd=data.completion_paths, ) + assert all( + out in res.stdout + for out in ( + "requirements.txt", + "resources", + ) + ), "autocomplete function could not complete <path>" @pytest.mark.parametrize("cl_opts", ["-U", "--user", "-h"]) def test_completion_not_files_after_nonexpecting_option( - autocomplete, data, cl_opts -): + autocomplete: DoAutocomplete, data: TestData, cl_opts: str +) -> None: """ Test not getting completion files after options which not applicable (e.g. ``pip install``) """ res, env = autocomplete( - words=(f'pip install {cl_opts} r'), - cword='2', + words=(f"pip install {cl_opts} r"), + cword="2", cwd=data.completion_paths, ) - assert not any(out in res.stdout for out in - ('requirements.txt', 'readme.txt',)), ( - "autocomplete function completed <file> when " - "it should not complete" - ) - assert not any(os.path.join(out, '') in res.stdout - for out in ('replay', 'resources')), ( - "autocomplete function completed <dir> when " - "it should not complete" - ) + assert not any( + out in res.stdout + for out in ( + "requirements.txt", + "readme.txt", + ) + ), "autocomplete function completed <file> when it should not complete" + assert not any( + os.path.join(out, "") in res.stdout for out in ("replay", "resources") + ), "autocomplete function completed <dir> when it should not complete" -def test_completion_directories_after_option(autocomplete, data): +def test_completion_directories_after_option( + autocomplete: DoAutocomplete, data: TestData +) -> None: """ Test getting completion <dir> after options in command (e.g. ``pip --cache-dir``) """ res, env = autocomplete( - words=('pip --cache-dir r'), - cword='2', + words=("pip --cache-dir r"), + cword="2", cwd=data.completion_paths, ) - assert os.path.join('resources', '') in res.stdout, ( - "autocomplete function could not complete <dir> after options" - ) - assert not any(out in res.stdout for out in ( - 'requirements.txt', 'README.txt', os.path.join('REPLAY', ''))), ( - "autocomplete function completed <dir> when " - "it should not complete" - ) - if sys.platform == 'win32': - assert os.path.join('replay', '') in res.stdout, ( - "autocomplete function could not complete <dir> after options" - ) - - -def test_completion_subdirectories_after_option(autocomplete, data): + assert ( + os.path.join("resources", "") in res.stdout + ), "autocomplete function could not complete <dir> after options" + assert not any( + out in res.stdout + for out in ("requirements.txt", "README.txt", os.path.join("REPLAY", "")) + ), "autocomplete function completed <dir> when it should not complete" + if sys.platform == "win32": + assert ( + os.path.join("replay", "") in res.stdout + ), "autocomplete function could not complete <dir> after options" + + +def test_completion_subdirectories_after_option( + autocomplete: DoAutocomplete, data: TestData +) -> None: """ Test getting completion <dir> after options in command given path of a directory """ res, env = autocomplete( - words=('pip --cache-dir ' + os.path.join('resources', '')), - cword='2', + words=("pip --cache-dir " + os.path.join("resources", "")), + cword="2", cwd=data.completion_paths, ) - assert os.path.join('resources', - os.path.join('images', '')) in res.stdout, ( + assert os.path.join("resources", os.path.join("images", "")) in res.stdout, ( "autocomplete function could not complete <dir> " "given path of a directory after options" ) -def test_completion_path_after_option(autocomplete, data): +def test_completion_path_after_option( + autocomplete: DoAutocomplete, data: TestData +) -> None: """ Test getting completion <path> after options in command given absolute path """ res, env = autocomplete( - words=('pip install -e ' + os.path.join(data.completion_paths, 'R')), - cword='3', + words=("pip install -e " + os.path.join(data.completion_paths, "R")), + cword="3", ) - assert all(os.path.normcase(os.path.join(data.completion_paths, out)) - in res.stdout for out in ( - 'README.txt', os.path.join('REPLAY', ''))), ( + assert all( + os.path.normcase(os.path.join(data.completion_paths, out)) in res.stdout + for out in ("README.txt", os.path.join("REPLAY", "")) + ), ( "autocomplete function could not complete <path> " "after options in command given absolute path" ) -@pytest.mark.parametrize('flag', ['--bash', '--zsh', '--fish']) +@pytest.mark.parametrize("flag", ["--bash", "--zsh", "--fish", "--powershell"]) def test_completion_uses_same_executable_name( - autocomplete_script, flag, deprecated_python -): - executable_name = 'pip{}'.format(sys.version_info[0]) + autocomplete_script: PipTestEnvironment, flag: str, deprecated_python: bool +) -> None: + executable_name = "pip{}".format(sys.version_info[0]) # Deprecated python versions produce an extra deprecation warning result = autocomplete_script.run( - executable_name, 'completion', flag, expect_stderr=deprecated_python, + executable_name, + "completion", + flag, + expect_stderr=deprecated_python, ) assert executable_name in result.stdout |