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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions .github/workflows/e2e-java-void-optimization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: E2E - Java Void Optimization (No Git)

on:
pull_request:
paths:
- 'codeflash/languages/java/**'
- 'codeflash/languages/base.py'
- 'codeflash/languages/registry.py'
- 'codeflash/optimization/**'
- 'codeflash/verification/**'
- 'code_to_optimize/java/**'
- 'codeflash-java-runtime/**'
- 'tests/scripts/end_to_end_test_java_void_optimization.py'
- '.github/workflows/e2e-java-void-optimization.yaml'

workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: true

jobs:
java-void-optimization-no-git:
environment: ${{ (github.event_name == 'workflow_dispatch' || (contains(toJSON(github.event.pull_request.files.*.filename), '.github/workflows/') && github.event.pull_request.user.login != 'misrasaurabh1' && github.event.pull_request.user.login != 'KRRT7')) && 'external-trusted-contributors' || '' }}

runs-on: ubuntu-latest
env:
CODEFLASH_AIS_SERVER: prod
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
CODEFLASH_API_KEY: ${{ secrets.CODEFLASH_API_KEY }}
COLUMNS: 110
MAX_RETRIES: 3
RETRY_DELAY: 5
EXPECTED_IMPROVEMENT_PCT: 70
CODEFLASH_END_TO_END: 1
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Validate PR
env:
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_STATE: ${{ github.event.pull_request.state }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -q "^.github/workflows/"; then
echo "⚠️ Workflow changes detected."
echo "PR Author: $PR_AUTHOR"
if [[ "$PR_AUTHOR" == "misrasaurabh1" || "$PR_AUTHOR" == "KRRT7" ]]; then
echo "✅ Authorized user ($PR_AUTHOR). Proceeding."
elif [[ "$PR_STATE" == "open" ]]; then
echo "✅ PR is open. Proceeding."
else
echo "⛔ Unauthorized user ($PR_AUTHOR) attempting to modify workflows. Exiting."
exit 1
fi
else
echo "✅ No workflow file changes detected. Proceeding."
fi

- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'temurin'
cache: maven

- name: Set up Python 3.11 for CLI
uses: astral-sh/setup-uv@v6
with:
python-version: 3.11.6

- name: Install dependencies (CLI)
run: uv sync

- name: Build codeflash-runtime JAR
run: |
cd codeflash-java-runtime
mvn clean package -q -DskipTests
mvn install -q -DskipTests

- name: Verify Java installation
run: |
java -version
mvn --version

- name: Remove .git
run: |
if [ -d ".git" ]; then
sudo rm -rf .git
echo ".git directory removed."
else
echo ".git directory does not exist."
exit 1
fi

- name: Run Codeflash to optimize void function
run: |
uv run python tests/scripts/end_to_end_test_java_void_optimization.py
21 changes: 21 additions & 0 deletions code_to_optimize/java/src/main/java/com/example/InPlaceSorter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example;

public class InPlaceSorter {

public static void bubbleSortInPlace(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}

int n = arr.length;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example;

public class InstanceSorter {

public void bubbleSortInPlace(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}

int n = arr.length;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.example;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class InPlaceSorterTest {

@Test
void testBubbleSortInPlace() {
int[] arr = {5, 3, 1, 4, 2};
InPlaceSorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
}

@Test
void testBubbleSortInPlaceAlreadySorted() {
int[] arr = {1, 2, 3, 4, 5};
InPlaceSorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
}

@Test
void testBubbleSortInPlaceReversed() {
int[] arr = {5, 4, 3, 2, 1};
InPlaceSorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
}

@Test
void testBubbleSortInPlaceWithDuplicates() {
int[] arr = {3, 2, 4, 1, 3, 2};
InPlaceSorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{1, 2, 2, 3, 3, 4}, arr);
}

@Test
void testBubbleSortInPlaceWithNegatives() {
int[] arr = {3, -2, 7, 0, -5};
InPlaceSorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{-5, -2, 0, 3, 7}, arr);
}

@Test
void testBubbleSortInPlaceSingleElement() {
int[] arr = {42};
InPlaceSorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{42}, arr);
}

@Test
void testBubbleSortInPlaceEmpty() {
int[] arr = {};
InPlaceSorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{}, arr);
}

@Test
void testBubbleSortInPlaceNull() {
InPlaceSorter.bubbleSortInPlace(null);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.example;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class InstanceSorterTest {

@Test
void testBubbleSortInPlace() {
InstanceSorter sorter = new InstanceSorter();
int[] arr = {5, 3, 1, 4, 2};
sorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
}

@Test
void testBubbleSortInPlaceAlreadySorted() {
InstanceSorter sorter = new InstanceSorter();
int[] arr = {1, 2, 3, 4, 5};
sorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
}

@Test
void testBubbleSortInPlaceReversed() {
InstanceSorter sorter = new InstanceSorter();
int[] arr = {5, 4, 3, 2, 1};
sorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
}

@Test
void testBubbleSortInPlaceWithDuplicates() {
InstanceSorter sorter = new InstanceSorter();
int[] arr = {3, 2, 4, 1, 3, 2};
sorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{1, 2, 2, 3, 3, 4}, arr);
}

@Test
void testBubbleSortInPlaceWithNegatives() {
InstanceSorter sorter = new InstanceSorter();
int[] arr = {3, -2, 7, 0, -5};
sorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{-5, -2, 0, 3, 7}, arr);
}

