Skip to content

codeflash-omni-java#1199

Draft
misrasaurabh1 wants to merge 188 commits intomainfrom
omni-java
Draft

codeflash-omni-java#1199
misrasaurabh1 wants to merge 188 commits intomainfrom
omni-java

Conversation

@misrasaurabh1
Copy link
Contributor

No description provided.

misrasaurabh1 and others added 18 commits January 30, 2026 00:37
- Add JaCoCo Maven plugin management to build_tools.py:
  - is_jacoco_configured() to check if plugin exists
  - add_jacoco_plugin_to_pom() to inject plugin configuration
  - get_jacoco_xml_path() for coverage report location

- Add JacocoCoverageUtils class to coverage_utils.py:
  - Parses JaCoCo XML reports into CoverageData objects
  - Handles method boundary detection and line/branch coverage

- Update test_runner.py to support coverage collection:
  - run_behavioral_tests() now handles enable_coverage=True
  - Automatically adds JaCoCo plugin and runs jacoco:report goal

- Update critic.py to enforce 60% coverage threshold for Java
  (previously Java was bypassed)

- Add comprehensive test suite with 19 tests for coverage functionality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix config parser to find codeflash.toml for Java projects
  (was only looking for pyproject.toml)

- Fix JaCoCo plugin addition to pom.xml:
  - Use string manipulation instead of ElementTree to avoid
    namespace prefix corruption (ns0:project issue)
  - ElementTree was changing <project> to <ns0:project> which
    broke Maven

- Add Java coverage parsing in parse_test_output.py:
  - Route Java coverage to JacocoCoverageUtils instead of
    Python's CoverageUtils

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix is_jacoco_configured() to search all build/plugins sections
  recursively, including those in profiles
- Fix add_jacoco_plugin_to_pom() to correctly find the main build
  section when profiles exist (not insert into profile builds)
- Add _find_closing_tag() helper to handle nested XML tags
- Remove explicit jacoco:report goal from Maven command since the
  plugin execution binds report to test phase automatically

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
misrasaurabh1 and others added 7 commits January 31, 2026 09:31
- Add _find_multi_module_root() to detect when tests are in a separate module
- Add _get_test_module_target_dir() to find the correct surefire reports dir
- Update run_behavioral_tests() and run_benchmarking_tests() to:
  - Run Maven from the parent project root for multi-module projects
  - Use -pl <module> -am to build only the test module and dependencies
  - Use -DfailIfNoTests=false to allow modules without tests to pass
  - Use -DskipTests=false to override pom.xml skipTests settings
- Look for surefire reports in the test module's target directory

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update TestConfig._detect_java_test_framework() to check parent pom.xml
  for multi-module projects where test deps are in a different module
- Add framework aliases in registry to map junit4/testng to Java support
- Correctly detect JUnit 4 projects and send correct framework to AI service

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use ^(?:public\s+)?class pattern to match class declaration at start of line
- Prevents matching words like "command" or text in comments that contain "class"
- Fixes issue where test files were named incorrectly (e.g., "and__perfinstrumented.java")

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…dule projects

- Fix duplicate test file issue: when multiple tests have the same class name,
  append unique index suffix (e.g., CryptoTest_2) to avoid file overwrites
- Fix multi-module JaCoCo support: add JaCoCo plugin to test module's pom.xml
  instead of source module, ensuring coverage data is collected where tests run
- Fix timeout: use minimum 60s (120s with coverage) for Java builds since Maven
  takes longer than the default 15s INDIVIDUAL_TESTCASE_TIMEOUT
- Fix Maven phase: use 'verify' instead of 'test' when coverage is enabled,
  with maven.test.failure.ignore=true to generate report even if tests fail
- Update JaCoCo report phase from 'test' to 'verify' to run after tests complete

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
feat: add JaCoCo test coverage support for Java optimization
- Update coverage_critic to skip coverage check when CoverageStatus.NOT_FOUND
  is returned (e.g., when JaCoCo report doesn't exist in multi-module projects
  where the test module has no source classes)
- Add JaCoCo configuration to include all class files for multi-module support

This fixes "threshold for test confidence was not met" errors that occurred
even when all tests passed, because JaCoCo couldn't generate coverage reports
for test modules without source classes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
fix: handle NOT_FOUND coverage status in Java multi-module projects
Comment on lines +231 to +235
project_root = Path.cwd()

# Check for existing codeflash config in pom.xml or a separate config file
codeflash_config_path = project_root / "codeflash.toml"
if codeflash_config_path.exists():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚡️Codeflash found 70% (0.70x) speedup for should_modify_java_config in codeflash/cli_cmds/init_java.py

⏱️ Runtime : 714 microseconds 421 microseconds (best of 60 runs)

📝 Explanation and details

The optimized code achieves a 69% speedup (714μs → 421μs) by replacing pathlib.Path operations with equivalent os module functions, which have significantly lower overhead.

Key optimizations:

  1. os.getcwd() instead of Path.cwd(): The line profiler shows Path.cwd() took 689,637ns (34.1% of total time) vs os.getcwd() taking only 68,036ns (7.4%). This is a ~10x improvement because Path.cwd() instantiates a Path object and performs additional normalization, while os.getcwd() returns a raw string from a system call.

  2. os.path.join() instead of Path division operator: Constructing the config path via project_root / "codeflash.toml" took 386,582ns (19.1%) vs os.path.join() taking 190,345ns (20.6%). Though the percentage appears similar, the absolute time is ~50% faster because the / operator creates a new Path object with its associated overhead.

  3. os.path.exists() instead of Path.exists(): The existence check dropped from 476,490ns (23.6%) to 223,477ns (24.2%) - roughly 2x faster. The os.path.exists() function directly calls the stat syscall, while Path.exists() goes through Path's object model.

Why this works:
Path objects provide a cleaner API but add object instantiation, method dispatch, and normalization overhead. For simple filesystem checks in initialization code that runs frequently, using lower-level os functions eliminates this overhead while maintaining identical functionality.

Test results:
All test cases show 68-111% speedup across scenarios including:

  • Empty directories (fastest: 82-87% improvement)
  • Large directories with 500 files (68-111% improvement)
  • Edge cases like symlinks and directory-as-file (75-82% improvement)

The optimization is particularly beneficial for CLI initialization code that may run on every command invocation, where sub-millisecond improvements in frequently-called functions compound into noticeable user experience gains.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 23 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Click to see Generated Regression Tests
from __future__ import annotations

# imports
import os
from pathlib import Path
from typing import Any

import pytest  # used for our unit tests
from codeflash.cli_cmds.init_java import should_modify_java_config

def test_no_config_file_does_not_prompt_and_returns_true(monkeypatch, tmp_path):
    # Arrange: ensure working directory has no codeflash.toml
    monkeypatch.chdir(tmp_path)  # set cwd to a clean temporary directory

    # Replace Confirm.ask with a function that fails the test if called.
    def fail_if_called(*args, **kwargs):
        raise AssertionError("Confirm.ask should not be called when no config file exists")

    # Patch the exact attribute that the function imports at runtime.
    monkeypatch.setattr("rich.prompt.Confirm.ask", fail_if_called, raising=True)

    # Act: call function under test
    codeflash_output = should_modify_java_config(); result = codeflash_output # 28.9μs -> 15.9μs (82.0% faster)

def test_config_file_exists_prompts_and_respects_true_choice(monkeypatch, tmp_path):
    # Arrange: create a codeflash.toml file so the function will detect it
    monkeypatch.chdir(tmp_path)
    config_file = tmp_path / "codeflash.toml"
    config_file.write_text("existing = true")  # create the file

    # Capture the arguments passed to Confirm.ask and return True to simulate user acceptance
    called = {}

    def fake_ask(prompt, default, show_default):
        # Record inputs for later assertions
        called["prompt"] = prompt
        called["default"] = default
        called["show_default"] = show_default
        return True

    # Patch Confirm.ask used inside the function
    monkeypatch.setattr("rich.prompt.Confirm.ask", fake_ask, raising=True)

    # Act
    codeflash_output = should_modify_java_config(); result = codeflash_output # 25.6μs -> 13.7μs (86.9% faster)

def test_config_file_exists_prompts_and_respects_false_choice(monkeypatch, tmp_path):
    # Arrange: create the config file
    monkeypatch.chdir(tmp_path)
    (tmp_path / "codeflash.toml").write_text("existing = true")

    # Simulate user declining re-configuration
    def fake_ask_decline(prompt, default, show_default):
        return False

    monkeypatch.setattr("rich.prompt.Confirm.ask", fake_ask_decline, raising=True)

    # Act
    codeflash_output = should_modify_java_config(); result = codeflash_output # 24.7μs -> 13.3μs (86.3% faster)

