diff --git a/lua/neotest-python/init.lua b/lua/neotest-python/init.lua index f5da1db..a934066 100644 --- a/lua/neotest-python/init.lua +++ b/lua/neotest-python/init.lua @@ -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 diff --git a/neotest_python/__init__.py b/neotest_python/__init__.py index e898d36..9f846e7 100644 --- a/neotest_python/__init__.py +++ b/neotest_python/__init__.py @@ -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: diff --git a/neotest_python/django_unittest.py b/neotest_python/django_unittest.py index 1a8e7bb..6ad9b07 100644 --- a/neotest_python/django_unittest.py +++ b/neotest_python/django_unittest.py @@ -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", diff --git a/neotest_python/pytest.py b/neotest_python/pytest.py index 603d82c..48d4514 100644 --- a/neotest_python/pytest.py +++ b/neotest_python/pytest.py @@ -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) diff --git a/neotest_python/unittest.py b/neotest_python/unittest.py index f7772a2..0f41a26 100644 --- a/neotest_python/unittest.py +++ b/neotest_python/unittest.py @@ -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__]