Skip to content

⚡️ Speed up method TestConfig._detect_java_test_framework by 51% in PR #1199 (omni-java)#1325

Closed
codeflash-ai[bot] wants to merge 1 commit intoomni-javafrom
codeflash/optimize-pr1199-2026-02-03T21.26.12
Closed

⚡️ Speed up method TestConfig._detect_java_test_framework by 51% in PR #1199 (omni-java)#1325
codeflash-ai[bot] wants to merge 1 commit intoomni-javafrom
codeflash/optimize-pr1199-2026-02-03T21.26.12

Conversation

@codeflash-ai
Copy link
Contributor

@codeflash-ai codeflash-ai bot commented Feb 3, 2026

⚡️ This pull request contains optimizations for PR #1199

If you approve this dependent PR, these changes will be merged into the original PR branch omni-java.

This PR will be automatically closed if the original PR is merged.


📄 51% (0.51x) speedup for TestConfig._detect_java_test_framework in codeflash/verification/verification_utils.py

⏱️ Runtime : 44.4 milliseconds 29.4 milliseconds (best of 35 runs)

📝 Explanation and details

The optimized code achieves a 50% speedup (44.4ms → 29.4ms) by eliminating redundant file I/O operations through strategic consolidation of parsing logic.

Key Optimizations

1. Single-Pass Build File Parsing
The original code parsed Maven's pom.xml file up to 4 separate times in different functions (_detect_test_framework, _detect_test_dependencies, _get_compiler_settings, _get_surefire_config). The optimization introduces _parse_maven_pom() which reads and parses the file once, extracting all needed information in a single pass:

  • Test framework dependencies (JUnit 5/4, TestNG)
  • Additional dependencies (Mockito, AssertJ)
  • Compiler settings (source/target versions)
  • Surefire configuration

This eliminates 3 redundant disk reads and XML parsing operations, which is the primary driver of the 50% speedup.

2. Fast String-Based Dependency Detection
Instead of always parsing XML to detect dependencies, the optimization first uses simple string containment checks on the file content ("org.junit.jupiter" in content). This is significantly faster than XML parsing and sufficient for dependency detection. XML parsing is only performed when compiler settings are needed.

3. Early Termination in Source Scanning
The _detect_test_framework_from_sources() function now accepts the current detection state and terminates early once all three frameworks are detected, avoiding unnecessary file scanning.

4. Gradle Support
Similarly introduces _parse_gradle_build() for Gradle projects to avoid redundant file reads.

Test Results Analysis

The optimization shows consistent improvements across all test cases:

  • Basic detection tests: 20-52% faster (e.g., test_default_junit5_when_no_framework_detected: 51.7% faster)
  • Multi-module projects: 32-50% faster, benefiting from reduced parent directory checks
  • Large-scale tests:
    • 500 mixed framework files: 1021% faster (11.8ms → 1.06ms) - demonstrates dramatic impact when many files would trigger multiple parses
    • 200 test files: 5% faster - early termination prevents scanning all files once framework is detected

The optimization is particularly effective when:

  • Build files exist (Maven/Gradle) - avoids redundant parsing
  • Multiple frameworks or dependencies are present - single parse extracts all information
  • Large test suites exist - early termination reduces file scanning

All changes preserve correctness while delivering substantial runtime improvements through intelligent I/O reduction.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 27 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Click to see Generated Regression Tests
import os
import stat
from pathlib import Path

import pytest  # used for our unit tests
from codeflash.verification.verification_utils import TestConfig

# NOTE:
# These tests create temporary file system layouts under pytest's tmp_path fixture,
# exercising the actual detection logic in TestConfig._detect_java_test_framework.
# We avoid mocking the function under test. We do monkeypatch the external
# detect_java_project in one test to simulate an exception path (allowed as an
# external dependency override). All domain classes used are imported from the real modules.

def _make_java_test_file(path: Path, name: str, content: str = "") -> Path:
    """Helper to create a test file under path/name with the provided content."""
    path.mkdir(parents=True, exist_ok=True)
    file_path = path / name
    file_path.write_text(content, encoding="utf-8")
    return file_path

def test_detects_junit5_from_test_file(tmp_path: Path):
    # Basic: When a test file imports org.junit.jupiter, expect junit5.
    project_root = tmp_path / "project_junit5"
    test_root = project_root / "src" / "test" / "java"
    # Create a single test file containing a JUnit 5 import string.
    _make_java_test_file(test_root, "ExampleTest.java", "import org.junit.jupiter.api.Test;\npublic class ExampleTest {}")
    # Instantiate TestConfig with the project root path and arbitrary other Path args.
    cfg = TestConfig(tests_root=tmp_path / "tests", project_root_path=project_root, tests_project_rootdir=tmp_path)
    # Call the instance method and assert detection is 'junit5'.
    codeflash_output = cfg._detect_java_test_framework() # 761μs -> 631μs (20.7% faster)

def test_detects_junit4_from_test_file(tmp_path: Path):
    # Basic: When a test file contains org.junit.Test or org.junit.Assert, expect junit4.
    project_root = tmp_path / "project_junit4"
    test_root = project_root / "src" / "test" / "java"
    # Create a JUnit 4 style test import.
    _make_java_test_file(test_root, "OldStyleTest.java", "import org.junit.Test;\nimport org.junit.Assert;\nclass OldStyleTest {}")
    cfg = TestConfig(tests_root=tmp_path / "tests", project_root_path=project_root, tests_project_rootdir=tmp_path)
    codeflash_output = cfg._detect_java_test_framework() # 749μs -> 617μs (21.3% faster)

def test_detects_testng_from_test_file(tmp_path: Path):
    # Basic: When a test file contains org.testng, expect testng framework detection.
    project_root = tmp_path / "project_testng"
    test_root = project_root / "src" / "test" / "java"
    _make_java_test_file(test_root, "TestNGTest.java", "import org.testng.annotations.Test;\nclass TestNGTest {}")
    cfg = TestConfig(tests_root=tmp_path / "tests", project_root_path=project_root, tests_project_rootdir=tmp_path)
    codeflash_output = cfg._detect_java_test_framework() # 757μs -> 627μs (20.8% faster)

def test_prefers_junit5_when_multiple_frameworks_present(tmp_path: Path):
    # Edge: If both JUnit 4 and JUnit 5 imports are present, JUnit 5 should be preferred.
    project_root = tmp_path / "project_mixed"
    test_root = project_root / "src" / "test" / "java"
    # Create a file containing both markers
    _make_java_test_file(test_root, "MixedTest.java", "import org.junit.jupiter.api.Test;\nimport org.junit.Test;\nclass MixedTest {}")
    cfg = TestConfig(tests_root=tmp_path / "tests", project_root_path=project_root, tests_project_rootdir=tmp_path)
    # Should return junit5 because detection prefers JUnit 5 when present.
    codeflash_output = cfg._detect_java_test_framework() # 747μs -> 616μs (21.2% faster)

def test_handles_unreadable_test_file_and_defaults_to_junit5(tmp_path: Path):
    # Edge: If a test file exists but reading it raises (e.g., permission error),
    # the code should ignore it and ultimately default to junit5 if nothing else detected.
    project_root = tmp_path / "project_badfile"
    test_root = project_root / "src" / "test" / "java"
    # Create a file with content but then remove read permissions to provoke an exception on read_text.
    bad_file = _make_java_test_file(test_root, "BadTest.java", "import some.unknown;\n")
    # Remove read permission for everyone (simulate unreadable file).
    bad_file.chmod(0)
    try:
        cfg = TestConfig(tests_root=tmp_path / "tests", project_root_path=project_root, tests_project_rootdir=tmp_path)
        # Since the unreadable file cannot provide framework info and no other data exists,
        # detection should gracefully fall back to the default 'junit5'.
        codeflash_output = cfg._detect_java_test_framework()
    finally:
        # Restore permissions so the tmp_path cleanup can remove files.
        bad_file.chmod(stat.S_IWUSR | stat.S_IRUSR)

def test_large_scale_many_test_files_prefers_detected_framework(tmp_path: Path):
    # Large-scale: Create many test files (under 200) to ensure detection scales and remains correct.
    project_root = tmp_path / "project_large"
    test_root = project_root / "src" / "test" / "java"
    test_root.mkdir(parents=True, exist_ok=True)
    # Create multiple files with no framework markers, and one file with JUnit 4 marker.
    # Keep total under 1000 as required; choose 200 for a reasonable large test.
    count = 200
    junit4_index = 137  # one file will contain the junit4 indicator
    for i in range(count):
        if i == junit4_index:
            content = f"// file {i}\nimport org.junit.Test;\nclass T{i} {{}}\n"
        else:
            content = f"// file {i}\nclass T{i} {{}}\n"
        (test_root / f"T{i}Test.java").write_text(content, encoding="utf-8")
    cfg = TestConfig(tests_root=tmp_path / "tests", project_root_path=project_root, tests_project_rootdir=tmp_path)
    # The presence of a single junit4 marker should lead to 'junit4' detection (no junit5 markers present).
    codeflash_output = cfg._detect_java_test_framework() # 5.95ms -> 5.66ms (5.06% faster)

def test_fallback_to_default_on_detect_java_project_exception(monkeypatch, tmp_path: Path):
    # Edge: If detect_java_project raises an exception when called, TestConfig should
    # catch it and return the default 'junit5'.
    project_root = tmp_path / "project_exception"
    project_root.mkdir(parents=True, exist_ok=True)

    # Monkeypatch the function in the module that TestConfig will import at runtime.
    import codeflash.languages.java.config as java_config_module

    def _raise_exc(path):
        raise RuntimeError("simulated failure")

    # Replace detect_java_project with a function that raises.
    monkeypatch.setattr(java_config_module, "detect_java_project", _raise_exc)

    cfg = TestConfig(tests_root=tmp_path / "tests", project_root_path=project_root, tests_project_rootdir=tmp_path)
    # Because detect_java_project raises, _detect_java_test_framework should catch and return 'junit5' fallback.
    codeflash_output = cfg._detect_java_test_framework() # 4.82μs -> 4.73μs (1.90% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
from pathlib import Path
from unittest.mock import MagicMock, Mock, patch

import pytest
from codeflash.languages.java.build_tools import BuildTool
from codeflash.verification.verification_utils import TestConfig

class TestDetectJavaTestFramework:
    """Test suite for TestConfig._detect_java_test_framework method."""

    # ==================== BASIC TEST CASES ====================
    # These tests verify the fundamental functionality under normal conditions

    def test_detect_junit5_from_maven_project(self, tmp_path):
        """Test detection of JUnit5 framework from Maven project with pom.xml."""
        # Create a minimal Maven project structure
        project_root = tmp_path / "maven_project"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create a test file with JUnit5 import
        test_file = test_root / "SampleTest.java"
        test_file.write_text(
            "import org.junit.jupiter.api.Test;\n"
            "public class SampleTest {\n"
            "    @Test\n"
            "    public void testExample() {}\n"
            "}\n"
        )

        # Create a pom.xml file indicating JUnit5
        pom_content = """<?xml version="1.0" encoding="UTF-8"?>
<project>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.9.0</version>
        </dependency>
    </dependencies>
</project>"""
        pom_file = project_root / "pom.xml"
        pom_file.write_text(pom_content)

        # Create TestConfig instance
        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        # Test that JUnit5 is detected
        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 651μs -> 444μs (46.4% faster)

    def test_detect_junit4_from_project(self, tmp_path):
        """Test detection of JUnit4 framework from project files."""
        # Create project structure
        project_root = tmp_path / "junit4_project"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create a test file with JUnit4 import
        test_file = test_root / "LegacyTest.java"
        test_file.write_text(
            "import org.junit.Test;\n"
            "import org.junit.Assert;\n"
            "public class LegacyTest {\n"
            "    @Test\n"
            "    public void testOldStyle() {}\n"
            "}\n"
        )

        # Create a minimal pom.xml
        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 548μs -> 399μs (37.2% faster)

    def test_detect_testng_from_project(self, tmp_path):
        """Test detection of TestNG framework from project files."""
        # Create project structure
        project_root = tmp_path / "testng_project"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create a test file with TestNG import
        test_file = test_root / "TestNGTest.java"
        test_file.write_text(
            "import org.testng.annotations.Test;\n"
            "public class TestNGTest {\n"
            "    @Test\n"
            "    public void testWithTestNG() {}\n"
            "}\n"
        )

        # Create a minimal pom.xml
        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 539μs -> 398μs (35.5% faster)

    def test_default_junit5_when_no_framework_detected(self, tmp_path):
        """Test that junit5 is returned as default when no framework is detected."""
        # Create a minimal project with no test files
        project_root = tmp_path / "empty_project"
        project_root.mkdir()

        # Create a pom.xml but with no test dependencies
        pom_file = project_root / "pom.xml"
        pom_file.write_text(
            "<?xml version=\"1.0\"?>\n"
            "<project>\n"
            "    <dependencies></dependencies>\n"
            "</project>"
        )

        config = TestConfig(
            tests_root=project_root / "src" / "test" / "java",
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 513μs -> 338μs (51.7% faster)

    def test_exception_handling_returns_default(self, tmp_path):
        """Test that exceptions during detection are caught and default is returned."""
        # Create a project with malformed pom.xml
        project_root = tmp_path / "broken_project"
        project_root.mkdir()

        # Create an invalid XML file
        pom_file = project_root / "pom.xml"
        pom_file.write_text("This is not valid XML <unclosed>")

        config = TestConfig(
            tests_root=project_root / "src" / "test" / "java",
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        # Should not raise, should return default
        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 1.24ms -> 1.09ms (14.1% faster)

    # ==================== EDGE TEST CASES ====================
    # These tests evaluate behavior under extreme or unusual conditions

    def test_junit5_preferred_when_multiple_frameworks_present(self, tmp_path):
        """Test that JUnit5 is preferred when multiple frameworks are present."""
        # Create project with both JUnit4 and JUnit5
        project_root = tmp_path / "multi_framework"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create test file with both JUnit4 and JUnit5 imports
        test_file = test_root / "MultiTest.java"
        test_file.write_text(
            "import org.junit.Test;\n"
            "import org.junit.jupiter.api.Test;\n"
            "public class MultiTest {}\n"
        )

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 546μs -> 398μs (36.9% faster)

    def test_junit4_preferred_over_testng_when_both_present(self, tmp_path):
        """Test that JUnit4 is preferred over TestNG when JUnit5 is not available."""
        # Create project with JUnit4 and TestNG but no JUnit5
        project_root = tmp_path / "junit4_testng"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create test file with both JUnit4 and TestNG
        test_file = test_root / "HybridTest.java"
        test_file.write_text(
            "import org.junit.Test;\n"
            "import org.testng.annotations.Test;\n"
            "public class HybridTest {}\n"
        )

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 542μs -> 397μs (36.3% faster)

    def test_nonexistent_test_root_directory(self, tmp_path):
        """Test behavior when test root directory doesn't exist."""
        # Create project with no test directory
        project_root = tmp_path / "no_test_dir"
        project_root.mkdir()

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        # Point to non-existent test root
        nonexistent_test_root = project_root / "nonexistent" / "test" / "java"

        config = TestConfig(
            tests_root=nonexistent_test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 491μs -> 326μs (50.6% faster)

    def test_corrupted_java_file(self, tmp_path):
        """Test handling of Java files that cannot be read."""
        # Create project with unreadable test file
        project_root = tmp_path / "corrupted"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create a file with encoding issues
        test_file = test_root / "BadTest.java"
        # Write some valid content first
        test_file.write_text("import org.junit.jupiter.api.Test;\n")

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        # Should handle gracefully and still detect
        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 536μs -> 396μs (35.4% faster)

    def test_multi_module_maven_project_parent_detection(self, tmp_path):
        """Test detection in multi-module Maven projects by checking parent directories."""
        # Create parent Maven project
        parent_root = tmp_path / "parent_project"
        parent_root.mkdir()

        # Create parent pom.xml with JUnit5
        parent_pom = parent_root / "pom.xml"
        parent_pom.write_text(
            "<?xml version=\"1.0\"?>\n"
            "<project>\n"
            "    <dependencies>\n"
            "        <dependency>\n"
            "            <groupId>org.junit.jupiter</groupId>\n"
            "            <artifactId>junit-jupiter</artifactId>\n"
            "        </dependency>\n"
            "    </dependencies>\n"
            "</project>"
        )

        # Create a submodule without its own pom.xml
        submodule = parent_root / "module1"
        submodule.mkdir()

        config = TestConfig(
            tests_root=submodule / "src" / "test" / "java",
            project_root_path=submodule,
            tests_project_rootdir=submodule,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 784μs -> 522μs (50.4% faster)

    def test_empty_test_file(self, tmp_path):
        """Test handling of empty test files."""
        project_root = tmp_path / "empty_tests"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create empty test file
        test_file = test_root / "EmptyTest.java"
        test_file.write_text("")

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 587μs -> 445μs (32.0% faster)

    def test_commented_import_statements_ignored(self, tmp_path):
        """Test that commented-out import statements don't trigger detection."""
        project_root = tmp_path / "commented_imports"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create test file with commented imports
        test_file = test_root / "CommentedTest.java"
        test_file.write_text(
            "// import org.junit.jupiter.api.Test;\n"
            "// import org.junit.Test;\n"
            "public class CommentedTest {}\n"
        )

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 534μs -> 398μs (34.3% faster)

    def test_junit4_assert_detection(self, tmp_path):
        """Test that JUnit4 is detected from Assert import alone."""
        project_root = tmp_path / "junit4_assert"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create test file with only Assert import
        test_file = test_root / "AssertTest.java"
        test_file.write_text(
            "import org.junit.Assert;\n"
            "public class AssertTest {\n"
            "    void test() { Assert.assertTrue(true); }\n"
            "}\n"
        )

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 531μs -> 395μs (34.3% faster)

    def test_partial_framework_names_not_matched(self, tmp_path):
        """Test that partial matches of framework names are handled correctly."""
        project_root = tmp_path / "partial_match"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create test file with text containing 'junit' but not as import
        test_file = test_root / "PartialTest.java"
        test_file.write_text(
            "// This is a junit style test\n"
            "public class PartialTest {\n"
            "    // junit framework would go here\n"
            "}\n"
        )

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 582μs -> 445μs (30.8% faster)

    # ==================== LARGE SCALE TEST CASES ====================
    # These tests assess performance and scalability with large data samples

    def test_many_test_files_with_junit5(self, tmp_path):
        """Test detection performance with many test files (200 files)."""
        project_root = tmp_path / "large_project"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create 200 test files
        for i in range(200):
            test_file = test_root / f"Test{i}.java"
            test_file.write_text(
                "import org.junit.jupiter.api.Test;\n"
                f"public class Test{i} {{\n"
                "    @Test\n"
                "    public void testMethod() {}\n"
                "}\n"
            )

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        # Should detect quickly even with many files
        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 5.07ms -> 4.81ms (5.28% faster)

    def test_deeply_nested_test_directory(self, tmp_path):
        """Test detection with deeply nested directory structure."""
        project_root = tmp_path / "deep_project"
        project_root.mkdir()

        # Create deeply nested test directory
        test_root = project_root
        for i in range(15):
            test_root = test_root / f"level{i}"
        test_root.mkdir(parents=True)

        test_file = test_root / "DeepTest.java"
        test_file.write_text(
            "import org.junit.jupiter.api.Test;\n"
            "public class DeepTest {}\n"
        )

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 497μs -> 332μs (49.8% faster)

    def test_very_large_test_file(self, tmp_path):
        """Test detection with a very large test file (1000 methods)."""
        project_root = tmp_path / "large_file"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create a large test file with 1000 test methods
        test_file = test_root / "LargeTest.java"
        content = "import org.junit.jupiter.api.Test;\npublic class LargeTest {\n"
        for i in range(1000):
            content += f"    @Test\n    public void test{i}() {{}}\n"
        content += "}\n"
        test_file.write_text(content)

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 603μs -> 450μs (33.8% faster)

    def test_mixed_frameworks_in_multiple_files(self, tmp_path):
        """Test detection with multiple files using different frameworks (500 total)."""
        project_root = tmp_path / "mixed_large"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create 200 JUnit5 files
        for i in range(200):
            test_file = test_root / f"JUnit5Test{i}.java"
            test_file.write_text(
                "import org.junit.jupiter.api.Test;\n"
                f"public class JUnit5Test{i} {{}}\n"
            )

        # Create 150 JUnit4 files
        for i in range(150):
            test_file = test_root / f"JUnit4Test{i}.java"
            test_file.write_text(
                "import org.junit.Test;\n"
                f"public class JUnit4Test{i} {{}}\n"
            )

        # Create 150 TestNG files
        for i in range(150):
            test_file = test_root / f"TestNGTest{i}.java"
            test_file.write_text(
                "import org.testng.annotations.Test;\n"
                f"public class TestNGTest{i} {{}}\n"
            )

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 11.8ms -> 1.06ms (1021% faster)

    def test_many_parent_directory_checks(self, tmp_path):
        """Test multi-module detection with many nested modules (10 levels)."""
        # Create a chain of 10 nested directories
        current = tmp_path / "root"
        current.mkdir()

        # Create root pom.xml with JUnit4
        root_pom = current / "pom.xml"
        root_pom.write_text("<?xml version=\"1.0\"?><project></project>")

        # Create 10 nested modules
        for i in range(10):
            current = current / f"module{i}"
            current.mkdir()

        # No pom.xml in deepest module, should check parents

        config = TestConfig(
            tests_root=current / "src" / "test" / "java",
            project_root_path=current,
            tests_project_rootdir=current,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 780μs -> 590μs (32.1% faster)

    def test_many_non_test_files_in_directory(self, tmp_path):
        """Test detection with many non-test files in the same directory."""
        project_root = tmp_path / "mixed_files"
        project_root.mkdir()
        test_root = project_root / "src" / "test" / "java"
        test_root.mkdir(parents=True)

        # Create 300 non-test Java files
        for i in range(300):
            java_file = test_root / f"Helper{i}.java"
            java_file.write_text(f"public class Helper{i} {{}}\n")

        # Create 1 test file with JUnit5
        test_file = test_root / "RealTest.java"
        test_file.write_text(
            "import org.junit.jupiter.api.Test;\n"
            "public class RealTest {}\n"
        )

        pom_file = project_root / "pom.xml"
        pom_file.write_text("<?xml version=\"1.0\"?><project></project>")

        config = TestConfig(
            tests_root=test_root,
            project_root_path=project_root,
            tests_project_rootdir=project_root,
        )

        codeflash_output = config._detect_java_test_framework(); result = codeflash_output # 7.19ms -> 6.94ms (3.60% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-pr1199-2026-02-03T21.26.12 and push.

Codeflash Static Badge

The optimized code achieves a **50% speedup** (44.4ms → 29.4ms) by eliminating redundant file I/O operations through strategic consolidation of parsing logic.

## Key Optimizations

**1. Single-Pass Build File Parsing**
The original code parsed Maven's `pom.xml` file up to **4 separate times** in different functions (`_detect_test_framework`, `_detect_test_dependencies`, `_get_compiler_settings`, `_get_surefire_config`). The optimization introduces `_parse_maven_pom()` which reads and parses the file **once**, extracting all needed information in a single pass:
- Test framework dependencies (JUnit 5/4, TestNG)
- Additional dependencies (Mockito, AssertJ)
- Compiler settings (source/target versions)
- Surefire configuration

This eliminates 3 redundant disk reads and XML parsing operations, which is the primary driver of the 50% speedup.

**2. Fast String-Based Dependency Detection**
Instead of always parsing XML to detect dependencies, the optimization first uses simple string containment checks on the file content (`"org.junit.jupiter" in content`). This is significantly faster than XML parsing and sufficient for dependency detection. XML parsing is only performed when compiler settings are needed.

**3. Early Termination in Source Scanning**
The `_detect_test_framework_from_sources()` function now accepts the current detection state and terminates early once all three frameworks are detected, avoiding unnecessary file scanning.

**4. Gradle Support**
Similarly introduces `_parse_gradle_build()` for Gradle projects to avoid redundant file reads.

## Test Results Analysis

The optimization shows consistent improvements across all test cases:
- **Basic detection tests**: 20-52% faster (e.g., `test_default_junit5_when_no_framework_detected`: 51.7% faster)
- **Multi-module projects**: 32-50% faster, benefiting from reduced parent directory checks
- **Large-scale tests**: 
  - 500 mixed framework files: **1021% faster** (11.8ms → 1.06ms) - demonstrates dramatic impact when many files would trigger multiple parses
  - 200 test files: 5% faster - early termination prevents scanning all files once framework is detected

The optimization is particularly effective when:
- Build files exist (Maven/Gradle) - avoids redundant parsing
- Multiple frameworks or dependencies are present - single parse extracts all information
- Large test suites exist - early termination reduces file scanning

All changes preserve correctness while delivering substantial runtime improvements through intelligent I/O reduction.
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Feb 3, 2026
@codeflash-ai codeflash-ai bot mentioned this pull request Feb 3, 2026
@KRRT7
Copy link
Collaborator

KRRT7 commented Feb 19, 2026

Closing stale bot PR.

@KRRT7 KRRT7 closed this Feb 19, 2026
@KRRT7 KRRT7 deleted the codeflash/optimize-pr1199-2026-02-03T21.26.12 branch February 19, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant