From 61d64fc84a6d53fe8984aecb2b2ca3001f906f97 Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Date: Wed, 1 Apr 2026 15:22:34 +0000 Subject: [PATCH] fix: Maven find_executable() walks up from build_root instead of checking CWD The CWD-based `Path("mvnw").exists()` fallback returned a relative "./mvnw" that failed with FileNotFoundError when subprocess ran with cwd=. Now walks up parent directories from build_root, matching the pattern already used by GradleStrategy. Co-Authored-By: Claude Opus 4.6 --- codeflash/languages/java/maven_strategy.py | 22 ++++--- .../test_java/test_build_tools.py | 62 +++++++++++++++++-- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/codeflash/languages/java/maven_strategy.py b/codeflash/languages/java/maven_strategy.py index 7f1f64ae6..db36d194b 100644 --- a/codeflash/languages/java/maven_strategy.py +++ b/codeflash/languages/java/maven_strategy.py @@ -647,16 +647,18 @@ def get_text(xpath: str, default: str | None = None) -> str | None: return None def find_executable(self, build_root: Path) -> str | None: - mvnw_path = build_root / "mvnw" - if mvnw_path.exists(): - return str(mvnw_path) - mvnw_cmd_path = build_root / "mvnw.cmd" - if mvnw_cmd_path.exists(): - return str(mvnw_cmd_path) - if Path("mvnw").exists(): - return "./mvnw" - if Path("mvnw.cmd").exists(): - return "mvnw.cmd" + current = build_root.resolve() + while True: + mvnw_path = current / "mvnw" + if mvnw_path.exists(): + return str(mvnw_path) + mvnw_cmd_path = current / "mvnw.cmd" + if mvnw_cmd_path.exists(): + return str(mvnw_cmd_path) + parent = current.parent + if parent == current: + break + current = parent return shutil.which("mvn") def find_runtime_jar(self) -> Path | None: diff --git a/tests/test_languages/test_java/test_build_tools.py b/tests/test_languages/test_java/test_build_tools.py index a4f01e1a6..a9225bd89 100644 --- a/tests/test_languages/test_java/test_build_tools.py +++ b/tests/test_languages/test_java/test_build_tools.py @@ -388,15 +388,65 @@ def test_find_wrapper_in_project_root(self, tmp_path): assert result is not None assert str(tmp_path / "mvnw") in result - def test_fallback_to_cwd(self, tmp_path): + def test_find_wrapper_in_parent_directory(self, tmp_path): + """Multi-module project: mvnw at repo root, build_root is a submodule.""" + repo_root = tmp_path / "repo" + repo_root.mkdir() + mvnw = repo_root / "mvnw" + mvnw.write_text("#!/bin/bash\necho Maven Wrapper") + mvnw.chmod(0o755) + + submodule = repo_root / "submodule" + submodule.mkdir() + (submodule / "pom.xml").write_text("") + strategy = MavenStrategy() - result = strategy.find_executable(tmp_path) - # Should not crash even with a dir that has no wrapper + result = strategy.find_executable(submodule.resolve()) + assert result is not None + assert Path(result).is_absolute(), f"Expected absolute path, got: {result}" + assert Path(result).exists(), f"Returned path does not exist: {result}" + assert result == str(mvnw.resolve()) + + def test_find_wrapper_in_grandparent_directory(self, tmp_path): + """Deeply nested submodule: mvnw two levels up.""" + repo_root = tmp_path / "repo" + repo_root.mkdir() + mvnw = repo_root / "mvnw" + mvnw.write_text("#!/bin/bash\necho Maven Wrapper") + mvnw.chmod(0o755) + + nested = repo_root / "parent-module" / "child-module" + nested.mkdir(parents=True) - def test_with_nonexistent_wrapper(self, tmp_path): strategy = MavenStrategy() - result = strategy.find_executable(tmp_path) - # Should not crash, may return system mvn or None + result = strategy.find_executable(nested.resolve()) + assert result is not None + assert Path(result).is_absolute() + assert result == str(mvnw.resolve()) + + def test_no_wrapper_returns_system_mvn_or_none(self, tmp_path): + strategy = MavenStrategy() + result = strategy.find_executable(tmp_path.resolve()) + # Should return system mvn (if available) or None — never crash + + def test_cwd_does_not_affect_result(self, tmp_path, monkeypatch): + """CWD should not influence find_executable — only build_root matters.""" + repo_with_mvnw = tmp_path / "repo_a" + repo_with_mvnw.mkdir() + mvnw = repo_with_mvnw / "mvnw" + mvnw.write_text("#!/bin/bash\necho Maven Wrapper") + mvnw.chmod(0o755) + + unrelated_dir = tmp_path / "repo_b" + unrelated_dir.mkdir() + + monkeypatch.chdir(repo_with_mvnw) + + strategy = MavenStrategy() + result = strategy.find_executable(unrelated_dir.resolve()) + # Should NOT find mvnw from CWD — only from build_root and its parents + if result is not None: + assert "repo_a" not in result, f"Found mvnw from CWD instead of build_root: {result}" class TestCustomSourceDirectoryDetection: