Merge pull request #6290 from cjerdonek/add-format-command

Add format_command_args().
This commit is contained in:
Chris Jerdonek 2019-02-24 13:23:51 -08:00 committed by GitHub
commit b88239918b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 54 additions and 38 deletions

2
news/6290.feature Normal file
View File

@ -0,0 +1,2 @@
Command arguments in ``subprocess`` log messages are now quoted using
``shlex.quote()``.

View File

@ -22,7 +22,7 @@ from pip._vendor import pkg_resources
# why we ignore the type on this import.
from pip._vendor.retrying import retry # type: ignore
from pip._vendor.six import PY2
from pip._vendor.six.moves import input
from pip._vendor.six.moves import input, shlex_quote
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
@ -66,6 +66,8 @@ __all__ = ['rmtree', 'display_path', 'backup_dir',
logger = std_logging.getLogger(__name__)
LOG_DIVIDER = '----------------------------------------'
WHEEL_EXTENSION = '.whl'
BZ2_EXTENSIONS = ('.tar.bz2', '.tbz')
XZ_EXTENSIONS = ('.tar.xz', '.txz', '.tlz', '.tar.lz', '.tar.lzma')
@ -647,6 +649,14 @@ def unpack_file(
)
def format_command_args(args):
# type: (List[str]) -> str
"""
Format command arguments for display.
"""
return ' '.join(shlex_quote(arg) for arg in args)
def call_subprocess(
cmd, # type: List[str]
show_stdout=False, # type: bool
@ -696,12 +706,8 @@ def call_subprocess(
else:
stdout = subprocess.PIPE
if command_desc is None:
cmd_parts = []
for part in cmd:
if ' ' in part or '\n' in part or '"' in part or "'" in part:
part = '"%s"' % part.replace('"', '\\"')
cmd_parts.append(part)
command_desc = ' '.join(cmd_parts)
command_desc = format_command_args(cmd)
logger.debug("Running command %s", command_desc)
env = os.environ.copy()
if extra_environ:
@ -751,10 +757,8 @@ def call_subprocess(
logger.info(
'Complete output from command %s:', command_desc,
)
logger.info(
''.join(all_output) +
'\n----------------------------------------'
)
# The all_output value already ends in a newline.
logger.info(''.join(all_output) + LOG_DIVIDER)
raise InstallationError(
'Command "%s" failed with error code %s in %s'
% (command_desc, proc.returncode, cwd))

View File

@ -33,7 +33,8 @@ from pip._internal.locations import (
from pip._internal.models.link import Link
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import (
call_subprocess, captured_stdout, ensure_dir, read_chunks,
LOG_DIVIDER, call_subprocess, captured_stdout, ensure_dir,
format_command_args, read_chunks,
)
from pip._internal.utils.setuptools_build import SETUPTOOLS_SHIM
from pip._internal.utils.temp_dir import TempDirectory
@ -785,7 +786,7 @@ def should_use_ephemeral_cache(
return True
def format_command(
def format_command_result(
command_args, # type: List[str]
command_output, # type: str
):
@ -793,7 +794,8 @@ def format_command(
"""
Format command information for logging.
"""
text = 'Command arguments: {}\n'.format(command_args)
command_desc = format_command_args(command_args)
text = 'Command arguments: {}\n'.format(command_desc)
if not command_output:
text += 'Command output: None'
@ -802,10 +804,7 @@ def format_command(
else:
if not command_output.endswith('\n'):
command_output += '\n'
text += (
'Command output:\n{}'
'-----------------------------------------'
).format(command_output)
text += 'Command output:\n{}{}'.format(command_output, LOG_DIVIDER)
return text
@ -827,7 +826,7 @@ def get_legacy_build_wheel_path(
msg = (
'Legacy build of wheel for {!r} created no files.\n'
).format(req.name)
msg += format_command(command_args, command_output)
msg += format_command_result(command_args, command_output)
logger.warning(msg)
return None
@ -836,7 +835,7 @@ def get_legacy_build_wheel_path(
'Legacy build of wheel for {!r} created more than one file.\n'
'Filenames (choosing first): {}\n'
).format(req.name, names)
msg += format_command(command_args, command_output)
msg += format_command_result(command_args, command_output)
logger.warning(msg)
return os.path.join(temp_dir, names[0])

View File

@ -24,10 +24,11 @@ from pip._internal.utils.encoding import auto_decode
from pip._internal.utils.glibc import check_glibc_version
from pip._internal.utils.hashes import Hashes, MissingHashes
from pip._internal.utils.misc import (
call_subprocess, egg_link_path, ensure_dir, get_installed_distributions,
get_prog, make_vcs_requirement_url, normalize_path, redact_netloc,
redact_password_from_url, remove_auth_from_url, rmtree,
split_auth_from_netloc, untar_file, unzip_file,
call_subprocess, egg_link_path, ensure_dir, format_command_args,
get_installed_distributions, get_prog, make_vcs_requirement_url,
normalize_path, redact_netloc, redact_password_from_url,
remove_auth_from_url, rmtree, split_auth_from_netloc, untar_file,
unzip_file,
)
from pip._internal.utils.packaging import check_dist_requires_python
from pip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory
@ -724,6 +725,16 @@ class TestGetProg(object):
assert get_prog() == expected
@pytest.mark.parametrize('args, expected', [
(['pip', 'list'], 'pip list'),
(['foo', 'space space', 'new\nline', 'double"quote', "single'quote"],
"""foo 'space space' 'new\nline' 'double"quote' 'single'"'"'quote'"""),
])
def test_format_command_args(args, expected):
actual = format_command_args(args)
assert actual == expected
def test_call_subprocess_works__no_keyword_arguments():
result = call_subprocess(
[sys.executable, '-c', 'print("Hello")'],

View File

@ -99,15 +99,15 @@ def test_should_use_ephemeral_cache__issue_6197(
assert ephem_cache is expected
def test_format_command__INFO(caplog):
def test_format_command_result__INFO(caplog):
caplog.set_level(logging.INFO)
actual = wheel.format_command(
command_args=['arg1', 'arg2'],
actual = wheel.format_command_result(
# Include an argument with a space to test argument quoting.
command_args=['arg1', 'second arg'],
command_output='output line 1\noutput line 2\n',
)
assert actual.splitlines() == [
"Command arguments: ['arg1', 'arg2']",
"Command arguments: arg1 'second arg'",
'Command output: [use --verbose to show]',
]
@ -118,30 +118,30 @@ def test_format_command__INFO(caplog):
# Test no trailing newline.
'output line 1\noutput line 2',
])
def test_format_command__DEBUG(caplog, command_output):
def test_format_command_result__DEBUG(caplog, command_output):
caplog.set_level(logging.DEBUG)
actual = wheel.format_command(
actual = wheel.format_command_result(
command_args=['arg1', 'arg2'],
command_output=command_output,
)
assert actual.splitlines() == [
"Command arguments: ['arg1', 'arg2']",
"Command arguments: arg1 arg2",
'Command output:',
'output line 1',
'output line 2',
'-----------------------------------------',
'----------------------------------------',
]
@pytest.mark.parametrize('log_level', ['DEBUG', 'INFO'])
def test_format_command__empty_output(caplog, log_level):
def test_format_command_result__empty_output(caplog, log_level):
caplog.set_level(log_level)
actual = wheel.format_command(
actual = wheel.format_command_result(
command_args=['arg1', 'arg2'],
command_output='',
)
assert actual.splitlines() == [
"Command arguments: ['arg1', 'arg2']",
"Command arguments: arg1 arg2",
'Command output: None',
]
@ -172,7 +172,7 @@ def test_get_legacy_build_wheel_path__no_names(caplog):
assert record.levelname == 'WARNING'
assert record.message.splitlines() == [
"Legacy build of wheel for 'pendulum' created no files.",
"Command arguments: ['arg1', 'arg2']",
"Command arguments: arg1 arg2",
'Command output: [use --verbose to show]',
]
@ -189,7 +189,7 @@ def test_get_legacy_build_wheel_path__multiple_names(caplog):
assert record.message.splitlines() == [
"Legacy build of wheel for 'pendulum' created more than one file.",
"Filenames (choosing first): ['name1', 'name2']",
"Command arguments: ['arg1', 'arg2']",
"Command arguments: arg1 arg2",
'Command output: [use --verbose to show]',
]