Skip to content

test: strengthen coverage of launcher/detect_project_type ahead of plenary.nvim removal (#317) - #328

Merged
rcasia merged 7 commits into
mainfrom
stack/plenary-1-e2e-coverage
Sep 5, 2026
Merged

rcasia merged 7 commits into
mainfrom
stack/plenary-1-e2e-coverage

Conversation

@rcasia

@rcasia rcasia commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Context

This is PR 1 of 2 addressing #317 (plenary.nvim is archived/unmaintained). PR 2 (branch stack/plenary-2-remove-dependency, stacked on top of this one) will replace the two remaining plenary.nvim usages:

  • lua/neotest-java/build_tool/launcher.lua — uses plenary.job to spawn the debug JVM, wait for the JDWP "Listening" line on stdout, and optionally stream to dap.repl.
  • lua/neotest-java/util/detect_project_type.lua — uses plenary.scandir as a recursive fallback scan when the fast top-level check finds no build markers.

This PR does not touch plenary or attempt the swap. It only strengthens tests around the current (plenary-based) behavior of both modules, so PR 2 has a safety net that proves it doesn't regress anything.

Gaps found

  • launcher.lua had zero dedicated tests (unit or otherwise) and no DI seam — it called require("plenary.job") directly at module scope, making it impossible to unit test in isolation.
  • detect_project_type.lua already had a DI seam (scandir/readdir params) but had zero dedicated tests at all — neither the fast top-level path nor the recursive plenary.scandir fallback path were exercised anywhere in the suite.
  • No test anywhere spawned a real JVM with a JDWP debug agent to exercise the actual plenary.job spawn + "Listening" stdout-detection + process-exit logic that launch_debug_test relies on.
  • No test exercised detect_project_type's recursive fallback against a real, nested multi-directory filesystem layout (only ever exercised, if at all, via stubs).

What was done

  • Added a DI seam to launcher.lua (BuildToolLauncher(deps) factory with an injectable job_runner, defaulting to plenary.job; also made dap_repl injectable to avoid a latent bug where a failed pcall(require, "dap.repl") left a truthy error string in place of repl). Kept require("neotest-java.build_tool.launcher").launch_debug_test working exactly as before via a module-level singleton, and exposed .new(deps) for callers that want to inject.
  • Added tests/unit/test_launcher_spec.lua — 5 fast unit tests using a stubbed job runner (start/stdout/stderr/exit wiring, "Listening" detection gating the returned event, nil-chunk handling, default-deps sanity check).
  • Added tests/integration/test_launcher_spec.lua — spawns a real JVM (java -agentlib:jdwp=...,suspend=n -version) through the real plenary.job, and asserts the real "Listening" stdout detection and process-exit event both work. This is the closest practical substitute for a full strategy = "dap" e2e run without wiring up a real nvim-dap attach flow (which the CI's main test.yml job doesn't have Java for anyway — see note below).
  • Added tests/unit/test_detect_project_type_spec.lua — 12 unit tests covering the fast top-level path (all maven/gradle marker filenames, maven-over-gradle precedence) and the fallback path (readdir errors, empty/no-match top level, precedence, scandir errors, and the exact scan opts passed to plenary.scandir).
  • Added tests/integration/test_detect_project_type_spec.lua — 4 tests against a real nested filesystem (vim.fn.mkdir/vim.fn.tempname), exercising the real plenary.scandir fallback for both maven and gradle markers several directories deep, an "unknown" case, and confirming the fast path still wins when a marker exists at both levels.
  • Fixed a couple of pre-existing gaps in lua/neotest-java/types.lua (nio.control.Event was missing is_set) needed to type the new tests correctly.

Known remaining gap (documented, not closed in this PR)

  • A true end-to-end test that runs a strategy = "dap" test through neotest.run.run() (full jdtls + nvim-dap attach flow) does not exist and was not added here. The project's main CI (test.yml) has no Java, and the e2e-test.yml harness (tests/e2e/run.lua) doesn't wire up nvim-dap or Neotest's dap strategy at all. Building that out would require installing nvim-dap in the e2e harness and is a bigger lift than fits this PR; the real-JVM integration test added here covers the actual behavior at risk from the plenary.nvim swap (job spawn, stdout "Listening" detection, process-exit) without that overhead. Flagging this so PR 2 (or a follow-up) can decide whether to invest in it.

Test results

  • bash scripts/test: 189/189 passing (was 173 before this PR)
  • stylua --check .: clean
  • luacheck lua/neotest-java/... tests/...: clean
  • lua-language-server --check .: clean

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.
…, 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.
…ction

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.
…llback

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.
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.
@rcasia
rcasia marked this pull request as ready for review September 5, 2026 12:09
…lash 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.
@rcasia
rcasia merged commit 6694449 into main Sep 5, 2026
16 checks passed
@rcasia
rcasia deleted the stack/plenary-1-e2e-coverage branch September 5, 2026 13:25
rcasia added a commit that referenced this pull request Sep 5, 2026
Mirrors the existing nio.wrap(vim.system) convention from
command/command_executor.lua. The DI seam PR #328 added
(BuildToolLauncher(deps).job_runner) made this a drop-in default swap —
same contract (new(opts):start(), command/args/cwd/on_stdout/on_stderr/on_exit),
no callers changed.

Also fixed the latent dap.repl bug PR #328 documented: a failed
pcall(require, 'dap.repl') left repl as the pcall's truthy error-message
string instead of nil, which would have crashed any repl.clear()/append()
call when dap.repl isn't installed.
rcasia added a commit that referenced this pull request Sep 5, 2026
…ew defaults

Same real-JVM / real-filesystem coverage PR #328 added, now exercising
the new vim.system-backed job runner and dir_scan-based fallback instead
of the old plenary-based defaults.
rcasia added a commit that referenced this pull request Sep 5, 2026
Mirrors the existing nio.wrap(vim.system) convention from
command/command_executor.lua. The DI seam PR #328 added
(BuildToolLauncher(deps).job_runner) made this a drop-in default swap —
same contract (new(opts):start(), command/args/cwd/on_stdout/on_stderr/on_exit),
no callers changed.

Also fixed the latent dap.repl bug PR #328 documented: a failed
pcall(require, 'dap.repl') left repl as the pcall's truthy error-message
string instead of nil, which would have crashed any repl.clear()/append()
call when dap.repl isn't installed.
rcasia added a commit that referenced this pull request Sep 5, 2026
…ew defaults

Same real-JVM / real-filesystem coverage PR #328 added, now exercising
the new vim.system-backed job runner and dir_scan-based fallback instead
of the old plenary-based defaults.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant