Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ require("neotest").setup({
-- !!EXPERIMENTAL!! Enable shelling out to `pytest` to discover test
-- instances for files containing a parametrize mark (default: false)
pytest_discover_instances = true,
-- Allow redirection of pytest execution inside a docker container
-- Can also be a function to return dynamic value.
-- !! Only pytest is supported [and](and) requires installation of pytest-json package inside the container
use_docker = false,
-- The name of the docker to use for execution.
-- Can also be a function to return dynamic value.
containers = "docker-name-1",
})
}
})
Expand Down
96 changes: 80 additions & 16 deletions lua/neotest-python/adapter.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local Path = require("plenary.path")
local nio = require("nio")
local lib = require("neotest.lib")
local pytest = require("neotest-python.pytest")
Expand All @@ -10,6 +11,8 @@ local base = require("neotest-python.base")
---@field get_python_command fun(root: string):string[]
---@field get_args fun(runner: string, position: neotest.Position, strategy: string): string[]
---@field get_runner fun(python_command: string[]): string
---@field use_docker? fun(): boolean
---@field get_container fun(): string

---@param config neotest-python._AdapterConfig
---@return neotest.Adapter
Expand Down Expand Up @@ -50,6 +53,32 @@ return function(config)
return script_args
end

---@param run_args neotest.RunArgs
---@param results_path string
---@param runner string
---@return string[]
local function build_docker_args(run_args, results_path, runner)
local script_args = { "exec" , config.get_container(), runner, "--json="..results_path }

local position = run_args.tree:data()

vim.list_extend(script_args, config.get_args(runner, position, run_args.strategy))

if run_args.extra_args then
vim.list_extend(script_args, run_args.extra_args)
end

if position then
local relpath = Path:new(position.path):make_relative(vim.loop.cwd())
table.insert(script_args, relpath)
if position.type == "test" then
vim.list_extend(script_args, {'-k', position.name})
end
end

return script_args
end

---@type neotest.Adapter
return {
name = "neotest-python",
Expand Down Expand Up @@ -77,30 +106,40 @@ return function(config)
---@param args neotest.RunArgs
---@return neotest.RunSpec
build_spec = function(args)
local position = args.tree:data()
local command
local results_path
local script_args
local script_path

local position = args.tree:data()
local root = base.get_root(position.path) or vim.loop.cwd() or ""

local python_command = config.get_python_command(root)
local runner = config.get_runner(python_command)

local results_path = nio.fn.tempname()
local stream_path = nio.fn.tempname()
lib.files.write(stream_path, "")

local stream_data, stop_stream = lib.files.stream_lines(stream_path)

local script_args = build_script_args(args, results_path, stream_path, runner)
local script_path = base.get_script_path()
local runner = config.get_runner(command)

if config.use_docker() == false then
command = config.get_python_command(root)

results_path = nio.fn.tempname()
script_args = build_script_args(args, results_path, stream_path, runner)
script_path = base.get_script_path()
else
command = {"docker"}
script_path = "container"
results_path = "report.json"
script_args = build_docker_args(args, results_path, runner)
end

local strategy_config
if args.strategy == "dap" then
strategy_config =
base.create_dap_config(python_command, script_path, script_args, config.dap_args)
strategy_config = base.create_dap_config(command, script_path, script_args, config.dap_args)
end

---@type neotest.RunSpec
return {
command = vim.iter({ python_command, script_path, script_args }):flatten():totable(),
command = vim.iter({ command, script_path, script_args }):flatten():totable(),
context = {
results_path = results_path,
stop_stream = stop_stream,
Expand All @@ -122,16 +161,41 @@ return function(config)
---@param spec neotest.RunSpec
---@param result neotest.StrategyResult
---@return neotest.Result[]
results = function(spec, result)
results = function(spec, result, tree)
local results = {}
spec.context.stop_stream()
local success, data = pcall(lib.files.read, spec.context.results_path)
if not success then
data = "{}"
end
local results = vim.json.decode(data, { luanil = { object = true } })
for _, pos_result in pairs(results) do
result.output_path = pos_result.output_path
local report = vim.json.decode(data, { luanil = { object = true } })

-- Native pytest execution
if config.use_docker() == false then
for _, pos_result in pairs(results) do
result.output_path = pos_result.output_path
end

-- docker delegated executionù
else
-- the path must be recomposed because docker has no the same absolute path
local path = vim.loop.cwd()

for _, v in pairs(report['report']['tests']) do
if v['outcome'] == 'failed' then
results[path .. "/" .. v['name']] = {
status = v['outcome'],
short = v['call']['longrepr']
}
else
results[path .. "/" .. v['name']] = {
status = v['outcome'],
short = ""
}
end
end
end

return results
end,
}
Expand Down
9 changes: 9 additions & 0 deletions lua/neotest-python/base.lua
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,13 @@ function M.get_runner(python_path)
return runner
end

function M.use_docker()
return false
end

function M.get_container()
-- Unknown container error will be throw if the field "container" is not defined in configuration
return ''
end

return M
22 changes: 22 additions & 0 deletions lua/neotest-python/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ local create_adapter = require("neotest-python.adapter")
---@field python? string|string[]|fun(root: string):string[]
---@field args? string[]|fun(runner: string, position: neotest.Position, strategy: string): string[]
---@field runner? string|fun(python_command: string[]): string
---@field use_docker? boolean|fun(): boolean
---@field container? string|fun(): string

local is_callable = function(obj)
return type(obj) == "function" or (type(obj) == "table" and obj.__call)
Expand Down Expand Up @@ -56,6 +58,24 @@ local augment_config = function(config)
end
end

local use_docker = base.use_docker
if is_callable(config.use_docker) then
use_docker = config.use_docker
else
use_docker = function ()
return config.use_docker
end
end

local get_container = base.get_container
if is_callable(config.container) then
get_container = config.container
elseif config.container then
get_container = function ()
return config.container
end
end

---@type neotest-python._AdapterConfig
return {
pytest_discovery = config.pytest_discover_instances,
Expand All @@ -64,6 +84,8 @@ local augment_config = function(config)
get_args = get_args,
is_test_file = config.is_test_file or base.is_test_file,
get_python_command = get_python_command,
use_docker = use_docker,
get_container = get_container,
}
end

Expand Down