Skip to content

Commit 27a2676

Browse files
committed
fix(pytest): handle parameterized tests without pytest discovery
Only emits position IDs with parameters when pytest discovery is enabled See #36 and #59
1 parent 48bf141 commit 27a2676

File tree

5 files changed

+59
-28
lines changed

5 files changed

+59
-28
lines changed

lua/neotest-python/init.lua

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,14 @@ local get_runner = function(python_command)
5959
if vim_test_runner == "pyunit" then
6060
return "unittest"
6161
end
62-
if vim_test_runner and lib.func_util.index({ "unittest", "pytest", "django" }, vim_test_runner) then
62+
if
63+
vim_test_runner and lib.func_util.index({ "unittest", "pytest", "django" }, vim_test_runner)
64+
then
6365
return vim_test_runner
6466
end
65-
local runner = base.module_exists("pytest", python_command) and "pytest" or base.module_exists("django", python_command) and "django" or "unittest"
67+
local runner = base.module_exists("pytest", python_command) and "pytest"
68+
or base.module_exists("django", python_command) and "django"
69+
or "unittest"
6670
stored_runners[command_str] = runner
6771
return runner
6872
end
@@ -71,7 +75,7 @@ end
7175
local PythonNeotestAdapter = { name = "neotest-python" }
7276

7377
PythonNeotestAdapter.root =
74-
lib.files.match_root_pattern("pyproject.toml", "setup.cfg", "mypy.ini", "pytest.ini", "setup.py")
78+
lib.files.match_root_pattern("pyproject.toml", "setup.cfg", "mypy.ini", "pytest.ini", "setup.py")
7579

7680
function PythonNeotestAdapter.is_test_file(file_path)
7781
return is_test_file(file_path)
@@ -147,9 +151,15 @@ function PythonNeotestAdapter.build_spec(args)
147151
stream_path,
148152
"--runner",
149153
runner,
150-
"--",
151-
vim.list_extend(get_args(runner, position, args.strategy), args.extra_args or {}),
152154
})
155+
if pytest_discover_instances then
156+
table.insert(script_args, "--emit-parameterized-ids")
157+
end
158+
vim.list_extend(script_args, get_args(runner, position, args.strategy))
159+
if args.extra_args then
160+
vim.list_extend(script_args, args.extra_args)
161+
end
162+
153163
if position then
154164
table.insert(script_args, position.id)
155165
end

neotest_python/__init__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ class TestRunner(str, Enum):
1212
DJANGO = "django"
1313

1414

15-
def get_adapter(runner: TestRunner) -> NeotestAdapter:
15+
def get_adapter(runner: TestRunner, emit_parameterized_ids: bool) -> NeotestAdapter:
1616
if runner == TestRunner.PYTEST:
1717
from .pytest import PytestNeotestAdapter
1818

19-
return PytestNeotestAdapter()
19+
return PytestNeotestAdapter(emit_parameterized_ids)
2020
elif runner == TestRunner.UNITTEST:
2121
from .unittest import UnittestNeotestAdapter
2222

@@ -42,18 +42,24 @@ def get_adapter(runner: TestRunner) -> NeotestAdapter:
4242
required=True,
4343
help="File to stream result JSON to",
4444
)
45+
parser.add_argument(
46+
"--emit-parameterized-ids",
47+
action="store_true",
48+
help="Emit parameterized test ids (pytest only)",
49+
)
4550
parser.add_argument("args", nargs="*")
4651

4752

4853
def main(argv: List[str]):
4954
if "--pytest-collect" in argv:
5055
argv.remove("--pytest-collect")
5156
from .pytest import collect
57+
5258
collect(argv)
5359
return
5460

5561
args = parser.parse_args(argv)
56-
adapter = get_adapter(TestRunner(args.runner))
62+
adapter = get_adapter(TestRunner(args.runner), args.emit_parameterized_ids)
5763

5864
with open(args.stream_file, "w") as stream_file:
5965

neotest_python/django_unittest.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import inspect
2-
import subprocess
32
import os
3+
import subprocess
44
import sys
55
import traceback
66
import unittest
77
from argparse import ArgumentParser
88
from pathlib import Path
99
from types import TracebackType
10-
from typing import Any, Tuple, Dict, List
10+
from typing import Any, Dict, List, Tuple
1111
from unittest import TestCase
1212
from unittest.runner import TextTestResult
13+
1314
from django import setup as django_setup
1415
from django.test.runner import DiscoverRunner
16+
1517
from .base import NeotestAdapter, NeotestError, NeotestResultStatus
1618

1719

@@ -67,11 +69,7 @@ def __init__(self, **kwargs):
6769
@classmethod
6870
def add_arguments(cls, parser):
6971
DiscoverRunner.add_arguments(parser)
70-
parser.add_argument(
71-
"--verbosity",
72-
nargs="?",
73-
default=2
74-
)
72+
parser.add_argument("--verbosity", nargs="?", default=2)
7573
parser.add_argument(
7674
"--failfast",
7775
action="store_true",

neotest_python/pytest.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,32 @@
22
from pathlib import Path
33
from typing import Callable, Dict, List, Optional, Union
44

5-
from .base import NeotestAdapter, NeotestError, NeotestResult, NeotestResultStatus
6-
75
import pytest
86
from _pytest._code.code import ExceptionRepr
97
from _pytest.terminal import TerminalReporter
108

9+
from .base import NeotestAdapter, NeotestError, NeotestResult, NeotestResultStatus
10+
1111

1212
class PytestNeotestAdapter(NeotestAdapter):
13+
def __init__(self, emit_parameterized_ids: bool):
14+
self.emit_parameterized_ids = emit_parameterized_ids
15+
1316
def run(
1417
self,
1518
args: List[str],
1619
stream: Callable[[str, NeotestResult], None],
1720
) -> Dict[str, NeotestResult]:
18-
result_collector = NeotestResultCollector(self, stream=stream)
19-
pytest.main(args=args, plugins=[
20-
result_collector,
21-
NeotestDebugpyPlugin(),
22-
])
21+
result_collector = NeotestResultCollector(
22+
self, stream=stream, emit_parameterized_ids=self.emit_parameterized_ids
23+
)
24+
pytest.main(
25+
args=args,
26+
plugins=[
27+
result_collector,
28+
NeotestDebugpyPlugin(),
29+
],
30+
)
2331
return result_collector.results
2432

2533

@@ -28,9 +36,11 @@ def __init__(
2836
self,
2937
adapter: PytestNeotestAdapter,
3038
stream: Callable[[str, NeotestResult], None],
39+
emit_parameterized_ids: bool,
3140
):
3241
self.stream = stream
3342
self.adapter = adapter
43+
self.emit_parameterized_ids = emit_parameterized_ids
3444

3545
self.pytest_config: Optional["pytest.Config"] = None # type: ignore
3646
self.results: Dict[str, NeotestResult] = {}
@@ -80,7 +90,9 @@ def pytest_cmdline_main(self, config: "pytest.Config"):
8090
self.pytest_config = config
8191

8292
@pytest.hookimpl(hookwrapper=True)
83-
def pytest_runtest_makereport(self, item: "pytest.Item", call: "pytest.CallInfo") -> None:
93+
def pytest_runtest_makereport(
94+
self, item: "pytest.Item", call: "pytest.CallInfo"
95+
) -> None:
8496
# pytest generates the report.outcome field in its internal
8597
# pytest_runtest_makereport implementation, so call it first. (We don't
8698
# implement pytest_runtest_logreport because it doesn't have access to
@@ -105,8 +117,10 @@ def pytest_runtest_makereport(self, item: "pytest.Item", call: "pytest.CallInfo"
105117
msg_prefix = ""
106118
if getattr(item, "callspec", None) is not None:
107119
# Parametrized test
108-
msg_prefix = f"[{item.callspec.id}] "
109-
pos_id += f"[{item.callspec.id}]"
120+
if self.emit_parameterized_ids:
121+
pos_id += f"[{item.callspec.id}]"
122+
else:
123+
msg_prefix = f"[{item.callspec.id}] "
110124
if report.outcome == "failed":
111125
exc_repr = report.longrepr
112126
# Test fails due to condition outside of test e.g. xfail
@@ -119,7 +133,9 @@ def pytest_runtest_makereport(self, item: "pytest.Item", call: "pytest.CallInfo"
119133
for traceback_entry in reversed(call.excinfo.traceback):
120134
if str(traceback_entry.path) == abs_path:
121135
error_line = traceback_entry.lineno
122-
errors.append({"message": msg_prefix + error_message, "line": error_line})
136+
errors.append(
137+
{"message": msg_prefix + error_message, "line": error_line}
138+
)
123139
else:
124140
# TODO: Figure out how these are returned and how to represent
125141
raise Exception(
@@ -159,6 +175,7 @@ def maybe_debugpy_postmortem(excinfo):
159175
"""
160176
# Reference: https://github.com/microsoft/debugpy/issues/723
161177
import threading
178+
162179
try:
163180
import pydevd
164181
except ImportError:
@@ -180,4 +197,4 @@ def maybe_debugpy_postmortem(excinfo):
180197

181198

182199
def collect(args):
183-
pytest.main(['--collect-only', '-q'] + args)
200+
pytest.main(["--collect-only", "-q"] + args)

neotest_python/unittest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def case_file(self, case) -> str:
1717
return str(Path(inspect.getmodule(case).__file__).absolute()) # type: ignore
1818

1919
def case_id_elems(self, case) -> List[str]:
20-
if case.__class__.__name__ == '_SubTest':
20+
if case.__class__.__name__ == "_SubTest":
2121
case = case.test_case
2222
file = self.case_file(case)
2323
elems = [file, case.__class__.__name__]

0 commit comments

Comments
 (0)