def test_presence_of_pom_xml_does_not_trigger_prompt(monkeypatch, tmp_path):
    # Arrange: create a pom.xml but NOT codeflash.toml
    monkeypatch.chdir(tmp_path)
    (tmp_path / "pom.xml").write_text("<project></project>")

    # If Confirm.ask is called, fail the test because only codeflash.toml should trigger it in current implementation
    def fail_if_called(*args, **kwargs):
        raise AssertionError("Confirm.ask should not be called when only pom.xml exists (implementation checks codeflash.toml)")

    monkeypatch.setattr("rich.prompt.Confirm.ask", fail_if_called, raising=True)

    # Act
    codeflash_output = should_modify_java_config(); result = codeflash_output # 28.3μs -> 16.6μs (69.9% faster)

def test_codeflash_config_is_directory_triggers_prompt(monkeypatch, tmp_path):
    # Arrange: create a directory named codeflash.toml (Path.exists will be True)
    monkeypatch.chdir(tmp_path)
    (tmp_path / "codeflash.toml").mkdir()

    # Simulate user selecting True
    monkeypatch.setattr("rich.prompt.Confirm.ask", lambda *a, **k: True, raising=True)

    # Act
    codeflash_output = should_modify_java_config(); result = codeflash_output # 23.6μs -> 12.9μs (82.2% faster)

def test_codeflash_config_symlink_triggers_prompt_if_supported(monkeypatch, tmp_path):
    # Arrange: attempt to create a symlink to a real file; skip if symlink not supported
    if not hasattr(os, "symlink"):
        pytest.skip("Platform does not support os.symlink; skipping symlink test")

    real = tmp_path / "real_config"
    real.write_text("x = 1")
    link = tmp_path / "codeflash.toml"

    try:
        os.symlink(real, link)  # may fail on Windows without privileges
    except (OSError, NotImplementedError) as e:
        pytest.skip(f"Could not create symlink on this platform/environment: {e}")

    monkeypatch.chdir(tmp_path)

    # Simulate user declining re-configuration
    monkeypatch.setattr("rich.prompt.Confirm.ask", lambda *a, **k: False, raising=True)

    # Act
    codeflash_output = should_modify_java_config(); result = codeflash_output # 24.9μs -> 14.2μs (75.7% faster)

def test_large_directory_without_config_is_fast_and_does_not_prompt(monkeypatch, tmp_path):
    # Large scale scenario: create many files (but under 1000) to simulate busy project directory.
    monkeypatch.chdir(tmp_path)
    num_files = 500  # under the 1000 element guideline
    for i in range(num_files):
        # Create many innocuous files; should not affect the function's behavior
        (tmp_path / f"file_{i}.txt").write_text(str(i))

    # Ensure Confirm.ask is not called
    def fail_if_called(*args, **kwargs):
        raise AssertionError("Confirm.ask should not be called when codeflash.toml is absent even in large directories")

    monkeypatch.setattr("rich.prompt.Confirm.ask", fail_if_called, raising=True)

    # Act
    codeflash_output = should_modify_java_config(); result = codeflash_output # 36.3μs -> 21.6μs (68.0% faster)

def test_large_directory_with_config_prompts_once(monkeypatch, tmp_path):
    # Large scale scenario with config present: many files plus codeflash.toml
    monkeypatch.chdir(tmp_path)
    num_files = 500
    for i in range(num_files):
        (tmp_path / f"file_{i}.txt").write_text(str(i))

    # Create the config file that should trigger prompting
    (tmp_path / "codeflash.toml").write_text("reconfigure = maybe")

    # Track how many times Confirm.ask is invoked to ensure single prompt
    counter = {"calls": 0}

    def fake_ask(prompt, default, show_default):
        counter["calls"] += 1
        return True

    monkeypatch.setattr("rich.prompt.Confirm.ask", fake_ask, raising=True)

    # Act
    codeflash_output = should_modify_java_config(); result = codeflash_output # 30.8μs -> 14.6μs (111% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
import os
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch

# imports
import pytest
from codeflash.cli_cmds.init_java import should_modify_java_config

class TestShouldModifyJavaConfigBasic:
    """Basic test cases for should_modify_java_config function."""

    def test_no_config_file_exists_returns_true(self):
        """
        Scenario: Project has no existing codeflash.toml file
        Expected: Function returns (True, None) without prompting user
        """
        # Create a temporary directory without codeflash.toml
        with tempfile.TemporaryDirectory() as tmpdir:
            original_cwd = os.getcwd()
            try:
                os.chdir(tmpdir)
                codeflash_output = should_modify_java_config(); result = codeflash_output
            finally:
                os.chdir(original_cwd)

    def test_config_file_exists_user_confirms(self):
        """
        Scenario: Project has existing codeflash.toml and user confirms re-configuration
        Expected: Function prompts user and returns (True, None) if user confirms
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            original_cwd = os.getcwd()
            try:
                os.chdir(tmpdir)
                # Create a codeflash.toml file
                config_file = Path(tmpdir) / "codeflash.toml"
                config_file.touch()

                # Mock the Confirm.ask to return True (user confirms)
                with patch('rich.prompt.Confirm.ask', return_value=True):
                    codeflash_output = should_modify_java_config(); result = codeflash_output
            finally:
                os.chdir(original_cwd)

    def test_config_file_exists_user_declines(self):
        """
        Scenario: Project has existing codeflash.toml and user declines re-configuration
        Expected: Function prompts user and returns (False, None) if user declines
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            original_cwd = os.getcwd()
            try:
                os.chdir(tmpdir)
                # Create a codeflash.toml file
                config_file = Path(tmpdir) / "codeflash.toml"
                config_file.touch()

                # Mock the Confirm.ask to return False (user declines)
                with patch('rich.prompt.Confirm.ask', return_value=False):
                    codeflash_output = should_modify_java_config(); result = codeflash_output
            finally:
                os.chdir(original_cwd)

    def test_return_tuple_structure(self):
        """
        Scenario: Verify the function always returns a tuple with specific structure
        Expected: Return value is a tuple of (bool, None)
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            original_cwd = os.getcwd()
            try:
                os.chdir(tmpdir)
                codeflash_output = should_modify_java_config(); result = codeflash_output
            finally:
                os.chdir(original_cwd)

class TestShouldModifyJavaConfigEdgeCases:
    """Edge case test cases for should_modify_java_config function."""

    def test_config_file_exists_but_empty(self):
        """
        Scenario: codeflash.toml file exists but is empty
        Expected: File is still considered as existing, prompts user
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            original_cwd = os.getcwd()
            try:
                os.chdir(tmpdir)
                # Create an empty codeflash.toml file
                config_file = Path(tmpdir) / "codeflash.toml"
                config_file.write_text("")

                with patch('rich.prompt.Confirm.ask', return_value=True):
                    codeflash_output = should_modify_java_config(); result = codeflash_output
            finally:
                os.chdir(original_cwd)

    def test_config_file_with_content(self):
        """
        Scenario: codeflash.toml file exists with actual TOML content
        Expected: Prompts user regardless of file content
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            original_cwd = os.getcwd()
            try:
                os.chdir(tmpdir)
                # Create a codeflash.toml file with content
                config_file = Path(tmpdir) / "codeflash.toml"
                config_file.write_text("[codeflash]\nversion = 1\n")

                with patch('rich.prompt.Confirm.ask', return_value=False):
                    codeflash_output = should_modify_java_config(); result = codeflash_output
            finally:
                os.chdir(original_cwd)

    def test_config_file_case_sensitive(self):
        """
        Scenario: Directory has 'Codeflash.toml' or 'CODEFLASH.TOML' instead of lowercase
        Expected: Function only recognizes 'codeflash.toml' (case-sensitive on Unix)
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            original_cwd = os.getcwd()
            try:
                os.chdir(tmpdir)
                # Create a file with different casing
                config_file = Path(tmpdir) / "Codeflash.toml"
                config_file.touch()

                codeflash_output = should_modify_java_config(); result = codeflash_output
            finally:
                os.chdir(original_cwd)

    def test_config_file_is_directory_not_file(self):
        """
        Scenario: codeflash.toml exists as a directory instead of a file
        Expected: Path.exists() still returns True, prompts user
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            original_cwd = os.getcwd()
            try:
                os.chdir(tmpdir)
                # Create codeflash.toml as a directory
                config_dir = Path(tmpdir) / "codeflash.toml"
                config_dir.mkdir()

                with patch('rich.prompt.Confirm.ask', return_value=True):
                    codeflash_output = should_modify_java_config(); result = codeflash_output
            finally:
                os.chdir(original_cwd)

    

To test or edit this optimization locally git merge codeflash/optimize-pr1199-2026-02-01T21.20.00

Suggested change
project_root = Path.cwd()
# Check for existing codeflash config in pom.xml or a separate config file
codeflash_config_path = project_root / "codeflash.toml"
if codeflash_config_path.exists():
project_root = os.getcwd()
# Check for existing codeflash config in pom.xml or a separate config file
codeflash_config_path = os.path.join(project_root, "codeflash.toml")
if os.path.exists(codeflash_config_path):

This optimization achieves a **26x speedup (2598% improvement)** by eliminating expensive logging operations that dominated the original runtime.

## Key Performance Improvements

### 1. **Conditional Logging Guard (95% of original time eliminated)**
The original code unconditionally formatted expensive log messages even when logging was disabled:
```python
logger.warning(
    f"Optimized code not found for {relative_path} In the context\n-------\n{optimized_code}\n-------\n"
    ...
)
```
This single operation consumed **111ms out of 117ms total runtime** (95%).

The optimization adds a guard check:
```python
if logger.isEnabledFor(logger.level):
    logger.warning(...)
```
This prevents string formatting and object serialization when the log message won't be emitted, dramatically reducing overhead in production scenarios where warning-level logging may be disabled.

### 2. **Eliminated Redundant Path Object Creation**
The original created `Path` objects repeatedly during filename matching:
```python
if file_path_str and Path(file_path_str).name == target_filename:
```

The optimized version uses string operations:
```python
if file_path_str.endswith(target_filename) and (len(file_path_str) == len(target_filename) or file_path_str[-len(target_filename)-1] in ('/', '\\')):
```
This removes overhead from Path instantiation (1.16ms → 44µs in the profiler).

### 3. **Minor Cache Lookup Optimization**
Changed from `self._cache.get("file_to_path") is not None` to `"file_to_path" in self._cache` and hoisted the dict assignment to avoid inline mutation, providing small gains in the caching path.

### 4. **String Conversion Hoisting**
Pre-computed `relative_path_str = str(relative_path)` to avoid repeated conversions.

## Test Case Performance Patterns

- **Exact path matches** (most common case): 10-20% faster due to optimized caching
- **No-match scenarios** (fallback paths): **78-189x faster** due to eliminated logger.warning overhead
  - `test_empty_code_strings`: 1.03ms → 12.9µs (7872% faster)
  - `test_no_match_multiple_blocks`: 1.28ms → 16.3µs (7753% faster)
  - `test_many_code_blocks_no_match`: 20.5ms → 107µs (18985% faster)

The optimization particularly benefits scenarios where file path mismatches occur, as these trigger the expensive warning path in the original code. For the common case of exact matches, the improvements are modest but consistent.
HeshamHM28 and others added 30 commits February 11, 2026 01:57
…2026-02-10T21.52.05

⚡️ Speed up method `JavaAssertTransformer._generate_exception_replacement` by 14% in PR #1443 (`fix/java-exception-assignment-instrumentation`)
…2026-02-10T21.31.59

⚡️ Speed up method `JavaAssertTransformer._detect_variable_assignment` by 33% in PR #1443 (`fix/java-exception-assignment-instrumentation`)
…tion

Resolved conflicts by merging the best of both branches:
- Kept exception_class field from PR for better exception type detection
- Adopted more general variable assignment detection from omni-java
- Combined exception replacement logic to use exception_class with fallback
- Added double catch (specific exception + generic Exception) for robustness
- Merged test cases from both branches with updated expectations

Changes:
- Updated AssertionMatch to include all fields: assigned_var_type, assigned_var_name, exception_class
- Lambda extraction now works for all exception assertions
- Exception class extraction specifically for assertThrows
- Variable assignment detection handles final modifier and fully qualified types
- Exception replacement uses exception_class or falls back to assigned_var_type
- All 80 tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ent-instrumentation

fix: handle assertThrows variable assignment in Java instrumentation
Fixed two test failures in omni-java:

1. test_formatter_cmds_non_existent:
   - Default formatter-cmds changed from ["black $file"] to [] (commit c587c47)
   - Updated test expectation to match new default
   - Formatter detection now handled by project detector
   - Empty list prevents "Could not find formatter: black" errors for Java projects

2. test_float_values_slightly_different:
   - Python comparator now uses math.isclose(rel_tol=1e-9) for numeric comparison (commit 98a5a43)
   - Updated test to expect equivalent=True for values within epsilon tolerance
   - Added test_float_values_significantly_different to verify detection of actual differences
   - Test added before epsilon-based comparison was implemented, causing mismatch

Both tests now pass and accurately reflect current codebase behavior.

Test results: 2 fixed tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- test_large_number_different now expects equivalent=True for 99999999999999999 vs 99999999999999998
- Both numbers convert to 1e+17 as floats, making them indistinguishable
- Added test_large_number_significantly_different to verify detection of actual differences
- This is a known limitation of floating-point comparison for very large integers

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…defaults

fix: update failing unit tests in omni-java to match current behavior
The JS/TS import normalization block used `if not is_python()` which
also matched Java, causing a Jest globals import line to be prepended
to every generated Java test file. This broke all compilation with
syntax errors (`<identifier> expected`, `unclosed character literal`).
Changed the guard to `if is_javascript()` which correctly targets only
JS/TS files.

Additionally, added JPMS module-info.java detection in
`_fix_java_test_paths()`. When a test module-info.java exists (e.g.,
declaring `module io.questdb.test`), generated test packages are now
remapped from the main module namespace to the test module namespace
(e.g., `io.questdb.cairo` -> `io.questdb.test.cairo`) to avoid the
Java split-package rule violation that produces "package exists in
another module" compilation errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ping

Add `is_javascript` to the import from `codeflash.languages` — the
previous commit changed the guard from `is_python()` to
`is_javascript()` but missed updating the import, causing a
NameError at runtime.

Fix the JPMS module-info.java path resolution: the main source
directory is a sibling of the test directory under `src/`
(i.e., `src/test/java` -> `src/main/java`), so the correct
traversal is `.parent.parent / "main" / "java"` (two levels up),
not `.parent.parent.parent` (three levels up, which lands at the
module root and produces `core/main/java` instead of
`core/src/main/java`).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d parens

Two bugs in the Java test instrumentation produced invalid code:

1. `stripped.startswith("@test")` matched `@TestOnly`, `@TestFactory`,
   etc. as test annotations, causing non-test methods to be wrapped
   with profiling boilerplate. Replaced with `_is_test_annotation()`
   using a regex that matches only `@Test` and `@Test(...)`.

2. The method-call regex used `[^)]*` to match arguments, which stops
   at the first `)` and fails for nested parentheses like
   `obj.func(a, Rows.toRowID(frame.getIndex(), row))`. Replaced the
   pure-regex approach with `_find_balanced_end()` that walks the
   string tracking paren depth, string literals, and char literals,
   plus `_find_method_calls_balanced()` that uses regex to locate
   `receiver.funcName(` then balanced matching for the arguments.

Removed the now-unused `_get_method_call_pattern()` cached function
and its `lru_cache` import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed two critical bugs preventing Java optimization E2E workflows:

Issue 1: Line profiler timeout was too short (15s) for Maven operations,
causing timeouts before tests could complete. Maven needs time for JVM
startup, dependency resolution, and test execution.

Issue 2: Test result categorization failed to match original test file
names to instrumented test files, causing all existing unit tests to
show as 0 passed/failed instead of their actual results.

Both issues blocked Java optimization from completing successfully.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…tion

Two more instrumentation bugs:

1. Class renaming only updated the `class` declaration line, leaving
   return types, constructors, and other self-references unchanged.
   When the class has a method like `public OriginalClass of(...)
   { return this; }`, the renamed `this` no longer matches the
   unrenamed return type. Fixed by replacing ALL word-boundary-
   matched occurrences of the original class name throughout the
   entire source, not just in the class declaration.

2. Captured result variables (`var _cf_result8_2 = ...`) were
   declared inside nested blocks (while/for/try) but the
   serialization line referencing them was placed at the end of
   the method body, outside the block scope. Java block scoping
   makes the variable invisible there. Fixed by serializing
   immediately after each capture, while the variable is still
   in scope.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The lambda detection regex only matched no-arg lambdas `() -> {`
but missed parameterized lambdas like `(a, b, c) -> {`. This caused
instrumentation to insert `_cf_serializedResult` assignments inside
lambda bodies, violating Java's effectively-final requirement for
captured variables.

Broadened the block lambda detection from `\(\s*\)\s*->\s*\{` to
`->\s*\{`, and the expression lambda detection from `\(\s*\)\s*->`
to `->\s+\S`. This correctly detects all lambda forms and skips
instrumenting calls inside them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eout-and-test-categorization

fix: Java line profiler timeout and test categorization
The 120-second Maven timeout was only applied when test_framework ==
"junit5", leaving junit4 and testng with the default 15-second Python
pytest timeout. This caused Maven performance tests to always time out
on the first loop (Maven needs ~38s just to compile), resulting in
"best of 1 runs" benchmarks with unreliable results.

Change the condition to apply the longer timeout for all Java test
frameworks: junit4, junit5, and testng. Both the behavioral test runner
and the benchmarking test runner are fixed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rations

The line profiler's class detection only checked for "public class " or
"class " prefixes, failing to match declarations with additional modifiers
like "public final class". This caused the profiler class to be inserted
before the package statement (at index 0), producing illegal Java code.

Use a regex pattern that handles any combination of Java modifiers
(public, private, protected, final, abstract, static, sealed, non-sealed)
before class/interface/enum/record declarations.

Also removed an unused variable (instrumented_source) that was computed
but never referenced.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes:

1. Line profiler class detection: The regex only matched "public class" or
   "class" prefixes, failing on declarations with modifiers like
   "public final class". Use a regex that handles any combination of Java
   modifiers before class/interface/enum/record declarations.

2. Line profiler timeout: The dispatcher passed INDIVIDUAL_TESTCASE_TIMEOUT
   (15s) directly to the Java line profiler test runner, which used
   `timeout or 120` (truthy 15 doesn't trigger the 120s fallback).
   Apply JAVA_TESTCASE_TIMEOUT consistently for all Java test phases,
   and use max() instead of `or` for the Java default timeout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tern

Same bug as the line profiler timeout: `timeout or max(120, ...)` doesn't
trigger the fallback when timeout is a truthy low value like 15.
Use max() to ensure Maven always gets at least 120 seconds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…imeout

The upstream omni-java branch already implemented a cleaner version of the
line profiler timeout fix (with min_timeout variable and debug logging).
Keep their version since it achieves the same goal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t-guard

fix(java): fix Java instrumentation, JPMS, and timeout bugs for QuestDB
## Changes

### Debug Logging Added
- parse_test_output.py: Added [RESOLVE], [PARSE-XML] logs for test file resolution
- function_optimizer.py: Added [JAVA-ROOT], [WRITE-PATH], [REGISTER] logs
- Traces complete flow from test generation → writing → registration → execution → parsing

### Bug Fixes
1. function_optimizer.py (lines 1855-1877): Fixed missing test_string parameter
   - Read test file content before passing to instrument_existing_test()
   - Pass test_string with test_path as optional parameter

2. java/support.py (lines 294-296): Fixed incorrect parameters in wrapper
   - Use named parameters matching new instrument_existing_test() signature
   - Removed obsolete call_positions, tests_project_root, analyzer parameters

## Testing
- Added debug logs confirmed working in Fibonacci optimization
- Test files written to correct locations with proper instrumentation
- TestFile registry contains accurate paths
- Ready for aerospike-client-java validation

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…tures

Enhanced _get_java_sources_root() to handle projects where tests_root points
to a module directory that contains a 'src' subdirectory (e.g., test/src).

Added checks for:
1. tests_root already ending with "src" (already a sources root)
2. Simple "src" subdirectory before checking Maven-standard src/test/java

This fixes the base_dir mismatch bug where tests were written to the wrong
directory in multi-module Maven projects like aerospike-client-java.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…e-dir-mismatch

fix: Java behavior test execution in multi-module Maven projects
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

workflow-modified This PR modifies GitHub Actions workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants