refactor: remove zero-config logic from java-config-redesign branch

Zero-config Java support (auto-detection from build files, codeflash.toml
elimination) is handled separately in cf-java-zero-config-strategy. This
commit strips those changes, keeping only bug fixes:
- JFR parser, ReplayHelper, instrumentation, replay tests
- Multi-module test root resolution
- JUnit 4/5 test framework detection
- add_help=False for optimize subparser

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ubuntu 2026-03-26 07:39:29 +00:00
parent 970c9f86da
commit c5b3687d52
17 changed files with 234 additions and 1305 deletions

View file

@ -0,0 +1,4 @@
[tool.codeflash]
module-root = "src/main/java"
tests-root = "src/test/java"
formatter-cmds = []

View file

@ -0,0 +1,6 @@
# Codeflash configuration for Java project
[tool.codeflash]
module-root = "src/main/java"
tests-root = "src/test/java"
formatter-cmds = []

View file

@ -190,12 +190,6 @@ def process_pyproject_config(args: Namespace) -> Namespace:
if args.benchmarks_root:
args.benchmarks_root = Path(args.benchmarks_root).resolve()
args.test_project_root = project_root_from_module_root(args.tests_root, pyproject_file_path)
if is_java_project and pyproject_file_path.is_dir():
# For Java projects, pyproject_file_path IS the project root directory (not a file).
# Override project_root which may have resolved to a sub-module.
args.project_root = pyproject_file_path.resolve()
args.test_project_root = pyproject_file_path.resolve()
if is_LSP_enabled():
args.all = None
return args
@ -214,6 +208,8 @@ def project_root_from_module_root(module_root: Path, pyproject_file_path: Path)
return current.resolve()
if (current / "build.gradle").exists() or (current / "build.gradle.kts").exists():
return current.resolve()
if (current / "codeflash.toml").exists():
return current.resolve()
current = current.parent
return module_root.parent.resolve()

View file

@ -12,29 +12,8 @@ PYPROJECT_TOML_CACHE: dict[Path, Path] = {}
ALL_CONFIG_FILES: dict[Path, dict[str, Path]] = {}
def _try_parse_java_build_config() -> tuple[dict[str, Any], Path] | None:
"""Detect Java project from build files and parse config from pom.xml/gradle.properties.
Returns (config_dict, project_root) if a Java project is found, None otherwise.
"""
dir_path = Path.cwd()
while dir_path != dir_path.parent:
if (
(dir_path / "pom.xml").exists()
or (dir_path / "build.gradle").exists()
or (dir_path / "build.gradle.kts").exists()
):
from codeflash.languages.java.build_tools import parse_java_project_config
config = parse_java_project_config(dir_path)
if config is not None:
return config, dir_path
dir_path = dir_path.parent
return None
def find_pyproject_toml(config_file: Path | None = None) -> Path:
# Find the pyproject.toml file on the root of the project
# Find the pyproject.toml or codeflash.toml file on the root of the project
if config_file is not None:
config_file = Path(config_file)
@ -50,13 +29,21 @@ def find_pyproject_toml(config_file: Path | None = None) -> Path:
# see if it was encountered before in search
if cur_path in PYPROJECT_TOML_CACHE:
return PYPROJECT_TOML_CACHE[cur_path]
# map current path to closest file - check both pyproject.toml and codeflash.toml
while dir_path != dir_path.parent:
# First check pyproject.toml (Python projects)
config_file = dir_path / "pyproject.toml"
if config_file.exists():
PYPROJECT_TOML_CACHE[cur_path] = config_file
return config_file
# Then check codeflash.toml (Java/other projects)
config_file = dir_path / "codeflash.toml"
if config_file.exists():
PYPROJECT_TOML_CACHE[cur_path] = config_file
return config_file
# Search in parent directories
dir_path = dir_path.parent
msg = f"Could not find pyproject.toml in the current directory {Path.cwd()} or any of the parent directories. Please create it by running `codeflash init`, or pass the path to the config file with the --config-file argument."
msg = f"Could not find pyproject.toml or codeflash.toml in the current directory {Path.cwd()} or any of the parent directories. Please create it by running `codeflash init`, or pass the path to the config file with the --config-file argument."
raise ValueError(msg) from None
@ -103,34 +90,33 @@ def find_conftest_files(test_paths: list[Path]) -> list[Path]:
return list(list_of_conftest_files)
# TODO for claude: There should be different functions to parse it per language, which should be chosen during runtime
def parse_config_file(
config_file_path: Path | None = None, override_formatter_check: bool = False
) -> tuple[dict[str, Any], Path]:
# Detect all config sources — Java, package.json, pyproject.toml
java_result = _try_parse_java_build_config() if config_file_path is None else None
package_json_path = find_package_json(config_file_path)
pyproject_toml_path = find_closest_config_file("pyproject.toml") if config_file_path is None else None
codeflash_toml_path = find_closest_config_file("codeflash.toml") if config_file_path is None else None
# Use Java config only if no closer JS/Python config exists (monorepo support).
# In a monorepo with a parent pom.xml and a child package.json, the closer config wins.
if java_result is not None:
java_depth = len(java_result[1].parts)
has_closer = (package_json_path is not None and len(package_json_path.parent.parts) >= java_depth) or (
pyproject_toml_path is not None and len(pyproject_toml_path.parent.parts) >= java_depth
)
if not has_closer:
return java_result
# Pick the closest toml config (pyproject.toml or codeflash.toml).
# Java projects use codeflash.toml; Python projects use pyproject.toml.
closest_toml_path = None
if pyproject_toml_path and codeflash_toml_path:
closest_toml_path = max(pyproject_toml_path, codeflash_toml_path, key=lambda p: len(p.parent.parts))
else:
closest_toml_path = pyproject_toml_path or codeflash_toml_path
# When both config files exist, prefer the one closer to CWD.
# This prevents a parent-directory package.json (e.g., monorepo root)
# from overriding a closer pyproject.toml.
# from overriding a closer pyproject.toml or codeflash.toml.
use_package_json = False
if package_json_path:
if pyproject_toml_path is None:
if closest_toml_path is None:
use_package_json = True
else:
# Compare depth: more path parts = closer to CWD = more specific
package_json_depth = len(package_json_path.parent.parts)
toml_depth = len(pyproject_toml_path.parent.parts)
toml_depth = len(closest_toml_path.parent.parts)
use_package_json = package_json_depth >= toml_depth
if use_package_json:
@ -174,7 +160,7 @@ def parse_config_file(
if config == {} and lsp_mode:
return {}, config_file_path
# Preserve language field if present (important for JS/TS projects)
# Preserve language field if present (important for Java/JS projects using codeflash.toml)
# default values:
path_keys = ["module-root", "tests-root", "benchmarks-root"]
path_list_keys = ["ignore-paths"]

View file

@ -10,8 +10,7 @@ import logging
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Any
from pathlib import Path # noqa: TC003 — used at runtime
logger = logging.getLogger(__name__)
@ -344,218 +343,6 @@ def _parse_surefire_reports(surefire_dir: Path) -> tuple[int, int, int, int]:
return tests_run, failures, errors, skipped
def parse_java_project_config(project_root: Path) -> dict[str, Any] | None:
"""Parse codeflash config from Maven/Gradle build files.
Reads codeflash.* properties from pom.xml <properties> or gradle.properties,
then fills in defaults from auto-detected build tool conventions.
Returns None if no Java build tool is detected.
"""
build_tool = detect_build_tool(project_root)
if build_tool == BuildTool.UNKNOWN:
return None
# Read explicit codeflash properties from build files
user_config: dict[str, str] = {}
if build_tool == BuildTool.MAVEN:
user_config = _read_maven_codeflash_properties(project_root)
elif build_tool == BuildTool.GRADLE:
user_config = _read_gradle_codeflash_properties(project_root)
# Auto-detect defaults — for multi-module Maven projects, scan module pom.xml files
source_root = find_source_root(project_root)
test_root = find_test_root(project_root)
if build_tool == BuildTool.MAVEN:
source_from_modules, test_from_modules = _detect_roots_from_maven_modules(project_root)
# Module-level pom.xml declarations are more precise than directory-name heuristics
if source_from_modules is not None:
source_root = source_from_modules
if test_from_modules is not None:
test_root = test_from_modules
# Build the config dict matching the format expected by the rest of codeflash
config: dict[str, Any] = {
"language": "java",
"module_root": str(
(project_root / user_config["moduleRoot"]).resolve()
if "moduleRoot" in user_config
else (source_root or project_root / "src" / "main" / "java")
),
"tests_root": str(
(project_root / user_config["testsRoot"]).resolve()
if "testsRoot" in user_config
else (test_root or project_root / "src" / "test" / "java")
),
"pytest_cmd": "pytest",
"git_remote": user_config.get("gitRemote", "origin"),
"disable_telemetry": user_config.get("disableTelemetry", "false").lower() == "true",
"disable_imports_sorting": False,
"override_fixtures": False,
"benchmark": False,
"formatter_cmds": [],
"ignore_paths": [],
}
if "ignorePaths" in user_config:
config["ignore_paths"] = [
str((project_root / p.strip()).resolve()) for p in user_config["ignorePaths"].split(",") if p.strip()
]
if "formatterCmds" in user_config:
config["formatter_cmds"] = [cmd.strip() for cmd in user_config["formatterCmds"].split(",") if cmd.strip()]
return config
def _read_maven_codeflash_properties(project_root: Path) -> dict[str, str]:
"""Read codeflash.* properties from pom.xml <properties> section."""
pom_path = project_root / "pom.xml"
if not pom_path.exists():
return {}
try:
tree = _safe_parse_xml(pom_path)
root = tree.getroot()
ns = {"m": "http://maven.apache.org/POM/4.0.0"}
result: dict[str, str] = {}
for props in [root.find("m:properties", ns), root.find("properties")]:
if props is None:
continue
for child in props:
tag = child.tag
# Strip Maven namespace prefix
if "}" in tag:
tag = tag.split("}", 1)[1]
if tag.startswith("codeflash.") and child.text:
key = tag[len("codeflash.") :]
result[key] = child.text.strip()
return result
except Exception:
logger.debug("Failed to read codeflash properties from pom.xml", exc_info=True)
return {}
def _read_gradle_codeflash_properties(project_root: Path) -> dict[str, str]:
"""Read codeflash.* properties from gradle.properties."""
props_path = project_root / "gradle.properties"
if not props_path.exists():
return {}
result: dict[str, str] = {}
try:
with props_path.open("r", encoding="utf-8") as f:
for line in f:
stripped = line.strip()
if stripped.startswith("#") or "=" not in stripped:
continue
key, value = stripped.split("=", 1)
key = key.strip()
if key.startswith("codeflash."):
result[key[len("codeflash.") :]] = value.strip()
return result
except Exception:
logger.debug("Failed to read codeflash properties from gradle.properties", exc_info=True)
return {}
def _detect_roots_from_maven_modules(project_root: Path) -> tuple[Path | None, Path | None]:
"""Scan Maven module pom.xml files for custom sourceDirectory/testSourceDirectory.
For multi-module projects like aerospike (client/, test/, benchmarks/),
finds the main source module and test module by parsing each module's build config.
"""
pom_path = project_root / "pom.xml"
if not pom_path.exists():
return None, None
try:
tree = _safe_parse_xml(pom_path)
root = tree.getroot()
ns = {"m": "http://maven.apache.org/POM/4.0.0"}
# Find <modules> to get module names
modules: list[str] = []
for modules_elem in [root.find("m:modules", ns), root.find("modules")]:
if modules_elem is not None:
for mod in modules_elem:
if mod.text:
modules.append(mod.text.strip())
if not modules:
return None, None
# Collect candidate source and test roots with Java file counts
source_candidates: list[tuple[Path, int]] = []
test_root: Path | None = None
skip_modules = {"example", "examples", "benchmark", "benchmarks", "demo", "sample", "samples"}
for module_name in modules:
module_pom = project_root / module_name / "pom.xml"
if not module_pom.exists():
continue
# Modules named "test" are test modules, not source modules
is_test_module = "test" in module_name.lower()
try:
mod_tree = _safe_parse_xml(module_pom)
mod_root = mod_tree.getroot()
for build in [mod_root.find("m:build", ns), mod_root.find("build")]:
if build is None:
continue
for src_elem in [build.find("m:sourceDirectory", ns), build.find("sourceDirectory")]:
if src_elem is not None and src_elem.text:
src_text = src_elem.text.replace("${project.basedir}", str(project_root / module_name))
src_path = Path(src_text)
if not src_path.is_absolute():
src_path = project_root / module_name / src_path
if src_path.exists():
if is_test_module and test_root is None:
test_root = src_path
elif module_name.lower() not in skip_modules:
java_count = sum(1 for _ in src_path.rglob("*.java"))
if java_count > 0:
source_candidates.append((src_path, java_count))
for test_elem in [build.find("m:testSourceDirectory", ns), build.find("testSourceDirectory")]:
if test_elem is not None and test_elem.text:
test_text = test_elem.text.replace("${project.basedir}", str(project_root / module_name))
test_path = Path(test_text)
if not test_path.is_absolute():
test_path = project_root / module_name / test_path
if test_path.exists() and test_root is None:
test_root = test_path
# Also check standard module layouts
if module_name.lower() not in skip_modules and not is_test_module:
std_src = project_root / module_name / "src" / "main" / "java"
if std_src.exists():
java_count = sum(1 for _ in std_src.rglob("*.java"))
if java_count > 0:
source_candidates.append((std_src, java_count))
if test_root is None:
std_test = project_root / module_name / "src" / "test" / "java"
if std_test.exists() and any(std_test.rglob("*.java")):
test_root = std_test
except Exception:
continue
# Pick the source root with the most Java files (likely the main library)
source_root = max(source_candidates, key=lambda x: x[1])[0] if source_candidates else None
return source_root, test_root
except Exception:
return None, None
def find_test_root(project_root: Path) -> Path | None:
"""Find the test root directory for a Java project.

View file

@ -8,7 +8,7 @@ This module writes Codeflash configuration to native config files:
from __future__ import annotations
import json
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING
import tomlkit
@ -38,7 +38,7 @@ def write_config(detected: DetectedProject, config: CodeflashConfig | None = Non
if detected.language == "python":
return _write_pyproject_toml(detected.project_root, config)
if detected.language == "java":
return _write_java_build_config(detected.project_root, config)
return _write_codeflash_toml(detected.project_root, config)
return _write_package_json(detected.project_root, config)
@ -92,10 +92,10 @@ def _write_pyproject_toml(project_root: Path, config: CodeflashConfig) -> tuple[
return False, f"Failed to write pyproject.toml: {e}"
def _write_java_build_config(project_root: Path, config: CodeflashConfig) -> tuple[bool, str]:
"""Write codeflash config to pom.xml properties or gradle.properties.
def _write_codeflash_toml(project_root: Path, config: CodeflashConfig) -> tuple[bool, str]:
"""Write config to codeflash.toml [tool.codeflash] section for Java projects.
Only writes non-default values. Standard Maven/Gradle layouts need no config.
Creates codeflash.toml if it doesn't exist.
Args:
project_root: Project root directory.
@ -105,141 +105,40 @@ def _write_java_build_config(project_root: Path, config: CodeflashConfig) -> tup
Tuple of (success, message).
"""
codeflash_toml_path = project_root / "codeflash.toml"
try:
# Load existing or create new
if codeflash_toml_path.exists():
with codeflash_toml_path.open("rb") as f:
doc = tomlkit.parse(f.read())
else:
doc = tomlkit.document()
# Ensure [tool] section exists
if "tool" not in doc:
doc["tool"] = tomlkit.table()
# Create codeflash section
codeflash_table = tomlkit.table()
codeflash_table.add(tomlkit.comment("Codeflash configuration for Java - https://docs.codeflash.ai"))
# Add config values
config_dict = config.to_pyproject_dict()
for key, value in config_dict.items():
codeflash_table[key] = value
# Filter out default values — only write overrides
defaults = {"module-root": "src/main/java", "tests-root": "src/test/java", "language": "java"}
non_default = {k: v for k, v in config_dict.items() if k not in defaults or str(v) != defaults.get(k)}
# Remove empty lists and False booleans
non_default = {k: v for k, v in non_default.items() if v not in ([], False, "", None)}
# Update the document
doc["tool"]["codeflash"] = codeflash_table
if not non_default:
return True, "Standard Maven/Gradle layout detected — no config needed"
# Write back
with codeflash_toml_path.open("w", encoding="utf8") as f:
f.write(tomlkit.dumps(doc))
pom_path = project_root / "pom.xml"
if pom_path.exists():
return _write_maven_properties(pom_path, non_default)
gradle_props_path = project_root / "gradle.properties"
return _write_gradle_properties(gradle_props_path, non_default)
_MAVEN_KEY_MAP: dict[str, str] = {
"module-root": "moduleRoot",
"tests-root": "testsRoot",
"git-remote": "gitRemote",
"disable-telemetry": "disableTelemetry",
"ignore-paths": "ignorePaths",
"formatter-cmds": "formatterCmds",
}
def _write_maven_properties(pom_path: Path, config: dict[str, Any]) -> tuple[bool, str]:
"""Add codeflash.* properties to pom.xml <properties> section.
Uses text-based manipulation to preserve comments, formatting, and namespace declarations.
"""
import re
try:
content = pom_path.read_text(encoding="utf-8")
# Remove existing codeflash.* property lines (with surrounding whitespace)
content = re.sub(r"\n[ \t]*<codeflash\.[^>]*>[^<]*</codeflash\.[^>]*>", "", content)
# Detect child indentation from existing properties or fall back to </properties> indent + 4 spaces
props_close = re.search(r"([ \t]*)</properties>", content)
if props_close:
parent_indent = props_close.group(1)
# Try to detect child indent from an existing property element
child_match = re.search(
r"\n([ \t]+)<[a-zA-Z]",
content[content.find("<properties>") : props_close.start()] if "<properties>" in content else "",
)
child_indent = child_match.group(1) if child_match else parent_indent + " "
else:
parent_indent = ""
child_indent = " "
# Build new property lines with detected indentation
new_lines = []
for key, value in config.items():
maven_key = f"codeflash.{_MAVEN_KEY_MAP.get(key, key)}"
if isinstance(value, list):
value = ",".join(str(v) for v in value)
elif isinstance(value, bool):
value = str(value).lower()
else:
value = str(value)
new_lines.append(f"{child_indent}<{maven_key}>{value}</{maven_key}>")
properties_block = "\n".join(new_lines)
# Insert before </properties>
if props_close:
content = (
content[: props_close.start()]
+ properties_block
+ "\n"
+ parent_indent
+ "</properties>"
+ content[props_close.end() :]
)
else:
# No <properties> section — create one before </project>
project_close = re.search(r"([ \t]*)</project>", content)
if project_close:
indent = project_close.group(1)
inner = " " + indent
props_section = (
f"{inner}<properties>\n"
+ "\n".join(f" {line}" for line in new_lines)
+ f"\n{inner}</properties>\n"
)
content = (
content[: project_close.start()]
+ props_section
+ indent
+ "</project>"
+ content[project_close.end() :]
)
pom_path.write_text(content, encoding="utf-8")
return True, f"Config saved to {pom_path} <properties>"
return True, f"Config saved to {codeflash_toml_path}"
except Exception as e:
return False, f"Failed to write Maven properties: {e}"
def _write_gradle_properties(props_path: Path, config: dict[str, Any]) -> tuple[bool, str]:
"""Add codeflash.* entries to gradle.properties."""
try:
lines = []
if props_path.exists():
lines = props_path.read_text(encoding="utf-8").splitlines()
# Remove existing codeflash.* lines
lines = [line for line in lines if not line.strip().startswith("codeflash.")]
# Add new config
if lines and lines[-1].strip():
lines.append("")
lines.append("# Codeflash configuration — https://docs.codeflash.ai")
for key, value in config.items():
gradle_key = f"codeflash.{_MAVEN_KEY_MAP.get(key, key)}"
if isinstance(value, list):
value = ",".join(str(v) for v in value)
elif isinstance(value, bool):
value = str(value).lower()
else:
value = str(value)
lines.append(f"{gradle_key}={value}")
props_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
return True, f"Config saved to {props_path}"
except Exception as e:
return False, f"Failed to write gradle.properties: {e}"
return False, f"Failed to write codeflash.toml: {e}"
def _write_package_json(project_root: Path, config: CodeflashConfig) -> tuple[bool, str]:
@ -307,7 +206,7 @@ def remove_config(project_root: Path, language: str) -> tuple[bool, str]:
if language == "python":
return _remove_from_pyproject(project_root)
if language == "java":
return _remove_java_build_config(project_root)
return _remove_from_codeflash_toml(project_root)
return _remove_from_package_json(project_root)
@ -336,42 +235,29 @@ def _remove_from_pyproject(project_root: Path) -> tuple[bool, str]:
return False, f"Failed to remove config: {e}"
def _remove_java_build_config(project_root: Path) -> tuple[bool, str]:
"""Remove codeflash.* properties from pom.xml or gradle.properties.
def _remove_from_codeflash_toml(project_root: Path) -> tuple[bool, str]:
"""Remove [tool.codeflash] section from codeflash.toml."""
codeflash_toml_path = project_root / "codeflash.toml"
if not codeflash_toml_path.exists():
return True, "No codeflash.toml found"
Priority matches _write_java_build_config: pom.xml first, then gradle.properties.
"""
# Try pom.xml first (matches write priority) — text-based removal preserves formatting
pom_path = project_root / "pom.xml"
if pom_path.exists():
try:
import re
with codeflash_toml_path.open("rb") as f:
doc = tomlkit.parse(f.read())
if "tool" in doc and "codeflash" in doc["tool"]:
del doc["tool"]["codeflash"]
with codeflash_toml_path.open("w", encoding="utf8") as f:
f.write(tomlkit.dumps(doc))
return True, "Removed [tool.codeflash] section from codeflash.toml"
return True, "No codeflash config found in codeflash.toml"
content = pom_path.read_text(encoding="utf-8")
updated = re.sub(r"\n[ \t]*<codeflash\.[^>]*>[^<]*</codeflash\.[^>]*>", "", content)
if updated != content:
pom_path.write_text(updated, encoding="utf-8")
return True, "Removed codeflash properties from pom.xml"
except Exception as e:
return False, f"Failed to remove config from pom.xml: {e}"
# Try gradle.properties
gradle_props = project_root / "gradle.properties"
if gradle_props.exists():
try:
lines = gradle_props.read_text(encoding="utf-8").splitlines()
filtered = [
line
for line in lines
if not line.strip().startswith("codeflash.")
and line.strip() != "# Codeflash configuration \u2014 https://docs.codeflash.ai"
]
gradle_props.write_text("\n".join(filtered) + "\n", encoding="utf-8")
return True, "Removed codeflash properties from gradle.properties"
except Exception as e:
return False, f"Failed to remove config from gradle.properties: {e}"
return True, "No Java build config found"
return False, f"Failed to remove config: {e}"
def _remove_from_package_json(project_root: Path) -> tuple[bool, str]:

View file

@ -886,26 +886,21 @@ def has_existing_config(project_root: Path) -> tuple[bool, str | None]:
Returns:
Tuple of (has_config, config_file_type).
config_file_type is "pyproject.toml", "pom.xml", "build.gradle", "package.json", or None.
config_file_type is "pyproject.toml", "codeflash.toml", "package.json", or None.
"""
# Check pyproject.toml (Python projects)
pyproject_path = project_root / "pyproject.toml"
if pyproject_path.exists():
# Check TOML config files (pyproject.toml, codeflash.toml)
for toml_filename in ("pyproject.toml", "codeflash.toml"):
toml_path = project_root / toml_filename
if toml_path.exists():
try:
with pyproject_path.open("rb") as f:
with toml_path.open("rb") as f:
data = tomlkit.parse(f.read())
if "tool" in data and "codeflash" in data["tool"]:
return True, "pyproject.toml"
return True, toml_filename
except Exception:
pass
# Check Java build files — for zero-config Java, any build file means "configured"
# because Java config is auto-detected from build files without explicit codeflash.* properties
for build_file in ("pom.xml", "build.gradle", "build.gradle.kts"):
if (project_root / build_file).exists():
return True, build_file
# Check package.json
package_json_path = project_root / "package.json"
if package_json_path.exists():

View file

@ -38,7 +38,7 @@ logger = logging.getLogger(__name__)
def _detect_non_python_language(args: Namespace | None) -> Language | None:
"""Detect if the project uses a non-Python language from --file or build files.
"""Detect if the project uses a non-Python language from --file or config.
Returns a Language enum value if non-Python detected, None otherwise.
"""
@ -66,23 +66,15 @@ def _detect_non_python_language(args: Namespace | None) -> Language | None:
except Exception:
pass
# Method 2: Detect Java from build files (pom.xml / build.gradle)
try:
from codeflash.languages.java.build_tools import BuildTool, detect_build_tool
cwd = Path.cwd()
if detect_build_tool(cwd) != BuildTool.UNKNOWN:
return Language.JAVA
except Exception:
pass
# Method 3: Check config file for language field (JS/TS via package.json)
# Method 2: Check project config for language field
try:
from codeflash.code_utils.config_parser import parse_config_file
config_file = getattr(args, "config_file_path", None) if args else None
config, _ = parse_config_file(config_file)
lang_str = config.get("language", "")
if lang_str == "java":
return Language.JAVA
if lang_str in ("javascript", "typescript"):
return Language(lang_str)
except Exception:

View file

@ -1,112 +1,101 @@
---
title: "Java Configuration"
description: "Configure Codeflash for Java projects — zero config for standard layouts"
description: "Configure Codeflash for Java projects using codeflash.toml"
icon: "java"
sidebarTitle: "Java (pom.xml / Gradle)"
sidebarTitle: "Java (codeflash.toml)"
keywords:
[
"configuration",
"codeflash.toml",
"java",
"maven",
"gradle",
"junit",
"pom.xml",
"gradle.properties",
"zero-config",
]
---
# Java Configuration
**Standard Maven/Gradle projects need zero configuration.** Codeflash auto-detects your project structure from `pom.xml` or `build.gradle` — no config file is required.
Codeflash stores its configuration in `codeflash.toml` under the `[tool.codeflash]` section.
For projects with non-standard layouts, you can add `codeflash.*` properties to your existing `pom.xml` or `gradle.properties`.
## Full Reference
```toml
[tool.codeflash]
# Required
module-root = "src/main/java"
tests-root = "src/test/java"
language = "java"
# Optional
test-framework = "junit5" # "junit5", "junit4", or "testng"
disable-telemetry = false
git-remote = "origin"
ignore-paths = ["src/main/java/generated/"]
```
All file paths are relative to the directory containing `codeflash.toml`.
<Info>
Codeflash auto-detects most settings from your project structure. Running `codeflash init` will set up the correct config — manual configuration is usually not needed.
</Info>
## Auto-Detection
Codeflash inspects your build files and auto-detects:
When you run `codeflash init`, Codeflash inspects your project and auto-detects:
| Setting | Detection logic |
|---------|----------------|
| **Language** | Presence of `pom.xml` or `build.gradle` / `build.gradle.kts` |
| **Source root** | `src/main/java` (standard), or `<sourceDirectory>` in `pom.xml`, or Gradle `sourceSets` |
| **Test root** | `src/test/java` (standard), or `<testSourceDirectory>` in `pom.xml` |
| **Test framework** | Checks build file dependencies for JUnit 5, JUnit 4, or TestNG |
| **Java version** | `<maven.compiler.source>`, `<java.version>` in `pom.xml` |
| `module-root` | Looks for `src/main/java` (Maven/Gradle standard layout) |
| `tests-root` | Looks for `src/test/java`, `test/`, `tests/` |
| `language` | Detected from build files (`pom.xml`, `build.gradle`) and `.java` files |
| `test-framework` | Checks build file dependencies for JUnit 5, JUnit 4, or TestNG |
### Multi-module Maven projects
## Required Options
For multi-module projects, Codeflash scans each module's `pom.xml` for `<sourceDirectory>` and `<testSourceDirectory>` declarations. It picks the module with the most Java source files as the main source root, and identifies test modules by name.
- **`module-root`**: The source directory to optimize. Only code under this directory is discovered for optimization. For standard Maven/Gradle projects, this is `src/main/java`.
- **`tests-root`**: The directory where your tests are located. Codeflash discovers existing tests and places generated replay tests here.
- **`language`**: Must be set to `"java"` for Java projects.
For example, with this layout:
## Optional Options
- **`test-framework`**: Test framework. Auto-detected from build dependencies. Supported values: `"junit5"` (default), `"junit4"`, `"testng"`.
- **`disable-telemetry`**: Disable anonymized telemetry. Defaults to `false`.
- **`git-remote`**: Git remote for pull requests. Defaults to `"origin"`.
- **`ignore-paths`**: Paths within `module-root` to skip during optimization.
## Multi-Module Projects
For multi-module Maven/Gradle projects, place `codeflash.toml` at the project root and set `module-root` to the module you want to optimize:
```text
my-project/
|- client/ ← main library (most .java files)
| |- src/com/example/
| |- pom.xml ← <sourceDirectory>${project.basedir}/src</sourceDirectory>
|- test/ ← test module
| |- src/com/example/
| |- pom.xml ← <testSourceDirectory>${project.basedir}/src</testSourceDirectory>
|- benchmarks/ ← skipped (benchmark module)
|- pom.xml ← <modules>client, test, benchmarks</modules>
|- client/
| |- src/main/java/com/example/client/
| |- src/test/java/com/example/client/
|- server/
| |- src/main/java/com/example/server/
|- pom.xml
|- codeflash.toml
```
Codeflash auto-detects `client/src` as the source root and `test/src` as the test root — no manual configuration needed.
## Custom Configuration
If auto-detection doesn't match your project layout, add `codeflash.*` properties to your build files.
<Tabs>
<Tab title="Maven (pom.xml)">
Add properties to your `pom.xml` `<properties>` section:
```xml
<properties>
<!-- Only set values that differ from auto-detected defaults -->
<codeflash.moduleRoot>client/src</codeflash.moduleRoot>
<codeflash.testsRoot>test/src</codeflash.testsRoot>
<codeflash.disableTelemetry>true</codeflash.disableTelemetry>
<codeflash.gitRemote>upstream</codeflash.gitRemote>
<codeflash.ignorePaths>src/main/java/generated/,src/main/java/proto/</codeflash.ignorePaths>
</properties>
```toml
[tool.codeflash]
module-root = "client/src/main/java"
tests-root = "client/src/test/java"
language = "java"
```
This follows the same pattern as SonarQube (`sonar.sources`), JaCoCo, and other Java tools — config lives in the build file, not a separate tool-specific file.
For non-standard layouts (like the Aerospike client where source is under `client/src/`), adjust paths accordingly:
</Tab>
<Tab title="Gradle (gradle.properties)">
Add properties to `gradle.properties`:
```properties
# Only set values that differ from auto-detected defaults
codeflash.moduleRoot=lib/src/main/java
codeflash.testsRoot=lib/src/test/java
codeflash.disableTelemetry=true
codeflash.gitRemote=upstream
codeflash.ignorePaths=src/main/java/generated/
```toml
[tool.codeflash]
module-root = "client/src"
tests-root = "test/src"
language = "java"
```
</Tab>
</Tabs>
## Available Properties
All properties are optional — only set values that differ from auto-detected defaults.
| Property | Description | Default |
|----------|------------|---------|
| `codeflash.moduleRoot` | Source directory to optimize | Auto-detected from `<sourceDirectory>` or `src/main/java` |
| `codeflash.testsRoot` | Test directory | Auto-detected from `<testSourceDirectory>` or `src/test/java` |
| `codeflash.disableTelemetry` | Disable anonymized telemetry | `false` |
| `codeflash.gitRemote` | Git remote for pull requests | `origin` |
| `codeflash.ignorePaths` | Comma-separated paths to skip during optimization | Empty |
| `codeflash.formatterCmds` | Comma-separated formatter commands (`$file` = file path) | Empty |
## Tracer CLI Options
## Tracer Options
When using `codeflash optimize` to trace a Java program, these CLI options are available:
@ -122,9 +111,9 @@ Example with timeout:
codeflash optimize --timeout 30 java -jar target/my-app.jar --app-args
```
## Examples
## Example
### Standard Maven project (zero config)
### Standard Maven project
```text
my-app/
@ -135,14 +124,17 @@ my-app/
| |- test/java/com/example/
| |- AppTest.java
|- pom.xml
|- codeflash.toml
```
Just run:
```bash
codeflash optimize java -jar target/my-app.jar
```toml
[tool.codeflash]
module-root = "src/main/java"
tests-root = "src/test/java"
language = "java"
```
### Standard Gradle project (zero config)
### Gradle project
```text
my-lib/
@ -150,55 +142,12 @@ my-lib/
| |- main/java/com/example/
| |- test/java/com/example/
|- build.gradle
|- codeflash.toml
```
Just run:
```bash
codeflash optimize java -cp build/classes/java/main com.example.Main
```toml
[tool.codeflash]
module-root = "src/main/java"
tests-root = "src/test/java"
language = "java"
```
### Non-standard layout (with config)
```text
aerospike-client-java/
|- client/
| |- src/com/aerospike/client/ ← source here (not src/main/java)
| |- pom.xml
|- test/
| |- src/com/aerospike/test/ ← tests here
| |- pom.xml
|- pom.xml
```
If auto-detection doesn't pick up the right modules, add to the root `pom.xml`:
```xml
<properties>
<codeflash.moduleRoot>client/src</codeflash.moduleRoot>
<codeflash.testsRoot>test/src</codeflash.testsRoot>
</properties>
```
<Info>
In most cases, even non-standard multi-module layouts are auto-detected correctly from `<sourceDirectory>` and `<testSourceDirectory>` in each module's `pom.xml`. Only add manual config if auto-detection gets it wrong.
</Info>
## FAQ
<AccordionGroup>
<Accordion title="Do I need to run codeflash init for Java projects?">
No. Codeflash auto-detects Java projects from `pom.xml` or `build.gradle`. No initialization step or config file is needed for standard layouts.
</Accordion>
<Accordion title="Where does codeflash store its config for Java?">
Codeflash reads config from your existing build files — `pom.xml` `<properties>` for Maven, `gradle.properties` for Gradle. No separate config file is created.
</Accordion>
<Accordion title="What if codeflash detects the wrong source/test directories?">
Add `<codeflash.moduleRoot>` and `<codeflash.testsRoot>` properties to your `pom.xml` or `gradle.properties`. These override auto-detection.
</Accordion>
<Accordion title="How does codeflash handle multi-module Maven projects?">
Codeflash scans each module's `pom.xml` for `<sourceDirectory>` and `<testSourceDirectory>`. It picks the module with the most Java files as the source root (skipping modules named `examples`, `benchmarks`, etc.) and identifies `test` modules for the test root.
</Accordion>
</AccordionGroup>

View file

@ -12,11 +12,10 @@ keywords:
"junit",
"junit5",
"tracing",
"zero-config",
]
---
Codeflash supports Java projects using Maven or Gradle build systems. **No configuration file is needed** — Codeflash auto-detects your project structure from `pom.xml` or `build.gradle`.
Codeflash supports Java projects using Maven or Gradle build systems. It uses a two-stage tracing approach to capture method arguments and profiling data from running Java programs, then optimizes the hottest functions.
### Prerequisites
@ -24,7 +23,7 @@ Before installing Codeflash, ensure you have:
1. **Java 11 or above** installed
2. **Maven or Gradle** as your build tool
3. **A Java project** with source code
3. **A Java project** with source code under a standard directory layout
Good to have (optional):
@ -46,15 +45,51 @@ uv pip install codeflash
```
</Step>
<Step title="Run your first optimization">
<Step title="Initialize your project">
Navigate to your Java project root (where `pom.xml` or `build.gradle` is) and run:
```bash
codeflash init
```
This will:
- Detect your build tool (Maven/Gradle)
- Find your source and test directories
- Create a `codeflash.toml` configuration file
</Step>
<Step title="Verify setup">
Check that the configuration looks correct:
```bash
cat codeflash.toml
```
You should see something like:
```toml
[tool.codeflash]
module-root = "src/main/java"
tests-root = "src/test/java"
language = "java"
```
</Step>
<Step title="Run your first optimization">
Trace and optimize a running Java program:
```bash
codeflash optimize java -jar target/my-app.jar
```
That's it — no `init` step, no config file. Codeflash detects Maven/Gradle automatically and infers source and test directories from your build files.
Or with Maven:
```bash
codeflash optimize mvn exec:java -Dexec.mainClass="com.example.Main"
```
Codeflash will:
1. Profile your program using JFR (Java Flight Recorder)
@ -66,29 +101,6 @@ Codeflash will:
</Step>
</Steps>
<Info>
**Zero config for standard projects.** If your project uses the standard Maven/Gradle layout (`src/main/java`, `src/test/java`), everything is auto-detected. For non-standard layouts, see the [configuration guide](/configuration/java).
</Info>
## Usage examples
**Trace and optimize a JAR application:**
```bash
codeflash optimize java -jar target/my-app.jar --app-args
```
**Optimize a specific file and function:**
```bash
codeflash --file src/main/java/com/example/Utils.java --function computeHash
```
**Trace a long-running program with a timeout:**
```bash
codeflash optimize --timeout 30 java -jar target/my-server.jar
```
Each tracing stage runs for at most 30 seconds, then the captured data is processed.
## How it works
Codeflash uses a **two-stage tracing** approach for Java:

View file

@ -1,87 +0,0 @@
"""Tests for config_parser.py — monorepo language detection priority."""
from __future__ import annotations
import json
import os
from pathlib import Path
from unittest.mock import patch
import pytest
from codeflash.code_utils.config_parser import parse_config_file
class TestMonorepoConfigPriority:
"""Verify that closer config files win over parent Java build files in monorepos."""
def test_closer_package_json_wins_over_parent_pom_xml(self, tmp_path: Path) -> None:
"""In monorepo/frontend/, a local package.json should win over a parent pom.xml."""
# Parent Java project
(tmp_path / "pom.xml").write_text("<project></project>", encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
# Child JS project
frontend = tmp_path / "frontend"
frontend.mkdir()
(frontend / "package.json").write_text(
json.dumps({"name": "frontend", "codeflash": {"moduleRoot": "src"}}),
encoding="utf-8",
)
(frontend / "src").mkdir()
with patch("codeflash.code_utils.config_parser.Path") as mock_path_cls:
mock_path_cls.cwd.return_value = frontend
# find_package_json also uses Path.cwd; mock it at the source
with patch("codeflash.code_utils.config_js.Path") as mock_js_path_cls:
mock_js_path_cls.cwd.return_value = frontend
# Also need to let normal Path operations work
mock_path_cls.side_effect = Path
mock_path_cls.cwd.return_value = frontend
mock_js_path_cls.side_effect = Path
mock_js_path_cls.cwd.return_value = frontend
config, root = parse_config_file()
# Should detect JS, not Java
assert config.get("language") != "java", (
"Closer package.json should take priority over parent pom.xml"
)
def test_java_wins_when_no_closer_js_config(self, tmp_path: Path) -> None:
"""When only a pom.xml exists (no package.json/pyproject.toml closer), Java config wins."""
(tmp_path / "pom.xml").write_text("<project></project>", encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
with patch("codeflash.code_utils.config_parser.Path") as mock_path_cls:
mock_path_cls.side_effect = Path
mock_path_cls.cwd.return_value = tmp_path
with patch("codeflash.code_utils.config_js.Path") as mock_js_path_cls:
mock_js_path_cls.side_effect = Path
mock_js_path_cls.cwd.return_value = tmp_path
config, root = parse_config_file()
assert config.get("language") == "java"
def test_same_level_package_json_wins_over_pom_xml(self, tmp_path: Path) -> None:
"""When pom.xml and package.json are at the same level, package.json wins (more specific)."""
(tmp_path / "pom.xml").write_text("<project></project>", encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
(tmp_path / "package.json").write_text(
json.dumps({"name": "mixed-project", "codeflash": {"moduleRoot": "src"}}),
encoding="utf-8",
)
with patch("codeflash.code_utils.config_parser.Path") as mock_path_cls:
mock_path_cls.side_effect = Path
mock_path_cls.cwd.return_value = tmp_path
with patch("codeflash.code_utils.config_js.Path") as mock_js_path_cls:
mock_js_path_cls.side_effect = Path
mock_js_path_cls.cwd.return_value = tmp_path
config, root = parse_config_file()
assert config.get("language") != "java", (
"Same-level package.json should take priority over pom.xml"
)

View file

@ -149,8 +149,8 @@ def build_command(
if config.function_name:
base_command.extend(["--function", config.function_name])
# Check if config exists (pyproject.toml, pom.xml, build.gradle) - if so, don't override it
has_codeflash_config = (cwd / "pom.xml").exists() or (cwd / "build.gradle").exists() or (cwd / "build.gradle.kts").exists()
# Check if config exists (pyproject.toml or codeflash.toml) - if so, don't override it
has_codeflash_config = (cwd / "codeflash.toml").exists()
if not has_codeflash_config:
pyproject_path = cwd / "pyproject.toml"
if pyproject_path.exists():

View file

@ -0,0 +1,5 @@
# Codeflash configuration for Java project
[tool.codeflash]
module-root = "src/main/java"
tests-root = "src/test/java"

View file

@ -0,0 +1,6 @@
# Codeflash configuration for Java project
[tool.codeflash]
module-root = "src/main/java"
tests-root = "src/test/java"
language = "java"

View file

@ -1,444 +0,0 @@
"""Tests for Java project auto-detection from Maven/Gradle build files.
Tests that codeflash can detect Java projects and infer module-root,
tests-root, and other config from pom.xml / build.gradle / gradle.properties
without requiring a standalone codeflash.toml config file.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from codeflash.languages.java.build_tools import (
BuildTool,
detect_build_tool,
find_source_root,
find_test_root,
parse_java_project_config,
)
# ---------------------------------------------------------------------------
# Build tool detection
# ---------------------------------------------------------------------------
class TestDetectBuildTool:
def test_detect_maven(self, tmp_path: Path) -> None:
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
assert detect_build_tool(tmp_path) == BuildTool.MAVEN
def test_detect_gradle(self, tmp_path: Path) -> None:
(tmp_path / "build.gradle").write_text("", encoding="utf-8")
assert detect_build_tool(tmp_path) == BuildTool.GRADLE
def test_detect_gradle_kts(self, tmp_path: Path) -> None:
(tmp_path / "build.gradle.kts").write_text("", encoding="utf-8")
assert detect_build_tool(tmp_path) == BuildTool.GRADLE
def test_maven_takes_priority_over_gradle(self, tmp_path: Path) -> None:
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
(tmp_path / "build.gradle").write_text("", encoding="utf-8")
assert detect_build_tool(tmp_path) == BuildTool.MAVEN
def test_unknown_when_no_build_file(self, tmp_path: Path) -> None:
assert detect_build_tool(tmp_path) == BuildTool.UNKNOWN
def test_detect_maven_in_parent(self, tmp_path: Path) -> None:
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
child = tmp_path / "module"
child.mkdir()
assert detect_build_tool(child) == BuildTool.MAVEN
# ---------------------------------------------------------------------------
# Source / test root detection (standard layouts)
# ---------------------------------------------------------------------------
class TestFindSourceRoot:
def test_standard_maven_layout(self, tmp_path: Path) -> None:
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
src = tmp_path / "src" / "main" / "java"
src.mkdir(parents=True)
assert find_source_root(tmp_path) == src
def test_fallback_to_src_with_java_files(self, tmp_path: Path) -> None:
src = tmp_path / "src"
src.mkdir()
(src / "App.java").write_text("class App {}", encoding="utf-8")
assert find_source_root(tmp_path) == src
def test_returns_none_when_no_source(self, tmp_path: Path) -> None:
assert find_source_root(tmp_path) is None
class TestFindTestRoot:
def test_standard_maven_layout(self, tmp_path: Path) -> None:
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
test = tmp_path / "src" / "test" / "java"
test.mkdir(parents=True)
assert find_test_root(tmp_path) == test
def test_fallback_to_test_dir(self, tmp_path: Path) -> None:
test = tmp_path / "test"
test.mkdir()
assert find_test_root(tmp_path) == test
def test_fallback_to_tests_dir(self, tmp_path: Path) -> None:
tests = tmp_path / "tests"
tests.mkdir()
assert find_test_root(tmp_path) == tests
def test_returns_none_when_no_test_dir(self, tmp_path: Path) -> None:
assert find_test_root(tmp_path) is None
# ---------------------------------------------------------------------------
# parse_java_project_config — standard layouts
# ---------------------------------------------------------------------------
class TestParseJavaProjectConfigStandard:
def test_standard_maven_project(self, tmp_path: Path) -> None:
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
src = tmp_path / "src" / "main" / "java"
src.mkdir(parents=True)
test = tmp_path / "src" / "test" / "java"
test.mkdir(parents=True)
config = parse_java_project_config(tmp_path)
assert config is not None
assert config["language"] == "java"
assert config["module_root"] == str(src)
assert config["tests_root"] == str(test)
def test_standard_gradle_project(self, tmp_path: Path) -> None:
(tmp_path / "build.gradle").write_text("", encoding="utf-8")
src = tmp_path / "src" / "main" / "java"
src.mkdir(parents=True)
test = tmp_path / "src" / "test" / "java"
test.mkdir(parents=True)
config = parse_java_project_config(tmp_path)
assert config is not None
assert config["language"] == "java"
assert config["module_root"] == str(src)
assert config["tests_root"] == str(test)
def test_returns_none_for_non_java_project(self, tmp_path: Path) -> None:
assert parse_java_project_config(tmp_path) is None
def test_defaults_when_dirs_missing(self, tmp_path: Path) -> None:
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
config = parse_java_project_config(tmp_path)
assert config is not None
# Falls back to default paths even if they don't exist
assert str(tmp_path / "src" / "main" / "java") == config["module_root"]
assert config["language"] == "java"
# ---------------------------------------------------------------------------
# parse_java_project_config — Maven properties (codeflash.*)
# ---------------------------------------------------------------------------
MAVEN_POM_WITH_PROPERTIES = """\
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>test</artifactId>
<version>1.0</version>
<properties>
<codeflash.moduleRoot>custom/src</codeflash.moduleRoot>
<codeflash.testsRoot>custom/test</codeflash.testsRoot>
<codeflash.disableTelemetry>true</codeflash.disableTelemetry>
<codeflash.gitRemote>upstream</codeflash.gitRemote>
<codeflash.ignorePaths>gen/,build/</codeflash.ignorePaths>
</properties>
</project>
"""
class TestMavenCodeflashProperties:
def test_reads_custom_properties(self, tmp_path: Path) -> None:
(tmp_path / "pom.xml").write_text(MAVEN_POM_WITH_PROPERTIES, encoding="utf-8")
(tmp_path / "custom" / "src").mkdir(parents=True)
(tmp_path / "custom" / "test").mkdir(parents=True)
config = parse_java_project_config(tmp_path)
assert config is not None
assert config["module_root"] == str((tmp_path / "custom" / "src").resolve())
assert config["tests_root"] == str((tmp_path / "custom" / "test").resolve())
assert config["disable_telemetry"] is True
assert config["git_remote"] == "upstream"
assert len(config["ignore_paths"]) == 2
def test_properties_override_auto_detection(self, tmp_path: Path) -> None:
(tmp_path / "pom.xml").write_text(MAVEN_POM_WITH_PROPERTIES, encoding="utf-8")
# Create standard dirs AND custom dirs
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
(tmp_path / "custom" / "src").mkdir(parents=True)
(tmp_path / "custom" / "test").mkdir(parents=True)
config = parse_java_project_config(tmp_path)
assert config is not None
# Should use custom paths from properties, not auto-detected standard paths
assert config["module_root"] == str((tmp_path / "custom" / "src").resolve())
def test_no_properties_uses_defaults(self, tmp_path: Path) -> None:
(tmp_path / "pom.xml").write_text(
'<project xmlns="http://maven.apache.org/POM/4.0.0"><modelVersion>4.0.0</modelVersion></project>',
encoding="utf-8",
)
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
config = parse_java_project_config(tmp_path)
assert config is not None
assert config["disable_telemetry"] is False
assert config["git_remote"] == "origin"
# ---------------------------------------------------------------------------
# parse_java_project_config — Gradle properties
# ---------------------------------------------------------------------------
class TestGradleCodeflashProperties:
def test_reads_gradle_properties(self, tmp_path: Path) -> None:
(tmp_path / "build.gradle").write_text("", encoding="utf-8")
(tmp_path / "gradle.properties").write_text(
"codeflash.moduleRoot=lib/src\ncodeflash.testsRoot=lib/test\ncodeflash.disableTelemetry=true\n",
encoding="utf-8",
)
(tmp_path / "lib" / "src").mkdir(parents=True)
(tmp_path / "lib" / "test").mkdir(parents=True)
config = parse_java_project_config(tmp_path)
assert config is not None
assert config["module_root"] == str((tmp_path / "lib" / "src").resolve())
assert config["tests_root"] == str((tmp_path / "lib" / "test").resolve())
assert config["disable_telemetry"] is True
def test_ignores_non_codeflash_properties(self, tmp_path: Path) -> None:
(tmp_path / "build.gradle").write_text("", encoding="utf-8")
(tmp_path / "gradle.properties").write_text(
"org.gradle.jvmargs=-Xmx2g\ncodeflash.gitRemote=upstream\n",
encoding="utf-8",
)
config = parse_java_project_config(tmp_path)
assert config is not None
assert config["git_remote"] == "upstream"
def test_no_gradle_properties_uses_defaults(self, tmp_path: Path) -> None:
(tmp_path / "build.gradle").write_text("", encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
(tmp_path / "src" / "test" / "java").mkdir(parents=True)
config = parse_java_project_config(tmp_path)
assert config is not None
assert config["git_remote"] == "origin"
assert config["disable_telemetry"] is False
# ---------------------------------------------------------------------------
# Multi-module Maven projects
# ---------------------------------------------------------------------------
PARENT_POM = """\
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<modules>
<module>client</module>
<module>test</module>
<module>examples</module>
</modules>
</project>
"""
CLIENT_POM = """\
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
</parent>
<artifactId>client</artifactId>
<build>
<sourceDirectory>${project.basedir}/src</sourceDirectory>
</build>
</project>
"""
TEST_POM = """\
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
</parent>
<artifactId>test</artifactId>
<build>
<testSourceDirectory>${project.basedir}/src</testSourceDirectory>
</build>
</project>
"""
EXAMPLES_POM = """\
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
</parent>
<artifactId>examples</artifactId>
<build>
<sourceDirectory>${project.basedir}/src</sourceDirectory>
</build>
</project>
"""
class TestMultiModuleMaven:
@pytest.fixture
def multi_module_project(self, tmp_path: Path) -> Path:
"""Create a multi-module Maven project mimicking aerospike's layout."""
(tmp_path / "pom.xml").write_text(PARENT_POM, encoding="utf-8")
# Client module — main library with the most Java files
client = tmp_path / "client"
client.mkdir()
(client / "pom.xml").write_text(CLIENT_POM, encoding="utf-8")
client_src = client / "src" / "com" / "example" / "client"
client_src.mkdir(parents=True)
for i in range(10):
(client_src / f"Class{i}.java").write_text(f"class Class{i} {{}}", encoding="utf-8")
# Test module — test code
test = tmp_path / "test"
test.mkdir()
(test / "pom.xml").write_text(TEST_POM, encoding="utf-8")
test_src = test / "src" / "com" / "example" / "test"
test_src.mkdir(parents=True)
(test_src / "ClientTest.java").write_text("class ClientTest {}", encoding="utf-8")
# Examples module — should be skipped
examples = tmp_path / "examples"
examples.mkdir()
(examples / "pom.xml").write_text(EXAMPLES_POM, encoding="utf-8")
examples_src = examples / "src" / "com" / "example"
examples_src.mkdir(parents=True)
(examples_src / "Example.java").write_text("class Example {}", encoding="utf-8")
return tmp_path
def test_detects_client_as_source_root(self, multi_module_project: Path) -> None:
config = parse_java_project_config(multi_module_project)
assert config is not None
assert config["module_root"] == str(multi_module_project / "client" / "src")
def test_detects_test_module_as_test_root(self, multi_module_project: Path) -> None:
config = parse_java_project_config(multi_module_project)
assert config is not None
assert config["tests_root"] == str(multi_module_project / "test" / "src")
def test_skips_examples_module(self, multi_module_project: Path) -> None:
config = parse_java_project_config(multi_module_project)
assert config is not None
# The module_root should be client/src, not examples/src
assert config["module_root"] == str(multi_module_project / "client" / "src")
def test_picks_module_with_most_java_files(self, multi_module_project: Path) -> None:
"""Client has 10 .java files, examples has 1 — client should win."""
config = parse_java_project_config(multi_module_project)
assert config is not None
assert "client" in config["module_root"]
# ---------------------------------------------------------------------------
# Language detection from config_parser
# ---------------------------------------------------------------------------
class TestLanguageDetectionViaConfigParser:
def test_java_detected_from_pom_xml(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
(tmp_path / "src" / "test" / "java").mkdir(parents=True)
monkeypatch.chdir(tmp_path)
from codeflash.code_utils.config_parser import _try_parse_java_build_config
result = _try_parse_java_build_config()
assert result is not None
config, project_root = result
assert config["language"] == "java"
assert project_root == tmp_path
def test_java_detected_from_build_gradle(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
(tmp_path / "build.gradle").write_text("", encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
monkeypatch.chdir(tmp_path)
from codeflash.code_utils.config_parser import _try_parse_java_build_config
result = _try_parse_java_build_config()
assert result is not None
config, _ = result
assert config["language"] == "java"
def test_no_java_detected_for_python_project(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
(tmp_path / "pyproject.toml").write_text("[tool.codeflash]\nmodule-root='src'\ntests-root='tests'\n", encoding="utf-8")
monkeypatch.chdir(tmp_path)
from codeflash.code_utils.config_parser import _try_parse_java_build_config
result = _try_parse_java_build_config()
assert result is None
# ---------------------------------------------------------------------------
# Language detection from tracer
# ---------------------------------------------------------------------------
class TestTracerLanguageDetection:
def test_detects_java_from_build_files(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
(tmp_path / "pom.xml").write_text("<project/>", encoding="utf-8")
monkeypatch.chdir(tmp_path)
from codeflash.languages.base import Language
from codeflash.tracer import _detect_non_python_language
result = _detect_non_python_language(None)
assert result == Language.JAVA
def test_no_detection_without_build_files(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.chdir(tmp_path)
from codeflash.tracer import _detect_non_python_language
result = _detect_non_python_language(None)
assert result is None
def test_detects_java_from_file_extension(self, tmp_path: Path) -> None:
java_file = tmp_path / "App.java"
java_file.write_text("class App {}", encoding="utf-8")
from argparse import Namespace
from codeflash.languages.base import Language
from codeflash.tracer import _detect_non_python_language
args = Namespace(file=str(java_file))
result = _detect_non_python_language(args)
assert result == Language.JAVA

View file

@ -1,148 +0,0 @@
"""Tests for config_writer module — Java pom.xml formatting preservation."""
from pathlib import Path
class TestWriteMavenProperties:
"""Tests for _write_maven_properties — text-based pom.xml editing."""
def test_preserves_comments(self, tmp_path: Path) -> None:
pom = tmp_path / "pom.xml"
pom.write_text(
'<?xml version="1.0" encoding="UTF-8"?>\n'
"<project>\n"
" <!-- Important comment -->\n"
" <properties>\n"
" <maven.compiler.source>17</maven.compiler.source>\n"
" </properties>\n"
"</project>\n",
encoding="utf-8",
)
from codeflash.setup.config_writer import _write_maven_properties
ok, _ = _write_maven_properties(pom, {"module-root": "src/main/java"})
result = pom.read_text(encoding="utf-8")
assert ok
assert "<!-- Important comment -->" in result
assert "<codeflash.moduleRoot>src/main/java</codeflash.moduleRoot>" in result
def test_preserves_namespace(self, tmp_path: Path) -> None:
pom = tmp_path / "pom.xml"
pom.write_text(
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<project xmlns="http://maven.apache.org/POM/4.0.0">\n'
" <properties>\n"
" <maven.compiler.source>17</maven.compiler.source>\n"
" </properties>\n"
"</project>\n",
encoding="utf-8",
)
from codeflash.setup.config_writer import _write_maven_properties
ok, _ = _write_maven_properties(pom, {"module-root": "src/main/java"})
result = pom.read_text(encoding="utf-8")
assert ok
assert 'xmlns="http://maven.apache.org/POM/4.0.0"' in result
# Must NOT have ns0: prefix (ElementTree bug)
assert "ns0:" not in result
def test_updates_existing_codeflash_properties(self, tmp_path: Path) -> None:
pom = tmp_path / "pom.xml"
pom.write_text(
"<project>\n"
" <properties>\n"
" <codeflash.moduleRoot>old/path</codeflash.moduleRoot>\n"
" </properties>\n"
"</project>\n",
encoding="utf-8",
)
from codeflash.setup.config_writer import _write_maven_properties
ok, _ = _write_maven_properties(pom, {"module-root": "new/path"})
result = pom.read_text(encoding="utf-8")
assert ok
assert "old/path" not in result
assert "<codeflash.moduleRoot>new/path</codeflash.moduleRoot>" in result
def test_creates_properties_section(self, tmp_path: Path) -> None:
pom = tmp_path / "pom.xml"
pom.write_text(
"<project>\n" " <modelVersion>4.0.0</modelVersion>\n" "</project>\n",
encoding="utf-8",
)
from codeflash.setup.config_writer import _write_maven_properties
ok, _ = _write_maven_properties(pom, {"module-root": "src/main/java"})
result = pom.read_text(encoding="utf-8")
assert ok
assert "<properties>" in result
assert "<codeflash.moduleRoot>src/main/java</codeflash.moduleRoot>" in result
def test_converts_kebab_to_camelcase(self, tmp_path: Path) -> None:
pom = tmp_path / "pom.xml"
pom.write_text(
"<project>\n <properties>\n </properties>\n</project>\n",
encoding="utf-8",
)
from codeflash.setup.config_writer import _write_maven_properties
ok, _ = _write_maven_properties(pom, {"ignore-paths": ["target", "build"]})
result = pom.read_text(encoding="utf-8")
assert ok
assert "<codeflash.ignorePaths>target,build</codeflash.ignorePaths>" in result
class TestRemoveJavaBuildConfig:
"""Tests for _remove_java_build_config — preserves formatting during removal."""
def test_removes_codeflash_from_pom_preserving_others(self, tmp_path: Path) -> None:
pom = tmp_path / "pom.xml"
pom.write_text(
"<project>\n"
" <!-- Keep me -->\n"
" <properties>\n"
" <maven.compiler.source>17</maven.compiler.source>\n"
" <codeflash.moduleRoot>src/main/java</codeflash.moduleRoot>\n"
" </properties>\n"
"</project>\n",
encoding="utf-8",
)
from codeflash.setup.config_writer import _remove_java_build_config
ok, _ = _remove_java_build_config(tmp_path)
result = pom.read_text(encoding="utf-8")
assert ok
assert "<!-- Keep me -->" in result
assert "<maven.compiler.source>17</maven.compiler.source>" in result
assert "codeflash.moduleRoot" not in result
def test_removes_codeflash_from_gradle_properties(self, tmp_path: Path) -> None:
gradle = tmp_path / "gradle.properties"
gradle.write_text(
"org.gradle.jvmargs=-Xmx2g\n"
"# Codeflash configuration \u2014 https://docs.codeflash.ai\n"
"codeflash.moduleRoot=src/main/java\n"
"codeflash.testsRoot=src/test/java\n",
encoding="utf-8",
)
from codeflash.setup.config_writer import _remove_java_build_config
ok, _ = _remove_java_build_config(tmp_path)
result = gradle.read_text(encoding="utf-8")
assert ok
assert "org.gradle.jvmargs=-Xmx2g" in result
assert "codeflash." not in result

View file

@ -558,22 +558,6 @@ class TestHasExistingConfig:
assert has_config is False
assert config_type is None
def test_java_pom_xml_is_zero_config(self, tmp_path):
"""Java projects with pom.xml are zero-config — build file presence means configured."""
(tmp_path / "pom.xml").write_text("<project><modelVersion>4.0.0</modelVersion></project>")
has_config, config_type = has_existing_config(tmp_path)
assert has_config is True
assert config_type == "pom.xml"
def test_java_build_gradle_is_zero_config(self, tmp_path):
"""Java projects with build.gradle are zero-config — build file presence means configured."""
(tmp_path / "build.gradle").write_text('plugins { id "java" }')
has_config, config_type = has_existing_config(tmp_path)
assert has_config is True
assert config_type == "build.gradle"
def test_returns_false_for_empty_directory(self, tmp_path):
"""Should return False for empty directory."""
has_config, config_type = has_existing_config(tmp_path)