fix(pytest): handle parameterized tests without pytest discovery
Only emits position IDs with parameters when pytest discovery is enabled See #36 and #59
This commit is contained in:
@@ -59,10 +59,14 @@ local get_runner = function(python_command)
|
||||
if vim_test_runner == "pyunit" then
|
||||
return "unittest"
|
||||
end
|
||||
if vim_test_runner and lib.func_util.index({ "unittest", "pytest", "django" }, vim_test_runner) then
|
||||
if
|
||||
vim_test_runner and lib.func_util.index({ "unittest", "pytest", "django" }, vim_test_runner)
|
||||
then
|
||||
return vim_test_runner
|
||||
end
|
||||
local runner = base.module_exists("pytest", python_command) and "pytest" or base.module_exists("django", python_command) and "django" or "unittest"
|
||||
local runner = base.module_exists("pytest", python_command) and "pytest"
|
||||
or base.module_exists("django", python_command) and "django"
|
||||
or "unittest"
|
||||
stored_runners[command_str] = runner
|
||||
return runner
|
||||
end
|
||||
@@ -71,7 +75,7 @@ end
|
||||
local PythonNeotestAdapter = { name = "neotest-python" }
|
||||
|
||||
PythonNeotestAdapter.root =
|
||||
lib.files.match_root_pattern("pyproject.toml", "setup.cfg", "mypy.ini", "pytest.ini", "setup.py")
|
||||
lib.files.match_root_pattern("pyproject.toml", "setup.cfg", "mypy.ini", "pytest.ini", "setup.py")
|
||||
|
||||
function PythonNeotestAdapter.is_test_file(file_path)
|
||||
return is_test_file(file_path)
|
||||
@@ -147,9 +151,15 @@ function PythonNeotestAdapter.build_spec(args)
|
||||
stream_path,
|
||||
"--runner",
|
||||
runner,
|
||||
"--",
|
||||
vim.list_extend(get_args(runner, position, args.strategy), args.extra_args or {}),
|
||||
})
|
||||
if pytest_discover_instances then
|
||||
table.insert(script_args, "--emit-parameterized-ids")
|
||||
end
|
||||
vim.list_extend(script_args, get_args(runner, position, args.strategy))
|
||||
if args.extra_args then
|
||||
vim.list_extend(script_args, args.extra_args)
|
||||
end
|
||||
|
||||
if position then
|
||||
table.insert(script_args, position.id)
|
||||
end
|
||||
|
@@ -12,11 +12,11 @@ class TestRunner(str, Enum):
|
||||
DJANGO = "django"
|
||||
|
||||
|
||||
def get_adapter(runner: TestRunner) -> NeotestAdapter:
|
||||
def get_adapter(runner: TestRunner, emit_parameterized_ids: bool) -> NeotestAdapter:
|
||||
if runner == TestRunner.PYTEST:
|
||||
from .pytest import PytestNeotestAdapter
|
||||
|
||||
return PytestNeotestAdapter()
|
||||
return PytestNeotestAdapter(emit_parameterized_ids)
|
||||
elif runner == TestRunner.UNITTEST:
|
||||
from .unittest import UnittestNeotestAdapter
|
||||
|
||||
@@ -42,6 +42,11 @@ parser.add_argument(
|
||||
required=True,
|
||||
help="File to stream result JSON to",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--emit-parameterized-ids",
|
||||
action="store_true",
|
||||
help="Emit parameterized test ids (pytest only)",
|
||||
)
|
||||
parser.add_argument("args", nargs="*")
|
||||
|
||||
|
||||
@@ -49,11 +54,12 @@ def main(argv: List[str]):
|
||||
if "--pytest-collect" in argv:
|
||||
argv.remove("--pytest-collect")
|
||||
from .pytest import collect
|
||||
|
||||
collect(argv)
|
||||
return
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
adapter = get_adapter(TestRunner(args.runner))
|
||||
adapter = get_adapter(TestRunner(args.runner), args.emit_parameterized_ids)
|
||||
|
||||
with open(args.stream_file, "w") as stream_file:
|
||||
|
||||
|
@@ -1,17 +1,19 @@
|
||||
import inspect
|
||||
import subprocess
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
import unittest
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
from types import TracebackType
|
||||
from typing import Any, Tuple, Dict, List
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from unittest import TestCase
|
||||
from unittest.runner import TextTestResult
|
||||
|
||||
from django import setup as django_setup
|
||||
from django.test.runner import DiscoverRunner
|
||||
|
||||
from .base import NeotestAdapter, NeotestError, NeotestResultStatus
|
||||
|
||||
|
||||
@@ -67,11 +69,7 @@ class DjangoNeotestAdapter(CaseUtilsMixin, NeotestAdapter):
|
||||
@classmethod
|
||||
def add_arguments(cls, parser):
|
||||
DiscoverRunner.add_arguments(parser)
|
||||
parser.add_argument(
|
||||
"--verbosity",
|
||||
nargs="?",
|
||||
default=2
|
||||
)
|
||||
parser.add_argument("--verbosity", nargs="?", default=2)
|
||||
parser.add_argument(
|
||||
"--failfast",
|
||||
action="store_true",
|
||||
|
@@ -2,24 +2,32 @@ from io import StringIO
|
||||
from pathlib import Path
|
||||
from typing import Callable, Dict, List, Optional, Union
|
||||
|
||||
from .base import NeotestAdapter, NeotestError, NeotestResult, NeotestResultStatus
|
||||
|
||||
import pytest
|
||||
from _pytest._code.code import ExceptionRepr
|
||||
from _pytest.terminal import TerminalReporter
|
||||
|
||||
from .base import NeotestAdapter, NeotestError, NeotestResult, NeotestResultStatus
|
||||
|
||||
|
||||
class PytestNeotestAdapter(NeotestAdapter):
|
||||
def __init__(self, emit_parameterized_ids: bool):
|
||||
self.emit_parameterized_ids = emit_parameterized_ids
|
||||
|
||||
def run(
|
||||
self,
|
||||
args: List[str],
|
||||
stream: Callable[[str, NeotestResult], None],
|
||||
) -> Dict[str, NeotestResult]:
|
||||
result_collector = NeotestResultCollector(self, stream=stream)
|
||||
pytest.main(args=args, plugins=[
|
||||
result_collector,
|
||||
NeotestDebugpyPlugin(),
|
||||
])
|
||||
result_collector = NeotestResultCollector(
|
||||
self, stream=stream, emit_parameterized_ids=self.emit_parameterized_ids
|
||||
)
|
||||
pytest.main(
|
||||
args=args,
|
||||
plugins=[
|
||||
result_collector,
|
||||
NeotestDebugpyPlugin(),
|
||||
],
|
||||
)
|
||||
return result_collector.results
|
||||
|
||||
|
||||
@@ -28,9 +36,11 @@ class NeotestResultCollector:
|
||||
self,
|
||||
adapter: PytestNeotestAdapter,
|
||||
stream: Callable[[str, NeotestResult], None],
|
||||
emit_parameterized_ids: bool,
|
||||
):
|
||||
self.stream = stream
|
||||
self.adapter = adapter
|
||||
self.emit_parameterized_ids = emit_parameterized_ids
|
||||
|
||||
self.pytest_config: Optional["pytest.Config"] = None # type: ignore
|
||||
self.results: Dict[str, NeotestResult] = {}
|
||||
@@ -80,7 +90,9 @@ class NeotestResultCollector:
|
||||
self.pytest_config = config
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_makereport(self, item: "pytest.Item", call: "pytest.CallInfo") -> None:
|
||||
def pytest_runtest_makereport(
|
||||
self, item: "pytest.Item", call: "pytest.CallInfo"
|
||||
) -> None:
|
||||
# pytest generates the report.outcome field in its internal
|
||||
# pytest_runtest_makereport implementation, so call it first. (We don't
|
||||
# implement pytest_runtest_logreport because it doesn't have access to
|
||||
@@ -105,8 +117,10 @@ class NeotestResultCollector:
|
||||
msg_prefix = ""
|
||||
if getattr(item, "callspec", None) is not None:
|
||||
# Parametrized test
|
||||
msg_prefix = f"[{item.callspec.id}] "
|
||||
pos_id += f"[{item.callspec.id}]"
|
||||
if self.emit_parameterized_ids:
|
||||
pos_id += f"[{item.callspec.id}]"
|
||||
else:
|
||||
msg_prefix = f"[{item.callspec.id}] "
|
||||
if report.outcome == "failed":
|
||||
exc_repr = report.longrepr
|
||||
# Test fails due to condition outside of test e.g. xfail
|
||||
@@ -119,7 +133,9 @@ class NeotestResultCollector:
|
||||
for traceback_entry in reversed(call.excinfo.traceback):
|
||||
if str(traceback_entry.path) == abs_path:
|
||||
error_line = traceback_entry.lineno
|
||||
errors.append({"message": msg_prefix + error_message, "line": error_line})
|
||||
errors.append(
|
||||
{"message": msg_prefix + error_message, "line": error_line}
|
||||
)
|
||||
else:
|
||||
# TODO: Figure out how these are returned and how to represent
|
||||
raise Exception(
|
||||
@@ -159,6 +175,7 @@ class NeotestDebugpyPlugin:
|
||||
"""
|
||||
# Reference: https://github.com/microsoft/debugpy/issues/723
|
||||
import threading
|
||||
|
||||
try:
|
||||
import pydevd
|
||||
except ImportError:
|
||||
@@ -180,4 +197,4 @@ class NeotestDebugpyPlugin:
|
||||
|
||||
|
||||
def collect(args):
|
||||
pytest.main(['--collect-only', '-q'] + args)
|
||||
pytest.main(["--collect-only", "-q"] + args)
|
||||
|
@@ -17,7 +17,7 @@ class UnittestNeotestAdapter(NeotestAdapter):
|
||||
return str(Path(inspect.getmodule(case).__file__).absolute()) # type: ignore
|
||||
|
||||
def case_id_elems(self, case) -> List[str]:
|
||||
if case.__class__.__name__ == '_SubTest':
|
||||
if case.__class__.__name__ == "_SubTest":
|
||||
case = case.test_case
|
||||
file = self.case_file(case)
|
||||
elems = [file, case.__class__.__name__]
|
||||
|
Reference in New Issue
Block a user