@Test
void testBubbleSortInPlaceSingleElement() {
InstanceSorter sorter = new InstanceSorter();
int[] arr = {42};
sorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{42}, arr);
}

@Test
void testBubbleSortInPlaceEmpty() {
InstanceSorter sorter = new InstanceSorter();
int[] arr = {};
sorter.bubbleSortInPlace(arr);
assertArrayEquals(new int[]{}, arr);
}

@Test
void testBubbleSortInPlaceNull() {
InstanceSorter sorter = new InstanceSorter();
sorter.bubbleSortInPlace(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,87 @@ void testIsDeserializationError() {
assertFalse(Comparator.isDeserializationError(42));
}

// ============================================================
// VOID METHOD STATE COMPARISON — proves we actually compare
// post-call state for void methods, not just skip them
// ============================================================

@Test
@DisplayName("void state: both sides sorted identically → equivalent")
void testVoidState_identicalMutation_equivalent() throws Exception {
createTestDb(originalDb);
createTestDb(candidateDb);

// Simulate: bubbleSortInPlace(arr) — both original and candidate sort correctly
// Post-call state: Object[]{sorted_array}
int[] sortedArr = {1, 2, 3, 4, 5};
byte[] origState = Serializer.serialize(new Object[]{sortedArr});
byte[] candState = Serializer.serialize(new Object[]{new int[]{1, 2, 3, 4, 5}});

insertRow(originalDb, "L1_1", 1, origState);
insertRow(candidateDb, "L1_1", 1, candState);

String json = Comparator.compareDatabases(originalDb.toString(), candidateDb.toString());
Map<String, Object> result = parseJson(json);

assertTrue((Boolean) result.get("equivalent"),
"Both sides produce same sorted array — should be equivalent");
assertEquals(1, ((Number) result.get("actualComparisons")).intValue());
}

@Test
@DisplayName("void state: candidate mutates array differently → NOT equivalent")
void testVoidState_differentMutation_rejected() throws Exception {
createTestDb(originalDb);
createTestDb(candidateDb);

// Simulate: original sorts [3,1,2] → [1,2,3]
// Bad optimization doesn't sort correctly → [3,1,2] unchanged
byte[] origState = Serializer.serialize(new Object[]{new int[]{1, 2, 3}});
byte[] candState = Serializer.serialize(new Object[]{new int[]{3, 1, 2}});

insertRow(originalDb, "L1_1", 1, origState);
insertRow(candidateDb, "L1_1", 1, candState);

String json = Comparator.compareDatabases(originalDb.toString(), candidateDb.toString());
Map<String, Object> result = parseJson(json);

assertFalse((Boolean) result.get("equivalent"),
"Candidate produced wrong array — must be rejected");
assertEquals(1, ((Number) result.get("actualComparisons")).intValue());
}

@Test
@DisplayName("void state: receiver + args both compared — wrong receiver state rejected")
void testVoidState_receiverAndArgs_wrongReceiverRejected() throws Exception {
createTestDb(originalDb);
createTestDb(candidateDb);

// Simulate: instance method sorter.sort(data)
// Post-call state is Object[]{receiver_fields_map, mutated_data}
// Original: receiver has size=3, data is [1,2,3]
// Candidate: receiver has size=0 (wrong), data is [1,2,3]
Map<String, Object> origReceiver = new HashMap<>();
origReceiver.put("size", 3);
origReceiver.put("sorted", true);
Map<String, Object> candReceiver = new HashMap<>();
candReceiver.put("size", 0);
candReceiver.put("sorted", true);

byte[] origState = Serializer.serialize(new Object[]{origReceiver, new int[]{1, 2, 3}});
byte[] candState = Serializer.serialize(new Object[]{candReceiver, new int[]{1, 2, 3}});

insertRow(originalDb, "L1_1", 1, origState);
insertRow(candidateDb, "L1_1", 1, candState);

String json = Comparator.compareDatabases(originalDb.toString(), candidateDb.toString());
Map<String, Object> result = parseJson(json);

assertFalse((Boolean) result.get("equivalent"),
"Receiver state differs (size 3 vs 0) — must be rejected even though args match");
assertEquals(1, ((Number) result.get("actualComparisons")).intValue());
}

// --- Helpers ---

private void createTestDb(Path dbPath) throws Exception {
Expand Down
6 changes: 4 additions & 2 deletions codeflash/discovery/functions_to_optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ def _find_all_functions_via_language_support(file_path: Path) -> dict[Path, list

try:
lang_support = get_language_support(file_path)
criteria = FunctionFilterCriteria(require_return=True)
require_return = lang_support.language != Language.JAVA
criteria = FunctionFilterCriteria(require_return=require_return)
functions[file_path] = lang_support.discover_functions(file_path, criteria)
except Exception as e:
logger.debug(f"Failed to discover functions in {file_path}: {e}")
Expand Down Expand Up @@ -454,7 +455,8 @@ def find_all_functions_in_file(file_path: Path) -> dict[Path, list[FunctionToOpt
from codeflash.languages.base import FunctionFilterCriteria

lang_support = get_language_support(file_path)
criteria = FunctionFilterCriteria(require_return=True)
require_return = lang_support.language != Language.JAVA
criteria = FunctionFilterCriteria(require_return=require_return)
source = file_path.read_text(encoding="utf-8")
return {file_path: lang_support.discover_functions(source, file_path, criteria)}
except Exception as e:
Expand Down
Loading
Loading