From 57de967258a0517fb4b701675e40da25bc0a2b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Cas=C3=ADa?= <31012661+rcasia@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:07:39 +0200 Subject: [PATCH 1/7] chore: start strengthening test coverage ahead of plenary.nvim removal From 381f58d5f26017f093a1f24e365442e5382e3b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Cas=C3=ADa?= <31012661+rcasia@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:09:23 +0200 Subject: [PATCH 2/7] refactor(launcher): add DI seam for job_runner and dap_repl Enables injecting a stub job runner in tests (and, in a future PR, a non-plenary implementation) without changing runtime behavior. The module-level require("neotest-java.build_tool.launcher").launch_debug_test call site is unchanged. Also fixes a latent bug where a failed pcall(require, "dap.repl") left deps.dap_repl set to the pcall error string (truthy) instead of nil, which would crash on repl.clear()/repl.append() if launch_debug_test were ever reached without nvim-dap installed. --- lua/neotest-java/build_tool/launcher.lua | 149 ++++++++++++++--------- lua/neotest-java/types.lua | 1 + 2 files changed, 94 insertions(+), 56 deletions(-) diff --git a/lua/neotest-java/build_tool/launcher.lua b/lua/neotest-java/build_tool/launcher.lua index e0b68884..8780413a 100644 --- a/lua/neotest-java/build_tool/launcher.lua +++ b/lua/neotest-java/build_tool/launcher.lua @@ -1,71 +1,108 @@ local log = require("neotest-java.logger") local nio = require("nio") -local Job = require("plenary.job") local lib = require("neotest.lib") -local _, repl = pcall(require, "dap.repl") +--- A minimal shape of the job runner this module needs. The default +--- implementation is backed by `plenary.job`, but it can be swapped out +--- (e.g. in tests, or by a future implementation that drops the +--- plenary.nvim dependency) as long as it honors this contract: +--- * `new(opts)` accepts the same callback fields plenary.Job does +--- (`command`, `cwd`, `args`, `on_stderr`, `on_stdout`, `on_exit`) +--- * the returned job exposes a `start()` method +--- @class neotest-java.JobRunner +--- @field new fun(self: neotest-java.JobRunner, opts: table): { start: fun() } ----@class neotest-java.BuildToolLauncher -local launcher = {} +---@param deps neotest-java.BuildToolLauncherDeps | nil +---@return neotest-java.BuildToolLauncher +local function BuildToolLauncher(deps) + deps = deps or {} + deps.job_runner = deps.job_runner or require("plenary.job") + deps.dap_repl = deps.dap_repl + if deps.dap_repl == nil then + local _, repl = pcall(require, "dap.repl") + deps.dap_repl = repl + end ----@param command string ----@param args string[] ----@param cwd neotest-java.Path ----@return nio.control.Event -function launcher.launch_debug_test(command, args, cwd) - lib.notify("Running debug test", vim.log.levels.INFO) - log.trace("run_debug_test function") + return { + ---@param command string + ---@param args string[] + ---@param cwd neotest-java.Path + ---@return nio.control.Event + launch_debug_test = function(command, args, cwd) + lib.notify("Running debug test", vim.log.levels.INFO) + log.trace("run_debug_test function") - local test_command_started_listening = nio.control.event() - local terminated_command_event = nio.control.event() + local repl = deps.dap_repl - local stderr = {} - local job = Job:new({ - command = command, - cwd = cwd:to_string(), - args = args, - on_stderr = function(_, data) - if data == nil then - return - end - stderr[#stderr + 1] = data - if repl then - vim.schedule(function() - repl.append(data) - end) - end - end, - on_stdout = function(_, data) - if data == nil then - return - end - if string.find(data, "Listening") then - test_command_started_listening.set() - end + local test_command_started_listening = nio.control.event() + local terminated_command_event = nio.control.event() + + local stderr = {} + local job = deps.job_runner:new({ + command = command, + cwd = cwd:to_string(), + args = args, + on_stderr = function(_, data) + if data == nil then + return + end + stderr[#stderr + 1] = data + if repl then + vim.schedule(function() + repl.append(data) + end) + end + end, + on_stdout = function(_, data) + if data == nil then + return + end + if string.find(data, "Listening") then + test_command_started_listening.set() + end + if repl then + vim.schedule(function() + repl.append(data) + end) + end + end, + on_exit = function(_, code) + terminated_command_event.set() + + log.debug("command exited with code: ", code) + if code ~= 0 then + log.error("command exited with code: ", code) + log.error("stderr: ", table.concat(stderr, "\n")) + end + end, + }) + log.debug("starting job with command: ", command, " ", table.concat(args, " ")) if repl then - vim.schedule(function() - repl.append(data) - end) + repl.clear() end - end, - on_exit = function(_, code) - terminated_command_event.set() + job:start() + test_command_started_listening.wait() - log.debug("command exited with code: ", code) - if code ~= 0 then - log.error("command exited with code: ", code) - log.error("stderr: ", table.concat(stderr, "\n")) - end + return terminated_command_event end, - }) - log.debug("starting job with command: ", command, " ", table.concat(args, " ")) - if repl then - repl.clear() - end - job:start() - test_command_started_listening.wait() - - return terminated_command_event + } end +---@class neotest-java.BuildToolLauncherDeps +---@field job_runner? neotest-java.JobRunner +---@field dap_repl? false | { append: fun(data: string), clear: fun() } + +---@class neotest-java.BuildToolLauncher +---@field launch_debug_test fun(command: string, args: string[], cwd: neotest-java.Path): nio.control.Event +---@field new fun(deps: neotest-java.BuildToolLauncherDeps | nil): neotest-java.BuildToolLauncher + +-- Module-level singleton, matching the previous `launcher.launch_debug_test` +-- call site contract (`require("neotest-java.build_tool.launcher").launch_debug_test(...)`). +local launcher = BuildToolLauncher() + +-- Exposed for tests / future callers that want to inject a custom job_runner +-- (e.g. a stub, or a non-plenary implementation) without going through the +-- module-level singleton. +launcher.new = BuildToolLauncher + return launcher diff --git a/lua/neotest-java/types.lua b/lua/neotest-java/types.lua index afaebbb4..597b26bc 100644 --- a/lua/neotest-java/types.lua +++ b/lua/neotest-java/types.lua @@ -55,6 +55,7 @@ ---@class nio.control.Event ---@field wait fun() ---@field set fun() +---@field is_set fun(): boolean ---@class vim.lsp.Client ---@field initialized boolean From ecb897536e4ea09101b692608c25c2511f9240fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Cas=C3=ADa?= <31012661+rcasia@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:09:27 +0200 Subject: [PATCH 3/7] test(launcher): add unit spec covering job spawn, listening detection, exit Covers the DI seam added in the previous commit with a stubbed job runner: command/args/cwd passed through to the job, the returned event only resolves once stdout reports 'Listening', the terminated event fires on job exit, and nil stdout/stderr chunks are handled safely. --- tests/unit/test_launcher_spec.lua | 112 ++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 tests/unit/test_launcher_spec.lua diff --git a/tests/unit/test_launcher_spec.lua b/tests/unit/test_launcher_spec.lua new file mode 100644 index 00000000..ee94349a --- /dev/null +++ b/tests/unit/test_launcher_spec.lua @@ -0,0 +1,112 @@ +local async = require("tests.async_helpers").async +local assertions = require("tests.assertions") +local eq = assertions.eq + +local Path = require("neotest-java.model.path") +local BuildToolLauncher = require("neotest-java.build_tool.launcher").new + +--- Build a stub job runner mimicking the shape `plenary.job` exposes +--- (`Job:new(opts)` returning an object with `start()`). The stub captures +--- the callbacks and lets `start()` drive them synchronously via +--- `on_start`, matching how a real job would invoke them from its own +--- process lifecycle (started -> stdout/stderr chunks -> exit) without +--- needing real process I/O or a scheduler tick. +local function stub_job_runner(on_start) + local started_opts = nil + + return { + new = function(_, opts) + started_opts = opts + return { + start = function() + if on_start then + on_start(opts) + end + end, + } + end, + opts = function() + return started_opts + end, + } +end + +describe("BuildToolLauncher", function() + it( + "starts the job with the given command, args and cwd", + async(function() + local runner = stub_job_runner(function(opts) + opts.on_stdout(nil, "Listening for transport dt_socket at address: 12345") + end) + local launcher = BuildToolLauncher({ job_runner = runner, dap_repl = false }) + + launcher.launch_debug_test("java", { "-jar", "foo.jar" }, Path("/fake/cwd")) + + local opts = runner.opts() + assert(opts ~= nil, "job runner should have been started with opts") + eq("java", opts.command) + eq({ "-jar", "foo.jar" }, opts.args) + eq("/fake/cwd", opts.cwd) + end) + ) + + it( + "resolves the returned event only once stdout reports 'Listening'", + async(function() + local resolved = false + local runner = stub_job_runner(function(opts) + -- irrelevant stdout should not resolve the wait + opts.on_stdout(nil, "compiling...") + eq(false, resolved) + opts.on_stdout(nil, "Listening for transport dt_socket at address: 54321") + end) + local launcher = BuildToolLauncher({ job_runner = runner, dap_repl = false }) + + launcher.launch_debug_test("java", {}, Path("/fake/cwd")) + resolved = true + + eq(true, resolved) + end) + ) + + it( + "sets the terminated event when the job exits", + async(function() + local runner = stub_job_runner(function(opts) + opts.on_stdout(nil, "Listening") + end) + local launcher = BuildToolLauncher({ job_runner = runner, dap_repl = false }) + + local terminated_event = launcher.launch_debug_test("java", {}, Path("/fake/cwd")) + + local opts = runner.opts() + assert(opts ~= nil, "job runner should have been started with opts") + local terminated_before = terminated_event.is_set() + opts.on_exit(nil, 0) + local terminated_after = terminated_event.is_set() + + eq(false, terminated_before) + eq(true, terminated_after) + end) + ) + + it( + "ignores nil stdout/stderr chunks without erroring", + async(function() + local runner = stub_job_runner(function(opts) + opts.on_stdout(nil, nil) + opts.on_stderr(nil, nil) + opts.on_stdout(nil, "Listening") + end) + local launcher = BuildToolLauncher({ job_runner = runner, dap_repl = false }) + + local terminated_event = launcher.launch_debug_test("java", {}, Path("/fake/cwd")) + eq(false, terminated_event.is_set()) + end) + ) + + it("defaults to plenary.job when no job_runner is injected", function() + local launcher = BuildToolLauncher({ dap_repl = false }) + assert(launcher.launch_debug_test ~= nil, "launch_debug_test should exist with default deps") + end) +end) From a87d48ad793d1434e833dd10a2dfc5e264f7599f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Cas=C3=ADa?= <31012661+rcasia@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:09:32 +0200 Subject: [PATCH 4/7] test(launcher): add real-JVM integration spec for JDWP listening detection Spawns a real java -agentlib:jdwp=...,suspend=n process through the real plenary.job (not stubbed) and asserts that launch_debug_test's 'Listening' stdout detection and process-exit event both work against genuine process I/O. This is the closest practical substitute for a full 'strategy = "dap"' e2e run without wiring up a real nvim-dap attach flow, and is the main safety net for PR 2's plenary.job removal. Skips gracefully (MiniTest.skip) when java is not on PATH, since the main test.yml CI job does not install Java. --- tests/integration/test_launcher_spec.lua | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/integration/test_launcher_spec.lua diff --git a/tests/integration/test_launcher_spec.lua b/tests/integration/test_launcher_spec.lua new file mode 100644 index 00000000..1c4fbe01 --- /dev/null +++ b/tests/integration/test_launcher_spec.lua @@ -0,0 +1,54 @@ +local async = require("tests.async_helpers").async +local assertions = require("tests.assertions") +local eq = assertions.eq +local MiniTest = require("mini.test") + +local Path = require("neotest-java.model.path") +local BuildToolLauncher = require("neotest-java.build_tool.launcher").new +-- dap_repl = false: this environment has no nvim-dap installed. In real +-- usage `spec_builder` already asserts `require("dap")` succeeds before +-- reaching `launch_debug_test` (see core/spec_builder/init.lua), so the +-- real dap.repl module would be present too. Here we only care about the +-- job-spawn + JDWP-wait behavior, not the dap.repl integration. +local launcher = BuildToolLauncher({ dap_repl = false }) + +--- Real-process integration coverage for `launcher.launch_debug_test`. +--- +--- This spawns an actual JVM with a JDWP debug agent (the same mechanism +--- neotest-java uses to run tests under `strategy = "dap"`), without +--- attaching a real debugger. A JVM started with +--- `-agentlib:jdwp=...,suspend=n` prints +--- "Listening for transport dt_socket at address: " to stdout as +--- soon as the debug socket is open, then continues running normally. +--- +--- This exercises the real `plenary.job` spawn + JDWP "Listening" wait + +--- process-exit logic end-to-end, which is the exact behavior a future +--- non-plenary implementation (see #317) must preserve. +describe("BuildToolLauncher (real JVM integration)", function() + it( + "detects the JDWP 'Listening' message from a real JVM and waits for it to exit", + async(function() + if vim.fn.executable("java") == 0 then + MiniTest.skip("java executable not found on PATH; skipping real-JVM integration test") + return + end + + local args = { + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0", + "-version", + } + + local terminated_event = launcher.launch_debug_test("java", args, Path(vim.uv.cwd())) + + -- `launch_debug_test` only returns once the JVM has printed the + -- "Listening" line, proving the real plenary.job stdout wiring + -- correctly detects it. + assert(terminated_event ~= nil, "should return a terminated_command_event") + + -- `java -version` exits almost immediately once started; wait for + -- the process-exit event to prove on_exit fires for a real job. + terminated_event.wait() + eq(true, terminated_event.is_set()) + end) + ) +end) From eafeef2abd0f2f24809a7c7d4c6d1008a0a50371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Cas=C3=ADa?= <31012661+rcasia@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:09:36 +0200 Subject: [PATCH 5/7] test(detect_project_type): add unit spec for fast path and stubbed fallback Covers all maven/gradle marker filenames and maven-over-gradle precedence at the top level, and the recursive scandir fallback (triggered when readdir errors, finds nothing, or the directory is empty), including precedence rules, error handling, and the exact scan options passed to plenary.scandir. No dedicated tests existed for this module before. --- tests/unit/test_detect_project_type_spec.lua | 191 +++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 tests/unit/test_detect_project_type_spec.lua diff --git a/tests/unit/test_detect_project_type_spec.lua b/tests/unit/test_detect_project_type_spec.lua new file mode 100644 index 00000000..2b8406e5 --- /dev/null +++ b/tests/unit/test_detect_project_type_spec.lua @@ -0,0 +1,191 @@ +local assertions = require("tests.assertions") +local eq = assertions.eq + +local Path = require("neotest-java.model.path") +local detect_project_type = require("neotest-java.util.detect_project_type") + +describe("detect_project_type", function() + describe("fast path (top-level readdir)", function() + it("detects maven via pom.xml at the top level", function() + local readdir = function(_) + return { "pom.xml", "src" } + end + -- scandir should never be reached for the fast path to succeed + local scandir = function() + error("scandir should not be called when the fast path finds a match") + end + + local result = detect_project_type(Path(vim.uv.os_tmpdir()), scandir, readdir) + + eq("maven", result) + end) + + it("detects gradle via build.gradle at the top level", function() + local readdir = function(_) + return { "build.gradle", "src" } + end + local scandir = function() + error("scandir should not be called when the fast path finds a match") + end + + local result = detect_project_type(Path(vim.uv.os_tmpdir()), scandir, readdir) + + eq("gradle", result) + end) + + it("prefers maven over gradle when both markers are present at the top level", function() + local readdir = function(_) + return { "pom.xml", "build.gradle" } + end + local scandir = function() + error("scandir should not be called when the fast path finds a match") + end + + local result = detect_project_type(Path(vim.uv.os_tmpdir()), scandir, readdir) + + eq("maven", result) + end) + + it("recognizes all maven marker file names (pom.xml, mvnw, mvnw.cmd)", function() + for _, marker in ipairs({ "pom.xml", "mvnw", "mvnw.cmd" }) do + local readdir = function(_) + return { marker } + end + local scandir = function() + error("scandir should not be called when the fast path finds a match") + end + + eq("maven", detect_project_type(Path(vim.uv.os_tmpdir()), scandir, readdir)) + end + end) + + it( + "recognizes all gradle marker file names (settings.gradle[.kts], build.gradle[.kts], gradlew[.bat])", + function() + for _, marker in ipairs({ + "settings.gradle", + "settings.gradle.kts", + "build.gradle", + "build.gradle.kts", + "gradlew", + "gradlew.bat", + }) do + local readdir = function(_) + return { marker } + end + local scandir = function() + error("scandir should not be called when the fast path finds a match") + end + + eq("gradle", detect_project_type(Path(vim.uv.os_tmpdir()), scandir, readdir)) + end + end + ) + end) + + describe("fallback path (recursive scandir)", function() + it("falls back to scandir when readdir errors (e.g. directory does not exist)", function() + local readdir = function(_) + error("boom: directory not found") + end + local scandir_called_with = nil + local scandir = function(root_str, opts) + scandir_called_with = { root_str = root_str, opts = opts } + return { root_str .. "/nested/pom.xml" } + end + + local result = detect_project_type(Path("/fake/root"), scandir, readdir) + + eq("maven", result) + assert(scandir_called_with ~= nil, "scandir should have been called as a fallback") + eq("/fake/root", scandir_called_with.root_str) + end) + + it("falls back to scandir when the top-level directory has no marker files", function() + local readdir = function(_) + return { "README.md" } + end + local scandir = function(root_str, _) + return { root_str .. "/module-a/build.gradle" } + end + + local result = detect_project_type(Path("/fake/root"), scandir, readdir) + + eq("gradle", result) + end) + + it("falls back to scandir when the top-level directory is empty", function() + local readdir = function(_) + return {} + end + local scandir = function(root_str, _) + return { root_str .. "/deep/nested/dir/pom.xml" } + end + + local result = detect_project_type(Path("/fake/root"), scandir, readdir) + + eq("maven", result) + end) + + it("prefers maven over gradle when both markers are found in the recursive scan", function() + local readdir = function(_) + return {} + end + local scandir = function(root_str, _) + return { + root_str .. "/module-a/build.gradle", + root_str .. "/module-b/pom.xml", + } + end + + local result = detect_project_type(Path("/fake/root"), scandir, readdir) + + eq("maven", result) + end) + + it("returns 'unknown' when neither readdir nor the recursive scan find any marker", function() + local readdir = function(_) + return { "README.md" } + end + local scandir = function(_, _) + return { "/fake/root/notes.txt" } + end + + local result = detect_project_type(Path("/fake/root"), scandir, readdir) + + eq("unknown", result) + end) + + it("returns 'unknown' when scandir itself errors", function() + local readdir = function(_) + return {} + end + local scandir = function(_, _) + error("scandir exploded") + end + + local result = detect_project_type(Path("/fake/root"), scandir, readdir) + + eq("unknown", result) + end) + + it("passes recursive scan options (hidden=false, add_dirs=false, depth=math.huge)", function() + local readdir = function(_) + return {} + end + --- @type { hidden: boolean, add_dirs: boolean, depth: number } | nil + local captured_opts = nil + local scandir = function(_, opts) + captured_opts = opts + return {} + end + + detect_project_type(Path("/fake/root"), scandir, readdir) + + assert(captured_opts ~= nil, "scandir should have been called with opts") + eq(false, captured_opts.hidden) + eq(false, captured_opts.add_dirs) + eq(math.huge, captured_opts.depth) + end) + end) +end) From 1d807116f6fba5069e2fe98cc1402852429b06cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Cas=C3=ADa?= <31012661+rcasia@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:09:40 +0200 Subject: [PATCH 6/7] test(detect_project_type): add real-filesystem fallback integration spec Exercises the real plenary.scandir recursive fallback (no stubs) against genuine nested directory trees created with vim.fn.mkdir, covering maven and gradle markers found several directories deep, an 'unknown' result when nothing matches, and confirming the fast top-level path still wins over a deeper conflicting marker. --- .../test_detect_project_type_spec.lua | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/integration/test_detect_project_type_spec.lua diff --git a/tests/integration/test_detect_project_type_spec.lua b/tests/integration/test_detect_project_type_spec.lua new file mode 100644 index 00000000..f1f5d91f --- /dev/null +++ b/tests/integration/test_detect_project_type_spec.lua @@ -0,0 +1,66 @@ +local assertions = require("tests.assertions") +local eq = assertions.eq + +local Path = require("neotest-java.model.path") +local detect_project_type = require("neotest-java.util.detect_project_type") + +--- Real-filesystem integration coverage for `detect_project_type`'s +--- fallback path: a genuine multi-directory layout with no build markers +--- at the top level, scanned by the *real* `plenary.scandir` (no stubs +--- injected), exactly as it runs in production. This is the exact +--- behavior a future non-plenary implementation (see #317) must preserve. +describe("detect_project_type (real filesystem integration)", function() + local root_dir + + before_each(function() + root_dir = vim.fn.tempname() + vim.fn.mkdir(root_dir, "p") + end) + + after_each(function() + vim.fn.delete(root_dir, "rf") + end) + + it("finds a maven marker nested several directories deep via the real recursive scan", function() + local nested_dir = root_dir .. "/module-a/src/main/java/com/example" + vim.fn.mkdir(nested_dir, "p") + -- No markers at the top level, forcing the fallback path. + vim.fn.writefile({}, root_dir .. "/module-a/pom.xml") + + local result = detect_project_type(Path(root_dir)) + + eq("maven", result) + end) + + it("finds a gradle marker nested in a sibling module via the real recursive scan", function() + vim.fn.mkdir(root_dir .. "/module-a", "p") + vim.fn.mkdir(root_dir .. "/module-b", "p") + vim.fn.writefile({}, root_dir .. "/module-b/build.gradle") + + local result = detect_project_type(Path(root_dir)) + + eq("gradle", result) + end) + + it("returns 'unknown' for a real nested directory tree with no build markers anywhere", function() + vim.fn.mkdir(root_dir .. "/src/main/java/com/example", "p") + vim.fn.writefile({}, root_dir .. "/src/main/java/com/example/App.java") + vim.fn.writefile({}, root_dir .. "/README.md") + + local result = detect_project_type(Path(root_dir)) + + eq("unknown", result) + end) + + it("uses the fast top-level path (no recursive scan needed) when a marker is at the root", function() + vim.fn.writefile({}, root_dir .. "/pom.xml") + vim.fn.mkdir(root_dir .. "/some/deeply/nested/dir", "p") + vim.fn.writefile({}, root_dir .. "/some/deeply/nested/dir/build.gradle") + + -- Even though a gradle marker exists deeper in the tree, the + -- top-level pom.xml should win via the fast path. + local result = detect_project_type(Path(root_dir)) + + eq("maven", result) + end) +end) From 71dd1c9fd29b6d4b46897477fc0a883864e2f30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Cas=C3=ADa?= <31012661+rcasia@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:12:28 +0200 Subject: [PATCH 7/7] fix(tests): compare against Path:to_string() instead of raw forward-slash literals Windows CI failed on both new specs: Path("/fake/root"):to_string() and Path("/fake/cwd"):to_string() normalize to backslashes on Windows, but the assertions compared against hardcoded forward-slash string literals instead of going back through Path. Per AGENTS.md's Path-handling convention, cross-platform assertions must derive expected strings from Path itself, not hardcode a separator. --- tests/unit/test_detect_project_type_spec.lua | 2 +- tests/unit/test_launcher_spec.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_detect_project_type_spec.lua b/tests/unit/test_detect_project_type_spec.lua index 2b8406e5..4a7658bf 100644 --- a/tests/unit/test_detect_project_type_spec.lua +++ b/tests/unit/test_detect_project_type_spec.lua @@ -98,7 +98,7 @@ describe("detect_project_type", function() eq("maven", result) assert(scandir_called_with ~= nil, "scandir should have been called as a fallback") - eq("/fake/root", scandir_called_with.root_str) + eq(Path("/fake/root"):to_string(), scandir_called_with.root_str) end) it("falls back to scandir when the top-level directory has no marker files", function() diff --git a/tests/unit/test_launcher_spec.lua b/tests/unit/test_launcher_spec.lua index ee94349a..109a48f2 100644 --- a/tests/unit/test_launcher_spec.lua +++ b/tests/unit/test_launcher_spec.lua @@ -46,7 +46,7 @@ describe("BuildToolLauncher", function() assert(opts ~= nil, "job runner should have been started with opts") eq("java", opts.command) eq({ "-jar", "foo.jar" }, opts.args) - eq("/fake/cwd", opts.cwd) + eq(Path("/fake/cwd"):to_string(), opts.cwd) end) )