From e4c3a941f72391becbc1d3b7c6987fc06233cd8c Mon Sep 17 00:00:00 2001 From: Shai-tan Date: Sat, 8 Feb 2025 23:52:13 +0100 Subject: [PATCH 1/4] Initial introduction of docker forward Signed-off-by: Shai-tan --- lua/neotest-python/adapter.lua | 92 ++++++++++++++++++++++++++++------ lua/neotest-python/init.lua | 16 ++++++ 2 files changed, 94 insertions(+), 14 deletions(-) diff --git a/lua/neotest-python/adapter.lua b/lua/neotest-python/adapter.lua index 1d9e143..4eabe00 100644 --- a/lua/neotest-python/adapter.lua +++ b/lua/neotest-python/adapter.lua @@ -1,3 +1,4 @@ +local Path = require("plenary.path") local nio = require("nio") local lib = require("neotest.lib") local pytest = require("neotest-python.pytest") @@ -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? boolean +---@field get_container fun(): string ---@param config neotest-python._AdapterConfig ---@return neotest.Adapter @@ -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(), "pytest", "--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", @@ -77,27 +106,37 @@ return function(config) ---@param args neotest.RunArgs ---@return neotest.RunSpec build_spec = function(args) - local position = args.tree:data() + local python_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() + if config.use_docker == false then + python_command = config.get_python_command(root) + local runner = config.get_runner(python_command) + + results_path = nio.fn.tempname() + script_args = build_script_args(args, results_path, stream_path, runner) + script_path = base.get_script_path() + else + python_command = {"docker"} + script_path = "container" + results_path = "report.json" + script_args = build_docker_args(args, results_path, "docker") + 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(python_command, script_path, script_args, config.dap_args) end + ---@type neotest.RunSpec return { command = vim.iter({ python_command, script_path, script_args }):flatten():totable(), @@ -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, } diff --git a/lua/neotest-python/init.lua b/lua/neotest-python/init.lua index 73cfc6d..d10c54c 100644 --- a/lua/neotest-python/init.lua +++ b/lua/neotest-python/init.lua @@ -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 +---@field containers? [string, string] local is_callable = function(obj) return type(obj) == "function" or (type(obj) == "table" and obj.__call) @@ -56,6 +58,18 @@ local augment_config = function(config) end end + get_container = function(root) + if config.containers then + for k, v in pairs(config.containers) do + print(k) + if string.find(vim.loop.cwd(), '/'..k) then + return v + end + end + end + return '' + end + ---@type neotest-python._AdapterConfig return { pytest_discovery = config.pytest_discover_instances, @@ -64,6 +78,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 = config.use_docker or false, + get_container = get_container, } end From cc1fb21b0e9ebbb058c29b15c26e5a31368d621a Mon Sep 17 00:00:00 2001 From: Shai-tan Date: Wed, 12 Feb 2025 20:58:06 +0100 Subject: [PATCH 2/4] Minor update tp rename variable Signed-off-by: Shai-tan --- lua/neotest-python/adapter.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lua/neotest-python/adapter.lua b/lua/neotest-python/adapter.lua index 4eabe00..00e5aa5 100644 --- a/lua/neotest-python/adapter.lua +++ b/lua/neotest-python/adapter.lua @@ -58,7 +58,7 @@ return function(config) ---@param runner string ---@return string[] local function build_docker_args(run_args, results_path, runner) - local script_args = { "exec" , config.get_container(), "pytest", "--json="..results_path } + local script_args = { "exec" , config.get_container(), runner, "--json="..results_path } local position = run_args.tree:data() @@ -106,7 +106,7 @@ return function(config) ---@param args neotest.RunArgs ---@return neotest.RunSpec build_spec = function(args) - local python_command + local command local results_path local script_args local script_path @@ -117,29 +117,29 @@ return function(config) lib.files.write(stream_path, "") local stream_data, stop_stream = lib.files.stream_lines(stream_path) + local runner = config.get_runner(command) if config.use_docker == false then - python_command = config.get_python_command(root) - local runner = config.get_runner(python_command) + 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 - python_command = {"docker"} + command = {"docker"} script_path = "container" results_path = "report.json" - script_args = build_docker_args(args, results_path, "docker") + 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, From 8aeac0d26a2e3354e444dc58c8f6539ba53d1372 Mon Sep 17 00:00:00 2001 From: Shai-tan Date: Wed, 12 Feb 2025 21:02:39 +0100 Subject: [PATCH 3/4] Rework the configuration to provide function instead of dict to retrieve container name Signed-off-by: Shai-tan --- README.md | 6 ++++++ lua/neotest-python/base.lua | 5 +++++ lua/neotest-python/init.lua | 19 ++++++++----------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 74dd888..d539c53 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,12 @@ 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 + -- !! 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", }) } }) diff --git a/lua/neotest-python/base.lua b/lua/neotest-python/base.lua index 90cd6da..aaaa6bc 100644 --- a/lua/neotest-python/base.lua +++ b/lua/neotest-python/base.lua @@ -159,4 +159,9 @@ function M.get_runner(python_path) return runner end +function M.get_container() + -- Unknown container error will be throw if the field "container" is not defined in configuration + return '' +end + return M diff --git a/lua/neotest-python/init.lua b/lua/neotest-python/init.lua index d10c54c..711fc86 100644 --- a/lua/neotest-python/init.lua +++ b/lua/neotest-python/init.lua @@ -9,7 +9,7 @@ local create_adapter = require("neotest-python.adapter") ---@field args? string[]|fun(runner: string, position: neotest.Position, strategy: string): string[] ---@field runner? string|fun(python_command: string[]): string ---@field use_docker? boolean ----@field containers? [string, string] +---@field container? string|fun(): string local is_callable = function(obj) return type(obj) == "function" or (type(obj) == "table" and obj.__call) @@ -58,16 +58,13 @@ local augment_config = function(config) end end - get_container = function(root) - if config.containers then - for k, v in pairs(config.containers) do - print(k) - if string.find(vim.loop.cwd(), '/'..k) then - return v - end - end - end - return '' + 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 From a28949deabc8f11ad86e4c9063725c7b89bb9d9e Mon Sep 17 00:00:00 2001 From: Shai-tan Date: Tue, 18 Feb 2025 20:33:50 +0100 Subject: [PATCH 4/4] Add capability to use function instead of static boolean in configuration Signed-off-by: Shai-tan --- README.md | 1 + lua/neotest-python/adapter.lua | 6 +++--- lua/neotest-python/base.lua | 4 ++++ lua/neotest-python/init.lua | 13 +++++++++++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d539c53..734d533 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ require("neotest").setup({ -- 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. diff --git a/lua/neotest-python/adapter.lua b/lua/neotest-python/adapter.lua index 00e5aa5..52f105e 100644 --- a/lua/neotest-python/adapter.lua +++ b/lua/neotest-python/adapter.lua @@ -11,7 +11,7 @@ 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? boolean +---@field use_docker? fun(): boolean ---@field get_container fun(): string ---@param config neotest-python._AdapterConfig @@ -119,7 +119,7 @@ return function(config) local stream_data, stop_stream = lib.files.stream_lines(stream_path) local runner = config.get_runner(command) - if config.use_docker == false then + if config.use_docker() == false then command = config.get_python_command(root) results_path = nio.fn.tempname() @@ -171,7 +171,7 @@ return function(config) local report = vim.json.decode(data, { luanil = { object = true } }) -- Native pytest execution - if config.use_docker == false then + if config.use_docker() == false then for _, pos_result in pairs(results) do result.output_path = pos_result.output_path end diff --git a/lua/neotest-python/base.lua b/lua/neotest-python/base.lua index aaaa6bc..4e1e9e3 100644 --- a/lua/neotest-python/base.lua +++ b/lua/neotest-python/base.lua @@ -159,6 +159,10 @@ 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 '' diff --git a/lua/neotest-python/init.lua b/lua/neotest-python/init.lua index 711fc86..a9a8448 100644 --- a/lua/neotest-python/init.lua +++ b/lua/neotest-python/init.lua @@ -8,7 +8,7 @@ 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 +---@field use_docker? boolean|fun(): boolean ---@field container? string|fun(): string local is_callable = function(obj) @@ -58,6 +58,15 @@ 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 @@ -75,7 +84,7 @@ 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 = config.use_docker or false, + use_docker = use_docker, get_container = get_container, } end