pip/tests/functional/test_completion.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

412 lines
12 KiB
Python
Raw Normal View History

import os
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Tuple, Union
import pytest
from tests.lib import PipTestEnvironment, ScriptFactory, 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",
"""\
_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",
"""\
function __fish_complete_pip
set -lx COMP_WORDS (commandline -o) ""
set -lx COMP_CWORD ( \\
math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
)
set -lx PIP_AUTO_COMPLETE 1
string split \\ -- (eval $COMP_WORDS[1])
end
complete -fa "(__fish_complete_pip)" -c pip""",
),
(
"zsh",
"""\
2022-08-28 11:29:25 +02:00
#compdef -P pip[0-9.]#
__pip() {
compadd $( COMP_WORDS="$words[*]" \\
COMP_CWORD=$((CURRENT-1)) \\
PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null )
}
if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
# autoload from fpath, call function directly
__pip "$@"
else
# eval/source/. command, register function for later
compdef __pip -P 'pip[0-9.]#'
fi""",
),
(
"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
}
}""",
),
)
2010-04-15 21:08:45 +02:00
@pytest.fixture(scope="session")
def 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)
return script
@pytest.mark.parametrize(
"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: PipTestEnvironment, shell: str, completion: str
) -> None:
"""
Test getting completion for bash shell
"""
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: pytest.TempPathFactory, script_factory: ScriptFactory
) -> PipTestEnvironment:
tmpdir = tmpdir_factory.mktemp("autocomplete_script")
return script_factory(tmpdir.joinpath("workspace"))
class DoAutocomplete(Protocol):
def __call__(
self, words: str, cword: str, cwd: Union[Path, str, None] = None
) -> Tuple[TestPipResult, PipTestEnvironment]:
...
@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()",
expect_error=True,
cwd=cwd,
)
return result, autocomplete_script
return do_autocomplete
def test_completion_for_unknown_shell(autocomplete_script: PipTestEnvironment) -> None:
2010-04-15 21:08:45 +02:00
"""
Test getting completion for an unknown shell
"""
2012-09-01 21:21:30 +02:00
error_msg = "no such option: --myfooshell"
result = autocomplete_script.pip("completion", "--myfooshell", expect_error=True)
2010-04-15 21:08:45 +02:00
assert error_msg in result.stderr, "tests for an unknown shell failed"
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 --powershell or --zsh"
in result.stderr
), ("completion alone failed -- " + result.stderr)
def test_completion_for_un_snippet(autocomplete: DoAutocomplete) -> None:
2012-12-14 14:53:02 +01:00
"""
Test getting completion for ``un`` should return uninstall
2012-12-14 14:53:02 +01:00
"""
res, env = autocomplete("pip un", "1")
assert res.stdout.strip().split() == ["uninstall"], res.stdout
def test_completion_for_default_parameters(autocomplete: DoAutocomplete) -> None:
"""
Test getting completion for ``--`` should contain --help
"""
2012-12-14 14:53:02 +01:00
res, env = autocomplete("pip --", "1")
assert "--help" in res.stdout, "autocomplete function could not complete ``--``"
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 ``--``"
def test_completion_short_option(autocomplete: DoAutocomplete) -> None:
"""
Test getting completion for short options after ``-`` (eg. pip -)
"""
res, env = autocomplete("pip -", "1")
assert (
"-h" in res.stdout.split()
), "autocomplete function could not complete short options after ``-``"
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")
assert (
"-h" in res.stdout.split()
), "autocomplete function could not complete short options after ``-``"
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",
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")
), (
"autocomplete function completed <file> or <dir> that "
"should not be completed"
)
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"
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 wheel``)
"""
res, env = autocomplete(
words=("pip wheel r"),
cword="2",
cwd=data.completion_paths,
)
assert not any(
out in res.stdout
for out in (
"requirements.txt",
"readme.txt",
2021-08-13 15:25:41 +02:00
)
), "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: 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",
cwd=data.completion_paths,
)
assert not any(
out in res.stdout
for out in (
"requirements.txt",
"readme.txt",
2021-08-13 15:25:41 +02:00
)
), "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: 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",
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: 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",
cwd=data.completion_paths,
)
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: 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",
)
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"
)
2022-08-28 11:29:25 +02:00
# zsh completion script doesn't contain pip3
@pytest.mark.parametrize("flag", ["--bash", "--fish", "--powershell"])
def test_completion_uses_same_executable_name(
autocomplete_script: PipTestEnvironment, flag: str, deprecated_python: bool
) -> None:
2023-11-07 10:14:56 +01:00
executable_name = f"pip{sys.version_info[0]}"
# Deprecated python versions produce an extra deprecation warning
result = autocomplete_script.run(
executable_name,
"completion",
flag,
expect_stderr=deprecated_python,
)
assert executable_name in result.stdout