From 4270069b332b75c1d1805ee47c6b596c3a6fde2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jona=20L=C3=B6ffler?= <37999659+JonaLoeffler@users.noreply.github.com> Date: Tue, 8 Jul 2025 18:42:50 +0200 Subject: [PATCH 1/4] Handle gradle submodules when constructing test command --- lua/neotest-kotlin/command.lua | 68 ++++++++++++++++++++++++++++++++-- lua/neotest-kotlin/init.lua | 4 +- test-logging.init.gradle.kts | 10 +++++ 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/lua/neotest-kotlin/command.lua b/lua/neotest-kotlin/command.lua index 16c5337..c36f723 100644 --- a/lua/neotest-kotlin/command.lua +++ b/lua/neotest-kotlin/command.lua @@ -1,11 +1,59 @@ local M = {} ----Constructs the gradle command to execute +---Finds the nearest parent directory containing a build.gradle or build.gradle.kts file +---@param filepath string the path to the file (e.g. test file) +---@return string|nil module_name the name of the gradle module, or nil if not found + +---Runs the Gradle printProjectPaths task and parses the output to map absolute paths to module names +---@param init_script_path string path to the init script +---@return table mapping from absolute dir to gradle module (e.g. /abs/path/app -> app) +local function get_gradle_project_paths(init_script_path) + local handle = io.popen(string.format("./gradlew -I %s printProjectPaths", init_script_path)) + if not handle then return {} end + local output = handle:read("*a") + handle:close() + local map = {} + for line in output:gmatch("[^\n]+") do + print("line: " .. line) + + local path, abs = line:match("NEOTEST_GRADLE_PROJECT%s+:(.-)%s+([^ ]+)$") + if not path then + -- Try with tab separator + local _, _, p, a = line:find("NEOTEST_GRADLE_PROJECT\t:?(.-)\t(.+)") + path, abs = p, a + end + if path and abs then + -- Remove leading : from path if present + path = path:gsub("^:", "") + map[abs] = path + end + end + return map +end + +---Finds the gradle module for a given file path using the mapping from get_gradle_project_paths +---@param filepath string +---@param project_map table +---@return string|nil module_name +local function find_gradle_module(filepath, project_map) + local sep = package.config:sub(1,1) + local dir = filepath + while dir and dir ~= "." and dir ~= sep do + dir = dir:gsub(sep .. "[^" .. sep .. "]+$", "") + if project_map[dir] then + return project_map[dir] + end + if dir == "." or dir == sep or #dir < 2 then break end + end + return nil +end + ---@param tests string the name of the test block ---@param specs string the package name of the file you are interpreting ---@param outfile string where the test output will be written to. +---@param filepath string the path to the test file (to determine module) ---@return string command the gradle command to execute -function M.build(tests, specs, outfile) +function M.build(tests, specs, outfile, filepath) local INIT_SCRIPT_NAME = "test-logging.init.gradle.kts" local init_script_path = @@ -16,10 +64,24 @@ function M.build(tests, specs, outfile) ) end + local module = nil + print("filepath " .. filepath) + if filepath then + local project_map = get_gradle_project_paths(init_script_path) + module = find_gradle_module(filepath, project_map) + end + print("module" .. module) + local gradle_cmd = "./gradlew" + if module then + gradle_cmd = string.format("%s :%s:test", gradle_cmd, module) + else + gradle_cmd = string.format("%s test", gradle_cmd) + end return string.format( - "kotest_filter_specs='%s' kotest_filter_tests='%s' ./gradlew -I %s test --console=plain | tee -a %s", + "kotest_filter_specs='%s' kotest_filter_tests='%s' %s -I %s --console=plain | tee -a %s", specs, tests, + gradle_cmd, init_script_path, outfile ) diff --git a/lua/neotest-kotlin/init.lua b/lua/neotest-kotlin/init.lua index a21f47f..4ecd95b 100644 --- a/lua/neotest-kotlin/init.lua +++ b/lua/neotest-kotlin/init.lua @@ -102,7 +102,7 @@ function M.Adapter.build_spec(args) if pos.type == "dir" then local package = dir_determine_package(pos.path) .. ".*" - run_spec.command = command.build(tests, package, results_path) + run_spec.command = command.build(tests, package, results_path, pos.path) elseif pos.type == "file" or pos.type == "namespace" @@ -114,7 +114,7 @@ function M.Adapter.build_spec(args) treesitter.list_all_classes(pos.path)[1] ) - run_spec.command = command.build(tests, package, results_path) + run_spec.command = command.build(tests, package, results_path, pos.path) end print(run_spec.command) diff --git a/test-logging.init.gradle.kts b/test-logging.init.gradle.kts index aa2e842..e991f61 100644 --- a/test-logging.init.gradle.kts +++ b/test-logging.init.gradle.kts @@ -23,4 +23,14 @@ allprojects { } } } + + tasks.register("printProjectPaths") { + group = "help" + description = "Prints all project names and their absolute paths in a parseable format" + doLast { + allprojects.forEach { p -> + println("NEOTEST_GRADLE_PROJECT\t${p.path}\t${p.projectDir.absolutePath}") + } + } + } } From 3bde5399846cff85e41ffb6ad8a558148d80542d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jona=20L=C3=B6ffler?= <37999659+JonaLoeffler@users.noreply.github.com> Date: Wed, 13 Aug 2025 17:08:54 +0200 Subject: [PATCH 2/4] Formatting --- lua/neotest-kotlin/command.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lua/neotest-kotlin/command.lua b/lua/neotest-kotlin/command.lua index c36f723..c959e8c 100644 --- a/lua/neotest-kotlin/command.lua +++ b/lua/neotest-kotlin/command.lua @@ -8,8 +8,12 @@ local M = {} ---@param init_script_path string path to the init script ---@return table mapping from absolute dir to gradle module (e.g. /abs/path/app -> app) local function get_gradle_project_paths(init_script_path) - local handle = io.popen(string.format("./gradlew -I %s printProjectPaths", init_script_path)) - if not handle then return {} end + local handle = io.popen( + string.format("./gradlew -I %s printProjectPaths", init_script_path) + ) + if not handle then + return {} + end local output = handle:read("*a") handle:close() local map = {} @@ -36,14 +40,16 @@ end ---@param project_map table ---@return string|nil module_name local function find_gradle_module(filepath, project_map) - local sep = package.config:sub(1,1) + local sep = package.config:sub(1, 1) local dir = filepath while dir and dir ~= "." and dir ~= sep do dir = dir:gsub(sep .. "[^" .. sep .. "]+$", "") if project_map[dir] then return project_map[dir] end - if dir == "." or dir == sep or #dir < 2 then break end + if dir == "." or dir == sep or #dir < 2 then + break + end end return nil end From e8a04f35b65b482d5b44ff041976beab5f9f2372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jona=20L=C3=B6ffler?= <37999659+JonaLoeffler@users.noreply.github.com> Date: Wed, 13 Aug 2025 17:11:10 +0200 Subject: [PATCH 3/4] Clean up comment --- lua/neotest-kotlin/command.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/neotest-kotlin/command.lua b/lua/neotest-kotlin/command.lua index c959e8c..e043973 100644 --- a/lua/neotest-kotlin/command.lua +++ b/lua/neotest-kotlin/command.lua @@ -54,6 +54,7 @@ local function find_gradle_module(filepath, project_map) return nil end +---Constructs the gradle command to execute ---@param tests string the name of the test block ---@param specs string the package name of the file you are interpreting ---@param outfile string where the test output will be written to. From e0a6eea006507c4920d9e9d4f7f62419bc326ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jona=20L=C3=B6ffler?= <37999659+JonaLoeffler@users.noreply.github.com> Date: Thu, 14 Aug 2025 13:23:49 +0200 Subject: [PATCH 4/4] Fix tests --- lua/neotest-kotlin/command.lua | 24 ++++++++++++++---------- tests/command_spec.lua | 5 +++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lua/neotest-kotlin/command.lua b/lua/neotest-kotlin/command.lua index e043973..e05eaf5 100644 --- a/lua/neotest-kotlin/command.lua +++ b/lua/neotest-kotlin/command.lua @@ -18,8 +18,6 @@ local function get_gradle_project_paths(init_script_path) handle:close() local map = {} for line in output:gmatch("[^\n]+") do - print("line: " .. line) - local path, abs = line:match("NEOTEST_GRADLE_PROJECT%s+:(.-)%s+([^ ]+)$") if not path then -- Try with tab separator @@ -40,6 +38,10 @@ end ---@param project_map table ---@return string|nil module_name local function find_gradle_module(filepath, project_map) + if project_map == nil or next(project_map) == nil then + return nil + end + local sep = package.config:sub(1, 1) local dir = filepath while dir and dir ~= "." and dir ~= sep do @@ -72,24 +74,26 @@ function M.build(tests, specs, outfile, filepath) end local module = nil - print("filepath " .. filepath) if filepath then - local project_map = get_gradle_project_paths(init_script_path) + local ok, project_map = pcall(get_gradle_project_paths, init_script_path) + if not ok then + error("Failed to get gradle project paths: " .. tostring(project_map)) + end module = find_gradle_module(filepath, project_map) end - print("module" .. module) - local gradle_cmd = "./gradlew" + + local gradle_cmd = "" if module then - gradle_cmd = string.format("%s :%s:test", gradle_cmd, module) + gradle_cmd = string.format(":%s:test", module) else - gradle_cmd = string.format("%s test", gradle_cmd) + gradle_cmd = "test" end return string.format( - "kotest_filter_specs='%s' kotest_filter_tests='%s' %s -I %s --console=plain | tee -a %s", + "kotest_filter_specs='%s' kotest_filter_tests='%s' ./gradlew -I %s %s --console=plain | tee -a %s", specs, tests, - gradle_cmd, init_script_path, + gradle_cmd, outfile ) end diff --git a/tests/command_spec.lua b/tests/command_spec.lua index 8e6f0e9..2c1def5 100644 --- a/tests/command_spec.lua +++ b/tests/command_spec.lua @@ -1,11 +1,12 @@ local command = require("neotest-kotlin.command") describe("command", function() - it("valid", function() + it("valid without modules", function() local actual = command.build( "An example namespace", "com.codymikol.gummibear.pizza.FooClass", - "/tmp/results_example.txt" + "/tmp/results_example.txt", + "tests/com/codymikol/gummibear/pizza/FooClassTest.kt" ) local init_script_path =