fix: detect JUnit 5 from submodule build files and default to JUnit 5

Multi-module Gradle projects declare JUnit 5 dependencies in submodule
build files, not the root. The detection function only checked the root
build.gradle, missing JUnit 5 entirely and falling back to JUnit 4.
Now scans immediate child directories for build files. Also changes the
default framework from JUnit 4 to JUnit 5 (standard since 2017).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mohamed Ashraf 2026-04-10 21:45:41 +00:00
parent 5ee642e35e
commit a601076510
2 changed files with 69 additions and 3 deletions

View file

@ -163,9 +163,9 @@ def _detect_test_framework(project_root: Path, build_tool: BuildTool) -> tuple[s
logger.debug("Selected TestNG as test framework")
return "testng", has_junit5, has_junit4, has_testng
# Default to JUnit 4 if nothing detected (more common in legacy projects)
logger.debug("No test framework detected, defaulting to JUnit 4")
return "junit4", has_junit5, has_junit4, has_testng
# Default to JUnit 5 if nothing detected (standard since 2017)
logger.debug("No test framework detected, defaulting to JUnit 5")
return "junit5", has_junit5, has_junit4, has_testng
def _detect_test_deps_from_pom(project_root: Path) -> tuple[bool, bool, bool]:
@ -286,6 +286,23 @@ def _detect_test_deps_from_gradle(project_root: Path) -> tuple[bool, bool, bool]
except Exception:
pass
# For multi-module projects, check submodule build files if root has no test deps
if not (has_junit5 or has_junit4 or has_testng):
for sub_gradle in sorted(project_root.glob("*/build.gradle*")):
if sub_gradle.name in ("build.gradle", "build.gradle.kts"):
try:
content = sub_gradle.read_text(encoding="utf-8")
if "junit-jupiter" in content or "useJUnitPlatform" in content:
has_junit5 = True
if "junit:junit" in content:
has_junit4 = True
if "testng" in content.lower():
has_testng = True
if has_junit5:
break # JUnit 5 found, no need to check more
except Exception:
pass
return has_junit5, has_junit4, has_testng

View file

@ -7,6 +7,8 @@ import pytest
from codeflash.languages.java.build_tools import BuildTool
from codeflash.languages.java.config import (
JavaProjectConfig,
_detect_test_deps_from_gradle,
_detect_test_framework,
detect_java_project,
get_test_class_pattern,
get_test_file_pattern,
@ -342,3 +344,50 @@ class TestDetectWithFixture:
assert config.source_root is not None
assert config.test_root is not None
assert config.has_junit5 is True
class TestGradleSubmoduleDetection:
def test_detect_gradle_junit5_from_submodule(self, tmp_path: Path) -> None:
root = tmp_path.resolve()
# Root build.gradle with no test deps
(root / "build.gradle.kts").write_text("plugins { java }\n", encoding="utf-8")
(root / "settings.gradle.kts").write_text('include("submodule-a")\n', encoding="utf-8")
# Submodule with JUnit 5
sub = root / "submodule-a"
sub.mkdir()
(sub / "build.gradle.kts").write_text(
'dependencies { testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") }\n'
"tasks.withType<Test> { useJUnitPlatform() }\n",
encoding="utf-8",
)
has_junit5, has_junit4, has_testng = _detect_test_deps_from_gradle(root)
assert has_junit5 is True
assert has_junit4 is False
assert has_testng is False
def test_detect_gradle_junit5_from_root(self, tmp_path: Path) -> None:
root = tmp_path.resolve()
(root / "build.gradle").write_text(
"dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' }\n"
"test { useJUnitPlatform() }\n",
encoding="utf-8",
)
has_junit5, has_junit4, has_testng = _detect_test_deps_from_gradle(root)
assert has_junit5 is True
assert has_junit4 is False
assert has_testng is False
def test_default_to_junit5_when_nothing_detected(self, tmp_path: Path) -> None:
root = tmp_path.resolve()
# Empty Gradle project — no test deps anywhere
(root / "build.gradle.kts").write_text("plugins { java }\n", encoding="utf-8")
framework, has_junit5, has_junit4, has_testng = _detect_test_framework(root, BuildTool.GRADLE)
assert framework == "junit5"
assert has_junit5 is False
assert has_junit4 is False
assert has_testng is False