From a50da30c89be8fd30bad33fe77d29c61ecfd5d3f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 22 May 2023 23:45:49 -0700 Subject: [PATCH] Report deprecated functions at the main function call. I've added stack level logic so that deprecated functions raise from the initial caller. This helps developers fix these warnings in their own scripts. Also cleaned up the deprecated calls in tests which don't test deprecated features, and made tests which do have deprecations more strict. --- pytest_console_scripts/__init__.py | 32 +++++++++++++++++++++++------- tests/test_run_scripts.py | 32 +++++++++++++++++------------- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/pytest_console_scripts/__init__.py b/pytest_console_scripts/__init__.py index b9bc1f0..3f0afc3 100644 --- a/pytest_console_scripts/__init__.py +++ b/pytest_console_scripts/__init__.py @@ -137,6 +137,7 @@ def _handle_command_args( command: _Command, *args: _StrOrPath, shell: bool = False, + stacklevel: int = 1, ) -> Sequence[_StrOrPath]: """Return command arguments in a consistent list format. @@ -146,7 +147,9 @@ def _handle_command_args( if args or not isinstance(command, (str, os.PathLike)): command = subprocess.list2cmdline( str(arg) - for arg in _handle_command_args(command, *args, shell=False) + for arg in _handle_command_args( + command, *args, shell=False, stacklevel=stacklevel + 1 + ) ) command = shlex.split(str(command), posix=os.name == 'posix') args = () @@ -158,6 +161,7 @@ def _handle_command_args( '\nReplace `script_runner.run(a, b, c)` calls with' ' `script_runner.run([a, b, c])`', DeprecationWarning, + stacklevel=stacklevel + 1, ) if not isinstance(command, (str, os.PathLike)): return [*command, *args] @@ -263,6 +267,7 @@ def run( env=env, stdin=stdin, check=check, + _stacklevel=2, **options, ) @@ -326,14 +331,18 @@ def run_inprocess( print_result: bool = True, stdin: io.IOBase | None = None, check: bool = False, + _stacklevel: int = 1, **options: Any, ) -> RunResult: for key in options: warnings.warn( f'Keyword argument {key!r} was ignored.' - '\nConsider using subprocess mode or raising an issue.' + '\nConsider using subprocess mode or raising an issue.', + stacklevel=_stacklevel + 1, ) - cmd_args = _handle_command_args(command, *arguments, shell=shell) + cmd_args = _handle_command_args( + command, *arguments, shell=shell, stacklevel=_stacklevel + 1 + ) script = cls._load_script(cmd_args[0], cwd=cwd, env=env) cmd_args = [str(cmd) for cmd in cmd_args] stdin_stream = stdin if stdin is not None else StreamMock() @@ -396,20 +405,29 @@ def run_subprocess( stdin: io.IOBase | None = None, check: bool = False, universal_newlines: bool = True, + _stacklevel: int = 1, **options: Any, ) -> RunResult: stdin_input: str | bytes | None = None if stdin is not None: stdin_input = stdin.read() - script_path = cls._locate_script(_handle_command_args( - command, *arguments, shell=shell)[0], cwd=cwd, env=env + script_path = cls._locate_script( + _handle_command_args( + command, *arguments, shell=shell, stacklevel=_stacklevel + 1 + )[0], + cwd=cwd, + env=env, ) if arguments: - command = _handle_command_args(command, *arguments, shell=shell) + command = _handle_command_args( + command, *arguments, shell=shell, stacklevel=_stacklevel + 1 + ) if _is_nonexecutable_python_file(script_path): - command = _handle_command_args(command, shell=shell) + command = _handle_command_args( + command, shell=shell, stacklevel=_stacklevel + 1 + ) command = [sys.executable or 'python', *command] cp = subprocess.run( diff --git a/tests/test_run_scripts.py b/tests/test_run_scripts.py index 1238872..95697df 100644 --- a/tests/test_run_scripts.py +++ b/tests/test_run_scripts.py @@ -1,6 +1,7 @@ """Test running of scripts with various modes and options.""" from __future__ import annotations +import contextlib import importlib import io import os @@ -8,7 +9,7 @@ from pathlib import Path from subprocess import CalledProcessError from types import ModuleType -from typing import Any +from typing import Any, ContextManager from unittest import mock import pytest @@ -92,9 +93,7 @@ def test_script(script_runner): # inner and outer script runners. result = script_runner.run( - 'pytest', - str(test), - '--script-launch-mode=' + launch_mode, + ['pytest', test, f'--script-launch-mode={launch_mode}'] ) assert result.success @@ -248,9 +247,7 @@ def test_fail(script_runner): """ ) result = script_runner.run( - 'pytest', - str(test), - '--script-launch-mode=' + launch_mode, + ['pytest', test, f'--script-launch-mode={launch_mode}'] ) assert result.success != fail if fail: @@ -306,7 +303,7 @@ def test_script(script_runner): script_runner.run(R'''{console_script}''', print_result=False) """ ) - result = script_runner.run('pytest', '-s', str(test)) + result = script_runner.run(['pytest', '-s', test]) assert result.success assert 'the answer is 42' not in result.stdout assert 'Running console script' not in result.stdout @@ -327,7 +324,7 @@ def test_script(script_runner): script_runner.run(R'''{console_script}''') """ ) - result = script_runner.run('pytest', '-s', '--hide-run-results', str(test)) + result = script_runner.run(['pytest', '-s', '--hide-run-results', test]) assert result.success assert 'the answer is 42' not in result.stdout assert 'Running console script' not in result.stdout @@ -411,9 +408,11 @@ def test_deprecated_args( print(sys.argv[1:]) """ ) - result = script_runner.run(console_script, 'A', 'B', check=True) + with pytest.warns(match=r".*multiple arguments."): + result = script_runner.run(console_script, 'A', 'B', check=True) assert result.stdout == "['A', 'B']\n" - result = script_runner.run([console_script, 'C'], 'D', check=True) + with pytest.warns(match=r".*multiple arguments."): + result = script_runner.run([console_script, 'C'], 'D', check=True) assert result.stdout == "['C', 'D']\n" @@ -430,9 +429,14 @@ def test_check( def test_ignore_universal_newlines( console_script: Path, script_runner: ScriptRunner ) -> None: - result = script_runner.run( - str(console_script), check=True, universal_newlines=True - ) + expectation: dict[str, ContextManager[Any]] = { + 'inprocess': pytest.warns(match=r"Keyword argument .* was ignored"), + 'subprocess': contextlib.nullcontext(), + } + with expectation[script_runner.launch_mode]: + result = script_runner.run( + str(console_script), check=True, universal_newlines=True + ) assert result.stdout == 'foo\n' assert result.stderr == ''