feat(pytest): use python_functions config option (#100)

This commit is contained in:
nicos68
2025-06-23 23:28:18 +02:00
committed by GitHub
parent 34c9f6f3dc
commit ed9b4d794b
4 changed files with 64 additions and 17 deletions

View File

@@ -52,6 +52,7 @@ return function(config)
---@type neotest.Adapter ---@type neotest.Adapter
return { return {
name = "neotest-python", name = "neotest-python",
root = base.get_root, root = base.get_root,
filter_dir = function(name) filter_dir = function(name)
@@ -64,7 +65,7 @@ return function(config)
local python_command = config.get_python_command(root) local python_command = config.get_python_command(root)
local runner = config.get_runner(python_command) local runner = config.get_runner(python_command)
local positions = lib.treesitter.parse_positions(path, base.treesitter_queries, { local positions = lib.treesitter.parse_positions(path, base.treesitter_queries(runner, config, python_command), {
require_namespaces = runner == "unittest", require_namespaces = runner == "unittest",
}) })

View File

@@ -92,18 +92,56 @@ function M.get_python_command(root)
return python_command_mem[root] return python_command_mem[root]
end end
M.treesitter_queries = [[ ---@return string
function M.get_script_path()
local paths = vim.api.nvim_get_runtime_file("neotest.py", true)
for _, path in ipairs(paths) do
if vim.endswith(path, ("neotest-python%sneotest.py"):format(lib.files.sep)) then
return path
end
end
error("neotest.py not found")
end
---@param python_command string[]
---@param config neotest-python._AdapterConfig
---@param runner string
---@return string
local function scan_test_function_pattern(runner, config, python_command)
local test_function_pattern = "^test"
if runner == "pytest" and config.pytest_discovery then
local cmd = vim.tbl_flatten({ python_command, M.get_script_path(), "--pytest-extract-test-name-template" })
local _, data = lib.process.run(cmd, { stdout = true, stderr = true })
for line in vim.gsplit(data.stdout, "\n", true) do
if string.sub(line, 1, 1) == "{" and string.find(line, "python_functions") ~= nil then
local pytest_option = vim.json.decode(line)
test_function_pattern = pytest_option.python_functions
end
end
end
return test_function_pattern
end
---@param python_command string[]
---@param config neotest-python._AdapterConfig
---@param runner string
---@return string
M.treesitter_queries = function(runner, config, python_command)
local test_function_pattern = scan_test_function_pattern(runner, config, python_command)
return string.format([[
;; Match undecorated functions ;; Match undecorated functions
((function_definition ((function_definition
name: (identifier) @test.name) name: (identifier) @test.name)
(#match? @test.name "^test")) (#match? @test.name "%s"))
@test.definition @test.definition
;; Match decorated function, including decorators in definition ;; Match decorated function, including decorators in definition
(decorated_definition (decorated_definition
((function_definition ((function_definition
name: (identifier) @test.name) name: (identifier) @test.name)
(#match? @test.name "^test"))) (#match? @test.name "%s")))
@test.definition @test.definition
;; Match decorated classes, including decorators in definition ;; Match decorated classes, including decorators in definition
@@ -120,23 +158,12 @@ M.treesitter_queries = [[
@namespace.definition @namespace.definition
(#not-has-parent? @namespace.definition decorated_definition) (#not-has-parent? @namespace.definition decorated_definition)
) )
]] ]], test_function_pattern, test_function_pattern)
end
M.get_root = M.get_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")
---@return string
function M.get_script_path()
local paths = vim.api.nvim_get_runtime_file("neotest.py", true)
for _, path in ipairs(paths) do
if vim.endswith(path, ("neotest-python%sneotest.py"):format(lib.files.sep)) then
return path
end
end
error("neotest.py not found")
end
function M.create_dap_config(python_path, script_path, script_args, dap_args) function M.create_dap_config(python_path, script_path, script_args, dap_args)
return vim.tbl_extend("keep", { return vim.tbl_extend("keep", {
type = "python", type = "python",

View File

@@ -58,6 +58,13 @@ def main(argv: List[str]):
collect(argv) collect(argv)
return return
if "--pytest-extract-test-name-template" in argv:
argv.remove("--pytest-extract-test-name-template")
from .pytest import extract_test_name_template
extract_test_name_template(argv)
return
args = parser.parse_args(argv) args = parser.parse_args(argv)
adapter = get_adapter(TestRunner(args.runner), args.emit_parameterized_ids) adapter = get_adapter(TestRunner(args.runner), args.emit_parameterized_ids)

View File

@@ -1,4 +1,5 @@
from io import StringIO from io import StringIO
import json
from pathlib import Path from pathlib import Path
from typing import Callable, Dict, List, Optional, Union from typing import Callable, Dict, List, Optional, Union
@@ -205,5 +206,16 @@ class NeotestDebugpyPlugin:
additional_info.is_tracing -= 1 additional_info.is_tracing -= 1
class TestNameTemplateExtractor:
@staticmethod
def pytest_collection_modifyitems(config):
config = {"python_functions": config.getini("python_functions")[0]}
print(f"\n{json.dumps(config)}\n")
def extract_test_name_template(args):
pytest.main(args=["-k", "neotest_none"], plugins=[TestNameTemplateExtractor])
def collect(args): def collect(args):
pytest.main(["--collect-only", "--verbosity=0", "-q"] + args) pytest.main(["--collect-only", "--verbosity=0", "-q"] + args)