From 01eb74b2b2469bac30642e164c066e703002db51 Mon Sep 17 00:00:00 2001 From: HeshamHM28 Date: Wed, 25 Feb 2026 07:02:58 +0200 Subject: [PATCH 01/13] feat: Enhance Java testing framework with stdout capture and sorting functionality - Added unit tests for sorting arrays and collecting Fibonacci numbers in FibonacciTest.java. - Modified Comparator.java to handle test results with stdout capturing. - Updated build_tools.py to ensure JaCoCo configuration uses a custom property for better compatibility. - Enhanced discovery.py to include return types for methods. - Improved instrumentation.py to capture stdout for void methods and serialize side effects. - Updated remove_asserts.py to handle void methods correctly during assertion transformation. - Modified CodeflashHelper.java to store captured stdout in the SQLite database. - Enhanced parse_test_output.py to read stdout from SQLite results for better test output analysis. - Updated function_types.py to include return type information in FunctionToOptimize model. --- .../src/main/java/com/example/Fibonacci.java | 73 ++++++++++ .../test/java/com/example/FibonacciTest.java | 102 ++++++++++++++ .../main/java/com/codeflash/Comparator.java | 106 +++++++++----- codeflash/languages/java/build_tools.py | 107 ++++++++++++++ codeflash/languages/java/comparator.py | 2 + codeflash/languages/java/discovery.py | 11 +- codeflash/languages/java/instrumentation.py | 132 ++++++++++++++---- codeflash/languages/java/remove_asserts.py | 29 ++-- .../java/resources/CodeflashHelper.java | 43 ++++-- codeflash/models/function_types.py | 1 + codeflash/verification/parse_test_output.py | 27 +++- 11 files changed, 538 insertions(+), 95 deletions(-) diff --git a/code_to_optimize/java/src/main/java/com/example/Fibonacci.java b/code_to_optimize/java/src/main/java/com/example/Fibonacci.java index b604fb928..8772905e2 100644 --- a/code_to_optimize/java/src/main/java/com/example/Fibonacci.java +++ b/code_to_optimize/java/src/main/java/com/example/Fibonacci.java @@ -172,4 +172,77 @@ public static boolean areConsecutiveFibonacci(long a, long b) { return Math.abs(indexA - indexB) == 1; } + + /** + * Sort an array in-place using bubble sort. + * Intentionally naive O(n^2) implementation for optimization testing. + * + * @param arr Array to sort (modified in-place) + */ + public static void sortArray(long[] arr) { + if (arr == null) { + throw new IllegalArgumentException("Array must not be null"); + } + for (int i = 0; i < arr.length; i++) { + for (int j = 0; j < arr.length - 1 - i; j++) { + if (arr[j] > arr[j + 1]) { + long temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } + } + + /** + * Append Fibonacci numbers up to a limit into the provided list. + * Clears the list first, then fills it with Fibonacci numbers less than limit. + * Uses repeated naive recursion — intentionally slow for optimization testing. + * + * @param output List to populate (cleared first) + * @param limit Upper bound (exclusive) + */ + public static void collectFibonacciInto(List output, long limit) { + if (output == null) { + throw new IllegalArgumentException("Output list must not be null"); + } + output.clear(); + + if (limit <= 0) { + return; + } + + int index = 0; + while (true) { + long fib = fibonacci(index); + if (fib >= limit) { + break; + } + output.add(fib); + index++; + if (index > 50) { + break; + } + } + } + + /** + * Compute running Fibonacci sums in-place. + * result[i] = sum of fibonacci(0) through fibonacci(i). + * Uses repeated naive recursion — intentionally O(n * 2^n). + * + * @param result Array to fill with running sums (must be pre-allocated) + */ + public static void fillFibonacciRunningSums(long[] result) { + if (result == null) { + throw new IllegalArgumentException("Array must not be null"); + } + for (int i = 0; i < result.length; i++) { + long sum = 0; + for (int j = 0; j <= i; j++) { + sum += fibonacci(j); + } + result[i] = sum; + } + } } diff --git a/code_to_optimize/java/src/test/java/com/example/FibonacciTest.java b/code_to_optimize/java/src/test/java/com/example/FibonacciTest.java index 86724917d..f58fd87b6 100644 --- a/code_to_optimize/java/src/test/java/com/example/FibonacciTest.java +++ b/code_to_optimize/java/src/test/java/com/example/FibonacciTest.java @@ -1,6 +1,7 @@ package com.example; import org.junit.jupiter.api.Test; +import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -136,4 +137,105 @@ void testAreConsecutiveFibonacci() { // Non-Fibonacci number assertFalse(Fibonacci.areConsecutiveFibonacci(4, 5)); // 4 is not Fibonacci } + + @Test + void testSortArray() { + long[] arr = {5, 3, 8, 1, 2, 7, 4, 6}; + Fibonacci.sortArray(arr); + assertArrayEquals(new long[]{1, 2, 3, 4, 5, 6, 7, 8}, arr); + } + + @Test + void testSortArrayAlreadySorted() { + long[] arr = {1, 2, 3, 4, 5}; + Fibonacci.sortArray(arr); + assertArrayEquals(new long[]{1, 2, 3, 4, 5}, arr); + } + + @Test + void testSortArrayReversed() { + long[] arr = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; + Fibonacci.sortArray(arr); + assertArrayEquals(new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, arr); + } + + @Test + void testSortArrayDuplicates() { + long[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3}; + Fibonacci.sortArray(arr); + assertArrayEquals(new long[]{1, 1, 2, 3, 3, 4, 5, 5, 6, 9}, arr); + } + + @Test + void testSortArrayEmpty() { + long[] arr = {}; + Fibonacci.sortArray(arr); + assertArrayEquals(new long[]{}, arr); + } + + @Test + void testSortArraySingle() { + long[] arr = {42}; + Fibonacci.sortArray(arr); + assertArrayEquals(new long[]{42}, arr); + } + + @Test + void testSortArrayNegatives() { + long[] arr = {-3, -1, -4, -1, -5}; + Fibonacci.sortArray(arr); + assertArrayEquals(new long[]{-5, -4, -3, -1, -1}, arr); + } + + @Test + void testSortArrayNull() { + assertThrows(IllegalArgumentException.class, () -> Fibonacci.sortArray(null)); + } + + @Test + void testCollectFibonacciInto() { + List output = new ArrayList<>(); + Fibonacci.collectFibonacciInto(output, 10); + assertEquals(7, output.size()); + assertEquals(List.of(0L, 1L, 1L, 2L, 3L, 5L, 8L), output); + } + + @Test + void testCollectFibonacciIntoZeroLimit() { + List output = new ArrayList<>(); + Fibonacci.collectFibonacciInto(output, 0); + assertTrue(output.isEmpty()); + } + + @Test + void testCollectFibonacciIntoClearsExisting() { + List output = new ArrayList<>(List.of(99L, 100L)); + Fibonacci.collectFibonacciInto(output, 5); + assertEquals(List.of(0L, 1L, 1L, 2L, 3L), output); + } + + @Test + void testCollectFibonacciIntoNull() { + assertThrows(IllegalArgumentException.class, () -> Fibonacci.collectFibonacciInto(null, 10)); + } + + @Test + void testFillFibonacciRunningSums() { + long[] result = new long[6]; + Fibonacci.fillFibonacciRunningSums(result); + // sums: fib(0)=0, 0+1=1, 0+1+1=2, 0+1+1+2=4, 0+1+1+2+3=7, 0+1+1+2+3+5=12 + assertArrayEquals(new long[]{0, 1, 2, 4, 7, 12}, result); + } + + @Test + void testFillFibonacciRunningSumsEmpty() { + long[] result = new long[0]; + Fibonacci.fillFibonacciRunningSums(result); + assertArrayEquals(new long[]{}, result); + } + + @Test + void testFillFibonacciRunningSumsNull() { + assertThrows(IllegalArgumentException.class, () -> Fibonacci.fillFibonacciRunningSums(null)); + } } diff --git a/codeflash-java-runtime/src/main/java/com/codeflash/Comparator.java b/codeflash-java-runtime/src/main/java/com/codeflash/Comparator.java index 32d9f6034..b1e720a12 100644 --- a/codeflash-java-runtime/src/main/java/com/codeflash/Comparator.java +++ b/codeflash-java-runtime/src/main/java/com/codeflash/Comparator.java @@ -61,8 +61,8 @@ public static void main(String[] args) { return; } - Map originalResults; - Map candidateResults; + Map originalResults; + Map candidateResults; try { originalResults = readTestResults(originalDbPath); @@ -88,36 +88,41 @@ public static void main(String[] args) { int totalInvocations = allKeys.size(); for (String key : allKeys) { - byte[] origBytes = originalResults.get(key); - byte[] candBytes = candidateResults.get(key); + TestResult origResult = originalResults.get(key); + TestResult candResult = candidateResults.get(key); - if (origBytes == null && candBytes == null) { - // Both null (void methods) — equivalent - continue; - } + byte[] origBytes = origResult != null ? origResult.returnValue : null; + byte[] candBytes = candResult != null ? candResult.returnValue : null; - if (origBytes == null) { + if (origBytes == null && candBytes == null) { + // Both null (void methods) — check stdout still + } else if (origBytes == null) { Object candObj = safeDeserialize(candBytes); diffs.add(formatDiff("missing", key, 0, null, safeToString(candObj))); continue; - } - - if (candBytes == null) { + } else if (candBytes == null) { Object origObj = safeDeserialize(origBytes); diffs.add(formatDiff("missing", key, 0, safeToString(origObj), null)); continue; - } - - Object origObj = safeDeserialize(origBytes); - Object candObj = safeDeserialize(candBytes); + } else { + Object origObj = safeDeserialize(origBytes); + Object candObj = safeDeserialize(candBytes); - try { - if (!compare(origObj, candObj)) { - diffs.add(formatDiff("return_value", key, 0, safeToString(origObj), safeToString(candObj))); + try { + if (!compare(origObj, candObj)) { + diffs.add(formatDiff("return_value", key, 0, safeToString(origObj), safeToString(candObj))); + } + } catch (KryoPlaceholderAccessException e) { + // Placeholder detected — skip comparison for this invocation + continue; } - } catch (KryoPlaceholderAccessException e) { - // Placeholder detected — skip comparison for this invocation - continue; + } + + // Compare stdout + String origStdout = origResult != null ? origResult.stdout : null; + String candStdout = candResult != null ? candResult.stdout : null; + if (origStdout != null && candStdout != null && !origStdout.equals(candStdout)) { + diffs.add(formatDiff("stdout", key, 0, truncate(origStdout, 200), truncate(candStdout, 200))); } } @@ -137,25 +142,47 @@ public static void main(String[] args) { System.exit(equivalent ? 0 : 1); } - private static Map readTestResults(String dbPath) throws Exception { - Map results = new LinkedHashMap<>(); + private static class TestResult { + final byte[] returnValue; + final String stdout; + + TestResult(byte[] returnValue, String stdout) { + this.returnValue = returnValue; + this.stdout = stdout; + } + } + + private static Map readTestResults(String dbPath) throws Exception { + Map results = new LinkedHashMap<>(); String url = "jdbc:sqlite:" + dbPath; try (Connection conn = DriverManager.getConnection(url); - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery( - "SELECT iteration_id, return_value FROM test_results WHERE loop_index = 1")) { - while (rs.next()) { - String iterationId = rs.getString("iteration_id"); - byte[] returnValue = rs.getBytes("return_value"); - // Strip the CODEFLASH_TEST_ITERATION suffix (e.g. "7_0" -> "7") - // Original runs with _0, candidate with _1, but the test iteration - // counter before the underscore is what identifies the invocation. - int lastUnderscore = iterationId.lastIndexOf('_'); - if (lastUnderscore > 0) { - iterationId = iterationId.substring(0, lastUnderscore); + Statement stmt = conn.createStatement()) { + + // Check if stdout column exists (backward compatibility) + boolean hasStdout = false; + try (ResultSet columns = conn.getMetaData().getColumns(null, null, "test_results", "stdout")) { + hasStdout = columns.next(); + } + + String query = hasStdout + ? "SELECT iteration_id, return_value, stdout FROM test_results WHERE loop_index = 1" + : "SELECT iteration_id, return_value FROM test_results WHERE loop_index = 1"; + + try (ResultSet rs = stmt.executeQuery(query)) { + while (rs.next()) { + String iterationId = rs.getString("iteration_id"); + byte[] returnValue = rs.getBytes("return_value"); + String stdout = hasStdout ? rs.getString("stdout") : null; + // Strip the CODEFLASH_TEST_ITERATION suffix (e.g. "7_0" -> "7") + // Original runs with _0, candidate with _1, but the test iteration + // counter before the underscore is what identifies the invocation. + int lastUnderscore = iterationId.lastIndexOf('_'); + if (lastUnderscore > 0) { + iterationId = iterationId.substring(0, lastUnderscore); + } + results.put(iterationId, new TestResult(returnValue, stdout)); } - results.put(iterationId, returnValue); } } return results; @@ -186,6 +213,11 @@ private static String safeToString(Object obj) { } } + private static String truncate(String s, int maxLen) { + if (s == null || s.length() <= maxLen) return s; + return s.substring(0, maxLen) + "..."; + } + private static String formatDiff(String scope, String methodId, int callId, String originalValue, String candidateValue) { StringBuilder sb = new StringBuilder(); diff --git a/codeflash/languages/java/build_tools.py b/codeflash/languages/java/build_tools.py index ba4a5ccd4..552bcc1d2 100644 --- a/codeflash/languages/java/build_tools.py +++ b/codeflash/languages/java/build_tools.py @@ -715,6 +715,76 @@ def is_jacoco_configured(pom_path: Path) -> bool: return False +JACOCO_PROPERTY_NAME = "jacoco.agent.argLine" + + +def ensure_jacoco_property_name(pom_path: Path) -> bool: + """Ensure the existing JaCoCo prepare-agent writes to a custom property. + + If the project already has JaCoCo configured with the default ``argLine`` + property, we must redirect it to ``jacoco.agent.argLine`` so that our + ``-DargLine=@{jacoco.agent.argLine} ...`` can compose both the agent arg + and the add-opens flags without one overriding the other. + + Also adds an empty default ```` property to + ```` so the reference resolves even if prepare-agent didn't run. + """ + if not pom_path.exists(): + return False + + try: + content = pom_path.read_text(encoding="utf-8") + + # Already using our custom property — nothing to do + if f"{JACOCO_PROPERTY_NAME}" in content: + content = _ensure_pom_property(content, JACOCO_PROPERTY_NAME, "") + pom_path.write_text(content, encoding="utf-8") + return True + + # Find the prepare-agent execution and inject + import re + + # Match prepare-agent ... ...prepare-agent... + # and check whether a block already exists for it. + prepare_agent_pattern = re.compile( + r"(\s*prepare-agent.*?)(.*?)()", + re.DOTALL, + ) + match = prepare_agent_pattern.search(content) + if not match: + # No prepare-agent execution found — nothing to patch + logger.debug("No prepare-agent execution found in %s", pom_path) + content = _ensure_pom_property(content, JACOCO_PROPERTY_NAME, "") + pom_path.write_text(content, encoding="utf-8") + return True + + between = match.group(2) # text between and + if "" in between: + # Configuration block exists — inject propertyName inside it + content = content[:match.start(2)] + between.replace( + "", + f"\n {JACOCO_PROPERTY_NAME}", + ) + content[match.end(2):] + else: + # No configuration block — add one before + config_block = ( + f"\n " + f"\n {JACOCO_PROPERTY_NAME}" + f"\n \n " + ) + insert_pos = match.start(3) + content = content[:insert_pos] + config_block + content[insert_pos:] + + content = _ensure_pom_property(content, JACOCO_PROPERTY_NAME, "") + pom_path.write_text(content, encoding="utf-8") + logger.info("Patched existing JaCoCo prepare-agent to use propertyName=%s", JACOCO_PROPERTY_NAME) + return True + + except Exception: + logger.exception("Failed to patch JaCoCo propertyName in %s", pom_path) + return False + + def add_jacoco_plugin_to_pom(pom_path: Path) -> bool: """Add JaCoCo Maven plugin to pom.xml for coverage collection. @@ -759,6 +829,9 @@ def add_jacoco_plugin_to_pom(pom_path: Path) -> bool: prepare-agent + + jacoco.agent.argLine + report @@ -835,6 +908,10 @@ def add_jacoco_plugin_to_pom(pom_path: Path) -> bool: """ content = content[:project_end] + build_section + content[project_end:] + # Add a default empty property so @{jacoco.agent.argLine} resolves to "" + # if prepare-agent doesn't run (avoids passing a literal to the JVM). + content = _ensure_pom_property(content, "jacoco.agent.argLine", "") + pom_path.write_text(content, encoding="utf-8") logger.info("Added JaCoCo plugin to pom.xml") return True @@ -844,6 +921,36 @@ def add_jacoco_plugin_to_pom(pom_path: Path) -> bool: return False +def _ensure_pom_property(content: str, prop_name: str, default_value: str) -> str: + """Ensure a Maven property exists in the pom.xml section. + + If the property already exists, the content is returned unchanged. + If there is no section, one is created. + """ + # Check if the property already exists + if f"<{prop_name}>" in content: + return content + + prop_xml = f" <{prop_name}>{default_value}\n " + + # Find main section (not inside ) + profiles_start = content.find("") + search_region = content[:profiles_start] if profiles_start != -1 else content + props_end = search_region.find("") + + if props_end != -1: + return content[:props_end] + prop_xml + content[props_end:] + + # No section — insert one before or + for anchor in ("", "", ""): + anchor_pos = search_region.find(anchor) + if anchor_pos != -1: + props_section = f" \n {prop_xml}\n\n" + return content[:anchor_pos] + props_section + content[anchor_pos:] + + return content + + def _find_closing_tag(content: str, start_pos: int, tag_name: str) -> int: """Find the position of the closing tag that matches the opening tag at start_pos. diff --git a/codeflash/languages/java/comparator.py b/codeflash/languages/java/comparator.py index 1916d1faf..8403fc242 100644 --- a/codeflash/languages/java/comparator.py +++ b/codeflash/languages/java/comparator.py @@ -245,6 +245,8 @@ def compare_test_results( scope = TestDiffScope.RETURN_VALUE if scope_str in {"exception", "missing"}: scope = TestDiffScope.DID_PASS + elif scope_str == "stdout": + scope = TestDiffScope.STDOUT # Build test identifier method_id = diff.get("methodId", "unknown") diff --git a/codeflash/languages/java/discovery.py b/codeflash/languages/java/discovery.py index 3d36e7d40..293120c09 100644 --- a/codeflash/languages/java/discovery.py +++ b/codeflash/languages/java/discovery.py @@ -104,6 +104,7 @@ def discover_functions_from_source( is_method=method.class_name is not None, language="java", doc_start_line=method.javadoc_start_line, + return_type=method.return_type, ) ) @@ -145,14 +146,10 @@ def _should_include_method( if criteria.matches_exclude_patterns(method.name): return False - # Check require_return - void methods don't have return values - - # Check require_return - void methods don't have return values + # Check require_return - void methods are allowed (verified via test pass/fail), + # but non-void methods must have an actual return statement if criteria.require_return: - if method.return_type == "void": - return False - # Also check if the method actually has a return statement - if not analyzer.has_return_statement(method, source): + if method.return_type != "void" and not analyzer.has_return_statement(method, source): return False # Check include_methods - in Java, all functions in classes are methods diff --git a/codeflash/languages/java/instrumentation.py b/codeflash/languages/java/instrumentation.py index 4a2a0e0a9..e7689e81d 100644 --- a/codeflash/languages/java/instrumentation.py +++ b/codeflash/languages/java/instrumentation.py @@ -227,9 +227,9 @@ def _generate_sqlite_write_code( f'{inner_indent} _cf_stmt{iter_id}_{call_counter}.execute("CREATE TABLE IF NOT EXISTS test_results (" +', f'{inner_indent} "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " +', f'{inner_indent} "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " +', - f'{inner_indent} "runtime INTEGER, return_value BLOB, verification_type TEXT)");', + f'{inner_indent} "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)");', f"{inner_indent} }}", - f'{inner_indent} String _cf_sql{iter_id}_{call_counter} = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";', + f'{inner_indent} String _cf_sql{iter_id}_{call_counter} = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";', f"{inner_indent} try (PreparedStatement _cf_pstmt{iter_id}_{call_counter} = _cf_conn{iter_id}_{call_counter}.prepareStatement(_cf_sql{iter_id}_{call_counter})) {{", f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setString(1, _cf_mod{iter_id});", f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setString(2, _cf_cls{iter_id});", @@ -240,6 +240,7 @@ def _generate_sqlite_write_code( f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setLong(7, _cf_dur{iter_id}_{call_counter});", f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setBytes(8, _cf_serializedResult{iter_id}_{call_counter});", f'{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setString(9, "function_call");', + f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setString(10, _cf_stdout{iter_id}_{call_counter});", f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.executeUpdate();", f"{inner_indent} }}", f"{inner_indent} }}", @@ -251,6 +252,25 @@ def _generate_sqlite_write_code( ] +def _build_void_serialize_expr(call: dict[str, Any]) -> str: + """Build a Serializer.serialize(...) expression for void function side-effect capture. + + For void methods, we serialize the arguments (and receiver for instance methods) + AFTER the call, to capture any mutations as side effects. + Static class receivers (uppercase first letter) are excluded since they aren't instances. + """ + parts: list[str] = [] + receiver = call.get("receiver") + arg_exprs = call.get("arg_exprs", []) + if receiver and not (receiver[0].isupper() and receiver.isidentifier()): + parts.append(receiver) + parts.extend(arg_exprs) + if not parts: + return "null" + items = ", ".join(parts) + return f"com.codeflash.Serializer.serialize(new Object[]{{{items}}})" + + def wrap_target_calls_with_treesitter( body_lines: list[str], func_name: str, @@ -258,6 +278,7 @@ def wrap_target_calls_with_treesitter( precise_call_timing: bool = False, class_name: str = "", test_method_name: str = "", + is_void: bool = False, ) -> tuple[list[str], int]: """Replace target method calls in body_lines with capture + serialize using tree-sitter. @@ -329,21 +350,35 @@ def wrap_target_calls_with_treesitter( cast_type = _infer_array_cast_type(body_line) var_with_cast = f"({cast_type}){var_name}" if cast_type else var_name - # Use per-call unique variables (with call_counter suffix) for behavior mode - # For behavior mode, we declare the variable outside try block, so use assignment not declaration here - # For performance mode, use shared variables without call_counter suffix - capture_stmt_with_decl = f"var {var_name} = {call['full_call']};" - capture_stmt_assign = f"{var_name} = {call['full_call']};" + # For void functions, we can't assign the return value to a variable + if is_void: + capture_stmt_with_decl = f"{call['full_call']};" + capture_stmt_assign = f"{call['full_call']};" + else: + # Use per-call unique variables (with call_counter suffix) for behavior mode + # For behavior mode, we declare the variable outside try block, so use assignment not declaration here + # For performance mode, use shared variables without call_counter suffix + capture_stmt_with_decl = f"var {var_name} = {call['full_call']};" + capture_stmt_assign = f"{var_name} = {call['full_call']};" + if precise_call_timing: # Behavior mode: per-call unique variables - serialize_stmt = f"_cf_serializedResult{iter_id}_{call_counter} = com.codeflash.Serializer.serialize((Object) {var_name});" + if is_void: + ser_expr = _build_void_serialize_expr(call) + serialize_stmt = f"_cf_serializedResult{iter_id}_{call_counter} = {ser_expr};" + else: + serialize_stmt = f"_cf_serializedResult{iter_id}_{call_counter} = com.codeflash.Serializer.serialize((Object) {var_name});" start_stmt = f"_cf_start{iter_id}_{call_counter} = System.nanoTime();" end_stmt = f"_cf_end{iter_id}_{call_counter} = System.nanoTime();" else: # Performance mode: shared variables without call_counter suffix - serialize_stmt = ( - f"_cf_serializedResult{iter_id} = com.codeflash.Serializer.serialize((Object) {var_name});" - ) + if is_void: + ser_expr = _build_void_serialize_expr(call) + serialize_stmt = f"_cf_serializedResult{iter_id} = {ser_expr};" + else: + serialize_stmt = ( + f"_cf_serializedResult{iter_id} = com.codeflash.Serializer.serialize((Object) {var_name});" + ) start_stmt = f"_cf_start{iter_id} = System.nanoTime();" end_stmt = f"_cf_end{iter_id} = System.nanoTime();" @@ -358,27 +393,35 @@ def wrap_target_calls_with_treesitter( if precise_call_timing: # For behavior mode: wrap each call in its own try-finally with SQLite write. # This ensures data from all calls is captured independently. - # Declare per-call variables + # Declare per-call variables (skip result variable for void) var_decls = [ - f"Object {var_name} = null;", f"long _cf_end{iter_id}_{call_counter} = -1;", f"long _cf_start{iter_id}_{call_counter} = 0;", f"byte[] _cf_serializedResult{iter_id}_{call_counter} = null;", + f"java.io.ByteArrayOutputStream _cf_stdoutCapture{iter_id}_{call_counter} = new java.io.ByteArrayOutputStream();", + f"java.io.PrintStream _cf_origOut{iter_id}_{call_counter} = System.out;", + f"String _cf_stdout{iter_id}_{call_counter} = null;", ] + if not is_void: + var_decls.insert(0, f"Object {var_name} = null;") # Start marker start_marker = f'System.out.println("!$######" + _cf_mod{iter_id} + ":" + _cf_cls{iter_id} + "." + _cf_test{iter_id} + ":" + _cf_fn{iter_id} + ":" + _cf_loop{iter_id} + ":{call_counter}" + "######$!");' - # Try block with capture (use assignment, not declaration, since variable is declared above) + # Try block with capture and stdout redirection try_block = [ "try {", + f" System.setOut(new java.io.PrintStream(_cf_stdoutCapture{iter_id}_{call_counter}));", f" {start_stmt}", f" {capture_stmt_assign}", f" {end_stmt}", f" {serialize_stmt}", ] - # Finally block with SQLite write + # Finally block with stdout restore and SQLite write finally_block = _generate_sqlite_write_code( iter_id, call_counter, "", class_name, func_name, test_method_name ) + # Insert stdout restore at the beginning of finally (after "} finally {" line) + finally_block.insert(1, f" System.setOut(_cf_origOut{iter_id}_{call_counter});") + finally_block.insert(2, f' try {{ _cf_stdout{iter_id}_{call_counter} = _cf_stdoutCapture{iter_id}_{call_counter}.toString("UTF-8"); }} catch (Exception _cf_encEx{iter_id}_{call_counter}) {{}}') replacement_lines = [*var_decls, start_marker, *try_block, *finally_block] # Don't add indent to first line (it's placed after existing indent), but add to subsequent lines @@ -401,25 +444,33 @@ def wrap_target_calls_with_treesitter( # Emit capture+serialize before the line, then replace the call with the variable. if precise_call_timing: # For behavior mode: wrap in try-finally with SQLite write - # Declare per-call variables - wrapped.append(f"{line_indent_str}Object {var_name} = null;") + # Declare per-call variables (skip result variable for void) + if not is_void: + wrapped.append(f"{line_indent_str}Object {var_name} = null;") wrapped.append(f"{line_indent_str}long _cf_end{iter_id}_{call_counter} = -1;") wrapped.append(f"{line_indent_str}long _cf_start{iter_id}_{call_counter} = 0;") wrapped.append(f"{line_indent_str}byte[] _cf_serializedResult{iter_id}_{call_counter} = null;") + wrapped.append(f"{line_indent_str}java.io.ByteArrayOutputStream _cf_stdoutCapture{iter_id}_{call_counter} = new java.io.ByteArrayOutputStream();") + wrapped.append(f"{line_indent_str}java.io.PrintStream _cf_origOut{iter_id}_{call_counter} = System.out;") + wrapped.append(f"{line_indent_str}String _cf_stdout{iter_id}_{call_counter} = null;") # Start marker wrapped.append( f'{line_indent_str}System.out.println("!$######" + _cf_mod{iter_id} + ":" + _cf_cls{iter_id} + "." + _cf_test{iter_id} + ":" + _cf_fn{iter_id} + ":" + _cf_loop{iter_id} + ":{call_counter}" + "######$!");' ) - # Try block (use assignment, not declaration, since variable is declared above) + # Try block with stdout redirection wrapped.append(f"{line_indent_str}try {{") + wrapped.append(f"{line_indent_str} System.setOut(new java.io.PrintStream(_cf_stdoutCapture{iter_id}_{call_counter}));") wrapped.append(f"{line_indent_str} {start_stmt}") wrapped.append(f"{line_indent_str} {capture_stmt_assign}") wrapped.append(f"{line_indent_str} {end_stmt}") wrapped.append(f"{line_indent_str} {serialize_stmt}") - # Finally block with SQLite write + # Finally block with stdout restore and SQLite write finally_lines = _generate_sqlite_write_code( iter_id, call_counter, line_indent_str, class_name, func_name, test_method_name ) + # Insert stdout restore at beginning of finally (after "} finally {" line) + finally_lines.insert(1, f"{line_indent_str} System.setOut(_cf_origOut{iter_id}_{call_counter});") + finally_lines.insert(2, f'{line_indent_str} try {{ _cf_stdout{iter_id}_{call_counter} = _cf_stdoutCapture{iter_id}_{call_counter}.toString("UTF-8"); }} catch (Exception _cf_encEx{iter_id}_{call_counter}) {{}}') wrapped.extend(finally_lines) else: capture_line = f"{line_indent_str}{capture_stmt_with_decl}" @@ -427,14 +478,17 @@ def wrap_target_calls_with_treesitter( serialize_line = f"{line_indent_str}{serialize_stmt}" wrapped.append(serialize_line) - call_start_byte = call["start_byte"] - line_byte_start - call_end_byte = call["end_byte"] - line_byte_start - call_start_char = len(line_bytes[:call_start_byte].decode("utf8")) - call_end_char = len(line_bytes[:call_end_byte].decode("utf8")) - adj_start = call_start_char + char_shift - adj_end = call_end_char + char_shift - new_line = new_line[:adj_start] + var_with_cast + new_line[adj_end:] - char_shift += len(var_with_cast) - (call_end_char - call_start_char) + # For void functions embedded in expressions, don't replace the call with a variable + # (this case is unusual for void methods but handle it gracefully) + if not is_void: + call_start_byte = call["start_byte"] - line_byte_start + call_end_byte = call["end_byte"] - line_byte_start + call_start_char = len(line_bytes[:call_start_byte].decode("utf8")) + call_end_char = len(line_bytes[:call_end_byte].decode("utf8")) + adj_start = call_start_char + char_shift + adj_end = call_end_char + char_shift + new_line = new_line[:adj_start] + var_with_cast + new_line[adj_end:] + char_shift += len(var_with_cast) - (call_end_char - call_start_char) # Keep the modified line only if it has meaningful content left if new_line.strip(): @@ -467,6 +521,16 @@ def _collect_calls( if parent_type == "expression_statement": es_start = parent.start_byte - prefix_len es_end = parent.end_byte - prefix_len + # Extract receiver and argument expressions for side-effect serialization + object_node = node.child_by_field_name("object") + receiver_text = analyzer.get_node_text(object_node, wrapper_bytes) if object_node else None + args_node = node.child_by_field_name("arguments") + arg_exprs = [] + if args_node: + for child in args_node.children: + if child.type not in ("(", ")", ","): + arg_exprs.append(analyzer.get_node_text(child, wrapper_bytes)) + out.append( { "start_byte": start, @@ -477,6 +541,8 @@ def _collect_calls( "in_complex": _is_inside_complex_expression(node), "es_start_byte": es_start, "es_end_byte": es_end, + "receiver": receiver_text, + "arg_exprs": arg_exprs, } ) for child in node.children: @@ -590,7 +656,7 @@ def instrument_for_benchmarking( def instrument_existing_test( test_string: str, - function_to_optimize: Any, # FunctionToOptimize or FunctionToOptimize + function_to_optimize: Any, mode: str, # "behavior" or "performance" test_path: Path | None = None, test_class_name: str | None = None, @@ -638,6 +704,8 @@ def instrument_existing_test( # replacing substrings of other identifiers. modified_source = re.sub(rf"\b{re.escape(original_class_name)}\b", new_class_name, source) + is_void = getattr(function_to_optimize, "return_type", None) == "void" + # Add timing instrumentation to test methods # Use original class name (without suffix) in timing markers for consistency with Python if mode == "performance": @@ -648,14 +716,14 @@ def instrument_existing_test( ) else: # Behavior mode: add timing instrumentation that also writes to SQLite - modified_source = _add_behavior_instrumentation(modified_source, original_class_name, func_name) + modified_source = _add_behavior_instrumentation(modified_source, original_class_name, func_name, is_void=is_void) logger.debug("Java %s testing for %s: renamed class %s -> %s", mode, func_name, original_class_name, new_class_name) # Why return True here? return True, modified_source -def _add_behavior_instrumentation(source: str, class_name: str, func_name: str) -> str: +def _add_behavior_instrumentation(source: str, class_name: str, func_name: str, is_void: bool = False) -> str: """Add behavior instrumentation to test methods. For behavior mode, this adds: @@ -796,6 +864,7 @@ def _add_behavior_instrumentation(source: str, class_name: str, func_name: str) precise_call_timing=True, class_name=class_name, test_method_name=test_method_name, + is_void=is_void, ) # Add behavior instrumentation setup code (shared variables for all calls in the method) @@ -1289,7 +1358,8 @@ def instrument_generated_java_test( from codeflash.languages.java.remove_asserts import transform_java_assertions - test_code = transform_java_assertions(test_code, function_name, qualified_name) + is_void = getattr(function_to_optimize, "return_type", None) == "void" + test_code = transform_java_assertions(test_code, function_name, qualified_name, is_void=is_void) # Extract class name from the test code # Use pattern that starts at beginning of line to avoid matching words in comments diff --git a/codeflash/languages/java/remove_asserts.py b/codeflash/languages/java/remove_asserts.py index 8544a771d..19a623aad 100644 --- a/codeflash/languages/java/remove_asserts.py +++ b/codeflash/languages/java/remove_asserts.py @@ -184,13 +184,18 @@ class JavaAssertTransformer: """ def __init__( - self, function_name: str, qualified_name: str | None = None, analyzer: JavaAnalyzer | None = None + self, + function_name: str, + qualified_name: str | None = None, + analyzer: JavaAnalyzer | None = None, + is_void: bool = False, ) -> None: self.analyzer = analyzer or get_java_analyzer() self.func_name = function_name self.qualified_name = qualified_name or function_name self.invocation_counter = 0 self._detected_framework: str | None = None + self.is_void = is_void # Precompile the assignment-detection regex to avoid recompiling on each call. self._assign_re = re.compile(r"(\w+(?:<[^>]+>)?)\s+(\w+)\s*=\s*$") @@ -919,11 +924,12 @@ def _generate_replacement(self, assertion: AssertionMatch) -> str: base_indent = assertion.leading_whitespace.lstrip("\n\r") for i, call in enumerate(assertion.target_calls): self.invocation_counter += 1 - var_name = f"_cf_result{self.invocation_counter}" - if i == 0: - replacements.append(f"{assertion.leading_whitespace}Object {var_name} = {call.full_call};") + ws = assertion.leading_whitespace if i == 0 else base_indent + if self.is_void: + replacements.append(f"{ws}{call.full_call};") else: - replacements.append(f"{base_indent}Object {var_name} = {call.full_call};") + var_name = f"_cf_result{self.invocation_counter}" + replacements.append(f"{ws}Object {var_name} = {call.full_call};") return "\n".join(replacements) @@ -983,7 +989,9 @@ def _generate_exception_replacement(self, assertion: AssertionMatch) -> str: return f"{ws}// Removed assertThrows: could not extract callable" -def transform_java_assertions(source: str, function_name: str, qualified_name: str | None = None) -> str: +def transform_java_assertions( + source: str, function_name: str, qualified_name: str | None = None, is_void: bool = False +) -> str: """Transform Java test code by removing assertions and capturing function calls. This is the main entry point for Java assertion transformation. @@ -992,12 +1000,13 @@ def transform_java_assertions(source: str, function_name: str, qualified_name: s source: The Java test source code. function_name: Name of the function being tested. qualified_name: Optional fully qualified name of the function. + is_void: Whether the target function returns void. Returns: Transformed source code with assertions replaced by capture statements. """ - transformer = JavaAssertTransformer(function_name=function_name, qualified_name=qualified_name) + transformer = JavaAssertTransformer(function_name=function_name, qualified_name=qualified_name, is_void=is_void) return transformer.transform(source) @@ -1015,6 +1024,10 @@ def remove_assertions_from_test(source: str, target_function: FunctionToOptimize Transformed source code. """ + is_void = getattr(target_function, "return_type", None) == "void" return transform_java_assertions( - source=source, function_name=target_function.function_name, qualified_name=target_function.qualified_name + source=source, + function_name=target_function.function_name, + qualified_name=target_function.qualified_name, + is_void=is_void, ) diff --git a/codeflash/languages/java/resources/CodeflashHelper.java b/codeflash/languages/java/resources/CodeflashHelper.java index 9ece32679..a83894653 100644 --- a/codeflash/languages/java/resources/CodeflashHelper.java +++ b/codeflash/languages/java/resources/CodeflashHelper.java @@ -3,7 +3,9 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.ObjectOutputStream; +import java.io.PrintStream; import java.io.Serializable; +import java.io.UnsupportedEncodingException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; @@ -78,14 +80,23 @@ public static T capture( String invocationKey = testModulePath + ":" + testClassName + ":" + testFunctionName + ":" + functionGettingTested; int iterationId = getNextIterationId(invocationKey); + // Capture stdout + PrintStream originalOut = System.out; + ByteArrayOutputStream capturedStdout = new ByteArrayOutputStream(); + long startTime = System.nanoTime(); T result; try { + System.setOut(new PrintStream(capturedStdout)); result = callable.call(); } finally { + System.setOut(originalOut); long endTime = System.nanoTime(); long durationNs = endTime - startTime; + String stdoutText = null; + try { stdoutText = capturedStdout.toString("UTF-8"); } catch (UnsupportedEncodingException ignored) {} + // Write to SQLite for behavior verification writeResultToSqlite( testModulePath, @@ -96,7 +107,8 @@ public static T capture( iterationId, durationNs, null, // return_value - TODO: serialize if needed - "output" + "output", + stdoutText ); // Print timing marker for stdout parsing (backup method) @@ -118,13 +130,22 @@ public static void captureVoid( String invocationKey = testModulePath + ":" + testClassName + ":" + testFunctionName + ":" + functionGettingTested; int iterationId = getNextIterationId(invocationKey); + // Capture stdout + PrintStream originalOut = System.out; + ByteArrayOutputStream capturedStdout = new ByteArrayOutputStream(); + long startTime = System.nanoTime(); try { + System.setOut(new PrintStream(capturedStdout)); callable.call(); } finally { + System.setOut(originalOut); long endTime = System.nanoTime(); long durationNs = endTime - startTime; + String stdoutText = null; + try { stdoutText = capturedStdout.toString("UTF-8"); } catch (UnsupportedEncodingException ignored) {} + // Write to SQLite writeResultToSqlite( testModulePath, @@ -135,7 +156,8 @@ public static void captureVoid( iterationId, durationNs, null, - "output" + "output", + stdoutText ); // Print timing marker @@ -177,7 +199,8 @@ public static T capturePerf( iterationId, durationNs, null, - "output" + "output", + null ); // Print end marker with timing @@ -219,7 +242,8 @@ public static void capturePerfVoid( iterationId, durationNs, null, - "output" + "output", + null ); // Print end marker with timing @@ -277,7 +301,8 @@ private static synchronized void writeResultToSqlite( int iterationId, long runtime, byte[] returnValue, - String verificationType + String verificationType, + String stdout ) { if (OUTPUT_FILE == null || OUTPUT_FILE.isEmpty()) { return; @@ -291,8 +316,8 @@ private static synchronized void writeResultToSqlite( String sql = "INSERT INTO test_results " + "(test_module_path, test_class_name, test_function_name, function_getting_tested, " + - "loop_index, iteration_id, runtime, return_value, verification_type) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + "loop_index, iteration_id, runtime, return_value, verification_type, stdout) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement stmt = dbConnection.prepareStatement(sql)) { stmt.setString(1, testModulePath); @@ -304,6 +329,7 @@ private static synchronized void writeResultToSqlite( stmt.setLong(7, runtime); stmt.setBytes(8, returnValue); stmt.setString(9, verificationType); + stmt.setString(10, stdout); stmt.executeUpdate(); } } catch (SQLException e) { @@ -348,7 +374,8 @@ private static void ensureDbInitialized() { "iteration_id INTEGER, " + "runtime INTEGER, " + "return_value BLOB, " + - "verification_type TEXT" + + "verification_type TEXT, " + + "stdout TEXT" + ")"; try (java.sql.Statement stmt = dbConnection.createStatement()) { diff --git a/codeflash/models/function_types.py b/codeflash/models/function_types.py index bea6672b0..8b2f4862b 100644 --- a/codeflash/models/function_types.py +++ b/codeflash/models/function_types.py @@ -61,6 +61,7 @@ class FunctionToOptimize: is_method: bool = False language: str = "python" doc_start_line: Optional[int] = None + return_type: Optional[str] = None @property def top_level_parent_name(self) -> str: diff --git a/codeflash/verification/parse_test_output.py b/codeflash/verification/parse_test_output.py index deb7d3a4b..ada5f682c 100644 --- a/codeflash/verification/parse_test_output.py +++ b/codeflash/verification/parse_test_output.py @@ -487,13 +487,26 @@ def parse_sqlite_test_results(sqlite_file_path: Path, test_files: TestFiles, tes console.rule() return test_results db = None + has_stdout_column = False try: db = sqlite3.connect(sqlite_file_path) cur = db.cursor() - data = cur.execute( - "SELECT test_module_path, test_class_name, test_function_name, " - "function_getting_tested, loop_index, iteration_id, runtime, return_value,verification_type FROM test_results" - ).fetchall() + # Check if stdout column exists (backward compatibility with older schemas) + columns_info = cur.execute("PRAGMA table_info(test_results)").fetchall() + column_names = {col[1] for col in columns_info} + has_stdout_column = "stdout" in column_names + if has_stdout_column: + data = cur.execute( + "SELECT test_module_path, test_class_name, test_function_name, " + "function_getting_tested, loop_index, iteration_id, runtime, return_value, " + "verification_type, stdout FROM test_results" + ).fetchall() + else: + data = cur.execute( + "SELECT test_module_path, test_class_name, test_function_name, " + "function_getting_tested, loop_index, iteration_id, runtime, return_value, " + "verification_type FROM test_results" + ).fetchall() except Exception as e: logger.warning(f"Failed to parse test results from {sqlite_file_path}. Exception: {e}") if db is not None: @@ -642,6 +655,11 @@ def parse_sqlite_test_results(sqlite_file_path: Path, test_files: TestFiles, tes logger.debug(f"Failed to deserialize return value for {test_function_name}: {e}") continue + # Extract stdout from SQLite (Java/JS behavior capture) + captured_stdout = None + if has_stdout_column and len(val) > 9 and val[9]: + captured_stdout = val[9] + test_results.add( function_test_invocation=FunctionTestInvocation( loop_index=loop_index, @@ -660,6 +678,7 @@ def parse_sqlite_test_results(sqlite_file_path: Path, test_files: TestFiles, tes return_value=ret_val, timed_out=False, verification_type=VerificationType(verification_type) if verification_type else None, + stdout=captured_stdout, ) ) except Exception: From 036caf448eb2c21f4bd451469177679efed4eb6b Mon Sep 17 00:00:00 2001 From: HeshamHM28 Date: Wed, 25 Feb 2026 23:51:51 +0200 Subject: [PATCH 02/13] fix falling tests --- .../test_java/test_discovery.py | 12 ++- .../test_java/test_instrumentation.py | 73 +++++++++++++++---- .../test_java/test_integration.py | 6 +- .../test_java/test_run_and_parse.py | 5 ++ 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/tests/test_languages/test_java/test_discovery.py b/tests/test_languages/test_java/test_discovery.py index 9411a30c4..d44e5b765 100644 --- a/tests/test_languages/test_java/test_discovery.py +++ b/tests/test_languages/test_java/test_discovery.py @@ -132,7 +132,11 @@ def test_filter_exclude_pattern(self): assert "setData" not in method_names def test_filter_require_return(self): - """Test filtering by require_return.""" + """Test filtering by require_return. + + With require_return=True, void methods are still included (verified via test pass/fail), + but non-void methods without an actual return statement are excluded. + """ source = """ public class Example { public void doSomething() {} @@ -144,8 +148,10 @@ def test_filter_require_return(self): """ criteria = FunctionFilterCriteria(require_return=True) functions = discover_functions_from_source(source, filter_criteria=criteria) - assert len(functions) == 1 - assert functions[0].function_name == "getValue" + names = {f.function_name for f in functions} + assert "getValue" in names + assert "doSomething" in names + assert len(functions) == 2 def test_filter_by_line_count(self): """Test filtering by line count.""" diff --git a/tests/test_languages/test_java/test_instrumentation.py b/tests/test_languages/test_java/test_instrumentation.py index 7f481ccb9..fb71582d2 100644 --- a/tests/test_languages/test_java/test_instrumentation.py +++ b/tests/test_languages/test_java/test_instrumentation.py @@ -151,13 +151,19 @@ def test_instrument_behavior_mode_simple(self, tmp_path: Path): long _cf_end1_1 = -1; long _cf_start1_1 = 0; byte[] _cf_serializedResult1_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture1_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut1_1 = System.out; + String _cf_stdout1_1 = null; System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":1" + "######$!"); try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture1_1)); _cf_start1_1 = System.nanoTime(); _cf_result1_1 = calc.add(2, 2); _cf_end1_1 = System.nanoTime(); _cf_serializedResult1_1 = com.codeflash.Serializer.serialize((Object) _cf_result1_1); } finally { + System.setOut(_cf_origOut1_1); + try { _cf_stdout1_1 = _cf_stdoutCapture1_1.toString("UTF-8"); } catch (Exception _cf_encEx1_1) {} long _cf_end1_1_finally = System.nanoTime(); long _cf_dur1_1 = (_cf_end1_1 != -1 ? _cf_end1_1 : _cf_end1_1_finally) - _cf_start1_1; System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":" + "1" + "######!"); @@ -170,9 +176,9 @@ def test_instrument_behavior_mode_simple(self, tmp_path: Path): _cf_stmt1_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + - "runtime INTEGER, return_value BLOB, verification_type TEXT)"); + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); } - String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement _cf_pstmt1_1 = _cf_conn1_1.prepareStatement(_cf_sql1_1)) { _cf_pstmt1_1.setString(1, _cf_mod1); _cf_pstmt1_1.setString(2, _cf_cls1); @@ -183,6 +189,7 @@ def test_instrument_behavior_mode_simple(self, tmp_path: Path): _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); + _cf_pstmt1_1.setString(10, _cf_stdout1_1); _cf_pstmt1_1.executeUpdate(); } } @@ -278,13 +285,19 @@ def test_instrument_behavior_mode_assert_throws_expression_lambda(self, tmp_path long _cf_end2_1 = -1; long _cf_start2_1 = 0; byte[] _cf_serializedResult2_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture2_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut2_1 = System.out; + String _cf_stdout2_1 = null; System.out.println("!$######" + _cf_mod2 + ":" + _cf_cls2 + "." + _cf_test2 + ":" + _cf_fn2 + ":" + _cf_loop2 + ":1" + "######$!"); try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture2_1)); _cf_start2_1 = System.nanoTime(); _cf_result2_1 = Fibonacci.fibonacci(0); _cf_end2_1 = System.nanoTime(); _cf_serializedResult2_1 = com.codeflash.Serializer.serialize((Object) _cf_result2_1); } finally { + System.setOut(_cf_origOut2_1); + try { _cf_stdout2_1 = _cf_stdoutCapture2_1.toString("UTF-8"); } catch (Exception _cf_encEx2_1) {} long _cf_end2_1_finally = System.nanoTime(); long _cf_dur2_1 = (_cf_end2_1 != -1 ? _cf_end2_1 : _cf_end2_1_finally) - _cf_start2_1; System.out.println("!######" + _cf_mod2 + ":" + _cf_cls2 + "." + _cf_test2 + ":" + _cf_fn2 + ":" + _cf_loop2 + ":" + "1" + "######!"); @@ -297,9 +310,9 @@ def test_instrument_behavior_mode_assert_throws_expression_lambda(self, tmp_path _cf_stmt2_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + - "runtime INTEGER, return_value BLOB, verification_type TEXT)"); + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); } - String _cf_sql2_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + String _cf_sql2_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement _cf_pstmt2_1 = _cf_conn2_1.prepareStatement(_cf_sql2_1)) { _cf_pstmt2_1.setString(1, _cf_mod2); _cf_pstmt2_1.setString(2, _cf_cls2); @@ -310,6 +323,7 @@ def test_instrument_behavior_mode_assert_throws_expression_lambda(self, tmp_path _cf_pstmt2_1.setLong(7, _cf_dur2_1); _cf_pstmt2_1.setBytes(8, _cf_serializedResult2_1); _cf_pstmt2_1.setString(9, "function_call"); + _cf_pstmt2_1.setString(10, _cf_stdout2_1); _cf_pstmt2_1.executeUpdate(); } } @@ -408,13 +422,19 @@ def test_instrument_behavior_mode_assert_throws_block_lambda(self, tmp_path: Pat long _cf_end2_1 = -1; long _cf_start2_1 = 0; byte[] _cf_serializedResult2_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture2_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut2_1 = System.out; + String _cf_stdout2_1 = null; System.out.println("!$######" + _cf_mod2 + ":" + _cf_cls2 + "." + _cf_test2 + ":" + _cf_fn2 + ":" + _cf_loop2 + ":1" + "######$!"); try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture2_1)); _cf_start2_1 = System.nanoTime(); _cf_result2_1 = Fibonacci.fibonacci(0); _cf_end2_1 = System.nanoTime(); _cf_serializedResult2_1 = com.codeflash.Serializer.serialize((Object) _cf_result2_1); } finally { + System.setOut(_cf_origOut2_1); + try { _cf_stdout2_1 = _cf_stdoutCapture2_1.toString("UTF-8"); } catch (Exception _cf_encEx2_1) {} long _cf_end2_1_finally = System.nanoTime(); long _cf_dur2_1 = (_cf_end2_1 != -1 ? _cf_end2_1 : _cf_end2_1_finally) - _cf_start2_1; System.out.println("!######" + _cf_mod2 + ":" + _cf_cls2 + "." + _cf_test2 + ":" + _cf_fn2 + ":" + _cf_loop2 + ":" + "1" + "######!"); @@ -427,9 +447,9 @@ def test_instrument_behavior_mode_assert_throws_block_lambda(self, tmp_path: Pat _cf_stmt2_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + - "runtime INTEGER, return_value BLOB, verification_type TEXT)"); + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); } - String _cf_sql2_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + String _cf_sql2_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement _cf_pstmt2_1 = _cf_conn2_1.prepareStatement(_cf_sql2_1)) { _cf_pstmt2_1.setString(1, _cf_mod2); _cf_pstmt2_1.setString(2, _cf_cls2); @@ -440,6 +460,7 @@ def test_instrument_behavior_mode_assert_throws_block_lambda(self, tmp_path: Pat _cf_pstmt2_1.setLong(7, _cf_dur2_1); _cf_pstmt2_1.setBytes(8, _cf_serializedResult2_1); _cf_pstmt2_1.setString(9, "function_call"); + _cf_pstmt2_1.setString(10, _cf_stdout2_1); _cf_pstmt2_1.executeUpdate(); } } @@ -763,13 +784,19 @@ class TestKryoSerializerUsage: long _cf_end1_1 = -1; long _cf_start1_1 = 0; byte[] _cf_serializedResult1_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture1_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut1_1 = System.out; + String _cf_stdout1_1 = null; System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":1" + "######$!"); try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture1_1)); _cf_start1_1 = System.nanoTime(); _cf_result1_1 = obj.foo(); _cf_end1_1 = System.nanoTime(); _cf_serializedResult1_1 = com.codeflash.Serializer.serialize((Object) _cf_result1_1); } finally { + System.setOut(_cf_origOut1_1); + try { _cf_stdout1_1 = _cf_stdoutCapture1_1.toString("UTF-8"); } catch (Exception _cf_encEx1_1) {} long _cf_end1_1_finally = System.nanoTime(); long _cf_dur1_1 = (_cf_end1_1 != -1 ? _cf_end1_1 : _cf_end1_1_finally) - _cf_start1_1; System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":" + "1" + "######!"); @@ -782,9 +809,9 @@ class TestKryoSerializerUsage: _cf_stmt1_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + - "runtime INTEGER, return_value BLOB, verification_type TEXT)"); + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); } - String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement _cf_pstmt1_1 = _cf_conn1_1.prepareStatement(_cf_sql1_1)) { _cf_pstmt1_1.setString(1, _cf_mod1); _cf_pstmt1_1.setString(2, _cf_cls1); @@ -795,6 +822,7 @@ class TestKryoSerializerUsage: _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); + _cf_pstmt1_1.setString(10, _cf_stdout1_1); _cf_pstmt1_1.executeUpdate(); } } @@ -1284,13 +1312,19 @@ def test_instrument_generated_test_behavior_mode(self): long _cf_end1_1 = -1; long _cf_start1_1 = 0; byte[] _cf_serializedResult1_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture1_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut1_1 = System.out; + String _cf_stdout1_1 = null; System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":1" + "######$!"); try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture1_1)); _cf_start1_1 = System.nanoTime(); _cf_result1_1 = new Calculator().add(2, 2); _cf_end1_1 = System.nanoTime(); _cf_serializedResult1_1 = com.codeflash.Serializer.serialize((Object) _cf_result1_1); } finally { + System.setOut(_cf_origOut1_1); + try { _cf_stdout1_1 = _cf_stdoutCapture1_1.toString("UTF-8"); } catch (Exception _cf_encEx1_1) {} long _cf_end1_1_finally = System.nanoTime(); long _cf_dur1_1 = (_cf_end1_1 != -1 ? _cf_end1_1 : _cf_end1_1_finally) - _cf_start1_1; System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":" + "1" + "######!"); @@ -1303,9 +1337,9 @@ def test_instrument_generated_test_behavior_mode(self): _cf_stmt1_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + - "runtime INTEGER, return_value BLOB, verification_type TEXT)"); + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); } - String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement _cf_pstmt1_1 = _cf_conn1_1.prepareStatement(_cf_sql1_1)) { _cf_pstmt1_1.setString(1, _cf_mod1); _cf_pstmt1_1.setString(2, _cf_cls1); @@ -1316,6 +1350,7 @@ def test_instrument_generated_test_behavior_mode(self): _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); + _cf_pstmt1_1.setString(10, _cf_stdout1_1); _cf_pstmt1_1.executeUpdate(); } } @@ -1935,6 +1970,11 @@ def java_project(self, tmp_path: Path): yield tmp_path, src_dir, test_dir + # Clean up any SQLite files left in the shared temp dir to prevent cross-test contamination + from codeflash.code_utils.code_utils import get_run_tmp_file + for i in range(10): + get_run_tmp_file(Path(f"test_return_values_{i}.sqlite")).unlink(missing_ok=True) + # Reset language back to Python current_module._current_language = None set_current_language(Language.PYTHON) @@ -2507,13 +2547,19 @@ def test_behavior_mode_writes_to_sqlite(self, java_project): long _cf_end1_1 = -1; long _cf_start1_1 = 0; byte[] _cf_serializedResult1_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture1_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut1_1 = System.out; + String _cf_stdout1_1 = null; System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":1" + "######$!"); try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture1_1)); _cf_start1_1 = System.nanoTime(); _cf_result1_1 = counter.increment(); _cf_end1_1 = System.nanoTime(); _cf_serializedResult1_1 = com.codeflash.Serializer.serialize((Object) _cf_result1_1); } finally { + System.setOut(_cf_origOut1_1); + try { _cf_stdout1_1 = _cf_stdoutCapture1_1.toString("UTF-8"); } catch (Exception _cf_encEx1_1) {} long _cf_end1_1_finally = System.nanoTime(); long _cf_dur1_1 = (_cf_end1_1 != -1 ? _cf_end1_1 : _cf_end1_1_finally) - _cf_start1_1; System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":" + "1" + "######!"); @@ -2526,9 +2572,9 @@ def test_behavior_mode_writes_to_sqlite(self, java_project): _cf_stmt1_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + - "runtime INTEGER, return_value BLOB, verification_type TEXT)"); + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); } - String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement _cf_pstmt1_1 = _cf_conn1_1.prepareStatement(_cf_sql1_1)) { _cf_pstmt1_1.setString(1, _cf_mod1); _cf_pstmt1_1.setString(2, _cf_cls1); @@ -2539,6 +2585,7 @@ def test_behavior_mode_writes_to_sqlite(self, java_project): _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); + _cf_pstmt1_1.setString(10, _cf_stdout1_1); _cf_pstmt1_1.executeUpdate(); } } @@ -2633,7 +2680,7 @@ def test_behavior_mode_writes_to_sqlite(self, java_project): for row in rows: test_module_path, test_class_name, test_function_name, function_getting_tested, \ - loop_index, iteration_id, runtime, return_value, verification_type = row + loop_index, iteration_id, runtime, return_value, verification_type, stdout = row # Verify fields assert test_module_path == "CounterTest" diff --git a/tests/test_languages/test_java/test_integration.py b/tests/test_languages/test_java/test_integration.py index d0820e38e..42f0d741e 100644 --- a/tests/test_languages/test_java/test_integration.py +++ b/tests/test_languages/test_java/test_integration.py @@ -334,11 +334,13 @@ def test_filter_by_various_criteria(self): assert "publicMethod" in public_names or len(functions) >= 0 # Test filtering by require_return + # With require_return=True, void methods are still included (verified via test pass/fail), + # but non-void methods without return statements would be excluded criteria = FunctionFilterCriteria(require_return=True) functions = discover_functions_from_source(source, filter_criteria=criteria) - # voidMethod should be excluded names = {f.function_name for f in functions} - assert "voidMethod" not in names + assert "voidMethod" in names + assert "publicMethod" in names class TestNormalizationIntegration: diff --git a/tests/test_languages/test_java/test_run_and_parse.py b/tests/test_languages/test_java/test_run_and_parse.py index 67c03b8da..a765a8fba 100644 --- a/tests/test_languages/test_java/test_run_and_parse.py +++ b/tests/test_languages/test_java/test_run_and_parse.py @@ -112,6 +112,11 @@ def java_project(tmp_path: Path): yield tmp_path, src_dir, test_dir + # Clean up any SQLite files left in the shared temp dir to prevent cross-test contamination + from codeflash.code_utils.code_utils import get_run_tmp_file + for i in range(10): + get_run_tmp_file(Path(f"test_return_values_{i}.sqlite")).unlink(missing_ok=True) + current_module._current_language = None set_current_language(Language.PYTHON) From 463a0642db75042d5c9c4a22d0d1e1a475e0e3de Mon Sep 17 00:00:00 2001 From: HeshamHM28 Date: Thu, 26 Feb 2026 01:27:28 +0200 Subject: [PATCH 03/13] feat: Add tests for void function comparison and instrumentation --- .../test_java/test_comparator.py | 264 +++++++++ .../test_java/test_instrumentation.py | 542 ++++++++++++++++++ 2 files changed, 806 insertions(+) diff --git a/tests/test_languages/test_java/test_comparator.py b/tests/test_languages/test_java/test_comparator.py index aa423bbca..1580ec170 100644 --- a/tests/test_languages/test_java/test_comparator.py +++ b/tests/test_languages/test_java/test_comparator.py @@ -1198,3 +1198,267 @@ def test_comparator_infinity_handling( assert equivalent is True assert len(diffs) == 0 + + +class TestVoidFunctionComparison: + """Tests for void function comparison using compare_invocations_directly.""" + + def test_void_both_null_result_equivalent(self): + """Both original and candidate have None result_json (void success).""" + original = { + "1": {"result_json": None, "error_json": None}, + } + candidate = { + "1": {"result_json": None, "error_json": None}, + } + + equivalent, diffs = compare_invocations_directly(original, candidate) + + assert equivalent is True + assert len(diffs) == 0 + + def test_void_null_vs_non_null_result(self): + """Original void (None) vs candidate with return value should differ.""" + original = { + "1": {"result_json": None, "error_json": None}, + } + candidate = { + "1": {"result_json": "42", "error_json": None}, + } + + equivalent, diffs = compare_invocations_directly(original, candidate) + + assert equivalent is False + assert len(diffs) == 1 + assert diffs[0].scope == TestDiffScope.RETURN_VALUE + + def test_void_non_null_vs_null_result(self): + """Original with return value vs candidate void (None) should differ.""" + original = { + "1": {"result_json": "42", "error_json": None}, + } + candidate = { + "1": {"result_json": None, "error_json": None}, + } + + equivalent, diffs = compare_invocations_directly(original, candidate) + + assert equivalent is False + assert len(diffs) == 1 + assert diffs[0].scope == TestDiffScope.RETURN_VALUE + + def test_void_same_serialized_side_effects(self): + """Identical side-effect serializations (Object[] arrays) should be equivalent.""" + original = { + "1": {"result_json": "[1, 2, 3]", "error_json": None}, + } + candidate = { + "1": {"result_json": "[1, 2, 3]", "error_json": None}, + } + + equivalent, diffs = compare_invocations_directly(original, candidate) + + assert equivalent is True + assert len(diffs) == 0 + + def test_void_different_serialized_side_effects(self): + """Different side-effect serializations should be detected.""" + original = { + "1": {"result_json": "[1, 2, 3]", "error_json": None}, + } + candidate = { + "1": {"result_json": "[1, 2, 99]", "error_json": None}, + } + + equivalent, diffs = compare_invocations_directly(original, candidate) + + assert equivalent is False + assert len(diffs) == 1 + assert diffs[0].scope == TestDiffScope.RETURN_VALUE + assert diffs[0].original_value == "[1, 2, 3]" + assert diffs[0].candidate_value == "[1, 2, 99]" + + def test_void_exception_in_candidate(self): + """Void success in original vs exception in candidate should differ.""" + original = { + "1": {"result_json": None, "error_json": None}, + } + candidate = { + "1": {"result_json": None, "error_json": '{"type": "NullPointerException"}'}, + } + + equivalent, diffs = compare_invocations_directly(original, candidate) + + assert equivalent is False + assert len(diffs) == 1 + assert diffs[0].scope == TestDiffScope.DID_PASS + + def test_void_multiple_invocations_mixed(self): + """Multiple void invocations: some matching, some differing.""" + original = { + "1": {"result_json": None, "error_json": None}, + "2": {"result_json": "[10, 20]", "error_json": None}, + "3": {"result_json": None, "error_json": None}, + } + candidate = { + "1": {"result_json": None, "error_json": None}, + "2": {"result_json": "[10, 99]", "error_json": None}, + "3": {"result_json": None, "error_json": '{"type": "RuntimeException"}'}, + } + + equivalent, diffs = compare_invocations_directly(original, candidate) + + assert equivalent is False + assert len(diffs) == 2 + assert diffs[0].scope == TestDiffScope.RETURN_VALUE + assert diffs[0].original_value == "[10, 20]" + assert diffs[0].candidate_value == "[10, 99]" + assert diffs[1].scope == TestDiffScope.DID_PASS + + +@requires_java +class TestVoidSqliteComparison: + """Tests for void function comparison via Java Comparator with 10-column SQLite schema.""" + + @pytest.fixture + def create_void_test_results_db(self): + """Create a test SQLite database with 10-column schema (including stdout).""" + + def _create(path: Path, results: list[dict]): + conn = sqlite3.connect(path) + cursor = conn.cursor() + + cursor.execute( + """ + CREATE TABLE test_results ( + test_module_path TEXT, + test_class_name TEXT, + test_function_name TEXT, + function_getting_tested TEXT, + loop_index INTEGER, + iteration_id TEXT, + runtime INTEGER, + return_value BLOB, + verification_type TEXT, + stdout TEXT + ) + """ + ) + + for result in results: + cursor.execute( + """ + INSERT INTO test_results + (test_module_path, test_class_name, test_function_name, + function_getting_tested, loop_index, iteration_id, + runtime, return_value, verification_type, stdout) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + result.get("test_module_path", "TestModule"), + result.get("test_class_name", "TestClass"), + result.get("test_function_name", "testMethod"), + result.get("function_getting_tested", "targetMethod"), + result.get("loop_index", 1), + result.get("iteration_id", "1_0"), + result.get("runtime", 1000000), + result.get("return_value"), + result.get("verification_type", "function_call"), + result.get("stdout"), + ), + ) + + conn.commit() + conn.close() + return path + + return _create + + def test_void_sqlite_both_null_return_same_stdout( + self, tmp_path: Path, create_void_test_results_db + ): + """Both DBs have NULL return_value and same stdout — equivalent.""" + original_path = tmp_path / "original.db" + candidate_path = tmp_path / "candidate.db" + + results = [ + { + "test_class_name": "PrinterTest", + "function_getting_tested": "printMessage", + "loop_index": 1, + "iteration_id": "1_0", + "return_value": None, + "stdout": "Hello World\n", + }, + ] + + create_void_test_results_db(original_path, results) + create_void_test_results_db(candidate_path, results) + + equivalent, diffs = compare_test_results(original_path, candidate_path) + + assert equivalent is True + assert len(diffs) == 0 + + def test_void_sqlite_different_stdout( + self, tmp_path: Path, create_void_test_results_db + ): + """Both DBs have NULL return_value but different stdout — not equivalent.""" + original_path = tmp_path / "original.db" + candidate_path = tmp_path / "candidate.db" + + original_results = [ + { + "test_class_name": "LoggerTest", + "function_getting_tested": "log", + "loop_index": 1, + "iteration_id": "1_0", + "return_value": None, + "stdout": "INFO: Starting\n", + }, + ] + + candidate_results = [ + { + "test_class_name": "LoggerTest", + "function_getting_tested": "log", + "loop_index": 1, + "iteration_id": "1_0", + "return_value": None, + "stdout": "DEBUG: Starting\n", + }, + ] + + create_void_test_results_db(original_path, original_results) + create_void_test_results_db(candidate_path, candidate_results) + + equivalent, diffs = compare_test_results(original_path, candidate_path) + + assert equivalent is False + assert len(diffs) == 1 + + def test_void_sqlite_null_stdout_both( + self, tmp_path: Path, create_void_test_results_db + ): + """Both DBs have NULL return_value and NULL stdout — equivalent.""" + original_path = tmp_path / "original.db" + candidate_path = tmp_path / "candidate.db" + + results = [ + { + "test_class_name": "WorkerTest", + "function_getting_tested": "doWork", + "loop_index": 1, + "iteration_id": "1_0", + "return_value": None, + "stdout": None, + }, + ] + + create_void_test_results_db(original_path, results) + create_void_test_results_db(candidate_path, results) + + equivalent, diffs = compare_test_results(original_path, candidate_path) + + assert equivalent is True + assert len(diffs) == 0 diff --git a/tests/test_languages/test_java/test_instrumentation.py b/tests/test_languages/test_java/test_instrumentation.py index fb71582d2..81324f2cf 100644 --- a/tests/test_languages/test_java/test_instrumentation.py +++ b/tests/test_languages/test_java/test_instrumentation.py @@ -2951,3 +2951,545 @@ def __init__(self, path): assert loop_id_2_count == 2, f"Expected 2 markers for loopId 2, got {loop_id_2_count}" assert loop_id_3_count == 2, f"Expected 2 markers for loopId 3, got {loop_id_3_count}" + + +class TestVoidFunctionInstrumentation: + """Tests for void function instrumentation with exact string equality.""" + + def test_void_instance_method_with_args(self, tmp_path: Path): + """Void instance method serializes receiver + args as side effects.""" + source = """import org.junit.jupiter.api.Test; + +public class WorkerTest { + @Test + public void testDoWork() { + Worker obj = new Worker(); + obj.doWork(42); + } +} +""" + test_file = tmp_path / "WorkerTest.java" + test_file.write_text(source) + + func = FunctionToOptimize( + function_name="doWork", + file_path=tmp_path / "Worker.java", + starting_line=1, + ending_line=5, + parents=[], + is_method=True, + language="java", + return_type="void", + ) + + success, result = instrument_existing_test( + test_string=source, + function_to_optimize=func, + mode="behavior", + test_path=test_file, + ) + + expected = """import org.junit.jupiter.api.Test; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; + +public class WorkerTest__perfinstrumented { + @Test + public void testDoWork() { + // Codeflash behavior instrumentation + int _cf_loop1 = Integer.parseInt(System.getenv("CODEFLASH_LOOP_INDEX")); + int _cf_iter1 = 1; + String _cf_mod1 = "WorkerTest"; + String _cf_cls1 = "WorkerTest"; + String _cf_fn1 = "doWork"; + String _cf_outputFile1 = System.getenv("CODEFLASH_OUTPUT_FILE"); + String _cf_testIteration1 = System.getenv("CODEFLASH_TEST_ITERATION"); + if (_cf_testIteration1 == null) _cf_testIteration1 = "0"; + String _cf_test1 = "testDoWork"; + Worker obj = new Worker(); + long _cf_end1_1 = -1; + long _cf_start1_1 = 0; + byte[] _cf_serializedResult1_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture1_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut1_1 = System.out; + String _cf_stdout1_1 = null; + System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":1" + "######$!"); + try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture1_1)); + _cf_start1_1 = System.nanoTime(); + obj.doWork(42); + _cf_end1_1 = System.nanoTime(); + _cf_serializedResult1_1 = com.codeflash.Serializer.serialize(new Object[]{obj, 42}); + } finally { + System.setOut(_cf_origOut1_1); + try { _cf_stdout1_1 = _cf_stdoutCapture1_1.toString("UTF-8"); } catch (Exception _cf_encEx1_1) {} + long _cf_end1_1_finally = System.nanoTime(); + long _cf_dur1_1 = (_cf_end1_1 != -1 ? _cf_end1_1 : _cf_end1_1_finally) - _cf_start1_1; + System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":" + "1" + "######!"); + // Write to SQLite if output file is set + if (_cf_outputFile1 != null && !_cf_outputFile1.isEmpty()) { + try { + Class.forName("org.sqlite.JDBC"); + try (Connection _cf_conn1_1 = DriverManager.getConnection("jdbc:sqlite:" + _cf_outputFile1)) { + try (java.sql.Statement _cf_stmt1_1 = _cf_conn1_1.createStatement()) { + _cf_stmt1_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); + } + String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + try (PreparedStatement _cf_pstmt1_1 = _cf_conn1_1.prepareStatement(_cf_sql1_1)) { + _cf_pstmt1_1.setString(1, _cf_mod1); + _cf_pstmt1_1.setString(2, _cf_cls1); + _cf_pstmt1_1.setString(3, _cf_test1); + _cf_pstmt1_1.setString(4, _cf_fn1); + _cf_pstmt1_1.setInt(5, _cf_loop1); + _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setLong(7, _cf_dur1_1); + _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); + _cf_pstmt1_1.setString(9, "function_call"); + _cf_pstmt1_1.setString(10, _cf_stdout1_1); + _cf_pstmt1_1.executeUpdate(); + } + } + } catch (Exception _cf_e1_1) { + System.err.println("CodeflashHelper: SQLite error: " + _cf_e1_1.getMessage()); + } + } + } + } +} +""" + assert success is True + assert result == expected + + def test_void_static_method_excludes_receiver(self, tmp_path: Path): + """Void static method excludes uppercase receiver from serialization.""" + source = """import org.junit.jupiter.api.Test; + +public class UtilsTest { + @Test + public void testProcess() { + Utils.process("data"); + } +} +""" + test_file = tmp_path / "UtilsTest.java" + test_file.write_text(source) + + func = FunctionToOptimize( + function_name="process", + file_path=tmp_path / "Utils.java", + starting_line=1, + ending_line=5, + parents=[], + is_method=True, + language="java", + return_type="void", + ) + + success, result = instrument_existing_test( + test_string=source, + function_to_optimize=func, + mode="behavior", + test_path=test_file, + ) + + expected = """import org.junit.jupiter.api.Test; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; + +public class UtilsTest__perfinstrumented { + @Test + public void testProcess() { + // Codeflash behavior instrumentation + int _cf_loop1 = Integer.parseInt(System.getenv("CODEFLASH_LOOP_INDEX")); + int _cf_iter1 = 1; + String _cf_mod1 = "UtilsTest"; + String _cf_cls1 = "UtilsTest"; + String _cf_fn1 = "process"; + String _cf_outputFile1 = System.getenv("CODEFLASH_OUTPUT_FILE"); + String _cf_testIteration1 = System.getenv("CODEFLASH_TEST_ITERATION"); + if (_cf_testIteration1 == null) _cf_testIteration1 = "0"; + String _cf_test1 = "testProcess"; + long _cf_end1_1 = -1; + long _cf_start1_1 = 0; + byte[] _cf_serializedResult1_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture1_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut1_1 = System.out; + String _cf_stdout1_1 = null; + System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":1" + "######$!"); + try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture1_1)); + _cf_start1_1 = System.nanoTime(); + Utils.process("data"); + _cf_end1_1 = System.nanoTime(); + _cf_serializedResult1_1 = com.codeflash.Serializer.serialize(new Object[]{"data"}); + } finally { + System.setOut(_cf_origOut1_1); + try { _cf_stdout1_1 = _cf_stdoutCapture1_1.toString("UTF-8"); } catch (Exception _cf_encEx1_1) {} + long _cf_end1_1_finally = System.nanoTime(); + long _cf_dur1_1 = (_cf_end1_1 != -1 ? _cf_end1_1 : _cf_end1_1_finally) - _cf_start1_1; + System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":" + "1" + "######!"); + // Write to SQLite if output file is set + if (_cf_outputFile1 != null && !_cf_outputFile1.isEmpty()) { + try { + Class.forName("org.sqlite.JDBC"); + try (Connection _cf_conn1_1 = DriverManager.getConnection("jdbc:sqlite:" + _cf_outputFile1)) { + try (java.sql.Statement _cf_stmt1_1 = _cf_conn1_1.createStatement()) { + _cf_stmt1_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); + } + String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + try (PreparedStatement _cf_pstmt1_1 = _cf_conn1_1.prepareStatement(_cf_sql1_1)) { + _cf_pstmt1_1.setString(1, _cf_mod1); + _cf_pstmt1_1.setString(2, _cf_cls1); + _cf_pstmt1_1.setString(3, _cf_test1); + _cf_pstmt1_1.setString(4, _cf_fn1); + _cf_pstmt1_1.setInt(5, _cf_loop1); + _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setLong(7, _cf_dur1_1); + _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); + _cf_pstmt1_1.setString(9, "function_call"); + _cf_pstmt1_1.setString(10, _cf_stdout1_1); + _cf_pstmt1_1.executeUpdate(); + } + } + } catch (Exception _cf_e1_1) { + System.err.println("CodeflashHelper: SQLite error: " + _cf_e1_1.getMessage()); + } + } + } + } +} +""" + assert success is True + assert result == expected + + def test_void_instance_no_args_serializes_receiver_only(self, tmp_path: Path): + """Void instance method with no args serializes only the receiver.""" + source = """import org.junit.jupiter.api.Test; + +public class CacheTest { + @Test + public void testReset() { + Cache cache = new Cache(); + cache.reset(); + } +} +""" + test_file = tmp_path / "CacheTest.java" + test_file.write_text(source) + + func = FunctionToOptimize( + function_name="reset", + file_path=tmp_path / "Cache.java", + starting_line=1, + ending_line=5, + parents=[], + is_method=True, + language="java", + return_type="void", + ) + + success, result = instrument_existing_test( + test_string=source, + function_to_optimize=func, + mode="behavior", + test_path=test_file, + ) + + expected = """import org.junit.jupiter.api.Test; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; + +public class CacheTest__perfinstrumented { + @Test + public void testReset() { + // Codeflash behavior instrumentation + int _cf_loop1 = Integer.parseInt(System.getenv("CODEFLASH_LOOP_INDEX")); + int _cf_iter1 = 1; + String _cf_mod1 = "CacheTest"; + String _cf_cls1 = "CacheTest"; + String _cf_fn1 = "reset"; + String _cf_outputFile1 = System.getenv("CODEFLASH_OUTPUT_FILE"); + String _cf_testIteration1 = System.getenv("CODEFLASH_TEST_ITERATION"); + if (_cf_testIteration1 == null) _cf_testIteration1 = "0"; + String _cf_test1 = "testReset"; + Cache cache = new Cache(); + long _cf_end1_1 = -1; + long _cf_start1_1 = 0; + byte[] _cf_serializedResult1_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture1_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut1_1 = System.out; + String _cf_stdout1_1 = null; + System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":1" + "######$!"); + try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture1_1)); + _cf_start1_1 = System.nanoTime(); + cache.reset(); + _cf_end1_1 = System.nanoTime(); + _cf_serializedResult1_1 = com.codeflash.Serializer.serialize(new Object[]{cache}); + } finally { + System.setOut(_cf_origOut1_1); + try { _cf_stdout1_1 = _cf_stdoutCapture1_1.toString("UTF-8"); } catch (Exception _cf_encEx1_1) {} + long _cf_end1_1_finally = System.nanoTime(); + long _cf_dur1_1 = (_cf_end1_1 != -1 ? _cf_end1_1 : _cf_end1_1_finally) - _cf_start1_1; + System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":" + "1" + "######!"); + // Write to SQLite if output file is set + if (_cf_outputFile1 != null && !_cf_outputFile1.isEmpty()) { + try { + Class.forName("org.sqlite.JDBC"); + try (Connection _cf_conn1_1 = DriverManager.getConnection("jdbc:sqlite:" + _cf_outputFile1)) { + try (java.sql.Statement _cf_stmt1_1 = _cf_conn1_1.createStatement()) { + _cf_stmt1_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); + } + String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + try (PreparedStatement _cf_pstmt1_1 = _cf_conn1_1.prepareStatement(_cf_sql1_1)) { + _cf_pstmt1_1.setString(1, _cf_mod1); + _cf_pstmt1_1.setString(2, _cf_cls1); + _cf_pstmt1_1.setString(3, _cf_test1); + _cf_pstmt1_1.setString(4, _cf_fn1); + _cf_pstmt1_1.setInt(5, _cf_loop1); + _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setLong(7, _cf_dur1_1); + _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); + _cf_pstmt1_1.setString(9, "function_call"); + _cf_pstmt1_1.setString(10, _cf_stdout1_1); + _cf_pstmt1_1.executeUpdate(); + } + } + } catch (Exception _cf_e1_1) { + System.err.println("CodeflashHelper: SQLite error: " + _cf_e1_1.getMessage()); + } + } + } + } +} +""" + assert success is True + assert result == expected + + def test_void_static_no_args_serializes_null(self, tmp_path: Path): + """Void static method with no args serializes null (no parts).""" + source = """import org.junit.jupiter.api.Test; + +public class ConfigTest { + @Test + public void testReload() { + Config.reload(); + } +} +""" + test_file = tmp_path / "ConfigTest.java" + test_file.write_text(source) + + func = FunctionToOptimize( + function_name="reload", + file_path=tmp_path / "Config.java", + starting_line=1, + ending_line=5, + parents=[], + is_method=True, + language="java", + return_type="void", + ) + + success, result = instrument_existing_test( + test_string=source, + function_to_optimize=func, + mode="behavior", + test_path=test_file, + ) + + expected = """import org.junit.jupiter.api.Test; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; + +public class ConfigTest__perfinstrumented { + @Test + public void testReload() { + // Codeflash behavior instrumentation + int _cf_loop1 = Integer.parseInt(System.getenv("CODEFLASH_LOOP_INDEX")); + int _cf_iter1 = 1; + String _cf_mod1 = "ConfigTest"; + String _cf_cls1 = "ConfigTest"; + String _cf_fn1 = "reload"; + String _cf_outputFile1 = System.getenv("CODEFLASH_OUTPUT_FILE"); + String _cf_testIteration1 = System.getenv("CODEFLASH_TEST_ITERATION"); + if (_cf_testIteration1 == null) _cf_testIteration1 = "0"; + String _cf_test1 = "testReload"; + long _cf_end1_1 = -1; + long _cf_start1_1 = 0; + byte[] _cf_serializedResult1_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture1_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut1_1 = System.out; + String _cf_stdout1_1 = null; + System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":1" + "######$!"); + try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture1_1)); + _cf_start1_1 = System.nanoTime(); + Config.reload(); + _cf_end1_1 = System.nanoTime(); + _cf_serializedResult1_1 = null; + } finally { + System.setOut(_cf_origOut1_1); + try { _cf_stdout1_1 = _cf_stdoutCapture1_1.toString("UTF-8"); } catch (Exception _cf_encEx1_1) {} + long _cf_end1_1_finally = System.nanoTime(); + long _cf_dur1_1 = (_cf_end1_1 != -1 ? _cf_end1_1 : _cf_end1_1_finally) - _cf_start1_1; + System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":" + "1" + "######!"); + // Write to SQLite if output file is set + if (_cf_outputFile1 != null && !_cf_outputFile1.isEmpty()) { + try { + Class.forName("org.sqlite.JDBC"); + try (Connection _cf_conn1_1 = DriverManager.getConnection("jdbc:sqlite:" + _cf_outputFile1)) { + try (java.sql.Statement _cf_stmt1_1 = _cf_conn1_1.createStatement()) { + _cf_stmt1_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); + } + String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + try (PreparedStatement _cf_pstmt1_1 = _cf_conn1_1.prepareStatement(_cf_sql1_1)) { + _cf_pstmt1_1.setString(1, _cf_mod1); + _cf_pstmt1_1.setString(2, _cf_cls1); + _cf_pstmt1_1.setString(3, _cf_test1); + _cf_pstmt1_1.setString(4, _cf_fn1); + _cf_pstmt1_1.setInt(5, _cf_loop1); + _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setLong(7, _cf_dur1_1); + _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); + _cf_pstmt1_1.setString(9, "function_call"); + _cf_pstmt1_1.setString(10, _cf_stdout1_1); + _cf_pstmt1_1.executeUpdate(); + } + } + } catch (Exception _cf_e1_1) { + System.err.println("CodeflashHelper: SQLite error: " + _cf_e1_1.getMessage()); + } + } + } + } +} +""" + assert success is True + assert result == expected + + def test_void_instance_multiple_args(self, tmp_path: Path): + """Void instance method with multiple args serializes receiver + all args.""" + source = """import org.junit.jupiter.api.Test; + +public class SwapperTest { + @Test + public void testSwap() { + Swapper s = new Swapper(); + int[] arr = {1, 2}; + s.swap(arr, 0, 1); + } +} +""" + test_file = tmp_path / "SwapperTest.java" + test_file.write_text(source) + + func = FunctionToOptimize( + function_name="swap", + file_path=tmp_path / "Swapper.java", + starting_line=1, + ending_line=5, + parents=[], + is_method=True, + language="java", + return_type="void", + ) + + success, result = instrument_existing_test( + test_string=source, + function_to_optimize=func, + mode="behavior", + test_path=test_file, + ) + + expected = """import org.junit.jupiter.api.Test; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; + +public class SwapperTest__perfinstrumented { + @Test + public void testSwap() { + // Codeflash behavior instrumentation + int _cf_loop1 = Integer.parseInt(System.getenv("CODEFLASH_LOOP_INDEX")); + int _cf_iter1 = 1; + String _cf_mod1 = "SwapperTest"; + String _cf_cls1 = "SwapperTest"; + String _cf_fn1 = "swap"; + String _cf_outputFile1 = System.getenv("CODEFLASH_OUTPUT_FILE"); + String _cf_testIteration1 = System.getenv("CODEFLASH_TEST_ITERATION"); + if (_cf_testIteration1 == null) _cf_testIteration1 = "0"; + String _cf_test1 = "testSwap"; + Swapper s = new Swapper(); + int[] arr = {1, 2}; + long _cf_end1_1 = -1; + long _cf_start1_1 = 0; + byte[] _cf_serializedResult1_1 = null; + java.io.ByteArrayOutputStream _cf_stdoutCapture1_1 = new java.io.ByteArrayOutputStream(); + java.io.PrintStream _cf_origOut1_1 = System.out; + String _cf_stdout1_1 = null; + System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":1" + "######$!"); + try { + System.setOut(new java.io.PrintStream(_cf_stdoutCapture1_1)); + _cf_start1_1 = System.nanoTime(); + s.swap(arr, 0, 1); + _cf_end1_1 = System.nanoTime(); + _cf_serializedResult1_1 = com.codeflash.Serializer.serialize(new Object[]{s, arr, 0, 1}); + } finally { + System.setOut(_cf_origOut1_1); + try { _cf_stdout1_1 = _cf_stdoutCapture1_1.toString("UTF-8"); } catch (Exception _cf_encEx1_1) {} + long _cf_end1_1_finally = System.nanoTime(); + long _cf_dur1_1 = (_cf_end1_1 != -1 ? _cf_end1_1 : _cf_end1_1_finally) - _cf_start1_1; + System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loop1 + ":" + "1" + "######!"); + // Write to SQLite if output file is set + if (_cf_outputFile1 != null && !_cf_outputFile1.isEmpty()) { + try { + Class.forName("org.sqlite.JDBC"); + try (Connection _cf_conn1_1 = DriverManager.getConnection("jdbc:sqlite:" + _cf_outputFile1)) { + try (java.sql.Statement _cf_stmt1_1 = _cf_conn1_1.createStatement()) { + _cf_stmt1_1.execute("CREATE TABLE IF NOT EXISTS test_results (" + + "test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " + + "function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " + + "runtime INTEGER, return_value BLOB, verification_type TEXT, stdout TEXT)"); + } + String _cf_sql1_1 = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + try (PreparedStatement _cf_pstmt1_1 = _cf_conn1_1.prepareStatement(_cf_sql1_1)) { + _cf_pstmt1_1.setString(1, _cf_mod1); + _cf_pstmt1_1.setString(2, _cf_cls1); + _cf_pstmt1_1.setString(3, _cf_test1); + _cf_pstmt1_1.setString(4, _cf_fn1); + _cf_pstmt1_1.setInt(5, _cf_loop1); + _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setLong(7, _cf_dur1_1); + _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); + _cf_pstmt1_1.setString(9, "function_call"); + _cf_pstmt1_1.setString(10, _cf_stdout1_1); + _cf_pstmt1_1.executeUpdate(); + } + } + } catch (Exception _cf_e1_1) { + System.err.println("CodeflashHelper: SQLite error: " + _cf_e1_1.getMessage()); + } + } + } + } +} +""" + assert success is True + assert result == expected From 8028174126baf3ac3b320f6e993a5e316770e9f7 Mon Sep 17 00:00:00 2001 From: HeshamHM28 Date: Thu, 26 Feb 2026 02:57:01 +0200 Subject: [PATCH 04/13] feat: Enhance wrap_target_calls_with_treesitter and _add_behavior_instrumentation to support return type handling --- codeflash/languages/java/instrumentation.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/codeflash/languages/java/instrumentation.py b/codeflash/languages/java/instrumentation.py index e7689e81d..5abd275d3 100644 --- a/codeflash/languages/java/instrumentation.py +++ b/codeflash/languages/java/instrumentation.py @@ -279,6 +279,7 @@ def wrap_target_calls_with_treesitter( class_name: str = "", test_method_name: str = "", is_void: bool = False, + return_type: str | None = None, ) -> tuple[list[str], int]: """Replace target method calls in body_lines with capture + serialize using tree-sitter. @@ -348,6 +349,8 @@ def wrap_target_calls_with_treesitter( call_counter += 1 var_name = f"_cf_result{iter_id}_{call_counter}" cast_type = _infer_array_cast_type(body_line) + if not cast_type and return_type and return_type not in ("void", "Object"): + cast_type = return_type var_with_cast = f"({cast_type}){var_name}" if cast_type else var_name # For void functions, we can't assign the return value to a variable @@ -704,7 +707,8 @@ def instrument_existing_test( # replacing substrings of other identifiers. modified_source = re.sub(rf"\b{re.escape(original_class_name)}\b", new_class_name, source) - is_void = getattr(function_to_optimize, "return_type", None) == "void" + return_type = getattr(function_to_optimize, "return_type", None) + is_void = return_type == "void" # Add timing instrumentation to test methods # Use original class name (without suffix) in timing markers for consistency with Python @@ -716,14 +720,14 @@ def instrument_existing_test( ) else: # Behavior mode: add timing instrumentation that also writes to SQLite - modified_source = _add_behavior_instrumentation(modified_source, original_class_name, func_name, is_void=is_void) + modified_source = _add_behavior_instrumentation(modified_source, original_class_name, func_name, is_void=is_void, return_type=return_type) logger.debug("Java %s testing for %s: renamed class %s -> %s", mode, func_name, original_class_name, new_class_name) # Why return True here? return True, modified_source -def _add_behavior_instrumentation(source: str, class_name: str, func_name: str, is_void: bool = False) -> str: +def _add_behavior_instrumentation(source: str, class_name: str, func_name: str, is_void: bool = False, return_type: str | None = None) -> str: """Add behavior instrumentation to test methods. For behavior mode, this adds: @@ -865,6 +869,7 @@ def _add_behavior_instrumentation(source: str, class_name: str, func_name: str, class_name=class_name, test_method_name=test_method_name, is_void=is_void, + return_type=return_type, ) # Add behavior instrumentation setup code (shared variables for all calls in the method) From 188fb6988b212cf0b0b8feb09831779076fe76b2 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 26 Feb 2026 02:27:15 +0000 Subject: [PATCH 05/13] fix: sort test class names for ConsoleLauncher and rebuild runtime JAR Sort class names in _get_test_class_names() to match Maven Surefire's alphabetical execution order. Without sorting, iteration_id collisions across test classes resolve differently between Maven (original) and direct JVM (candidate) runs, causing spurious "DIFFERENT" comparisons. Also rebuild the codeflash-runtime JAR to include the Comparator$TestResult inner class needed for stdout comparison support. Co-Authored-By: Claude Opus 4.6 --- .../resources/codeflash-runtime-1.0.0.jar | Bin 14629214 -> 14630444 bytes codeflash/languages/java/test_runner.py | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/codeflash/languages/java/resources/codeflash-runtime-1.0.0.jar b/codeflash/languages/java/resources/codeflash-runtime-1.0.0.jar index 8486632948b3f07474f81831d9d00f722b722b63..f3577951de73c757103efdd300b092fd9f1c0cd7 100644 GIT binary patch delta 44650 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 Q)pF-vGNaJj>}S*d4|DRe{{R30 diff --git a/codeflash/languages/java/test_runner.py b/codeflash/languages/java/test_runner.py index 45a23b9e7..5edbf85ee 100644 --- a/codeflash/languages/java/test_runner.py +++ b/codeflash/languages/java/test_runner.py @@ -845,7 +845,10 @@ def _get_test_class_names(test_paths: Any, mode: str = "performance") -> list[st elif isinstance(path, str): class_names.append(path) - return class_names + # Sort to match Maven Surefire's alphabetical execution order. + # Without sorting, iteration_id collisions across test classes resolve + # differently between Maven (original) and direct JVM (candidate) runs. + return sorted(class_names) def _get_empty_result(maven_root: Path, test_module: str | None) -> tuple[Path, Any]: From 0f9321c44104e58dae7e2db19e26f945015f4984 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 26 Feb 2026 04:33:05 +0000 Subject: [PATCH 06/13] fix: use globally unique iteration_id for Java behavioral test comparison The iteration_id stored in SQLite was just the call_counter (e.g., "1"), which caused collisions when multiple test files/methods each had their first call to the target function. The Comparator's HashMap would keep only the last writer for each key, causing different test data to be compared between original and candidate runs. Changed iteration_id format to "ClassName.testMethodName.callCounter_testIteration" which is globally unique across all test files and properly maps between original (suffix _0) and candidate (suffix _1) runs. Co-Authored-By: Claude Opus 4.6 --- codeflash/languages/java/instrumentation.py | 2 +- .../test_java/test_instrumentation.py | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/codeflash/languages/java/instrumentation.py b/codeflash/languages/java/instrumentation.py index 5abd275d3..1d7928d26 100644 --- a/codeflash/languages/java/instrumentation.py +++ b/codeflash/languages/java/instrumentation.py @@ -236,7 +236,7 @@ def _generate_sqlite_write_code( f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setString(3, _cf_test{iter_id});", f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setString(4, _cf_fn{iter_id});", f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setInt(5, _cf_loop{iter_id});", - f'{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setString(6, "{call_counter}");', + f'{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setString(6, _cf_cls{iter_id} + "." + _cf_test{iter_id} + ".{call_counter}_" + _cf_testIteration{iter_id});', f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setLong(7, _cf_dur{iter_id}_{call_counter});", f"{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setBytes(8, _cf_serializedResult{iter_id}_{call_counter});", f'{inner_indent} _cf_pstmt{iter_id}_{call_counter}.setString(9, "function_call");', diff --git a/tests/test_languages/test_java/test_instrumentation.py b/tests/test_languages/test_java/test_instrumentation.py index 7b3434830..402f31f26 100644 --- a/tests/test_languages/test_java/test_instrumentation.py +++ b/tests/test_languages/test_java/test_instrumentation.py @@ -185,7 +185,7 @@ def test_instrument_behavior_mode_simple(self, tmp_path: Path): _cf_pstmt1_1.setString(3, _cf_test1); _cf_pstmt1_1.setString(4, _cf_fn1); _cf_pstmt1_1.setInt(5, _cf_loop1); - _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setString(6, _cf_cls1 + "." + _cf_test1 + ".1_" + _cf_testIteration1); _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); @@ -319,7 +319,7 @@ def test_instrument_behavior_mode_assert_throws_expression_lambda(self, tmp_path _cf_pstmt2_1.setString(3, _cf_test2); _cf_pstmt2_1.setString(4, _cf_fn2); _cf_pstmt2_1.setInt(5, _cf_loop2); - _cf_pstmt2_1.setString(6, "1"); + _cf_pstmt2_1.setString(6, _cf_cls2 + "." + _cf_test2 + ".1_" + _cf_testIteration2); _cf_pstmt2_1.setLong(7, _cf_dur2_1); _cf_pstmt2_1.setBytes(8, _cf_serializedResult2_1); _cf_pstmt2_1.setString(9, "function_call"); @@ -456,7 +456,7 @@ def test_instrument_behavior_mode_assert_throws_block_lambda(self, tmp_path: Pat _cf_pstmt2_1.setString(3, _cf_test2); _cf_pstmt2_1.setString(4, _cf_fn2); _cf_pstmt2_1.setInt(5, _cf_loop2); - _cf_pstmt2_1.setString(6, "1"); + _cf_pstmt2_1.setString(6, _cf_cls2 + "." + _cf_test2 + ".1_" + _cf_testIteration2); _cf_pstmt2_1.setLong(7, _cf_dur2_1); _cf_pstmt2_1.setBytes(8, _cf_serializedResult2_1); _cf_pstmt2_1.setString(9, "function_call"); @@ -818,7 +818,7 @@ class TestKryoSerializerUsage: _cf_pstmt1_1.setString(3, _cf_test1); _cf_pstmt1_1.setString(4, _cf_fn1); _cf_pstmt1_1.setInt(5, _cf_loop1); - _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setString(6, _cf_cls1 + "." + _cf_test1 + ".1_" + _cf_testIteration1); _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); @@ -1346,7 +1346,7 @@ def test_instrument_generated_test_behavior_mode(self): _cf_pstmt1_1.setString(3, _cf_test1); _cf_pstmt1_1.setString(4, _cf_fn1); _cf_pstmt1_1.setInt(5, _cf_loop1); - _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setString(6, _cf_cls1 + "." + _cf_test1 + ".1_" + _cf_testIteration1); _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); @@ -2738,7 +2738,7 @@ def test_behavior_mode_writes_to_sqlite(self, java_project): _cf_pstmt1_1.setString(3, _cf_test1); _cf_pstmt1_1.setString(4, _cf_fn1); _cf_pstmt1_1.setInt(5, _cf_loop1); - _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setString(6, _cf_cls1 + "." + _cf_test1 + ".1_" + _cf_testIteration1); _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); @@ -3412,7 +3412,7 @@ def test_void_instance_method_with_args(self, tmp_path: Path): _cf_pstmt1_1.setString(3, _cf_test1); _cf_pstmt1_1.setString(4, _cf_fn1); _cf_pstmt1_1.setInt(5, _cf_loop1); - _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setString(6, _cf_cls1 + "." + _cf_test1 + ".1_" + _cf_testIteration1); _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); @@ -3518,7 +3518,7 @@ def test_void_static_method_excludes_receiver(self, tmp_path: Path): _cf_pstmt1_1.setString(3, _cf_test1); _cf_pstmt1_1.setString(4, _cf_fn1); _cf_pstmt1_1.setInt(5, _cf_loop1); - _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setString(6, _cf_cls1 + "." + _cf_test1 + ".1_" + _cf_testIteration1); _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); @@ -3626,7 +3626,7 @@ def test_void_instance_no_args_serializes_receiver_only(self, tmp_path: Path): _cf_pstmt1_1.setString(3, _cf_test1); _cf_pstmt1_1.setString(4, _cf_fn1); _cf_pstmt1_1.setInt(5, _cf_loop1); - _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setString(6, _cf_cls1 + "." + _cf_test1 + ".1_" + _cf_testIteration1); _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); @@ -3732,7 +3732,7 @@ def test_void_static_no_args_serializes_null(self, tmp_path: Path): _cf_pstmt1_1.setString(3, _cf_test1); _cf_pstmt1_1.setString(4, _cf_fn1); _cf_pstmt1_1.setInt(5, _cf_loop1); - _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setString(6, _cf_cls1 + "." + _cf_test1 + ".1_" + _cf_testIteration1); _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); @@ -3842,7 +3842,7 @@ def test_void_instance_multiple_args(self, tmp_path: Path): _cf_pstmt1_1.setString(3, _cf_test1); _cf_pstmt1_1.setString(4, _cf_fn1); _cf_pstmt1_1.setInt(5, _cf_loop1); - _cf_pstmt1_1.setString(6, "1"); + _cf_pstmt1_1.setString(6, _cf_cls1 + "." + _cf_test1 + ".1_" + _cf_testIteration1); _cf_pstmt1_1.setLong(7, _cf_dur1_1); _cf_pstmt1_1.setBytes(8, _cf_serializedResult1_1); _cf_pstmt1_1.setString(9, "function_call"); From ebe1ace4df4a2ca62fbfc79d0a71d2c8ee4352ed Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 26 Feb 2026 05:03:21 +0000 Subject: [PATCH 07/13] style: apply ruff formatting to instrumentation files Co-Authored-By: Claude Opus 4.6 --- codeflash/languages/java/instrumentation.py | 30 +- .../test_java/test_instrumentation.py | 492 ++++++++---------- 2 files changed, 254 insertions(+), 268 deletions(-) diff --git a/codeflash/languages/java/instrumentation.py b/codeflash/languages/java/instrumentation.py index 1d7928d26..7ed61aa1c 100644 --- a/codeflash/languages/java/instrumentation.py +++ b/codeflash/languages/java/instrumentation.py @@ -424,7 +424,10 @@ def wrap_target_calls_with_treesitter( ) # Insert stdout restore at the beginning of finally (after "} finally {" line) finally_block.insert(1, f" System.setOut(_cf_origOut{iter_id}_{call_counter});") - finally_block.insert(2, f' try {{ _cf_stdout{iter_id}_{call_counter} = _cf_stdoutCapture{iter_id}_{call_counter}.toString("UTF-8"); }} catch (Exception _cf_encEx{iter_id}_{call_counter}) {{}}') + finally_block.insert( + 2, + f' try {{ _cf_stdout{iter_id}_{call_counter} = _cf_stdoutCapture{iter_id}_{call_counter}.toString("UTF-8"); }} catch (Exception _cf_encEx{iter_id}_{call_counter}) {{}}', + ) replacement_lines = [*var_decls, start_marker, *try_block, *finally_block] # Don't add indent to first line (it's placed after existing indent), but add to subsequent lines @@ -453,8 +456,12 @@ def wrap_target_calls_with_treesitter( wrapped.append(f"{line_indent_str}long _cf_end{iter_id}_{call_counter} = -1;") wrapped.append(f"{line_indent_str}long _cf_start{iter_id}_{call_counter} = 0;") wrapped.append(f"{line_indent_str}byte[] _cf_serializedResult{iter_id}_{call_counter} = null;") - wrapped.append(f"{line_indent_str}java.io.ByteArrayOutputStream _cf_stdoutCapture{iter_id}_{call_counter} = new java.io.ByteArrayOutputStream();") - wrapped.append(f"{line_indent_str}java.io.PrintStream _cf_origOut{iter_id}_{call_counter} = System.out;") + wrapped.append( + f"{line_indent_str}java.io.ByteArrayOutputStream _cf_stdoutCapture{iter_id}_{call_counter} = new java.io.ByteArrayOutputStream();" + ) + wrapped.append( + f"{line_indent_str}java.io.PrintStream _cf_origOut{iter_id}_{call_counter} = System.out;" + ) wrapped.append(f"{line_indent_str}String _cf_stdout{iter_id}_{call_counter} = null;") # Start marker wrapped.append( @@ -462,7 +469,9 @@ def wrap_target_calls_with_treesitter( ) # Try block with stdout redirection wrapped.append(f"{line_indent_str}try {{") - wrapped.append(f"{line_indent_str} System.setOut(new java.io.PrintStream(_cf_stdoutCapture{iter_id}_{call_counter}));") + wrapped.append( + f"{line_indent_str} System.setOut(new java.io.PrintStream(_cf_stdoutCapture{iter_id}_{call_counter}));" + ) wrapped.append(f"{line_indent_str} {start_stmt}") wrapped.append(f"{line_indent_str} {capture_stmt_assign}") wrapped.append(f"{line_indent_str} {end_stmt}") @@ -473,7 +482,10 @@ def wrap_target_calls_with_treesitter( ) # Insert stdout restore at beginning of finally (after "} finally {" line) finally_lines.insert(1, f"{line_indent_str} System.setOut(_cf_origOut{iter_id}_{call_counter});") - finally_lines.insert(2, f'{line_indent_str} try {{ _cf_stdout{iter_id}_{call_counter} = _cf_stdoutCapture{iter_id}_{call_counter}.toString("UTF-8"); }} catch (Exception _cf_encEx{iter_id}_{call_counter}) {{}}') + finally_lines.insert( + 2, + f'{line_indent_str} try {{ _cf_stdout{iter_id}_{call_counter} = _cf_stdoutCapture{iter_id}_{call_counter}.toString("UTF-8"); }} catch (Exception _cf_encEx{iter_id}_{call_counter}) {{}}', + ) wrapped.extend(finally_lines) else: capture_line = f"{line_indent_str}{capture_stmt_with_decl}" @@ -720,14 +732,18 @@ def instrument_existing_test( ) else: # Behavior mode: add timing instrumentation that also writes to SQLite - modified_source = _add_behavior_instrumentation(modified_source, original_class_name, func_name, is_void=is_void, return_type=return_type) + modified_source = _add_behavior_instrumentation( + modified_source, original_class_name, func_name, is_void=is_void, return_type=return_type + ) logger.debug("Java %s testing for %s: renamed class %s -> %s", mode, func_name, original_class_name, new_class_name) # Why return True here? return True, modified_source -def _add_behavior_instrumentation(source: str, class_name: str, func_name: str, is_void: bool = False, return_type: str | None = None) -> str: +def _add_behavior_instrumentation( + source: str, class_name: str, func_name: str, is_void: bool = False, return_type: str | None = None +) -> str: """Add behavior instrumentation to test methods. For behavior mode, this adds: diff --git a/tests/test_languages/test_java/test_instrumentation.py b/tests/test_languages/test_java/test_instrumentation.py index 402f31f26..a0a0b4a1a 100644 --- a/tests/test_languages/test_java/test_instrumentation.py +++ b/tests/test_languages/test_java/test_instrumentation.py @@ -122,10 +122,7 @@ def test_instrument_behavior_mode_simple(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="behavior", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="behavior", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -241,10 +238,7 @@ def test_instrument_behavior_mode_assert_throws_expression_lambda(self, tmp_path ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="behavior", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="behavior", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -376,10 +370,7 @@ def test_instrument_behavior_mode_assert_throws_block_lambda(self, tmp_path: Pat ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="behavior", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="behavior", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -502,10 +493,7 @@ def test_instrument_performance_mode_simple(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="performance", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="performance", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -574,10 +562,7 @@ def test_instrument_performance_mode_multiple_tests(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="performance", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="performance", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -677,10 +662,7 @@ def test_instrument_preserves_annotations(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="performance", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="performance", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -742,11 +724,7 @@ def test_missing_file(self, tmp_path: Path): ) with pytest.raises(ValueError): - instrument_existing_test( - test_string="", - function_to_optimize=func, - mode="behavior", - ) + instrument_existing_test(test_string="", function_to_optimize=func, mode="behavior") class TestKryoSerializerUsage: @@ -1182,12 +1160,7 @@ def test_create_benchmark_different_iterations(self): language="java", ) - result = create_benchmark_test( - func, - test_setup_code="", - invocation_code="multiply(5, 3)", - iterations=5000, - ) + result = create_benchmark_test(func, test_setup_code="", invocation_code="multiply(5, 3)", iterations=5000) # Note: Empty test_setup_code still has 8-space indentation on its line expected = ( @@ -1283,11 +1256,7 @@ def test_instrument_generated_test_behavior_mode(self): language="java", ) result = instrument_generated_java_test( - test_code, - function_name="add", - qualified_name="Calculator.add", - mode="behavior", - function_to_optimize=func, + test_code, function_name="add", qualified_name="Calculator.add", mode="behavior", function_to_optimize=func ) expected = """import org.junit.jupiter.api.Test; @@ -1567,10 +1536,7 @@ def test_instrumented_code_has_balanced_braces(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="performance", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="performance", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -1648,10 +1614,7 @@ def test_instrumented_code_preserves_imports(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="performance", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="performance", test_path=test_file ) expected = """package com.example; @@ -1723,10 +1686,7 @@ def test_empty_test_method(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="performance", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="performance", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -1771,10 +1731,7 @@ def test_test_with_nested_braces(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="performance", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="performance", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -1852,10 +1809,7 @@ class InnerTests { ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="performance", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="performance", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -1916,22 +1870,19 @@ def test_instrument_with_cjk_in_string_literal(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="performance", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="performance", test_path=test_file ) # The blank line between _cf_fn1 and the prefix body has 8 trailing spaces # (the indent level) — this is the f"{indent}\n" separator in the instrumentation code. expected = ( - 'import org.junit.jupiter.api.Test;\n' - 'import static org.junit.jupiter.api.Assertions.*;\n' - '\n' - 'public class Utf8Test__perfonlyinstrumented {\n' - ' @Test\n' - ' public void testWithCjk() {\n' - ' // Codeflash timing instrumentation with inner loop for JIT warmup\n' + "import org.junit.jupiter.api.Test;\n" + "import static org.junit.jupiter.api.Assertions.*;\n" + "\n" + "public class Utf8Test__perfonlyinstrumented {\n" + " @Test\n" + " public void testWithCjk() {\n" + " // Codeflash timing instrumentation with inner loop for JIT warmup\n" ' int _cf_outerLoop1 = Integer.parseInt(System.getenv("CODEFLASH_LOOP_INDEX"));\n' ' int _cf_maxInnerIterations1 = Integer.parseInt(System.getenv().getOrDefault("CODEFLASH_INNER_ITERATIONS", "10"));\n' ' int _cf_innerIterations1 = Integer.parseInt(System.getenv().getOrDefault("CODEFLASH_INNER_ITERATIONS", "10"));\n' @@ -1939,25 +1890,25 @@ def test_instrument_with_cjk_in_string_literal(self, tmp_path: Path): ' String _cf_cls1 = "Utf8Test";\n' ' String _cf_test1 = "testWithCjk";\n' ' String _cf_fn1 = "compute";\n' - ' \n' + " \n" ' String label = "\u30c6\u30b9\u30c8\u540d\u524d";\n' - ' for (int _cf_i1 = 0; _cf_i1 < _cf_innerIterations1; _cf_i1++) {\n' - ' int _cf_loopId1 = _cf_outerLoop1 * _cf_maxInnerIterations1 + _cf_i1;\n' + " for (int _cf_i1 = 0; _cf_i1 < _cf_innerIterations1; _cf_i1++) {\n" + " int _cf_loopId1 = _cf_outerLoop1 * _cf_maxInnerIterations1 + _cf_i1;\n" ' System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loopId1 + ":" + _cf_i1 + "######$!");\n' - ' long _cf_end1 = -1;\n' - ' long _cf_start1 = 0;\n' - ' try {\n' - ' _cf_start1 = System.nanoTime();\n' - ' assertEquals(42, compute(21));\n' - ' _cf_end1 = System.nanoTime();\n' - ' } finally {\n' - ' long _cf_end1_finally = System.nanoTime();\n' - ' long _cf_dur1 = (_cf_end1 != -1 ? _cf_end1 : _cf_end1_finally) - _cf_start1;\n' + " long _cf_end1 = -1;\n" + " long _cf_start1 = 0;\n" + " try {\n" + " _cf_start1 = System.nanoTime();\n" + " assertEquals(42, compute(21));\n" + " _cf_end1 = System.nanoTime();\n" + " } finally {\n" + " long _cf_end1_finally = System.nanoTime();\n" + " long _cf_dur1 = (_cf_end1 != -1 ? _cf_end1 : _cf_end1_finally) - _cf_start1;\n" ' System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loopId1 + ":" + _cf_i1 + ":" + _cf_dur1 + "######!");\n' - ' }\n' - ' }\n' - ' }\n' - '}\n' + " }\n" + " }\n" + " }\n" + "}\n" ) assert success is True assert result == expected @@ -1990,22 +1941,19 @@ def test_instrument_with_multibyte_in_comment(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="performance", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="performance", test_path=test_file ) assert success is True expected = ( - 'import org.junit.jupiter.api.Test;\n' - 'import static org.junit.jupiter.api.Assertions.*;\n' - '\n' - 'public class AccentTest__perfonlyinstrumented {\n' - ' @Test\n' - ' public void testWithAccent() {\n' - ' // Codeflash timing instrumentation with inner loop for JIT warmup\n' + "import org.junit.jupiter.api.Test;\n" + "import static org.junit.jupiter.api.Assertions.*;\n" + "\n" + "public class AccentTest__perfonlyinstrumented {\n" + " @Test\n" + " public void testWithAccent() {\n" + " // Codeflash timing instrumentation with inner loop for JIT warmup\n" ' int _cf_outerLoop1 = Integer.parseInt(System.getenv("CODEFLASH_LOOP_INDEX"));\n' ' int _cf_maxInnerIterations1 = Integer.parseInt(System.getenv().getOrDefault("CODEFLASH_INNER_ITERATIONS", "10"));\n' ' int _cf_innerIterations1 = Integer.parseInt(System.getenv().getOrDefault("CODEFLASH_INNER_ITERATIONS", "10"));\n' @@ -2013,34 +1961,33 @@ def test_instrument_with_multibyte_in_comment(self, tmp_path: Path): ' String _cf_cls1 = "AccentTest";\n' ' String _cf_test1 = "testWithAccent";\n' ' String _cf_fn1 = "calculate";\n' - ' \n' - ' // R\u00e9sum\u00e9 processing test with accented chars\n' + " \n" + " // R\u00e9sum\u00e9 processing test with accented chars\n" ' String name = "caf\u00e9";\n' - ' for (int _cf_i1 = 0; _cf_i1 < _cf_innerIterations1; _cf_i1++) {\n' - ' int _cf_loopId1 = _cf_outerLoop1 * _cf_maxInnerIterations1 + _cf_i1;\n' + " for (int _cf_i1 = 0; _cf_i1 < _cf_innerIterations1; _cf_i1++) {\n" + " int _cf_loopId1 = _cf_outerLoop1 * _cf_maxInnerIterations1 + _cf_i1;\n" ' System.out.println("!$######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loopId1 + ":" + _cf_i1 + "######$!");\n' - ' long _cf_end1 = -1;\n' - ' long _cf_start1 = 0;\n' - ' try {\n' - ' _cf_start1 = System.nanoTime();\n' - ' assertEquals(10, calculate(5));\n' - ' _cf_end1 = System.nanoTime();\n' - ' } finally {\n' - ' long _cf_end1_finally = System.nanoTime();\n' - ' long _cf_dur1 = (_cf_end1 != -1 ? _cf_end1 : _cf_end1_finally) - _cf_start1;\n' + " long _cf_end1 = -1;\n" + " long _cf_start1 = 0;\n" + " try {\n" + " _cf_start1 = System.nanoTime();\n" + " assertEquals(10, calculate(5));\n" + " _cf_end1 = System.nanoTime();\n" + " } finally {\n" + " long _cf_end1_finally = System.nanoTime();\n" + " long _cf_dur1 = (_cf_end1 != -1 ? _cf_end1 : _cf_end1_finally) - _cf_start1;\n" ' System.out.println("!######" + _cf_mod1 + ":" + _cf_cls1 + "." + _cf_test1 + ":" + _cf_fn1 + ":" + _cf_loopId1 + ":" + _cf_i1 + ":" + _cf_dur1 + "######!");\n' - ' }\n' - ' }\n' - ' }\n' - '}\n' + " }\n" + " }\n" + " }\n" + "}\n" ) assert result == expected # Skip all E2E tests if Maven is not available requires_maven = pytest.mark.skipif( - find_maven_executable() is None, - reason="Maven not found - skipping execution tests", + find_maven_executable() is None, reason="Maven not found - skipping execution tests" ) @@ -2115,6 +2062,7 @@ def java_project(self, tmp_path: Path): """Create a temporary Maven project and set up Java language context.""" # Force set the language to Java (reset the singleton first) import codeflash.languages.current as current_module + current_module._current_language = None set_current_language(Language.JAVA) @@ -2129,6 +2077,7 @@ def java_project(self, tmp_path: Path): # Clean up any SQLite files left in the shared temp dir to prevent cross-test contamination from codeflash.code_utils.code_utils import get_run_tmp_file + for i in range(10): get_run_tmp_file(Path(f"test_return_values_{i}.sqlite")).unlink(missing_ok=True) @@ -2147,14 +2096,17 @@ def test_run_and_parse_behavior_mode(self, java_project): project_root, src_dir, test_dir = java_project # Create source file - (src_dir / "Calculator.java").write_text("""package com.example; + (src_dir / "Calculator.java").write_text( + """package com.example; public class Calculator { public int add(int a, int b) { return a + b; } } -""", encoding="utf-8") +""", + encoding="utf-8", + ) # Create and instrument test test_source = """package com.example; @@ -2193,32 +2145,33 @@ def test_run_and_parse_behavior_mode(self, java_project): # Create Optimizer and FunctionOptimizer fto = FunctionToOptimize( - function_name="add", - file_path=src_dir / "Calculator.java", - parents=[], - language="java", + function_name="add", file_path=src_dir / "Calculator.java", parents=[], language="java" ) - opt = Optimizer(Namespace( - project_root=project_root, - disable_telemetry=True, - tests_root=test_dir, - test_project_root=project_root, - pytest_cmd="pytest", - experiment_id=None, - )) + opt = Optimizer( + Namespace( + project_root=project_root, + disable_telemetry=True, + tests_root=test_dir, + test_project_root=project_root, + pytest_cmd="pytest", + experiment_id=None, + ) + ) func_optimizer = opt.create_function_optimizer(fto) assert func_optimizer is not None - func_optimizer.test_files = TestFiles(test_files=[ - TestFile( - instrumented_behavior_file_path=instrumented_file, - test_type=TestType.EXISTING_UNIT_TEST, - original_file_path=test_file, - benchmarking_file_path=instrumented_file, # Use same file for behavior tests - ) - ]) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=instrumented_file, + test_type=TestType.EXISTING_UNIT_TEST, + original_file_path=test_file, + benchmarking_file_path=instrumented_file, # Use same file for behavior tests + ) + ] + ) # Run and parse tests test_env = os.environ.copy() @@ -2259,14 +2212,17 @@ def test_run_and_parse_performance_mode(self, java_project): project_root, src_dir, test_dir = java_project # Create source file - (src_dir / "MathUtils.java").write_text("""package com.example; + (src_dir / "MathUtils.java").write_text( + """package com.example; public class MathUtils { public int multiply(int a, int b) { return a * b; } } -""", encoding="utf-8") +""", + encoding="utf-8", + ) # Create and instrument test test_source = """package com.example; @@ -2343,32 +2299,33 @@ def test_run_and_parse_performance_mode(self, java_project): # Create Optimizer and FunctionOptimizer fto = FunctionToOptimize( - function_name="multiply", - file_path=src_dir / "MathUtils.java", - parents=[], - language="java", + function_name="multiply", file_path=src_dir / "MathUtils.java", parents=[], language="java" ) - opt = Optimizer(Namespace( - project_root=project_root, - disable_telemetry=True, - tests_root=test_dir, - test_project_root=project_root, - pytest_cmd="pytest", - experiment_id=None, - )) + opt = Optimizer( + Namespace( + project_root=project_root, + disable_telemetry=True, + tests_root=test_dir, + test_project_root=project_root, + pytest_cmd="pytest", + experiment_id=None, + ) + ) func_optimizer = opt.create_function_optimizer(fto) assert func_optimizer is not None - func_optimizer.test_files = TestFiles(test_files=[ - TestFile( - instrumented_behavior_file_path=test_file, - test_type=TestType.EXISTING_UNIT_TEST, - original_file_path=test_file, - benchmarking_file_path=instrumented_file, - ) - ]) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=test_file, + test_type=TestType.EXISTING_UNIT_TEST, + original_file_path=test_file, + benchmarking_file_path=instrumented_file, + ) + ] + ) # Run performance tests with inner_iterations=2 for fast test test_env = os.environ.copy() @@ -2417,14 +2374,17 @@ def test_run_and_parse_multiple_test_methods(self, java_project): project_root, src_dir, test_dir = java_project # Create source file - (src_dir / "StringUtils.java").write_text("""package com.example; + (src_dir / "StringUtils.java").write_text( + """package com.example; public class StringUtils { public String reverse(String s) { return new StringBuilder(s).reverse().toString(); } } -""", encoding="utf-8") +""", + encoding="utf-8", + ) # Create test with multiple methods test_source = """package com.example; @@ -2471,30 +2431,31 @@ def test_run_and_parse_multiple_test_methods(self, java_project): instrumented_file.write_text(instrumented, encoding="utf-8") fto = FunctionToOptimize( - function_name="reverse", - file_path=src_dir / "StringUtils.java", - parents=[], - language="java", + function_name="reverse", file_path=src_dir / "StringUtils.java", parents=[], language="java" ) - opt = Optimizer(Namespace( - project_root=project_root, - disable_telemetry=True, - tests_root=test_dir, - test_project_root=project_root, - pytest_cmd="pytest", - experiment_id=None, - )) + opt = Optimizer( + Namespace( + project_root=project_root, + disable_telemetry=True, + tests_root=test_dir, + test_project_root=project_root, + pytest_cmd="pytest", + experiment_id=None, + ) + ) func_optimizer = opt.create_function_optimizer(fto) - func_optimizer.test_files = TestFiles(test_files=[ - TestFile( - instrumented_behavior_file_path=instrumented_file, - test_type=TestType.EXISTING_UNIT_TEST, - original_file_path=test_file, - benchmarking_file_path=instrumented_file, # Use same file for behavior tests - ) - ]) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=instrumented_file, + test_type=TestType.EXISTING_UNIT_TEST, + original_file_path=test_file, + benchmarking_file_path=instrumented_file, # Use same file for behavior tests + ) + ] + ) test_env = os.environ.copy() test_env["CODEFLASH_TEST_ITERATION"] = "0" @@ -2528,14 +2489,17 @@ def test_run_and_parse_failing_test(self, java_project): project_root, src_dir, test_dir = java_project # Create source file with a bug - (src_dir / "BrokenCalc.java").write_text("""package com.example; + (src_dir / "BrokenCalc.java").write_text( + """package com.example; public class BrokenCalc { public int add(int a, int b) { return a + b + 1; // Bug: adds extra 1 } } -""", encoding="utf-8") +""", + encoding="utf-8", + ) # Create test that will fail test_source = """package com.example; @@ -2573,30 +2537,31 @@ def test_run_and_parse_failing_test(self, java_project): instrumented_file.write_text(instrumented, encoding="utf-8") fto = FunctionToOptimize( - function_name="add", - file_path=src_dir / "BrokenCalc.java", - parents=[], - language="java", + function_name="add", file_path=src_dir / "BrokenCalc.java", parents=[], language="java" ) - opt = Optimizer(Namespace( - project_root=project_root, - disable_telemetry=True, - tests_root=test_dir, - test_project_root=project_root, - pytest_cmd="pytest", - experiment_id=None, - )) + opt = Optimizer( + Namespace( + project_root=project_root, + disable_telemetry=True, + tests_root=test_dir, + test_project_root=project_root, + pytest_cmd="pytest", + experiment_id=None, + ) + ) func_optimizer = opt.create_function_optimizer(fto) - func_optimizer.test_files = TestFiles(test_files=[ - TestFile( - instrumented_behavior_file_path=instrumented_file, - test_type=TestType.EXISTING_UNIT_TEST, - original_file_path=test_file, - benchmarking_file_path=instrumented_file, # Use same file for behavior tests - ) - ]) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=instrumented_file, + test_type=TestType.EXISTING_UNIT_TEST, + original_file_path=test_file, + benchmarking_file_path=instrumented_file, # Use same file for behavior tests + ) + ] + ) test_env = os.environ.copy() test_env["CODEFLASH_TEST_ITERATION"] = "0" @@ -2634,7 +2599,8 @@ def test_behavior_mode_writes_to_sqlite(self, java_project): project_root, src_dir, test_dir = java_project # Create source file - (src_dir / "Counter.java").write_text("""package com.example; + (src_dir / "Counter.java").write_text( + """package com.example; public class Counter { private int value = 0; @@ -2643,7 +2609,9 @@ def test_behavior_mode_writes_to_sqlite(self, java_project): return ++value; } } -""", encoding="utf-8") +""", + encoding="utf-8", + ) # Create test file - single test method for simplicity test_source = """package com.example; @@ -2762,32 +2730,33 @@ def test_behavior_mode_writes_to_sqlite(self, java_project): # Create Optimizer and FunctionOptimizer fto = FunctionToOptimize( - function_name="increment", - file_path=src_dir / "Counter.java", - parents=[], - language="java", + function_name="increment", file_path=src_dir / "Counter.java", parents=[], language="java" ) - opt = Optimizer(Namespace( - project_root=project_root, - disable_telemetry=True, - tests_root=test_dir, - test_project_root=project_root, - pytest_cmd="pytest", - experiment_id=None, - )) + opt = Optimizer( + Namespace( + project_root=project_root, + disable_telemetry=True, + tests_root=test_dir, + test_project_root=project_root, + pytest_cmd="pytest", + experiment_id=None, + ) + ) func_optimizer = opt.create_function_optimizer(fto) assert func_optimizer is not None - func_optimizer.test_files = TestFiles(test_files=[ - TestFile( - instrumented_behavior_file_path=instrumented_file, - test_type=TestType.EXISTING_UNIT_TEST, - original_file_path=test_file, - benchmarking_file_path=instrumented_file, - ) - ]) + func_optimizer.test_files = TestFiles( + test_files=[ + TestFile( + instrumented_behavior_file_path=instrumented_file, + test_type=TestType.EXISTING_UNIT_TEST, + original_file_path=test_file, + benchmarking_file_path=instrumented_file, + ) + ] + ) # Run tests test_env = os.environ.copy() @@ -2813,11 +2782,13 @@ def test_behavior_mode_writes_to_sqlite(self, java_project): # Find the SQLite file that was created # SQLite is created at get_run_tmp_file path from codeflash.code_utils.code_utils import get_run_tmp_file + sqlite_file = get_run_tmp_file(Path("test_return_values_0.sqlite")) if not sqlite_file.exists(): # Fall back to checking temp directory for any SQLite files import tempfile + sqlite_files = list(Path(tempfile.gettempdir()).glob("**/test_return_values_*.sqlite")) assert len(sqlite_files) >= 1, f"SQLite file should have been created at {sqlite_file} or in temp dir" sqlite_file = max(sqlite_files, key=lambda p: p.stat().st_mtime) @@ -2836,8 +2807,18 @@ def test_behavior_mode_writes_to_sqlite(self, java_project): rows = cursor.fetchall() for row in rows: - test_module_path, test_class_name, test_function_name, function_getting_tested, \ - loop_index, iteration_id, runtime, return_value, verification_type, stdout = row + ( + test_module_path, + test_class_name, + test_function_name, + function_getting_tested, + loop_index, + iteration_id, + runtime, + return_value, + verification_type, + stdout, + ) = row # Verify fields assert test_module_path == "CounterTest" @@ -2866,7 +2847,8 @@ def test_performance_mode_inner_loop_timing_markers(self, java_project): project_root, src_dir, test_dir = java_project # Create a simple function to optimize - (src_dir / "Fibonacci.java").write_text("""package com.example; + (src_dir / "Fibonacci.java").write_text( + """package com.example; public class Fibonacci { public int fib(int n) { @@ -2874,7 +2856,9 @@ def test_performance_mode_inner_loop_timing_markers(self, java_project): return fib(n - 1) + fib(n - 2); } } -""", encoding="utf-8") +""", + encoding="utf-8", + ) # Create test file test_source = """package com.example; @@ -3015,14 +2999,17 @@ def test_performance_mode_multiple_methods_inner_loop(self, java_project): project_root, src_dir, test_dir = java_project # Create a simple math class - (src_dir / "MathOps.java").write_text("""package com.example; + (src_dir / "MathOps.java").write_text( + """package com.example; public class MathOps { public int add(int a, int b) { return a + b; } } -""", encoding="utf-8") +""", + encoding="utf-8", + ) # Create test with multiple test methods test_source = """package com.example; @@ -3109,8 +3096,6 @@ def __init__(self, path): assert loop_id_2_count == 2, f"Expected 2 markers for loopId 2, got {loop_id_2_count}" assert loop_id_3_count == 2, f"Expected 2 markers for loopId 3, got {loop_id_3_count}" - - def test_time_correction_instrumentation(self, java_project): """Test timing accuracy of performance instrumentation with known durations. @@ -3128,7 +3113,8 @@ def test_time_correction_instrumentation(self, java_project): project_root, src_dir, test_dir = java_project # Create SpinWait class — Java equivalent of Python's accurate_sleepfunc - (src_dir / "SpinWait.java").write_text("""package com.example; + (src_dir / "SpinWait.java").write_text( + """package com.example; public class SpinWait { public static long spinWait(long durationNs) { @@ -3138,7 +3124,9 @@ def test_time_correction_instrumentation(self, java_project): return durationNs; } } -""", encoding="utf-8") +""", + encoding="utf-8", + ) # Two test methods with known durations — mirrors Python's parametrize with # (0.01, 0.010) and (0.02, 0.020) which map to 100ms and 200ms @@ -3174,10 +3162,7 @@ def test_time_correction_instrumentation(self, java_project): # Instrument for performance mode success, instrumented = instrument_existing_test( - test_string=test_source, - function_to_optimize=func_info, - mode="performance", - test_path=test_file, + test_string=test_source, function_to_optimize=func_info, mode="performance", test_path=test_file ) assert success, "Instrumentation should succeed" @@ -3350,10 +3335,7 @@ def test_void_instance_method_with_args(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="behavior", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="behavior", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -3457,10 +3439,7 @@ def test_void_static_method_excludes_receiver(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="behavior", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="behavior", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -3564,10 +3543,7 @@ def test_void_instance_no_args_serializes_receiver_only(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="behavior", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="behavior", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -3671,10 +3647,7 @@ def test_void_static_no_args_serializes_null(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="behavior", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="behavior", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; @@ -3779,10 +3752,7 @@ def test_void_instance_multiple_args(self, tmp_path: Path): ) success, result = instrument_existing_test( - test_string=source, - function_to_optimize=func, - mode="behavior", - test_path=test_file, + test_string=source, function_to_optimize=func, mode="behavior", test_path=test_file ) expected = """import org.junit.jupiter.api.Test; From 8330afa7e5cff802c208ebb9d09991943c5e7b51 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 4 Mar 2026 02:58:49 +0000 Subject: [PATCH 08/13] fix: use Object type for assertTrue/assertFalse target call capture When assertTrue/assertFalse wrap expressions containing target calls that return non-boolean types (e.g., assertTrue(fibonacci(n) < 0L)), the inferred return type was boolean, causing compilation errors. Use Object instead to safely capture any return type via autoboxing. Also includes optimized Fibonacci.fibonacci (O(2^n) -> O(n)) and Fibonacci.sortArray (bubble sort -> Arrays.sort) as validation of the void function and fibonacci optimization pipeline. Co-Authored-By: Claude Opus 4.6 --- .../src/main/java/com/example/Fibonacci.java | 24 ++++++++------- codeflash/languages/java/remove_asserts.py | 6 ++-- .../resources/codeflash-runtime-1.0.0.jar | Bin 14629805 -> 14630964 bytes .../test_java/test_remove_asserts.py | 28 +++++++++--------- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/code_to_optimize/java/src/main/java/com/example/Fibonacci.java b/code_to_optimize/java/src/main/java/com/example/Fibonacci.java index 8772905e2..d6ec0e444 100644 --- a/code_to_optimize/java/src/main/java/com/example/Fibonacci.java +++ b/code_to_optimize/java/src/main/java/com/example/Fibonacci.java @@ -21,7 +21,17 @@ public static long fibonacci(int n) { if (n <= 1) { return n; } - return fibonacci(n - 1) + fibonacci(n - 2); + + long prev = 0; + long curr = 1; + + for (int i = 2; i <= n; i++) { + long next = prev + curr; + prev = curr; + curr = next; + } + + return curr; } /** @@ -183,15 +193,9 @@ public static void sortArray(long[] arr) { if (arr == null) { throw new IllegalArgumentException("Array must not be null"); } - for (int i = 0; i < arr.length; i++) { - for (int j = 0; j < arr.length - 1 - i; j++) { - if (arr[j] > arr[j + 1]) { - long temp = arr[j]; - arr[j] = arr[j + 1]; - arr[j + 1] = temp; - } - } - } + // Use the JDK's optimized sort for primitive long arrays (dual-pivot quicksort). + // This preserves in-place behavior while providing O(n log n) performance. + java.util.Arrays.sort(arr); } /** diff --git a/codeflash/languages/java/remove_asserts.py b/codeflash/languages/java/remove_asserts.py index 09ca49999..0a84b687c 100644 --- a/codeflash/languages/java/remove_asserts.py +++ b/codeflash/languages/java/remove_asserts.py @@ -916,9 +916,11 @@ def _infer_return_type(self, assertion: AssertionMatch) -> str: """ method = assertion.assertion_method - # assertTrue/assertFalse always deal with boolean values + # assertTrue/assertFalse: the assertion argument is boolean, but the extracted + # target call may return any type (e.g., assertTrue(fibonacci(n) < 0L) where + # fibonacci returns long). Use Object to safely capture via autoboxing. if method in {"assertTrue", "assertFalse"}: - return "boolean" + return "Object" # assertNull/assertNotNull — keep Object (reference type) if method in {"assertNull", "assertNotNull"}: diff --git a/codeflash/languages/java/resources/codeflash-runtime-1.0.0.jar b/codeflash/languages/java/resources/codeflash-runtime-1.0.0.jar index 92ad8be00e76b5d416b4c0e36b22808878f3e084..ab469c59e97341e7ec2821b7825424b111529519 100644 GIT binary patch delta 29760 zcmZU52Rv2(|3CNId+(i{JT19V9TH{ zspql9kFFlr@;E@YhE_iz*;31W1Q6fP`8*iaCn%ul$gk9-qOh&O;?yw1F|p#l%awwB zPZAUNT_BsV<$WZswwboj<(f(5Ti{ZqHJ)+VLqPS~_mH{ntWRGyi?04w#%f5c5M-fI zXi$pf7a#nyP0Yrtz5uQVj8!Pz8{>3~w30RW@XKF<=Gz%1z4G|@Tm3~?PETZQ6ccIRrTs!g@p_3` zjMa|&?_&ogM4%4NteMnGi~0wBL8K{urr3~5%3faCSI~aJS7gck>!Xvg{922XV|3%$ zhi~}OwyL_T)+*~Z0{;w{IkGOCRi!V>iBAd$c8Qu>%C(=Kss3bHmhD4Io_S~MO#qd>`1T?9|x2aPnQc0n!6G-Y>*O$!r7@hsVINFD> zb|RJaIE)Vk4o&To<^^nwMOy?WYOQF-1PMBQEoET}-cdU!3ngk_w@?!vu^(!z;7319 zNKeB~xt9A7Bh0S*`I}t#3vl-rad5Zy^Gh(+*M%9=DE3tL`Iyv-{?#Z}5%&G|)FqDs z_gXSnMA?t+{uCcx)a!Zn&1w%$)wnw<%7RT^MAjO;(?~zmEXq&h&z;aW&G2&{6Jr>r z@u=t?Xp7VE?q!eI{7P83bT6igR()Y`l_lUKMWtB?<1<8xxSiGKx7WVD*uR0?ikcpt zZEHYn6y3D;o$YLXPB8d_Kn~%idC2Al%*+$9x{_v{DudN3EjYGZ+BL_aauU`8Ob~V!S zclbSge*Hx4Azm+NC%sMfG+n*V&JTY~QqUsBSgzYcU8A$3lH7b=Ry8A#BA@xaQSU2m zi(X<*$BhS}m@U3P)gkLhQeK;7}d{%_quVO(QuwMlS1 z-lK^l@8N)BCa$k$j17tz2c_s7IE|TW@*as=Qm@;`GmCZvixv?AGoLR)2<^;!}%dsMqJ2i0j~j6ft77(hC`OxAxv@i_y3xYQhe-FyqX;1$+Hshu&no`b~l@K4nL($*ijcZ3LdBLbtVrgi7h2%a_+yCvTDaNvi;sC z!H|ufVBl^xZA`?O8kf5|)bkqr+HsMDgMS?k%nw|!uH+yd*e^P8eaub${;5356vv+` zfv!v8!!uJ&Vb-bcM^Ouph`L3I-G7pJZq(zdRo9GOekM6tARslAJD`_&VPHV|vKQQf z=twq<(3Ur4ii4YH?ac>LowLvOD!u;=+&7sXwna)R5t^uT^=XIC#t%0(q`!XW=r=4wkhqGMq=n^9x=?%_utL-c zR0$NWibAw*dRDrs?#%t~=IQK$%H=4>gHuPfU-vOhozoxlC?+8$CHlUyJFwc9tyC$FHO6 zkJ~&$#F*~aAP6GY^>-y~S{;cZKTI`v-l60Z2n2V z{t=F|W7T4;IoLLZqzW4W(F1Z7Y0R??Bu(KZg8Q!xliU$xqVDVUIKz=+R`8U0Vd0gR zgobDE4@px!pFOs_(0BRl9*LQ3>RwTAU}{qYqpU)5i||4o^Zf%>UgX94HkHY^_o9EU zbKHULYyTlj{YB#^ez(tZnYFr0;$?Z*qhFK|LUbT`4R2>+E&jmId3^R6NzYFbqI}z? zBo?+N_M-G(2+8Z$Ae~q9W7Fs5nzq zhqvO0E9x!|4VvtC2A@tFtM;AYm(b>mc}mY@VLs;-qq>m3iI>k56}5Hmu(amqF2`3F z{qxrqcNo-su1Tcs-YDj?GcvzkA;#<9v(=lbXU$WzQg;iH<*f0FSz`3hpGlVPMwRVb z-OqmTq=#I4Y012CO}QE?8>K6noR4K`)d=0u7G>Uu)lq(W$(}nHyFYbxGiCClisKvQ zh;l4jNqi;pJSE2`Z+p#zE21Er;Qe3TePa{1 zOS)Ii5#_8G)p0wmj%sf!9G1$=@UZtr@$TP0h^3CmasP!_`ib?o59^DEw;+2AZNP)V zgD3MHbCwCUP9h>hRBq(;fiH0_0y)&5*IcmTF0p1*vvMUbkNlpJU-zk8TX^5j&HP3w zqFnHWwe|dxCh;6p$fiBt-&jl6CFnsMC2xVJUJjA`8IAWATpl)EMxpi<1!zQ+x?S7gCej~c|z&IkLA2~+6;evd7VRXbc(pgEqj%S z??ut-6_EF^1>Sv@zq2K_@lJ1+Rr{NRFI~tKUzq>LxkoeIA2PJl9y$;`=KUeoQRcox z=SJZpblHPel2k&mbtY5<>waIixv_i(|Aq3;msQW*;&@Alpck*$Zh4uz!Ou9Y%CErq zVT5BwGlNKq+fU*BOrG`KjXd?Y%L1S1eoM~YKYaS-n1|A^b>C`-tQG6Glj(;1DK|WLLz!E#?WQ|*x7r_;6Nk0Clj6uduv}Q3b!Iosv{Er3bThpb*)Ca->3!zMW$G(e zR73>-thhIdHb&T&R{I<+q8)p^Ksbu3ozCP?qWklY+GMm z9B50LRHzj57>!IIZsC;#T_#GPog{ziR2g-z|31fX{^Rdcf%9tQ#$UYCqhz&ZEmZK* zm|5bq49ogm?<^M5#?(F6pxJxM9sI zMiskrFVE$MmamRLY*Bx=(c3SSfx7Q9bwsazfgXoy?pffKi$*#mPnGE%#F8*Gq?ZZy zei|VlzIt75v&FwSdNaPJC{J`VuQfgq~p^Eu<9rb-Y3t28XG|Cx%js_a( zozl~RszLefva4NH2*zCMAdStSk9V;a{mwe=JkH0{P;F8%@eRFjZk%o7b;$?VoocbCIg6Zox19j}Q8<4v#^$=~juw9!HsUOxNgNg!v9N={6R z{^e1JHT6C3wQIsk_=QeIwm;r~T6|---q&}26}|D6 zf}4I_4B=Jo?7306fv|nl#+%ytH=^s{RYu<%yR|s3n1%+sa8)9lk#6x#WH zlbOkvJD0Q@Y&@S!xE`GKv3&8)D?W$P+LYae@a6ZPJJ;_F&9JWU3A+ghWV<4doY~S@#Z( zrNE+LzkSU|ha(=lBi!HkJZ|Y5psjjB4dt31=jeYblkq z&@z+YT{}_|3y~XXMhSAnOFs&$RE-j_Ow2QP)J-XZMsg>O2=ZF%OAHB2yQ~fG=1a=wS9|{zx;;<8_smt_ zyomSvmny7_2nC9 zvS!ur-Ym^1MH$uVNnh!*XY$=BJy#sc6MfN=&ApmvXtw5Mn;Bx=(LPYD>D)BA=DnvE za-a2?t*3?J$KYKXt|(74J=Y1n^B**`@+y|>e^V&UAyvLwzuEYfHbv^OYxSU-uj%mk zH_GUrR7iCjiN--Q=aTE`T-_r>I0JPgmSf+v$x<({8h#h=Zc9>voWw))i!OMygxzRA+}vpR zcsOGk<}R#D|LuL!{3Qzk_C&{&Wa_IAc^H;nJrT;N8e{US-M_6`8GVyw5$^rKWX%W8 zWwCq7x8!So%Y((FuXNhe>yEbUzq#8TL(O5IRr*Mh2Jo#zIbqpv4nEdAWZY>z^Jv!= zsh*eA6#L28so`FfEMoU{=gs19njg_-`>1_&#(^}(&8P|-zk#6ath^by^zV;j6)zNY zo`DO7GasqHk;G z0alsk3)P!jH-zlLqaj$<)$yY{hQBi9sVlLs@Q!Tc1CC}p+90=`)jgwnC9)W z2m+cG(YILvhBz^WHWWJ7asqeY;hLAQ-(4tksQZLSoO&40dhfxwuWVo}r!sdjd2ajz zuH|@#*UbuZe<`cA5c~HjSpqo>Lf=qR=V`#X!t-)c2fik@zv2C&EZD2+Y1f~fjZ%Kv z{^@NQ%V6u9D|*k9r(PU>_ZSfWrA%l1j;Fqfdg+fwVMqRgB&Wc@qw>IV*C!k(J_nKK zMSLBSx1@{|I)?|aOzvtEHq@FUs_E07h+K~*($^-p54_GJiS+|v6pW>h>JAgg8ZOr- z6fe4p_myc3TbKQzJxVS{yYSWhXBtXg+lX_iDpzuE-`&THiwuxg;OxtMMBzNqi4_(d z89D1LBI1Lf?r${QBl(b>{V4kEZ86Iw@{X#(%x{l&5}ziK>^|9MkEpd;<6m6a=m{p; zeP19&e35m3JmoN(15qCs{j@>bn789M+q2k=N4*^z3x(ZxA5&bFRXsGqWyxFfv}C@j z*fwo=IN1&ncFhoVIoantvQKz^wnuDlNBvOoZ%K*gT!*^9#|v}uf;q8yP3XX2X`%+# z++5e08|J#iG%5M)`~HaQnz8An&{c_R+J`R)20ojKJ}AkgNb5oT#JX?l<<}tDPxXlP z;d?<*#>|g|3N;D5?MARI{lzfGDQ_XBxNZ4z;iqg-_{OC>@fRN1lN=MR%uZ}9o9 zkQl!5{krv>4-I^5=X!8wja9sL@{d50ExFJ{&Pa!19S2sP&y=Y!GA;*O_9|+YMf%6J zDY8*Kq+{8^R5H0dUmCUbMqzz@2~#V(-HB#H3cK=ZqiXH(l3@r$+a-DC4|+2?UCGJ( zHgJhI(ZggmFS*x6vqC5Ia7!yZhQlIUzKB;lp1UAPdKhZdKtTM6*N9et>+aUs7Awti zt6tMRO41JvapMd(K75V|vwLkr8xq4#MM2v-P)47`!IxlG)+Zs%@!~?o2YFrGAvt2~ zoQq_8ImHFjgd@vRh)H`1BHv*Co*`XLa@y_S8$bJ>tOTbiO1Ubt8V>5F1(&`RuYP;J z8ZvMh*MZMn+BDJ+owLux`T4rz?kHQPLcy~*l1~P{a@UD8Xdfh9bm30OH!V&y;ELkC zNlE(NPoY6B+_xcA+xx&qP$B<88;A88HM0ghN|C6mTv70 zY%ASfD^1eO<&xHO@oPiDLwML(cafv>ymh>(G1bX*CUHb%WUqhp8-)ZC&K8pj!i+D7 zD?N&T+lZI&#Y^hvGj&HMsaXozRjIG3dm;=*6!~Q_Bu4T6O-~`GFO~1}=drqMUqW-k za<{unZ$}W_WEVC<)D8Y9H<)M5mBg;A8=m$1m0$bFbv%*jo$+^{LC5ybM1@wBJ+G3w zuP;nH-QY|Leo#2*bZMvNPoLaLWP@bpGLh__$%9eMK5=1SEN+*|8zjz{JJ zHsPIeC7Gaf-rcpF0$0^>b=jK({2afAqu1wmY@0qnUd;p1`%71pMwFUZAMn(EZb%^E zTRHFgx+=ngU^WS+-uHsGXCh{MfU8C+-1Ka9@e};2;kt;UPJc%m! z{wq)GS?wjcNaQy@)+FS4jweE|*3Zf5vPwIp-1zNBpGKcipTeQc@At+%r|k2%mSKCl zK}t#mFZY_@T#H-RpWfP#=pd<{*@5|BUsu0fW}2*GO3!Ob3T+4&|D4*RaYnH!Cx*6b z3|$#wxjbU&1-_83s+9rryn9o8cb>-vPgStUayX^pMd1yG7yV^4xg##p&8luc>Fd%_ zA7Tw%SXm108(W7DBz(`OE1dGg6`zBAWVvL`B2=-GN>hR7UUQ$)VM zz*QY+)u1WschOnIEm@lnJ-tJFM7I+r-v0Gy%0?K>5B2lSac1B|jT(FzOGRa_;F%4kkqL z)Au+oZht55Y~eQE_DjgL@7-iiwKo{Bm7q5)3y{dxYR!>dzo3CTur2A5sQPDh($FQD zYV|y4Luzj8rWA2Dv+Zyr(Ot%{+!y;;%~+CqcN{+4*~RYLLzLPEmk@exe)5K@?C3L< z)vaglUZwH>onLQbwz$>!r_XEZs*Td0S~JdOWwGUF{Z{IDvD9#Vd#eF)Y%=^4Pp8~To$@Mk;yUrpbd_-dy1cN-rRdQ zN4d1ZzxA_CB&n5i(}eljJl?3`6S0H1d7q}t>|Ew~4kTewVvi(;n zB4##XuH-9PX0le6U$4Jt!W9WtJnL>WPv%wp7*hTT>Bk9 z>N68QT{Ps;>fTGhR^SrEzFxG0%D9+a!tgY)!tth)!T|dsP5dM+2H`FB0`T8z(vL-7j)oQNT z<=3p$RLiv_Gkod4K}>BIXg{-D$XEI0EjZv67cn(?&-=Z>E-MF0EAmh)exHy6QP{U( zuSS$lSvPIM0I4Bzqgb-Jgz+l(@~Izp&~jK8vzUa(Q_vW&2g?fPDbgn9tz`afwB^4@ z|5n+1i=8L%-NhH3L_;pik=I?_C{f00A+zkP-{hOgU1U)yw_rcl8i|7F9*9NTk=fo? zFm!fg@X*L#EAHc!y68V0)ve}V8WLn2x~3-;+Wj!Rz(TzUadY+Qa}pB{Zd=O@fjy49 zf>c$E(LHgK`c@oOGIJV3@QXwB9^G6Es!t-c-N zo;(`bQ@_}GH-as!djGYMc}?ReUn529_=;RM4eeQ@Bmz<&+I6e|b=YRXOESY6Bhd*{ z+n>sL3Xz;H7cCY4#wB0oP-&?9Y2aB_d zfiaSTP^)yMsB?vGj_MYy7t(=$y82?C%`G{!WVg!w~4lIJm_D( zIZNBXO}mZqWQrf@_U!nycm+B0VKLk+8fha{Y|?^i3{%jW&qH5kff@doYQ$0f(M|5ZR0PRMVw?W4NLJH`yU?&ze7 z$q>#35=rIJA939BIP?K{uJ1LA zNqaP|HAK95t2@tR8*cTRWwiUOaeR*MA1>kX`>u=jWW7qZ`V-#+HsH2~mA(v?xAB|1 z#CIRcK6U^4HYijHTG1>G-ksVzMmG;a1M4k@eY7 z`1a!4Loci-+3-&yZ`gk$Zz*>mMQFM@}qCX!?9OrJ44L^HxcXd}Z0i@?JbNQT^qp9Rx%$+{t2s$Vd%>pW0TXF-G!}HQg&(j@Khn*YU*0%m>{i+@ zq&4wXwfuS-!clvZxoY55kz8lU&L5kHl3z- zMbgrfy4_=CYVqBO+3rG58GSCj{=+g`jZRhj4vUFP%B`2<#}|0&Sp12Xjg-yG!~*${ z&ulMr#{9G`u~qF%xLBdGPavGc9}{f8f0^0g-Y%h4dZ2KgeSz4cw^{GZ99KHGAAe*& zQ#D7bsHe`FXpLY~NcfRQ5njmr8jc`yO!?wj-ae~t_Vb*^JC*^duo|rBHL>}$3C1zX z9VYppjB6z&S{r^z1<679+q2hyw!DOrma6@AV7PT!CWALa(jGYO4%p5vJbnwlusBTB zJCLQlJ4v}GV16$Mx{YV}}@;LrU(Gy=uw3$zE_L zGW|n+;IfYF^E{NEN^?e7@aTap*8I#Z_mM5XIz zuk$~e&oBkjvtBZuSulU+hD;kCTWP!)a)-z5;cFgbyou zCsz#aZW|Q#m0q}G5clj4jU25*QAvB|H%*RfQnV%Y6wTvixb}N6VK|Jb%uB35?@9LO z!@^JbELIi|{rVg?bz9F@-Ao8(7IvQQZ@U{C$CZ>7mDka0|KWO0*k{M3;LM!r0uyqL znps@hzk}L^7ZLk)WE%oj#ngg9oOw_0YkSaHY?jv8gefMLl$f-*&c?-ta^xAWb!pUx z4(;m3Y^?Uf6xPxmAY6hl~BashbKC7N{? zrA^DMN(+J>Xe!KXCZ>rf<0GZr#+L(<&2x=_p^Vn!deHw11<-L|M3fK~z`Y zr&_o=qM*{?Y(;%Yr5qbf^Gm>RL!ikogSEln(Cm9)+|6eC zgPYbr8}K6p23X8~mQm&KIDJM|SP}MR6ZgGTL&7MQT{H8C`z2_f(Ymr$nAu0ylTMTN zF%79M4&o8zD}2knMq~OUW~Q9>jYcdj`4;~R+J;!Cl8ME?wbfFW%IZxUqQWZsVW{GR zFuFyHt7UY>f2fUq^W{#LDx}w}k+*j}IFw{^Rmtf?$Z62Jw5R>yo7syP{511%Kq*#P ztvyGRV@NBn@E$+)_mI2>TC4W6R9bn0vkrC(LF0B$KBc;5g({2}8H8=j!t>;e4L%w> znDNrb%q(Ab*6y^0W;<(!gKmrD$Sx0F`25hWBhstgV4Kc4!9M5uOHpR$9hDUOoY~;4 zf~N$54G3oEZ~FBM^#O&QQ!?5%(UQ#s&cTfOMpsHKYz!oa(xk4~=&J^hY7Pd}(bl}N zK!OKhnFR~k{)z?OCUvCR&9Y_x%7sF&Qr3;Iri88Hv3dIdKE={s!_G)-K0jgWQT%gV z?oA^~o9;%Y`3Di=qPSCE56YDy3P<{RH|cq-lo8Z&>z9^MPNt7{nf>4968gvN%1>(W zZB5sSjubUt+E<^*xp=mO`dTI(Dk*HlYRCB6tTOf4!;H&}BdN8Yi51%;16lG?XW2{< zKPQVHl`=Ws=8ZvI%6P&M?p60DE1HQlr6Ie?=vU~p>!xV&P+Uy(T20_vjj;e#Yh;-# zRm=*)z_(2__CP`RN@zDMKvdIMu*`IJZh>$=qwzDp?}TQT(xnt}Q+`>VS1O?;9JAbt6sj>dEn3Xm>J~98u52?{a>0Ecl#?D@N}}^ zmNse$-*~Sh1l@b#a5>)rDh$O@n7(+Wdv`&rnQSZ+p0Avrve2dRF_pYbktPnNVzI8+f+E%v)FAL`>q6m{^^}%Q^`uBk*)$AtoUv@%^HZ zV7s2Kp2(_RwSKpuj((?(Pd z0H^_I0B8Z|0O$c202l$70GI(-09XOo0N4RI05}1-0L}n#1MmRw0`LLw0|)>J0-Oa9 z0uTle0T2Zc0}uz00FVTb0+0rf0gwea2XG!h4nQ720YDKz2|yV@1wa)*4L}`013(kt z0)Q5PHh>PmMF3p@Jpg?G0{}w+BLHIn697{HGXQe{3xG=imjNsRtN^Y6SOeGq*aBPy zumi9MxCY<=;0SOXzzM(^zy;t2fGdC-fIEN(fG2<#fH!~-fG>a_fImP0Kp?wT10WONF+di;6M$@h9DrPaJb{RMg*6UOo%+lJSZHZO(h6MFP@a43NA~A`;1ZWEqaOd@X$(f)hHwd2Ib0ZT&{}SV52~ znn47WETKq_T4OVuxqcoG3yTPhfbc)9>DBnp15!sJnU1VQKDlo$6JTM9Q~qCTN8SId zQ=3}IAV#9&__yD_n%)85Z!_UzVafaxzuXLX@Irr4NH)y4lo7aP0?P$3AFTga5?+Q7 zqsX&IR^X%XlW)QW{;~RHm73+ljD!ASADS9NG8_#`=Qq&zGV4^;ekN$q1VwceBV#(l zsuN`L1epZ?i7{{&s+vTeJ94!~Pwv)zC}X20Nb?n(M}4Y`T;2ZlO$#0#5}cfj10<@Ly6~icQZ&Pc+4-1Lu%Xt1H^F_(5$n7u3tNjV>pWxgSFE`;j>6{*c%4)l@+>b zkAn|&(_rI6?W;%%h}Yc7Ejq`VbsG z=;9Bg7@Umz*qVX^n+VGKffR%r@M1&qTwp?fqa{ne|Aixf&i@46apXT0^YKp*Q%vyK zXyzwU7JeuSB=kr~&V!Mdl{{q~H;)vD3CKWi=8=YQtMkWp*3#GnknjRh5SFe09WEjn zAz!qBJQkD{K+~4a0h9qvLDf})*h`a+FwM+g>^L+_;t3;gYfl(M3dxYqJ z26D?_0`5f~ci(*(DGxJ=hJFJUa{YrOfqIrf-DDmyrZA=qjD{l(n+o#&1x9x|0~_i| z!{)-wf6p%v%sb~e81`@Ed6-xURK12|hA#aEy$d=P!V0k2p=LCrT7gjmh5@oG!N!AF zctIgatbk^JtUS`CAyBMP$j`Gdb0t;P^^IgQ))h?zOx3@KU&d@1&nDJqX9t)Afa`jp&B`{rGt{!L0SKvIbrA_ z(kU>td+VS()<2GD&}2c#V*}XbES$1yo&!UF0~_@3=LV7q6lm)vQV_Dc0mFkNH-WLu z%CYe#QW@613bkz_tzf^_Pa-ftMq5a6Y!YZ{3&{dyZz0uSdAk_x{uWXjc76{dsBDAw zfY}p-#hzl)cR)3@{{eYu2_Sn?6f=aj11u#Zx(zzXPxuc@4;kRXXdzLwSKBt24hQmo z^p>z`s(%Q2h=3eM0x|JoCIAPj-T_`Vv>2l>ZWJAKu!GLdc+3=F(?i#GLFU`c7^Z3$ zsS1l?!3e#}*klmPU!*4NAqR%J^A~v$mcfk?ayej((7|8OM;1N|14@VjvEuh9ONHB@Ua^KO#Vztzm0M7$q1ZQV16tB?cRF#4w<_6wozn zU|;EsVXClEI@leMF%0yT4u+D0>3W-Q}1Kmst z$Q_Pig53(h=#Iv_2@X=V2A#4d!$DoZo`QUEK|b_dogMvN*IduChiV8Df^|&BsZY)NZ>w5hu(54Cwn4YB8qJ8lJ>^A8gpz?751jJyB?2}thEEt` z=u1D00dxBtRq75A=(zki#(-=KMhT70BWW;$VoMBkD=3U^Zxr08NBeIJn*KR=9Fqw} z1`(2g9^tK>WFdn%eu1uE+5)o~PJ$lU%~QHgv^8uO!?2NpaSS>{3x=>SM^_#Q`4>h7 zDGdF0C+v`d5CjwuHZw{XT46 zHV9+GMBpU{V`9XG5u(UZQm{H+jL^jeCxEmMz=m|Rd%`Gy7qJi~tP%yv1oloCEpS4; z0&s3f5C^QWKhQP;;uwE1N)VY&8sm^W1cJp=f_a3?V$|!DpyP+@&2$pp6l5X@C%@2ic>W zxjzc-J0)DOYlYE(5Pu9XHWa#`WnVWyH;+nS3ax80!Gu350TEgdgvcDt9QBnP8lOkf zLnXApki`NMWDRXIa2X>knSqE`=s-VAY@if2lnC^K4qfDS7=ex+2qlh~3`g^GpB|+J zi*Pw%xF8A!;2j@=iFbnm1Yf>`5jq%vclKS3Ajk*=yhx1WZAO$ntm6T?voa9-7Mu_w zW&(~s9%9mZFacXA10!^!g)>jkj<&Erd1xOem}3Pdkar230rI8+^GV49`bb=jNoK_Y ze0J+FHWe%=J(zqWCYk^%&|o)XG^CAiW_0^0pdwb_edR628&s+)%=;r+5Q46 zpu96+BTu%5Q$ys3K=;2}OR>ctCe!_rJnB za|Rf&lN~!(z;Gcj4-}Bs8PLvD8Z_+)tEa=L!Inc0o#zJK(PG3fV6WGP{bj}o;IKsi zEpr1$ECCE7#{+g4P7#a%4q4hTEeVWZAOb>x1E3&mMe2wFyWLqRkr#|0xKrDBQ5LX# zd5r1N@j#6aED`IfXhsq$;{#?48W=T&0NCsI`9P%W|6>RsJ${rT40YkehB;jwhX7q( z$2Z(Je$ej@ofEw-?6Lld0FKQ9pl+m%PMD*BB#@CFdhpQaIs!r9|I*~dfEcWS{ipBTLxioq?e<3PsT*k_>o;$X{MKwFwR zLt)b3T*V>;D$>af!WKnQL&^R)q>#4|N({RN$`MAfL#;w!7>K=~TwxR&L?jG0&kAph z;35omC2?Q0UAd{oE#c>LDmy48;T|yL;3xE@(&$6iIwE z+FAe-69-+YjXeoU2DL_lQH~c!3BpI>k0S9y65L=Uw#7j!4U(WR888w?aX7S)vII!a zlY(KwB~a3EV9PK!@z!LU3 z7ZbB13x@`s<+y4HrGW0~(-S{3s4^S0RbL7;W4sX4p9pc(@!s8pwiYPCL|T$U$-qB9 zKW>1&G-!ZQHF{F@u_GW(85H=h9L8~>s1|UlJ37N2wFOTGL=gLgi2xqZi6Dh{pj+to z7m%F{iVf!T8LiWUUGF*7rr$Csad<`_4iwpi!w)Hc1g$TT1vR_8iz0(H^+Wb@;E9*K z2Zs`RA`2Q$1`aF73>~zAW}HtmcX~h%qo+Ye+jAhJBRHp=SciPYIezQ#4(Q-8@VG1f z4Tl=~a}G?N21cTV&YuTT^nX&wd2m-0gARTFI15S30l|C%t#N>D9bHAC zO*xb>+;bKL{sCT60DBThOCIE^#Zb)9J$Vp)|39fy9;JwV6Y5g{kCKS*V3Fal0X^4O z0JHBge-wlricvsm!8I0-Bpzs60c4$Ghw{|G`mUmgQh@#DI1zB5Y(><0*fs7`j3);! z6-2BAyu5iZ^es+YdI%*Cn%1iXR-<4gFl(T5M-k_^fVKQOM#l!LIE$uvp%y+|aC$$1c#x&>vFF)SS{muSOEF0xUU96^nx{14P^uScmbl+04oZ+I_M^~ z7FrO1{MAv?*pDCz4e-oc;ebmCRcL^QPO5<}Z>j@l8#lDwRakvA`U-la0T!L9I83Ca zC|qKwED3a&S`*k7m1AfFO)%g&H5ftcH7*WRsR@oH59|JA@Su_^Tn5aYc$84*0tf;g z{Eu3BVh087eNk&KPNhV42`V|XujPOniew9 z1@~6f{wbzw2bAAY5ER;49UI~_M)5;oa6AI&>i&NT4D^7`2H zCBp>b)Cb17BpA9!A3O^9>x0$IhWv!lhxt&S2xO4ZU9eue8UU{koEZAK0ZIdg&wV0L zK_Z4=OYjjuGXhYIA?SvxCPw&e2+j@77fu95Mi4L77d^It6Ib2#m6=8Cr11 zPKG#5P-;-GG0F(m<%H%0Aw3gNS@2s>-zAhXB;t?9in#%?O+mTzLV2brRmjE^r4FMH z$C#+y#$&>m5SxKK9``Zyqi{Sh)973G0h;cN!O+WQC`}k+B1X`N#bdklw= za^imxIV}L)oQ|PGEl?L=Z?li%Wa2TSUBSzmODIMdYd(gKxdd#^m!JhNm{%FpXN{7F zZeIo@P1TW*ZV7r|+aoem8DxZgnzK!*p8<0raa2hi>d z6sH&u5_~#LBp*j$9Y&4OiCsrALfwwwQ5L*bJMssN87vBHTqnVvGvgmm#@uz_`iAS2 zPSFV*Pyd1q>%_X=33U->1CHt^jFK~$H36|xjFAvN4W=;@&LEWq$8c8D@gE1`(OHa zY^Gd6g7bQ(bavWcl#Sd#q*%j$X&mU48%Qo;c8az%!apu7TXzr%2~O!J$zQmGym!Ib z9L;cefE7g4;Y0w-q6b)=rkze08i>dN|G3n2JwY%<*Hgh9o&Foak7(U(_Y)lz#Nq`O zJ!>BfbR!tt?4JUFO>N@|O$hbAz&~0(z&Sk-^wRbfMt3U^oFv&=P6RSY!64jtdI(Am>JA3mXxL8(I|Stln_NU+ z&qIYFpmeuZ(84*0Jrop-J`CcFL02Xx0SV-BfX@Wwgo52oF{=ORG33s+8A#YLdhv9lrL```pYb{}-M?Aj@9w=KcZ)=dIc-3L{85u7_t(t2D2 z!}h50cmhTz5(nz38Jy10L6Graxc_JPISv>GJjCc2 z;z8X@Kf(wJ@xaULF-Ay&{s}Lb0KEFM(K<=_Kh zaY%>}N79t`t z_9caBgkfUFFvk1Ze&)q6%`+T4EKIb_!($`CEl;pkv3YS(Pm{|`#ROl`2uKSSDQON^qJw<22WZSPW zsekg{3YtgOs+85p#+;WQimpnc(E23F*rjHTjQP!ncBGM1A* zAx)S%#_Vg~g!wE5e%eNIW~BU-Gvh@ugm``&U}kWKs1K4Al#FT=5~?w5 z5SM+{&&HN!UqusRS+F3dHP}%9I*iLQhS+$pAMJSYz6K`kM=;ulj;+BAZ83&}=;1b2 z_vlOp{OQhGG|2GT9Q0cU$?3TqRD5Hjpxku`IQ%=# z^q*^EO1WPu{H@);bpVNdPWu})XL|kLfAUdCcu+qyp2+2fg zS8s4oTVca`mX7vj!fx^%#s<>DDzxOgOyukN8(y)&8&Q161`f6`u(cTt-G&3fN0rtJ zYG90$*n}Ce30qwcoHf4JNe)uD^Q#h-n=mh5QZ$N4vQ#K2Xfw)7)it4KZ#IUDds;#B z8cqPcwqRnApd{*j8>x@Q*G-TYWp6RI6TAW`Z=10dRc^+DrP_*`O$%gxedxQ^3P<_c zTuKf$Thp1SYd9C}-ilRvLJtn?w;@R{Mlmo}sQL$U_mvPjm5Q_Q0w5v_kL0@VPagklAl_>_o$-7On-e7-jSxLagD+; z5B!cUjrs~o$iwVf*PWK-p=s6)p;LLruGaHnmCdHkn20GfAJrUKR-qAcV<}<}+O;;8 z#_h$fYWW`6X@+ueb&oN;c{t_mMH;#cQ)>AMTF<@6nl^!{#0u9(an;ICl%}1m89NAyu@qK>6V(~}(5CSwFm{yEiE@u(0N>n)R!jVh>$v5^sdhXEx+zL)T9ps0 z@r{g@?&o7GG3`sUj+T~=SK4!Da|^H`#+%svIHLSy;(PWESix$V*&>)LYmQ=WAM+N= zx92P_TQFUzqK*4e;x~j#)1DqY+-zpI7m8ZOc1hG zn}Iu>+lS3a)FF78u$I&F4&khJUK$6rhp;MUrPKF^u^Pl5Mw~BXkSN4%Vq_?Novv)j z4`e$YLG#sZ=Cm>sU3J9~7#zrAj4w$?@KP>jC-aLs?Ns7Yc4+sLi!PuH#ya7evPnn}HK$6r*2D z#hiIlY#b+4o#5cx-%#^_GaTIg%@`ugF@cW9u_+8Y&n1y(P-FRqLQFn7VNrR7MszDx zQrtQ8%i7}@zn^o|iE@93!noftDdb-=DHNSox-dRay!@w$GF(tPljaXldn!!S{xYQ> zw_f@OtQA!zUTai(asDCW=iN5(J}1!R&#UR{6X@Wb?@{&%RAtRQ>V6Uz0Blbpp+*_R z2qE_w*@|*cVlrIb2rw6%!eTJ^0S!HkA^y!N*rdq7pAMZwH4LYa)*8;FRAZ|0I}MeD z8m6BpOs!+j5UOHipLdjb1|#493_y|$ylEB#Be(ylcQ|Z*7S&MqKEjqM;>fXBu(N*j0@2 zR#Bw8DwNMT(SKp$ynGH;+kB}0oN<&;+J<7z!+L)prJhG$c!A4YrC2B5oX2by(3S($ zzf{)L!(XMN*h{GV_ZQ$m3g)t-7w`kDPX{V4#h~!Lh#B`rxQfn);s@ybi>TzLPE606 za@(sc=^~isFDbDUIrk_%mLm(JBET*U?{Hl&MA(x5WD*6p3k z73EeOwG%mzV?R}fGG6WBWmp(+>XDC&SIpxm17=BF_%3Gcc9=>2eSoE5uyD7CY1q-7 z0%U8_Ib>k^b$C%7r=;tcT8Nab zIr?4x1mS8|TEg!BbaxI=RvUUJ!C;dFgOz>Fw4SOZ zbk$vPW-A57RH9AdhI3|1C7gaXn}LzSkY7GD)YGgR7>8q1)Rb9*m(riDQd`ow8<^j6 zHmLbP-M0?al(QLLg`x}6DXz*mSe%)OePx>3mA$ys(HV%u)Xi$DU!-xRO&MwjDl7xv z<|g>k?P@x*MeQ!{;aKm+)|Tw(i=(}lYEMeI3%6JCvk4^sH-ep|X>UW=l-{zPYMp*3bD3(t-|U5@-l)K z9Rhxm%*E&HFeMIL$0c5mP=$mv4#uuU^vfQ>aB~I~KqB7G(9rZ<8Yc>Qj8u8l8{3OF zwl<}{t;b!A|K!P3C%RvcW~$uVs1PlTEM&fi2@enP(rXGe_Ehs2WxPJjn3i&=Q$g5PWI$NMQ7q^f~NKQ(p}ecowE`$pqSN1h^p z@wf{c`%dFWx@SPkt+W)T*E-TlQLE!q(EmL{Iiv9z%`?n~&9r2EW*jblXRD>JHCi9G z57N<>&(Zq59b{8?%6JY(`qo++T7~>pKF1H8w;utOsO>4_1qO|=7xjFJc>((pH%fbf zO|(~U#<=0~IBGCi23~}JFC|bYl))%*Xq+Quy2RmiN3_KXyvfkxJza>^n7Cs78id&{fhsvMvg@1|G`%?J8+Z!4PKoIZ`d=C)-~WY z1^W#ldQ9ru#N~>&*ip>-Z_0`uyhZPy=t0GQNb&6QajXPyTogyVo=GOu?#FRFRJwlxaXb#f8zh#r4ey}Uxh+S@!T6zA z`wj{Ep^+INK7ThuR!&ZmI*PrT&3q4(H013JyW|*&x2r}KMWs5iGn0|0Uh-gBo`r5W)j40a;M)dq_(1astM^VQDCnm{6t{|PO?NW zE8e5z9}DE%2MQ7QDcVZ%p?(b*OTJd9?Gl;!RE$kM$xREbq%d*Adt_lP^%QUWUF5PH zYsr!(StI-otsALm;lT=eWG#Ip4qsVDzxW&MY1#uN<~tjBnwi#=+HWIu7W5ma-bU&q z4B9{e3TdF|onA(o)G|+gQzuj@wV*8ugw{HPiWSmFLc&I}P)Y-YJqIXGiEx$`mC?Tn z%DmYPB^-M){Xdk@|Mwxfp_F=P<{dASPY`BK`7(TR2(8qjtp}?2_M4zQKqH!E)dHl_^DCC*)vQQFD$-HH`J0HWviud z;o>z0h6q6h3e!jlLVX2!=_Gqf*GPdv2O|fPM(QaPR+@p9?&+nLG+GO<^KYAJOZr7C zbrky6G+_o;m)Xh@r|2Z?tLvC(xZwGit9aIyIq`!(Im|pA92UIbvVtdNEvZZ)>Bvei zxirUWqDM!K)?+yIvNn*nPO?i*GuY4yy%ZtdvEdAr>!m2Mr^fQ@}=D^4eBOIw6a5T$-`t3VJCGGzxOnuMRro0INsZY?%5$>TiTGPy%Z_lY-6B{ ztqlI`d`L&D?GgEq0LrzOhKqv(4b-Kz!ISFr5Os8bXloD+b%1DdTLbH3E>!G*e)tfm zw5`FHY+C|Fx08`SO>8N(6K{pes69W&8sGy@W7rZfCyc&#Mg!S6qJa*YK!&5FFrPN! z`fn$$13FrG)0=}g(FQxRae~$A{$|F8x&)#nCpjU;BSSfTB*vhlboSxeF?aONLMHPc z&1BB>&5cXor$LV-%XV85`zfHQ)f?`4aaY-4LKd*_Ck z%2VLz0nh0%W*N?RJdo5i z%Q&;vLy8jCuHc}?17kXkIOy()Os&CnBNlGd=IdHo$C#)rGvnGM)3#nHW?v4Ixln&E z#J*+^175Vw3*koPGcZbcQox>`)UXF(%Ttn^?D5{PE-q&>ds^jJ3tw_J9tHRkQw^)$VW)Q+symfQ3Dp>mn= zZJ^j;Gn0)GzC#k3=bAQBZ@~e#6ar9itUuCQTw(?mbm3IFhJ8+?qZ9tHUUZeoe5gf$ z)T4PZWdumLHW7gQXmRf#K$;-r+~x|0Zj|E+bs(znw3-+6_Exz&_p~n%o{Tl7@&nN( zThS&gn4G8OUetfr`;I#SB<1%XNCKu3en8LF+RJ_uFVu4PXT`e$3@8Hc$5?sU2> zTEf-41fPK%wCQU$4TX+#9HU*xb1*)wluZj6T{(==u2dI_I$Oju;7voqFl0B5 zVgNshzF<#xYTpsouI*tnWFohT{~RUAuBJ1(&16moe~C|xWxABn^ZvnTXR_>o64p%P zU`z)@x@sl|H1&N*mpZ^8@*7U~n1PhIh9hHEb2yV2E)5cL=bM3={tU+uOI*mA3G>i8 zC^%3Uu$YQFVk9r=h|X|xDFg9>%TMgtin?|}o2P7Hz=c*uNEWoblN2d*!s{h7bGMTe zj_-C$BQQsJ?5xo6`izP|csV!=GU-JNp7~~AL4kRwhum1UouSwYM?*|8U@yKjmh&-_ z(T+ttTDJnU;la*`_US>HN$Dc>66}w2=~xCPp5)+A7qo|62?tJ*7?Gc!=U~zqoB+ta zDH6VilybTx5;Og!OB_UWMUF0E^Tt}?>g5W3%eX-n_{SoeH7gVg#i^e+Z??O<0_gt% D>~p%| delta 28830 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 lKjVIX Date: Wed, 4 Mar 2026 03:22:02 +0000 Subject: [PATCH 09/13] Optimize JavaAssertTransformer._infer_return_type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The optimization adds a fast-path check in `_infer_type_from_assertion_args` that looks for the first comma and extracts the substring before it directly when no special delimiter characters (quotes, parentheses, braces) precede that comma, bypassing the expensive full `_extract_first_arg` parser for simple literals like `42`, `100L`, or `true`. Line profiler shows the original `_extract_first_arg` call consumed 49% of method runtime (~11.1 ms); the optimized version reduces this to 6.7% (~0.98 ms) by handling 1518 of 1632 cases via the cheap substring path, cutting per-call overhead from ~6808 ns to ~140–280 ns for common assertions. Runtime improves 43% (4.73 ms → 3.30 ms) with no correctness regressions; the fallback preserves exact behavior for nested or quoted arguments. --- codeflash/languages/java/remove_asserts.py | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/codeflash/languages/java/remove_asserts.py b/codeflash/languages/java/remove_asserts.py index 0a84b687c..a5a5986c9 100644 --- a/codeflash/languages/java/remove_asserts.py +++ b/codeflash/languages/java/remove_asserts.py @@ -956,8 +956,29 @@ def _infer_type_from_assertion_args(self, original_text: str, method: str) -> st elif args_str.endswith(")"): args_str = args_str[:-1] - # Fast-path: only extract the first top-level argument instead of splitting all arguments. - first_arg = self._extract_first_arg(args_str) + # Fast-path: try to cheaply obtain the first top-level argument without invoking + # the full parser. If the first comma occurs before any special characters that + # would affect top-levelness (quotes/parens/braces), we can take the substring + # up to that comma as the first argument. + if not args_str: + return "Object" + + # Find first comma; if none, the entire args_str is the single argument + comma_idx = args_str.find(",") + if comma_idx == -1: + first_arg = args_str + else: + # If there are no special delimiter characters before this comma, we can + # safely take the substring as the first argument. + before = args_str[:comma_idx] + if not self._special_re.search(before): + first_arg = before + else: + # Fallback: use the full extractor which respects nesting/strings/generics. + first_arg = self._extract_first_arg(args_str) + if first_arg is None: + return "Object" + if not first_arg: return "Object" From 5f32ec1703f6e8faef28ec2cf21d89d049f1ddab Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 4 Mar 2026 04:07:38 +0000 Subject: [PATCH 10/13] revert: restore original Fibonacci.java in code_to_optimize Roll back the optimized fibonacci and sortArray to keep the sample code in its original unoptimized state for testing purposes. Co-Authored-By: Claude Opus 4.6 --- .../src/main/java/com/example/Fibonacci.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/code_to_optimize/java/src/main/java/com/example/Fibonacci.java b/code_to_optimize/java/src/main/java/com/example/Fibonacci.java index d6ec0e444..8772905e2 100644 --- a/code_to_optimize/java/src/main/java/com/example/Fibonacci.java +++ b/code_to_optimize/java/src/main/java/com/example/Fibonacci.java @@ -21,17 +21,7 @@ public static long fibonacci(int n) { if (n <= 1) { return n; } - - long prev = 0; - long curr = 1; - - for (int i = 2; i <= n; i++) { - long next = prev + curr; - prev = curr; - curr = next; - } - - return curr; + return fibonacci(n - 1) + fibonacci(n - 2); } /** @@ -193,9 +183,15 @@ public static void sortArray(long[] arr) { if (arr == null) { throw new IllegalArgumentException("Array must not be null"); } - // Use the JDK's optimized sort for primitive long arrays (dual-pivot quicksort). - // This preserves in-place behavior while providing O(n log n) performance. - java.util.Arrays.sort(arr); + for (int i = 0; i < arr.length; i++) { + for (int j = 0; j < arr.length - 1 - i; j++) { + if (arr[j] > arr[j + 1]) { + long temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } } /** From 9b4ba309581eb66f1554210ffa0dd8411b85ecb6 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 4 Mar 2026 04:12:04 +0000 Subject: [PATCH 11/13] fix: add SuppressWarnings annotation to void function test expectations The omni-java merge adds @SuppressWarnings("CheckReturnValue") to all instrumented test classes. Update the void function instrumentation test expectations to include this annotation. Co-Authored-By: Claude Opus 4.6 --- tests/test_languages/test_java/test_instrumentation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_languages/test_java/test_instrumentation.py b/tests/test_languages/test_java/test_instrumentation.py index cdfb0943c..65a6845b7 100644 --- a/tests/test_languages/test_java/test_instrumentation.py +++ b/tests/test_languages/test_java/test_instrumentation.py @@ -3364,6 +3364,7 @@ def test_void_instance_method_with_args(self, tmp_path: Path): import java.sql.DriverManager; import java.sql.PreparedStatement; +@SuppressWarnings("CheckReturnValue") public class WorkerTest__perfinstrumented { @Test public void testDoWork() { @@ -3468,6 +3469,7 @@ def test_void_static_method_excludes_receiver(self, tmp_path: Path): import java.sql.DriverManager; import java.sql.PreparedStatement; +@SuppressWarnings("CheckReturnValue") public class UtilsTest__perfinstrumented { @Test public void testProcess() { @@ -3572,6 +3574,7 @@ def test_void_instance_no_args_serializes_receiver_only(self, tmp_path: Path): import java.sql.DriverManager; import java.sql.PreparedStatement; +@SuppressWarnings("CheckReturnValue") public class CacheTest__perfinstrumented { @Test public void testReset() { @@ -3676,6 +3679,7 @@ def test_void_static_no_args_serializes_null(self, tmp_path: Path): import java.sql.DriverManager; import java.sql.PreparedStatement; +@SuppressWarnings("CheckReturnValue") public class ConfigTest__perfinstrumented { @Test public void testReload() { @@ -3781,6 +3785,7 @@ def test_void_instance_multiple_args(self, tmp_path: Path): import java.sql.DriverManager; import java.sql.PreparedStatement; +@SuppressWarnings("CheckReturnValue") public class SwapperTest__perfinstrumented { @Test public void testSwap() { From a57236a5e73d9cb1be1bd2b539f5f94cdb508964 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 4 Mar 2026 04:48:26 +0000 Subject: [PATCH 12/13] chore: apply ruff formatting to PR-changed files Co-Authored-By: Claude Opus 4.6 --- codeflash/languages/java/build_tools.py | 15 +- codeflash/languages/java/comparator.py | 8 +- codeflash/languages/java/test_runner.py | 10 +- .../test_java/test_comparator.py | 414 +++++------------- .../test_java/test_integration.py | 10 +- .../test_java/test_run_and_parse.py | 28 +- 6 files changed, 132 insertions(+), 353 deletions(-) diff --git a/codeflash/languages/java/build_tools.py b/codeflash/languages/java/build_tools.py index 32b6db77b..4db43a92d 100644 --- a/codeflash/languages/java/build_tools.py +++ b/codeflash/languages/java/build_tools.py @@ -773,8 +773,7 @@ def ensure_jacoco_property_name(pom_path: Path) -> bool: # Match prepare-agent ... ...prepare-agent... # and check whether a block already exists for it. prepare_agent_pattern = re.compile( - r"(\s*prepare-agent.*?)(.*?)()", - re.DOTALL, + r"(\s*prepare-agent.*?)(.*?)()", re.DOTALL ) match = prepare_agent_pattern.search(content) if not match: @@ -787,10 +786,14 @@ def ensure_jacoco_property_name(pom_path: Path) -> bool: between = match.group(2) # text between and if "" in between: # Configuration block exists — inject propertyName inside it - content = content[:match.start(2)] + between.replace( - "", - f"\n {JACOCO_PROPERTY_NAME}", - ) + content[match.end(2):] + content = ( + content[: match.start(2)] + + between.replace( + "", + f"\n {JACOCO_PROPERTY_NAME}", + ) + + content[match.end(2) :] + ) else: # No configuration block — add one before config_block = ( diff --git a/codeflash/languages/java/comparator.py b/codeflash/languages/java/comparator.py index eda687222..a287300c3 100644 --- a/codeflash/languages/java/comparator.py +++ b/codeflash/languages/java/comparator.py @@ -289,7 +289,8 @@ def compare_test_results( "(total=%s, skipped_placeholders=%s, skipped_deser_errors=%s). " "Treating as NOT equivalent.", comparison.get("totalInvocations", 0), - skipped_placeholders, skipped_deser_errors, + skipped_placeholders, + skipped_deser_errors, ) return False, [] @@ -297,7 +298,10 @@ def compare_test_results( "Java comparison: %s (%s invocations, %s compared, %s placeholder skips, %s deser skips, %s diffs)", "equivalent" if equivalent else "DIFFERENT", comparison.get("totalInvocations", 0), - actual_comparisons, skipped_placeholders, skipped_deser_errors, len(test_diffs), + actual_comparisons, + skipped_placeholders, + skipped_deser_errors, + len(test_diffs), ) return equivalent, test_diffs diff --git a/codeflash/languages/java/test_runner.py b/codeflash/languages/java/test_runner.py index 9d1df1179..363492461 100644 --- a/codeflash/languages/java/test_runner.py +++ b/codeflash/languages/java/test_runner.py @@ -284,15 +284,7 @@ def ensure_multi_module_deps_installed(maven_root: Path, test_module: str | None logger.error("Maven not found — cannot pre-install multi-module dependencies") return False - cmd = [ - mvn, - "install", - "-DskipTests", - "-B", - "-pl", - test_module, - "-am", - ] + cmd = [mvn, "install", "-DskipTests", "-B", "-pl", test_module, "-am"] cmd.extend(_MAVEN_VALIDATION_SKIP_FLAGS) logger.info("Pre-installing multi-module dependencies: %s (module: %s)", maven_root, test_module) diff --git a/tests/test_languages/test_java/test_comparator.py b/tests/test_languages/test_java/test_comparator.py index bd44f317b..17220b444 100644 --- a/tests/test_languages/test_java/test_comparator.py +++ b/tests/test_languages/test_java/test_comparator.py @@ -1,24 +1,17 @@ """Tests for Java test result comparison.""" -import json import shutil import sqlite3 -import tempfile from pathlib import Path import pytest -from codeflash.languages.java.comparator import ( - compare_invocations_directly, - compare_test_results, - values_equal, -) +from codeflash.languages.java.comparator import compare_invocations_directly, compare_test_results, values_equal from codeflash.models.models import TestDiffScope # Skip tests that require Java runtime if Java is not available requires_java = pytest.mark.skipif( - shutil.which("java") is None, - reason="Java not found - skipping Comparator integration tests", + shutil.which("java") is None, reason="Java not found - skipping Comparator integration tests" ) # Kryo-serialized bytes for common test values. @@ -38,7 +31,9 @@ KRYO_STR_VALUE1 = bytes([0x03, 0x01, 0x7B, 0x22, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x22, 0x3A, 0x20, 0x31, 0xFD]) KRYO_STR_VALUE2 = bytes([0x03, 0x01, 0x7B, 0x22, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x22, 0x3A, 0x20, 0x32, 0xFD]) KRYO_STR_VALUE42 = bytes([0x03, 0x01, 0x7B, 0x22, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x22, 0x3A, 0x20, 0x34, 0x32, 0xFD]) -KRYO_STR_VALUE100 = bytes([0x03, 0x01, 0x7B, 0x22, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x22, 0x3A, 0x20, 0x31, 0x30, 0x30, 0xFD]) +KRYO_STR_VALUE100 = bytes( + [0x03, 0x01, 0x7B, 0x22, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x22, 0x3A, 0x20, 0x31, 0x30, 0x30, 0xFD] +) KRYO_DOUBLE_1_0000000001 = bytes([0x0A, 0x38, 0xDF, 0x06, 0x00, 0x00, 0x00, 0xF0, 0x3F]) KRYO_DOUBLE_1_0000000002 = bytes([0x0A, 0x70, 0xBE, 0x0D, 0x00, 0x00, 0x00, 0xF0, 0x3F]) KRYO_NAN = bytes([0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x7F]) @@ -67,12 +62,8 @@ def test_identical_results(self): def test_different_return_values(self): """Test detecting different return values.""" - original = { - "1": {"result_json": '{"value": 42}', "error_json": None}, - } - candidate = { - "1": {"result_json": '{"value": 99}', "error_json": None}, - } + original = {"1": {"result_json": '{"value": 42}', "error_json": None}} + candidate = {"1": {"result_json": '{"value": 99}', "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) @@ -89,7 +80,7 @@ def test_missing_invocation_in_candidate(self): "2": {"result_json": '{"value": 100}', "error_json": None}, } candidate = { - "1": {"result_json": '{"value": 42}', "error_json": None}, + "1": {"result_json": '{"value": 42}', "error_json": None} # Missing invocation 2 } @@ -101,9 +92,7 @@ def test_missing_invocation_in_candidate(self): def test_extra_invocation_in_candidate(self): """Test detecting extra invocation in candidate.""" - original = { - "1": {"result_json": '{"value": 42}', "error_json": None}, - } + original = {"1": {"result_json": '{"value": 42}', "error_json": None}} candidate = { "1": {"result_json": '{"value": 42}', "error_json": None}, "2": {"result_json": '{"value": 100}', "error_json": None}, # Extra @@ -116,11 +105,9 @@ def test_extra_invocation_in_candidate(self): def test_exception_differences(self): """Test detecting exception differences.""" - original = { - "1": {"result_json": None, "error_json": '{"type": "NullPointerException"}'}, - } + original = {"1": {"result_json": None, "error_json": '{"type": "NullPointerException"}'}} candidate = { - "1": {"result_json": '{"value": 42}', "error_json": None}, # No exception + "1": {"result_json": '{"value": 42}', "error_json": None} # No exception } equivalent, diffs = compare_invocations_directly(original, candidate) @@ -176,12 +163,8 @@ def test_non_numeric_strings_differ(self): def test_numeric_comparison_in_direct_invocation(self): """Test that compare_invocations_directly uses numeric-aware comparison.""" - original = { - "1": {"result_json": "0", "error_json": None}, - } - candidate = { - "1": {"result_json": "0.0", "error_json": None}, - } + original = {"1": {"result_json": "0", "error_json": None}} + candidate = {"1": {"result_json": "0.0", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -189,12 +172,8 @@ def test_numeric_comparison_in_direct_invocation(self): def test_integer_long_mismatch_resolved(self): """Test that Integer(42) vs Long(42) serialized differently are still equal.""" - original = { - "1": {"result_json": "42", "error_json": None}, - } - candidate = { - "1": {"result_json": "42.0", "error_json": None}, - } + original = {"1": {"result_json": "42", "error_json": None}} + candidate = {"1": {"result_json": "42.0", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -263,46 +242,30 @@ def test_negative_zero(self): def test_boolean_invocation_comparison(self): """Test boolean return values in full invocation comparison.""" - original = { - "1": {"result_json": "true", "error_json": None}, - } - candidate = { - "1": {"result_json": "true", "error_json": None}, - } + original = {"1": {"result_json": "true", "error_json": None}} + candidate = {"1": {"result_json": "true", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True def test_boolean_mismatch_invocation_comparison(self): """Test boolean mismatch is correctly detected.""" - original = { - "1": {"result_json": "true", "error_json": None}, - } - candidate = { - "1": {"result_json": "false", "error_json": None}, - } + original = {"1": {"result_json": "true", "error_json": None}} + candidate = {"1": {"result_json": "false", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is False assert len(diffs) == 1 def test_array_invocation_comparison(self): """Test array return values in full invocation comparison.""" - original = { - "1": {"result_json": "[1, 2, 3]", "error_json": None}, - } - candidate = { - "1": {"result_json": "[1, 2, 3]", "error_json": None}, - } + original = {"1": {"result_json": "[1, 2, 3]", "error_json": None}} + candidate = {"1": {"result_json": "[1, 2, 3]", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True def test_array_mismatch_invocation_comparison(self): """Test array mismatch is correctly detected.""" - original = { - "1": {"result_json": "[1, 2, 3]", "error_json": None}, - } - candidate = { - "1": {"result_json": "[1, 2, 4]", "error_json": None}, - } + original = {"1": {"result_json": "[1, 2, 3]", "error_json": None}} + candidate = {"1": {"result_json": "[1, 2, 4]", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is False assert len(diffs) == 1 @@ -382,35 +345,25 @@ class TestComparisonWithRealData: def test_string_result_comparison(self): """Test comparing string results.""" - original = { - "1": {"result_json": '"Hello World"', "error_json": None}, - } - candidate = { - "1": {"result_json": '"Hello World"', "error_json": None}, - } + original = {"1": {"result_json": '"Hello World"', "error_json": None}} + candidate = {"1": {"result_json": '"Hello World"', "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True def test_array_result_comparison(self): """Test comparing array results.""" - original = { - "1": {"result_json": "[1, 2, 3, 4, 5]", "error_json": None}, - } - candidate = { - "1": {"result_json": "[1, 2, 3, 4, 5]", "error_json": None}, - } + original = {"1": {"result_json": "[1, 2, 3, 4, 5]", "error_json": None}} + candidate = {"1": {"result_json": "[1, 2, 3, 4, 5]", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True def test_array_order_matters(self): """Test that array order matters for comparison.""" - original = { - "1": {"result_json": "[1, 2, 3]", "error_json": None}, - } + original = {"1": {"result_json": "[1, 2, 3]", "error_json": None}} candidate = { - "1": {"result_json": "[3, 2, 1]", "error_json": None}, # Different order + "1": {"result_json": "[3, 2, 1]", "error_json": None} # Different order } equivalent, diffs = compare_invocations_directly(original, candidate) @@ -418,24 +371,16 @@ def test_array_order_matters(self): def test_object_result_comparison(self): """Test comparing object results.""" - original = { - "1": {"result_json": '{"name": "John", "age": 30}', "error_json": None}, - } - candidate = { - "1": {"result_json": '{"name": "John", "age": 30}', "error_json": None}, - } + original = {"1": {"result_json": '{"name": "John", "age": 30}', "error_json": None}} + candidate = {"1": {"result_json": '{"name": "John", "age": 30}', "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True def test_null_result(self): """Test comparing null results.""" - original = { - "1": {"result_json": "null", "error_json": None}, - } - candidate = { - "1": {"result_json": "null", "error_json": None}, - } + original = {"1": {"result_json": "null", "error_json": None}} + candidate = {"1": {"result_json": "null", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -462,11 +407,9 @@ class TestEdgeCases: def test_whitespace_in_json(self): """Test that whitespace differences in JSON don't cause issues.""" - original = { - "1": {"result_json": '{"a":1,"b":2}', "error_json": None}, - } + original = {"1": {"result_json": '{"a":1,"b":2}', "error_json": None}} candidate = { - "1": {"result_json": '{ "a": 1, "b": 2 }', "error_json": None}, # With spaces + "1": {"result_json": '{ "a": 1, "b": 2 }', "error_json": None} # With spaces } # Note: Direct string comparison will see these as different @@ -486,12 +429,8 @@ def test_large_number_of_invocations(self): def test_unicode_in_results(self): """Test handling unicode in results.""" - original = { - "1": {"result_json": '"Hello 世界 🌍"', "error_json": None}, - } - candidate = { - "1": {"result_json": '"Hello 世界 🌍"', "error_json": None}, - } + original = {"1": {"result_json": '"Hello 世界 🌍"', "error_json": None}} + candidate = {"1": {"result_json": '"Hello 世界 🌍"', "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -499,12 +438,8 @@ def test_unicode_in_results(self): def test_deeply_nested_objects(self): """Test handling deeply nested objects.""" nested = '{"a": {"b": {"c": {"d": {"e": 1}}}}}' - original = { - "1": {"result_json": nested, "error_json": None}, - } - candidate = { - "1": {"result_json": nested, "error_json": None}, - } + original = {"1": {"result_json": nested, "error_json": None}} + candidate = {"1": {"result_json": nested, "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -573,9 +508,7 @@ def _create(path: Path, results: list[dict]): return _create - def test_comparator_reads_test_results_table_identical( - self, tmp_path: Path, create_test_results_db - ): + def test_comparator_reads_test_results_table_identical(self, tmp_path: Path, create_test_results_db): """Test that Comparator correctly reads test_results table with identical results.""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" @@ -607,9 +540,7 @@ def test_comparator_reads_test_results_table_identical( assert equivalent is True assert len(diffs) == 0 - def test_comparator_reads_test_results_table_different_values( - self, tmp_path: Path, create_test_results_db - ): + def test_comparator_reads_test_results_table_different_values(self, tmp_path: Path, create_test_results_db): """Test that Comparator detects different return values from test_results table.""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" @@ -621,7 +552,7 @@ def test_comparator_reads_test_results_table_different_values( "loop_index": 1, "iteration_id": "1_0", "return_value": KRYO_STR_OLLEH, - }, + } ] candidate_results = [ @@ -631,7 +562,7 @@ def test_comparator_reads_test_results_table_different_values( "loop_index": 1, "iteration_id": "1_0", "return_value": KRYO_STR_WRONG, # Different result - }, + } ] create_test_results_db(original_path, original_results) @@ -644,9 +575,7 @@ def test_comparator_reads_test_results_table_different_values( assert len(diffs) == 1 assert diffs[0].scope == TestDiffScope.RETURN_VALUE - def test_comparator_handles_multiple_loop_iterations( - self, tmp_path: Path, create_test_results_db - ): + def test_comparator_handles_multiple_loop_iterations(self, tmp_path: Path, create_test_results_db): """Test that Comparator correctly handles multiple loop iterations.""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" @@ -676,9 +605,7 @@ def test_comparator_handles_multiple_loop_iterations( assert equivalent is True assert len(diffs) == 0 - def test_comparator_iteration_id_parsing( - self, tmp_path: Path, create_test_results_db - ): + def test_comparator_iteration_id_parsing(self, tmp_path: Path, create_test_results_db): """Test that Comparator correctly parses iteration_id format 'iter_testIteration'.""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" @@ -711,32 +638,18 @@ def test_comparator_iteration_id_parsing( assert equivalent is True assert len(diffs) == 0 - def test_comparator_missing_result_in_candidate( - self, tmp_path: Path, create_test_results_db - ): + def test_comparator_missing_result_in_candidate(self, tmp_path: Path, create_test_results_db): """Test that Comparator detects missing results in candidate.""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" original_results = [ - { - "loop_index": 1, - "iteration_id": "1_0", - "return_value": KRYO_INT_1, - }, - { - "loop_index": 1, - "iteration_id": "2_0", - "return_value": KRYO_INT_2, - }, + {"loop_index": 1, "iteration_id": "1_0", "return_value": KRYO_INT_1}, + {"loop_index": 1, "iteration_id": "2_0", "return_value": KRYO_INT_2}, ] candidate_results = [ - { - "loop_index": 1, - "iteration_id": "1_0", - "return_value": KRYO_INT_1, - }, + {"loop_index": 1, "iteration_id": "1_0", "return_value": KRYO_INT_1} # Missing second iteration ] @@ -779,12 +692,8 @@ def test_float_values_slightly_different(self): For truly different values, the difference must exceed the epsilon threshold. """ # These values differ by ~3e-10, which is within epsilon tolerance (1e-9) - original = { - "1": {"result_json": "3.14159", "error_json": None}, - } - candidate = { - "1": {"result_json": "3.141590001", "error_json": None}, - } + original = {"1": {"result_json": "3.14159", "error_json": None}} + candidate = {"1": {"result_json": "3.141590001", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True # Within epsilon tolerance @@ -792,11 +701,9 @@ def test_float_values_slightly_different(self): def test_float_values_significantly_different(self): """Float strings outside epsilon tolerance should be detected as different.""" - original = { - "1": {"result_json": "3.14159", "error_json": None}, - } + original = {"1": {"result_json": "3.14159", "error_json": None}} candidate = { - "1": {"result_json": "3.14160", "error_json": None}, # Differs by ~1e-5 + "1": {"result_json": "3.14160", "error_json": None} # Differs by ~1e-5 } equivalent, diffs = compare_invocations_directly(original, candidate) @@ -806,12 +713,8 @@ def test_float_values_significantly_different(self): def test_nan_string_comparison(self): """NaN as a string return value should be comparable.""" - original = { - "1": {"result_json": "NaN", "error_json": None}, - } - candidate = { - "1": {"result_json": "NaN", "error_json": None}, - } + original = {"1": {"result_json": "NaN", "error_json": None}} + candidate = {"1": {"result_json": "NaN", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -819,12 +722,8 @@ def test_nan_string_comparison(self): def test_nan_vs_number(self): """NaN vs a normal number should be detected as different.""" - original = { - "1": {"result_json": "NaN", "error_json": None}, - } - candidate = { - "1": {"result_json": "0.0", "error_json": None}, - } + original = {"1": {"result_json": "NaN", "error_json": None}} + candidate = {"1": {"result_json": "0.0", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is False @@ -832,12 +731,8 @@ def test_nan_vs_number(self): def test_infinity_string_comparison(self): """Infinity as a string return value should be comparable.""" - original = { - "1": {"result_json": "Infinity", "error_json": None}, - } - candidate = { - "1": {"result_json": "Infinity", "error_json": None}, - } + original = {"1": {"result_json": "Infinity", "error_json": None}} + candidate = {"1": {"result_json": "Infinity", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -845,12 +740,8 @@ def test_infinity_string_comparison(self): def test_negative_infinity(self): """-Infinity as a string return value should be comparable.""" - original = { - "1": {"result_json": "-Infinity", "error_json": None}, - } - candidate = { - "1": {"result_json": "-Infinity", "error_json": None}, - } + original = {"1": {"result_json": "-Infinity", "error_json": None}} + candidate = {"1": {"result_json": "-Infinity", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -858,12 +749,8 @@ def test_negative_infinity(self): def test_infinity_vs_negative_infinity(self): """Infinity and -Infinity should be detected as different.""" - original = { - "1": {"result_json": "Infinity", "error_json": None}, - } - candidate = { - "1": {"result_json": "-Infinity", "error_json": None}, - } + original = {"1": {"result_json": "Infinity", "error_json": None}} + candidate = {"1": {"result_json": "-Infinity", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is False @@ -871,12 +758,8 @@ def test_infinity_vs_negative_infinity(self): def test_empty_collection_results(self): """Empty array '[]' as return value should be comparable.""" - original = { - "1": {"result_json": "[]", "error_json": None}, - } - candidate = { - "1": {"result_json": "[]", "error_json": None}, - } + original = {"1": {"result_json": "[]", "error_json": None}} + candidate = {"1": {"result_json": "[]", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -884,12 +767,8 @@ def test_empty_collection_results(self): def test_empty_object_results(self): """Empty object '{}' as return value should be comparable.""" - original = { - "1": {"result_json": "{}", "error_json": None}, - } - candidate = { - "1": {"result_json": "{}", "error_json": None}, - } + original = {"1": {"result_json": "{}", "error_json": None}} + candidate = {"1": {"result_json": "{}", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -917,12 +796,8 @@ def test_large_number_different(self): 1e+17 as floats due to precision limits, making them indistinguishable. This is a known limitation of floating-point comparison for very large integers. """ - original = { - "1": {"result_json": "99999999999999999", "error_json": None}, - } - candidate = { - "1": {"result_json": "99999999999999998", "error_json": None}, - } + original = {"1": {"result_json": "99999999999999999", "error_json": None}} + candidate = {"1": {"result_json": "99999999999999998", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) # Due to float precision limits, these are considered equal @@ -931,12 +806,8 @@ def test_large_number_different(self): def test_large_number_significantly_different(self): """Large numbers with significant differences should be detected.""" - original = { - "1": {"result_json": "100000000000000000", "error_json": None}, - } - candidate = { - "1": {"result_json": "200000000000000000", "error_json": None}, - } + original = {"1": {"result_json": "100000000000000000", "error_json": None}} + candidate = {"1": {"result_json": "200000000000000000", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is False @@ -944,12 +815,8 @@ def test_large_number_significantly_different(self): def test_null_vs_empty_string(self): """'null' and '""' should NOT be equivalent.""" - original = { - "1": {"result_json": "null", "error_json": None}, - } - candidate = { - "1": {"result_json": '""', "error_json": None}, - } + original = {"1": {"result_json": "null", "error_json": None}} + candidate = {"1": {"result_json": '""', "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is False @@ -958,10 +825,7 @@ def test_null_vs_empty_string(self): def test_boolean_string_comparison(self): """Boolean strings 'true'/'false' should compare correctly.""" - original = { - "1": {"result_json": "true", "error_json": None}, - "2": {"result_json": "false", "error_json": None}, - } + original = {"1": {"result_json": "true", "error_json": None}, "2": {"result_json": "false", "error_json": None}} candidate = { "1": {"result_json": "true", "error_json": None}, "2": {"result_json": "false", "error_json": None}, @@ -972,12 +836,8 @@ def test_boolean_string_comparison(self): def test_boolean_true_vs_false(self): """'true' vs 'false' should be detected as different.""" - original = { - "1": {"result_json": "true", "error_json": None}, - } - candidate = { - "1": {"result_json": "false", "error_json": None}, - } + original = {"1": {"result_json": "true", "error_json": None}} + candidate = {"1": {"result_json": "false", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is False @@ -1024,12 +884,8 @@ def test_compare_schema_mismatch_db(self, tmp_path: Path): def test_compare_with_none_return_values_direct(self): """Rows where result_json is None should be handled in direct comparison.""" - original = { - "1": {"result_json": None, "error_json": None}, - } - candidate = { - "1": {"result_json": None, "error_json": None}, - } + original = {"1": {"result_json": None, "error_json": None}} + candidate = {"1": {"result_json": None, "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -1037,12 +893,8 @@ def test_compare_with_none_return_values_direct(self): def test_compare_one_none_one_value_direct(self): """One None result vs a real value should detect the difference.""" - original = { - "1": {"result_json": None, "error_json": None}, - } - candidate = { - "1": {"result_json": "42", "error_json": None}, - } + original = {"1": {"result_json": None, "error_json": None}} + candidate = {"1": {"result_json": "42", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is False @@ -1050,12 +902,8 @@ def test_compare_one_none_one_value_direct(self): def test_compare_both_errors_identical(self): """Identical errors in both original and candidate should be equivalent.""" - original = { - "1": {"result_json": None, "error_json": '{"type": "IOException", "message": "file not found"}'}, - } - candidate = { - "1": {"result_json": None, "error_json": '{"type": "IOException", "message": "file not found"}'}, - } + original = {"1": {"result_json": None, "error_json": '{"type": "IOException", "message": "file not found"}'}} + candidate = {"1": {"result_json": None, "error_json": '{"type": "IOException", "message": "file not found"}'}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is True @@ -1063,12 +911,8 @@ def test_compare_both_errors_identical(self): def test_compare_different_error_types(self): """Different error types should be detected.""" - original = { - "1": {"result_json": None, "error_json": '{"type": "IOException"}'}, - } - candidate = { - "1": {"result_json": None, "error_json": '{"type": "NullPointerException"}'}, - } + original = {"1": {"result_json": None, "error_json": '{"type": "IOException"}'}} + candidate = {"1": {"result_json": None, "error_json": '{"type": "NullPointerException"}'}} equivalent, diffs = compare_invocations_directly(original, candidate) assert equivalent is False @@ -1083,9 +927,7 @@ class TestComparatorJavaEdgeCases(TestTestResultsTableSchema): Extends TestTestResultsTableSchema to reuse the create_test_results_db fixture. """ - def test_comparator_float_epsilon_tolerance( - self, tmp_path: Path, create_test_results_db - ): + def test_comparator_float_epsilon_tolerance(self, tmp_path: Path, create_test_results_db): """Values differing by less than EPSILON (1e-9) should be treated as equivalent. The Java Comparator uses EPSILON=1e-9 for float comparison. @@ -1102,7 +944,7 @@ def test_comparator_float_epsilon_tolerance( "loop_index": 1, "iteration_id": "1_0", "return_value": KRYO_DOUBLE_1_0000000001, - }, + } ] candidate_results = [ @@ -1112,7 +954,7 @@ def test_comparator_float_epsilon_tolerance( "loop_index": 1, "iteration_id": "1_0", "return_value": KRYO_DOUBLE_1_0000000002, - }, + } ] create_test_results_db(original_path, original_results) @@ -1124,9 +966,7 @@ def test_comparator_float_epsilon_tolerance( assert equivalent is True assert len(diffs) == 0 - def test_comparator_nan_handling( - self, tmp_path: Path, create_test_results_db - ): + def test_comparator_nan_handling(self, tmp_path: Path, create_test_results_db): """Java Comparator should handle NaN return values.""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" @@ -1138,7 +978,7 @@ def test_comparator_nan_handling( "loop_index": 1, "iteration_id": "1_0", "return_value": KRYO_NAN, - }, + } ] create_test_results_db(original_path, results) @@ -1150,9 +990,7 @@ def test_comparator_nan_handling( assert equivalent is True assert len(diffs) == 0 - def test_comparator_empty_table( - self, tmp_path: Path, create_test_results_db - ): + def test_comparator_empty_table(self, tmp_path: Path, create_test_results_db): """Empty test_results tables should result in equivalent=False (vacuous equivalence guard).""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" @@ -1167,9 +1005,7 @@ def test_comparator_empty_table( assert equivalent is False assert len(diffs) == 0 - def test_comparator_infinity_handling( - self, tmp_path: Path, create_test_results_db - ): + def test_comparator_infinity_handling(self, tmp_path: Path, create_test_results_db): """Java Comparator should handle Infinity return values correctly.""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" @@ -1205,12 +1041,8 @@ class TestVoidFunctionComparison: def test_void_both_null_result_equivalent(self): """Both original and candidate have None result_json (void success).""" - original = { - "1": {"result_json": None, "error_json": None}, - } - candidate = { - "1": {"result_json": None, "error_json": None}, - } + original = {"1": {"result_json": None, "error_json": None}} + candidate = {"1": {"result_json": None, "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) @@ -1219,12 +1051,8 @@ def test_void_both_null_result_equivalent(self): def test_void_null_vs_non_null_result(self): """Original void (None) vs candidate with return value should differ.""" - original = { - "1": {"result_json": None, "error_json": None}, - } - candidate = { - "1": {"result_json": "42", "error_json": None}, - } + original = {"1": {"result_json": None, "error_json": None}} + candidate = {"1": {"result_json": "42", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) @@ -1234,12 +1062,8 @@ def test_void_null_vs_non_null_result(self): def test_void_non_null_vs_null_result(self): """Original with return value vs candidate void (None) should differ.""" - original = { - "1": {"result_json": "42", "error_json": None}, - } - candidate = { - "1": {"result_json": None, "error_json": None}, - } + original = {"1": {"result_json": "42", "error_json": None}} + candidate = {"1": {"result_json": None, "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) @@ -1249,12 +1073,8 @@ def test_void_non_null_vs_null_result(self): def test_void_same_serialized_side_effects(self): """Identical side-effect serializations (Object[] arrays) should be equivalent.""" - original = { - "1": {"result_json": "[1, 2, 3]", "error_json": None}, - } - candidate = { - "1": {"result_json": "[1, 2, 3]", "error_json": None}, - } + original = {"1": {"result_json": "[1, 2, 3]", "error_json": None}} + candidate = {"1": {"result_json": "[1, 2, 3]", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) @@ -1263,12 +1083,8 @@ def test_void_same_serialized_side_effects(self): def test_void_different_serialized_side_effects(self): """Different side-effect serializations should be detected.""" - original = { - "1": {"result_json": "[1, 2, 3]", "error_json": None}, - } - candidate = { - "1": {"result_json": "[1, 2, 99]", "error_json": None}, - } + original = {"1": {"result_json": "[1, 2, 3]", "error_json": None}} + candidate = {"1": {"result_json": "[1, 2, 99]", "error_json": None}} equivalent, diffs = compare_invocations_directly(original, candidate) @@ -1280,12 +1096,8 @@ def test_void_different_serialized_side_effects(self): def test_void_exception_in_candidate(self): """Void success in original vs exception in candidate should differ.""" - original = { - "1": {"result_json": None, "error_json": None}, - } - candidate = { - "1": {"result_json": None, "error_json": '{"type": "NullPointerException"}'}, - } + original = {"1": {"result_json": None, "error_json": None}} + candidate = {"1": {"result_json": None, "error_json": '{"type": "NullPointerException"}'}} equivalent, diffs = compare_invocations_directly(original, candidate) @@ -1375,9 +1187,7 @@ def _create(path: Path, results: list[dict]): return _create - def test_void_sqlite_both_null_return_same_stdout( - self, tmp_path: Path, create_void_test_results_db - ): + def test_void_sqlite_both_null_return_same_stdout(self, tmp_path: Path, create_void_test_results_db): """Both DBs have NULL return_value and same stdout — equivalent.""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" @@ -1390,7 +1200,7 @@ def test_void_sqlite_both_null_return_same_stdout( "iteration_id": "1_0", "return_value": None, "stdout": "Hello World\n", - }, + } ] create_void_test_results_db(original_path, results) @@ -1401,9 +1211,7 @@ def test_void_sqlite_both_null_return_same_stdout( assert equivalent is True assert len(diffs) == 0 - def test_void_sqlite_different_stdout( - self, tmp_path: Path, create_void_test_results_db - ): + def test_void_sqlite_different_stdout(self, tmp_path: Path, create_void_test_results_db): """Both DBs have NULL return_value but different stdout — not equivalent.""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" @@ -1416,7 +1224,7 @@ def test_void_sqlite_different_stdout( "iteration_id": "1_0", "return_value": None, "stdout": "INFO: Starting\n", - }, + } ] candidate_results = [ @@ -1427,7 +1235,7 @@ def test_void_sqlite_different_stdout( "iteration_id": "1_0", "return_value": None, "stdout": "DEBUG: Starting\n", - }, + } ] create_void_test_results_db(original_path, original_results) @@ -1438,9 +1246,7 @@ def test_void_sqlite_different_stdout( assert equivalent is False assert len(diffs) == 1 - def test_void_sqlite_null_stdout_both( - self, tmp_path: Path, create_void_test_results_db - ): + def test_void_sqlite_null_stdout_both(self, tmp_path: Path, create_void_test_results_db): """Both DBs have NULL return_value and NULL stdout — equivalent.""" original_path = tmp_path / "original.db" candidate_path = tmp_path / "candidate.db" @@ -1453,7 +1259,7 @@ def test_void_sqlite_null_stdout_both( "iteration_id": "1_0", "return_value": None, "stdout": None, - }, + } ] create_void_test_results_db(original_path, results) diff --git a/tests/test_languages/test_java/test_integration.py b/tests/test_languages/test_java/test_integration.py index 42f0d741e..f174ed92c 100644 --- a/tests/test_languages/test_java/test_integration.py +++ b/tests/test_languages/test_java/test_integration.py @@ -6,17 +6,11 @@ from codeflash.languages.base import FunctionFilterCriteria, Language from codeflash.languages.java import ( - JavaSupport, - detect_build_tool, detect_java_project, discover_functions, discover_functions_from_source, discover_test_methods, - discover_tests, extract_code_context, - find_helper_functions, - find_test_root, - format_java_code, get_java_analyzer, get_java_support, is_java_project, @@ -226,9 +220,7 @@ def test_full_optimization_cycle(self, support, tmp_path: Path): return new String(chars); }""" - optimized = support.replace_function( - src_file.read_text(), functions[0], new_code - ) + optimized = support.replace_function(src_file.read_text(), functions[0], new_code) assert "Optimized version" in optimized assert "StringUtils" in optimized diff --git a/tests/test_languages/test_java/test_run_and_parse.py b/tests/test_languages/test_java/test_run_and_parse.py index 218161fc9..f14cdb36e 100644 --- a/tests/test_languages/test_java/test_run_and_parse.py +++ b/tests/test_languages/test_java/test_run_and_parse.py @@ -114,6 +114,7 @@ def java_project(tmp_path: Path): # Clean up any SQLite files left in the shared temp dir to prevent cross-test contamination from codeflash.code_utils.code_utils import get_run_tmp_file + for i in range(10): get_run_tmp_file(Path(f"test_return_values_{i}.sqlite")).unlink(missing_ok=True) @@ -123,12 +124,7 @@ def java_project(tmp_path: Path): def _make_optimizer(project_root: Path, test_dir: Path, function_name: str, src_file: Path) -> tuple: """Create an Optimizer and FunctionOptimizer for the given function.""" - fto = FunctionToOptimize( - function_name=function_name, - file_path=src_file, - parents=[], - language="java", - ) + fto = FunctionToOptimize(function_name=function_name, file_path=src_file, parents=[], language="java") opt = Optimizer( Namespace( project_root=project_root, @@ -498,12 +494,7 @@ def test_performance_inner_loop_count_and_timing(self, java_project): project_root, src_dir, test_dir = self._setup_precise_waiter_project(java_project) test_results = self._instrument_and_run( - project_root, - src_dir, - test_dir, - self.PRECISE_WAITER_TEST, - "PreciseWaiterTest.java", - inner_iterations=2, + project_root, src_dir, test_dir, self.PRECISE_WAITER_TEST, "PreciseWaiterTest.java", inner_iterations=2 ) # 2 outer loops × 2 inner iterations = 4 total results @@ -548,9 +539,7 @@ def test_performance_inner_loop_count_and_timing(self, java_project): runtime_by_test = test_results.usable_runtime_data_by_test_case() # Should have 1 test case (constant iteration_id per call site) - assert len(runtime_by_test) == 1, ( - f"Expected 1 test case (constant iteration_id), got {len(runtime_by_test)}" - ) + assert len(runtime_by_test) == 1, f"Expected 1 test case (constant iteration_id), got {len(runtime_by_test)}" # The single test case should have 4 runtimes (2 outer loops × 2 inner iterations) for test_id, test_runtimes in runtime_by_test.items(): @@ -590,12 +579,7 @@ def test_performance_multiple_test_methods_inner_loop(self, java_project): } """ test_results = self._instrument_and_run( - project_root, - src_dir, - test_dir, - multi_test_source, - "PreciseWaiterMultiTest.java", - inner_iterations=2, + project_root, src_dir, test_dir, multi_test_source, "PreciseWaiterMultiTest.java", inner_iterations=2 ) # 2 test methods × 2 outer loops × 2 inner iterations = 8 total results @@ -658,5 +642,3 @@ def test_performance_multiple_test_methods_inner_loop(self, java_project): f"total_passed_runtime {total_runtime / 1_000_000:.3f}ms not close to expected " f"{expected_total_ns / 1_000_000:.1f}ms (2 methods × min of 4 runtimes × 10ms, ±3%)" ) - - From 39546f2eaa3bfc426c785614a68adad4efdfa3f3 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 4 Mar 2026 05:04:41 +0000 Subject: [PATCH 13/13] fix: update test_java_assertion_removal.py for Object type in assertTrue/assertFalse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same fix as test_remove_asserts.py — assertTrue/assertFalse target call capture uses Object type instead of boolean to handle non-boolean inner call return types. Co-Authored-By: Claude Opus 4.6 --- tests/test_java_assertion_removal.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_java_assertion_removal.py b/tests/test_java_assertion_removal.py index ec37e7c27..75cca13da 100644 --- a/tests/test_java_assertion_removal.py +++ b/tests/test_java_assertion_removal.py @@ -52,7 +52,7 @@ def test_assert_true(self): expected = """\ @Test void testIsValid() { - boolean _cf_result1 = validator.isValid("test"); + Object _cf_result1 = validator.isValid("test"); }""" result = transform_java_assertions(source, "isValid") assert result == expected @@ -66,7 +66,7 @@ def test_assert_false(self): expected = """\ @Test void testIsInvalid() { - boolean _cf_result1 = validator.isValid(""); + Object _cf_result1 = validator.isValid(""); }""" result = transform_java_assertions(source, "isValid") assert result == expected @@ -1102,7 +1102,7 @@ def test_volatile_field_read_preserved(self): expected = """\ @Test void testVolatileRead() { - boolean _cf_result1 = buffer.isReady(); + Object _cf_result1 = buffer.isReady(); }""" result = transform_java_assertions(source, "isReady") assert result == expected @@ -1230,7 +1230,7 @@ def test_wait_notify_pattern_preserved(self): synchronized (monitor) { monitor.notify(); } - boolean _cf_result1 = listener.wasNotified(); + Object _cf_result1 = listener.wasNotified(); }""" result = transform_java_assertions(source, "wasNotified") assert result == expected @@ -1292,8 +1292,8 @@ def test_token_bucket_synchronized_method(self): @Test void testTokenBucketAllowRequest() { TokenBucket bucket = new TokenBucket(10, 1); - boolean _cf_result1 = bucket.allowRequest(); - boolean _cf_result2 = bucket.allowRequest(); + Object _cf_result1 = bucket.allowRequest(); + Object _cf_result2 = bucket.allowRequest(); }""" result = transform_java_assertions(source, "allowRequest") assert result == expected @@ -1315,9 +1315,9 @@ def test_circular_buffer_atomic_integer_pattern(self): @Test void testCircularBufferOperations() { CircularBuffer buffer = new CircularBuffer<>(3); - boolean _cf_result1 = buffer.isEmpty(); + Object _cf_result1 = buffer.isEmpty(); buffer.put(1); - boolean _cf_result2 = buffer.isEmpty(); + Object _cf_result2 = buffer.isEmpty(); }""" result = transform_java_assertions(source, "isEmpty") assert result == expected