diff --git a/news/3997.bugfix b/news/3997.bugfix new file mode 100644 index 00000000000..78e9895f8c8 --- /dev/null +++ b/news/3997.bugfix @@ -0,0 +1,2 @@ +Shell completion scripts now use correct executable names (e.g., ``pip3`` +instead of ``pip``) diff --git a/src/pip/_internal/commands/completion.py b/src/pip/_internal/commands/completion.py index 40cd32e1dce..c4b387364a5 100644 --- a/src/pip/_internal/commands/completion.py +++ b/src/pip/_internal/commands/completion.py @@ -4,6 +4,7 @@ import textwrap from pip._internal.basecommand import Command +from pip._internal.utils.misc import get_prog BASE_COMPLETION = """ # pip %(shell)s completion start%(script)s# pip %(shell)s completion end @@ -17,7 +18,7 @@ COMP_CWORD=$COMP_CWORD \\ PIP_AUTO_COMPLETE=1 $1 ) ) } - complete -o default -F _pip_completion pip + complete -o default -F _pip_completion %(prog)s """, 'zsh': """ function _pip_completion { @@ -28,17 +29,19 @@ COMP_CWORD=$(( cword-1 )) \\ PIP_AUTO_COMPLETE=1 $words[1] ) ) } - compctl -K _pip_completion pip + compctl -K _pip_completion %(prog)s """, 'fish': """ function __fish_complete_pip set -lx COMP_WORDS (commandline -o) "" - set -lx COMP_CWORD {cword} + 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 - """.format(cword="(math (contains -i -- (commandline -t) $COMP_WORDS)-1)") + complete -fa "(__fish_complete_pip)" -c %(prog)s + """, } @@ -80,7 +83,9 @@ def run(self, options, args): shell_options = ['--' + shell for shell in sorted(shells)] if options.shell in shells: script = textwrap.dedent( - COMPLETION_SCRIPTS.get(options.shell, '') + COMPLETION_SCRIPTS.get(options.shell, '') % { + 'prog': get_prog(), + } ) print(BASE_COMPLETION % {'script': script, 'shell': options.shell}) else: diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index afd033f88b1..09c7646bff9 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -91,8 +91,11 @@ def ensure_dir(path): def get_prog(): try: - if os.path.basename(sys.argv[0]) in ('__main__.py', '-c'): + prog = os.path.basename(sys.argv[0]) + if prog in ('__main__.py', '-c'): return "%s -m pip" % sys.executable + else: + return prog except (AttributeError, TypeError, IndexError): pass return 'pip' diff --git a/tests/functional/test_completion.py b/tests/functional/test_completion.py index f6b34b211ad..c5483bf0301 100644 --- a/tests/functional/test_completion.py +++ b/tests/functional/test_completion.py @@ -1,4 +1,7 @@ import os +import sys + +import pytest def test_completion_for_bash(script): @@ -44,7 +47,9 @@ def test_completion_for_fish(script): fish_completion = """\ function __fish_complete_pip set -lx COMP_WORDS (commandline -o) "" - set -lx COMP_CWORD (math (contains -i -- (commandline -t) $COMP_WORDS)-1) + 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 @@ -114,3 +119,13 @@ def test_completion_option_for_command(script): res, env = setup_completion(script, 'pip search --', '2') assert '--help' in res.stdout,\ "autocomplete function could not complete ``--``" + + +@pytest.mark.parametrize('flag', ['--bash', '--zsh', '--fish']) +def test_completion_uses_same_executable_name(script, flag): + expect_stderr = sys.version_info[:2] == (3, 3) + executable_name = 'pip{}'.format(sys.version_info[0]) + result = script.run( + executable_name, 'completion', flag, expect_stderr=expect_stderr + ) + assert executable_name in result.stdout diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 9ab2e81c0b1..acfa6193ae3 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -23,8 +23,8 @@ from pip._internal.utils.glibc import check_glibc_version from pip._internal.utils.hashes import Hashes, MissingHashes from pip._internal.utils.misc import ( - egg_link_path, ensure_dir, get_installed_distributions, normalize_path, - rmtree, untar_file, unzip_file + egg_link_path, ensure_dir, get_installed_distributions, get_prog, + normalize_path, rmtree, untar_file, unzip_file ) from pip._internal.utils.packaging import check_dist_requires_python from pip._internal.utils.temp_dir import TempDirectory @@ -592,3 +592,23 @@ def test_check_requires(self, metadata, should_raise): check_dist_requires_python(fake_dist) else: check_dist_requires_python(fake_dist) + + +class TestGetProg(object): + + @pytest.mark.parametrize( + ("argv", "executable", "expected"), + [ + ('/usr/bin/pip', '', 'pip'), + ('-c', '/usr/bin/python', '/usr/bin/python -m pip'), + ('__main__.py', '/usr/bin/python', '/usr/bin/python -m pip'), + ('/usr/bin/pip3', '', 'pip3'), + ] + ) + def test_get_prog(self, monkeypatch, argv, executable, expected): + monkeypatch.setattr('pip._internal.utils.misc.sys.argv', [argv]) + monkeypatch.setattr( + 'pip._internal.utils.misc.sys.executable', + executable + ) + assert get_prog() == expected