Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions codeflash/code_utils/config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,15 @@ def parse_config_file(

# Pick the closest toml config (pyproject.toml or codeflash.toml).
# Java projects use codeflash.toml; Python projects use pyproject.toml.
# When both exist at the same depth, prefer codeflash.toml (dedicated config).
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))
pyproject_depth = len(pyproject_toml_path.parent.parts)
codeflash_depth = len(codeflash_toml_path.parent.parts)
if codeflash_depth >= pyproject_depth:
closest_toml_path = codeflash_toml_path
else:
closest_toml_path = pyproject_toml_path
else:
closest_toml_path = pyproject_toml_path or codeflash_toml_path

Expand Down Expand Up @@ -134,8 +140,13 @@ def parse_config_file(
)
return config, path

# Fall back to pyproject.toml
config_file_path = find_pyproject_toml(config_file_path)
# Fall back to the closest toml config file (pyproject.toml or codeflash.toml).
# Use closest_toml_path when available to respect codeflash.toml over pyproject.toml
# when codeflash.toml is closer to CWD (e.g., Java projects in a Python repo).
if config_file_path is None and closest_toml_path is not None:
config_file_path = closest_toml_path
else:
config_file_path = find_pyproject_toml(config_file_path)
try:
with config_file_path.open("rb") as f:
data = tomlkit.parse(f.read())
Expand Down
115 changes: 115 additions & 0 deletions tests/code_utils/test_config_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from __future__ import annotations

from pathlib import Path

import pytest

from codeflash.code_utils.config_parser import ALL_CONFIG_FILES, PYPROJECT_TOML_CACHE, parse_config_file


@pytest.fixture(autouse=True)
def clear_caches() -> None:
PYPROJECT_TOML_CACHE.clear()
ALL_CONFIG_FILES.clear()


PYPROJECT_TOML_CONTENT = """\
[tool.codeflash]
module-root = "src/python"
tests-root = "tests"
"""

CODEFLASH_TOML_CONTENT = """\
[tool.codeflash]
module-root = "src/main/java"
tests-root = "src/test/java"
"""


class TestParseConfigFileTomlPriority:
def test_codeflash_toml_preferred_over_pyproject_when_same_dir(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
(tmp_path / "pyproject.toml").write_text(PYPROJECT_TOML_CONTENT, encoding="utf-8")
(tmp_path / "codeflash.toml").write_text(CODEFLASH_TOML_CONTENT, encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
(tmp_path / "src" / "test" / "java").mkdir(parents=True)
(tmp_path / "src" / "python").mkdir(parents=True)
(tmp_path / "tests").mkdir(parents=True)
monkeypatch.chdir(tmp_path)

config, config_path = parse_config_file()

assert config_path == tmp_path / "codeflash.toml"
assert config["module_root"] == str((tmp_path / "src" / "main" / "java").resolve())
assert config["tests_root"] == str((tmp_path / "src" / "test" / "java").resolve())

def test_only_pyproject_toml_still_works(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
(tmp_path / "pyproject.toml").write_text(PYPROJECT_TOML_CONTENT, encoding="utf-8")
(tmp_path / "src" / "python").mkdir(parents=True)
(tmp_path / "tests").mkdir(parents=True)
monkeypatch.chdir(tmp_path)

config, config_path = parse_config_file()

assert config_path == tmp_path / "pyproject.toml"
assert config["module_root"] == str((tmp_path / "src" / "python").resolve())

def test_only_codeflash_toml_still_works(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
(tmp_path / "codeflash.toml").write_text(CODEFLASH_TOML_CONTENT, encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
(tmp_path / "src" / "test" / "java").mkdir(parents=True)
monkeypatch.chdir(tmp_path)

config, config_path = parse_config_file()

assert config_path == tmp_path / "codeflash.toml"
assert config["module_root"] == str((tmp_path / "src" / "main" / "java").resolve())

def test_closer_codeflash_toml_preferred_over_parent_pyproject(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
# pyproject.toml in parent, codeflash.toml in child (closer to CWD)
(tmp_path / "pyproject.toml").write_text(PYPROJECT_TOML_CONTENT, encoding="utf-8")
(tmp_path / "src" / "python").mkdir(parents=True)
(tmp_path / "tests").mkdir(parents=True)

child = tmp_path / "subproject"
child.mkdir()
(child / "codeflash.toml").write_text(CODEFLASH_TOML_CONTENT, encoding="utf-8")
(child / "src" / "main" / "java").mkdir(parents=True)
(child / "src" / "test" / "java").mkdir(parents=True)
monkeypatch.chdir(child)

config, config_path = parse_config_file()

assert config_path == child / "codeflash.toml"
assert config["module_root"] == str((child / "src" / "main" / "java").resolve())

def test_closer_pyproject_preferred_over_parent_codeflash_toml(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
# codeflash.toml in parent, pyproject.toml in child (closer to CWD)
(tmp_path / "codeflash.toml").write_text(CODEFLASH_TOML_CONTENT, encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
(tmp_path / "src" / "test" / "java").mkdir(parents=True)

child = tmp_path / "subproject"
child.mkdir()
(child / "pyproject.toml").write_text(PYPROJECT_TOML_CONTENT, encoding="utf-8")
(child / "src" / "python").mkdir(parents=True)
(child / "tests").mkdir(parents=True)
monkeypatch.chdir(child)

config, config_path = parse_config_file()

assert config_path == child / "pyproject.toml"
assert config["module_root"] == str((child / "src" / "python").resolve())

def test_explicit_config_file_path_bypasses_discovery(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
(tmp_path / "pyproject.toml").write_text(PYPROJECT_TOML_CONTENT, encoding="utf-8")
(tmp_path / "codeflash.toml").write_text(CODEFLASH_TOML_CONTENT, encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
(tmp_path / "src" / "test" / "java").mkdir(parents=True)
(tmp_path / "src" / "python").mkdir(parents=True)
(tmp_path / "tests").mkdir(parents=True)
monkeypatch.chdir(tmp_path)

config, config_path = parse_config_file(config_file_path=tmp_path / "pyproject.toml")

assert config_path == tmp_path / "pyproject.toml"
assert config["module_root"] == str((tmp_path / "src" / "python").resolve())
Loading