Merge branch 'main' into dependabot/uv/uv-0.11.6

This commit is contained in:
Kevin Turcios 2026-04-23 03:12:55 -05:00 committed by GitHub
commit 970aeb4430
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 546 additions and 24 deletions

38
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1,38 @@
# Default fallback
* @KRRT7
# Java
/codeflash/languages/java/ @mashraf-222 @HeshamHM28 @misrasaurabh1
# JavaScript / TypeScript
/codeflash/languages/javascript/ @Saga4 @mohammedahmed18 @KRRT7
# Python language support
/codeflash/languages/python/ @KRRT7
# Core pipeline
/codeflash/optimization/ @KRRT7 @aseembits93 @misrasaurabh1
/codeflash/verification/ @KRRT7 @misrasaurabh1
/codeflash/benchmarking/ @KRRT7
/codeflash/discovery/ @KRRT7 @misrasaurabh1
# CLI & setup
/codeflash/cli_cmds/ @KRRT7 @misrasaurabh1
# LSP
/codeflash/lsp/ @mohammedahmed18
# API
/codeflash/api/ @KRRT7 @aseembits93
# Tracing & entry points
/codeflash/tracing/ @misrasaurabh1 @KRRT7
/codeflash/main.py @misrasaurabh1 @KRRT7
/codeflash/tracer.py @misrasaurabh1 @KRRT7
# Shared utilities
/codeflash/code_utils/ @KRRT7 @aseembits93 @misrasaurabh1
/codeflash/models/ @KRRT7
# CI / workflows
/.github/ @KRRT7

18
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,18 @@
## Linked issue or discussion
<!-- Every PR must link to an issue or discussion — this ensures the approach has been discussed with maintainers before implementation begins, so your work fits the project's direction and doesn't need to be reworked. -->
<!-- Replace the line below with one of: -->
<!-- Closes #<number> -->
<!-- Fixes #<number> -->
<!-- Relates to #<number> -->
<!-- Discussion: <url> -->
**Required:** <!-- CI will fail if no linked issue or discussion is found. -->
## What changed
<!-- Brief description of the changes. -->
## Test plan
<!-- How was this tested? Link to passing CI, new tests, or manual verification steps. -->

View file

@ -22,6 +22,57 @@ concurrency:
cancel-in-progress: true
jobs:
# ---------------------------------------------------------------------------
# Linked issue check — every PR must reference an issue or discussion.
# Skipped on push to main and workflow_dispatch.
# ---------------------------------------------------------------------------
check-linked-issue:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- name: Check PR body for linked issue or discussion
env:
PR_BODY: ${{ github.event.pull_request.body }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
run: |
# Skip for bots (dependabot, renovate, github-actions)
if [[ "$PR_AUTHOR" == *"[bot]"* || "$PR_AUTHOR" == "dependabot" ]]; then
echo "Bot PR — skipping linked issue check."
exit 0
fi
if [ -z "$PR_BODY" ]; then
echo "::error::PR body is empty. Every PR must link an issue or discussion."
echo "Use 'Closes #<number>', 'Fixes #<number>', 'Relates to #<number>', or include a discussion URL."
exit 1
fi
# Match: #123, GH-123, org/repo#123, Closes/Fixes/Relates/Resolves #123,
# or a github.com URL to an issue or discussion
if echo "$PR_BODY" | grep -qiP '(close[sd]?|fix(e[sd])?|relate[sd]?\s+to|resolve[sd]?)\s+#\d+'; then
echo "Found linked issue keyword."
exit 0
fi
if echo "$PR_BODY" | grep -qP '#\d+'; then
echo "Found issue reference."
exit 0
fi
if echo "$PR_BODY" | grep -qiP 'github\.com/[^\s]+/(issues|discussions)/\d+'; then
echo "Found GitHub issue/discussion URL."
exit 0
fi
if echo "$PR_BODY" | grep -qiP 'CF-#?\d+'; then
echo "Found Linear ticket reference."
exit 0
fi
echo "::error::No linked issue or discussion found in PR body."
echo "Every PR must reference an issue or discussion. See CONTRIBUTING.md for details."
echo "Use 'Closes #<number>', 'Fixes #<number>', 'Relates to #<number>', or include a discussion URL."
exit 1
# ---------------------------------------------------------------------------
# Change detection — decides which downstream jobs actually run.
# On push/workflow_dispatch every flag is true so all jobs execute.
@ -506,6 +557,7 @@ jobs:
name: required checks passed
if: always()
needs:
- check-linked-issue
- unit-tests
- type-check
- prek

157
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,157 @@
# Contributing to Codeflash
Thanks for your interest in contributing. This guide covers both contributing changes back to Codeflash itself and running Codeflash from this repository in editable mode to optimize your own projects.
## Table of contents
- [Quick links](#quick-links)
- [Ways to contribute](#ways-to-contribute)
- [Development environment](#development-environment)
- [Running tests and checks](#running-tests-and-checks)
- [Code style](#code-style)
- [Branches, commits, and pull requests](#branches-commits-and-pull-requests)
- [Using Codeflash in editable mode](#using-codeflash-in-editable-mode)
- [Reporting bugs and requesting features](#reporting-bugs-and-requesting-features)
- [Security issues](#security-issues)
## Quick links
- Issues: https://github.com/codeflash-ai/codeflash/issues
- Discussions and support: [Discord](https://www.codeflash.ai/discord)
- Documentation: https://docs.codeflash.ai
- Security policy: [`SECURITY.md`](SECURITY.md)
- Project conventions for AI agents and humans alike: [`CLAUDE.md`](CLAUDE.md)
## Ways to contribute
- **Bug reports**: open an issue with a minimal reproducer that fails on `main`.
- **Bug fixes**: follow the bug-fix workflow in [`CLAUDE.md`](CLAUDE.md) - read the code, write a failing test, apply the fix, confirm the test now passes.
- **Features**: open an issue first for anything non-trivial so the approach can be agreed before implementation.
- **Documentation**: the full documentation lives at https://docs.codeflash.ai. Fixes to README, docstrings, and this guide can be submitted as PRs here.
- **Language support**: Codeflash supports Python, JavaScript / TypeScript, and Java today. New language support is a significant effort - please start with an issue.
## Development environment
### Prerequisites
- Python 3.9 or newer
- [`uv`](https://github.com/astral-sh/uv) for dependency management (required - do not use `pip` directly)
- `git`
- For JavaScript end-to-end tests: Node.js and `npm`
- For Java end-to-end tests: a JDK (see `.github/workflows/java-e2e.yaml` for the tested version)
### Setup
Fork the repository, clone your fork, and install the dev dependencies with `uv`:
```bash
git clone https://github.com/<your-username>/codeflash.git
cd codeflash
uv sync
```
`uv sync` installs Codeflash plus the `dev` dependency group (ruff, mypy, ipython, type stubs). The `codeflash` CLI is installed into the virtualenv and can be invoked via `uv run codeflash ...`.
### Optional: point at your fork's upstream
```bash
git remote add upstream https://github.com/codeflash-ai/codeflash.git
git fetch upstream
```
## Running tests and checks
Use `uv run prek` as the single verification command. It runs ruff (lint + format), mypy (strict), and related checks in one pass, matching what CI runs.
```bash
# Check every changed file against the pre-commit hooks locally
uv run prek
# Match CI behavior: check everything changed against the PR base branch
BASE=$(gh pr view --json baseRefName -q .baseRefName 2>/dev/null || echo main)
uv run prek run --from-ref origin/$BASE
```
Run the test suite with pytest via `uv`:
```bash
uv run pytest tests/
```
To run a subset:
```bash
uv run pytest tests/code_utils/ -k "test_something"
```
End-to-end tests live under `code_to_optimize/` and are exercised by CI (`.github/workflows/ci.yaml`, `java-e2e.yaml`). They can be run locally by invoking the scripts referenced from those workflows if you have the relevant runtime installed.
## Code style
The full ruleset is in [`.claude/rules/code-style.md`](.claude/rules/code-style.md). Highlights:
- **Line length**: 120 characters.
- **Python**: 3.9+ syntax. Use type annotations consistent with surrounding code.
- **Package management**: `uv` only. Do not add dependencies with `pip install`.
- **Docstrings**: do not add docstrings to new or changed code unless explicitly requested. The codebase intentionally keeps functions self-documenting through clear naming and type annotations.
- **Naming**: no leading underscores on Python names (`_private` style). Python has no true private functions; use public names.
- **File I/O**: always pass `encoding="utf-8"` to `open()`, `Path.read_text()`, `Path.write_text()`, and similar calls in new or changed code. Windows defaults to `cp1252`, which breaks on non-ASCII content.
- **Paths**: prefer absolute paths internally.
- **Verification**: `uv run prek` is the canonical check. Don't run `ruff`, `mypy`, or `python -c "import ..."` separately; `prek` handles them together.
## Branches, commits, and pull requests
- **Every PR must link an issue or discussion.** Use `Closes #<number>`, `Fixes #<number>`, or `Relates to #<number>` in the PR body. CI will fail if no linked issue or discussion is found. For trivial fixes (typos, formatting), open a lightweight issue first — it only takes a moment and keeps the history traceable. The goal is to have a conversation before the code — discussing the approach on an issue or discussion helps maintainers point you in the right direction early, so your implementation fits the project's needs and you don't spend time on work that gets reworked.
- Create a feature branch off an up-to-date `main`. Never commit directly to `main`.
- Use conventional-commit prefixes: `fix:`, `feat:`, `refactor:`, `docs:`, `test:`, `chore:`. Keep commit messages concise (1-2 sentence body max).
- Keep commits atomic - one logical change per commit.
- PR titles also use the conventional format. The PR body should be short and link the related issue.
- If the change corresponds to a Linear ticket, include `CF-#<number>` in the PR body.
- Run `uv run prek` (or `uv run prek run --from-ref origin/main`) before pushing. CI will block merge if hooks fail.
## Using Codeflash in editable mode
If you want to use Codeflash itself to optimize your own Python projects while developing or testing changes to Codeflash, you can install it in editable mode from this repository.
### Install as an editable dependency
From your target project's directory:
```bash
# Using uv (recommended)
uv add --editable /absolute/path/to/your/codeflash/checkout
# Or, if you use pip inside a virtualenv
pip install -e /absolute/path/to/your/codeflash/checkout
```
From the Codeflash repository root you can also run the CLI directly without installing into the target project:
```bash
cd /absolute/path/to/your/codeflash/checkout
uv run codeflash init # in the target project, cwd matters
uv run codeflash --all # optimize the entire target codebase
uv run codeflash optimize script.py
```
You will still need a Codeflash API key - `uv run codeflash init` walks through key generation and GitHub app setup. See the [Quick Start in the README](README.md#quick-start) for the full flow.
### When to use editable mode
- You are iterating on a Codeflash change and want to dogfood it against a real codebase.
- You need to reproduce a bug your target project hits, with your local patches applied.
- You are developing a new optimization rule, heuristic, or language integration and want end-to-end coverage beyond `tests/`.
For day-to-day optimization of a project you are not hacking on Codeflash itself, install the released package from PyPI (`pip install codeflash` or `uv add codeflash`) instead.
## Reporting bugs and requesting features
Before filing a new issue, please:
1. Search existing [open and closed issues](https://github.com/codeflash-ai/codeflash/issues?q=is%3Aissue) to avoid duplicates.
2. Include the Codeflash version (`codeflash --version`) and Python / uv versions.
3. Include the smallest reproducer you can. For bugs, a failing test that exercises the behavior is ideal.
## Security issues
Do not report suspected security issues in public GitHub issues. See [`SECURITY.md`](SECURITY.md) for the reporting process.

View file

@ -533,7 +533,15 @@ class GradleStrategy(BuildToolStrategy):
logger.error("Gradle not found — cannot pre-install multi-module dependencies")
return False
cmd = [gradle, f":{test_module}:testClasses", "-x", "test", "--build-cache", "--no-daemon"]
cmd = [
gradle,
f":{test_module}:testClasses",
"-x",
"test",
"--build-cache",
"--no-daemon",
"--configure-on-demand",
]
cmd.extend(["--init-script", _get_skip_validation_init_script()])
logger.info("Pre-installing multi-module dependencies: %s (module: %s)", build_root, test_module)
@ -568,9 +576,9 @@ class GradleStrategy(BuildToolStrategy):
return subprocess.CompletedProcess(args=["gradle"], returncode=-1, stdout="", stderr="Gradle not found")
if test_module:
cmd = [gradle, f":{test_module}:testClasses", "--no-daemon"]
cmd = [gradle, f":{test_module}:testClasses", "--no-daemon", "--configure-on-demand"]
else:
cmd = [gradle, "testClasses", "--no-daemon"]
cmd = [gradle, "testClasses", "--no-daemon", "--configure-on-demand"]
cmd.extend(["--init-script", _get_skip_validation_init_script()])
logger.debug("Compiling tests: %s in %s", " ".join(cmd), build_root)
@ -592,9 +600,9 @@ class GradleStrategy(BuildToolStrategy):
return subprocess.CompletedProcess(args=["gradle"], returncode=-1, stdout="", stderr="Gradle not found")
if test_module:
cmd = [gradle, f":{test_module}:classes", "--no-daemon"]
cmd = [gradle, f":{test_module}:classes", "--no-daemon", "--configure-on-demand"]
else:
cmd = [gradle, "classes", "--no-daemon"]
cmd = [gradle, "classes", "--no-daemon", "--configure-on-demand"]
cmd.extend(["--init-script", _get_skip_validation_init_script()])
logger.debug("Compiling source only: %s in %s", " ".join(cmd), build_root)
@ -638,7 +646,7 @@ class GradleStrategy(BuildToolStrategy):
else:
task = "codeflashPrintClasspath"
cmd = [gradle, "--init-script", init_script_path, task, "-q", "--no-daemon"]
cmd = [gradle, "--init-script", init_script_path, task, "-q", "--no-daemon", "--configure-on-demand"]
logger.debug("Getting classpath: %s", " ".join(cmd))
@ -789,7 +797,7 @@ class GradleStrategy(BuildToolStrategy):
with os.fdopen(init_fd, "w", encoding="utf-8") as f:
f.write(init_script_content)
cmd = [gradle, task, "--no-daemon", "--rerun", "--init-script", init_path]
cmd = [gradle, task, "--no-daemon", "--rerun", "--configure-on-demand", "--init-script", init_path]
cmd.extend(["--init-script", _get_skip_validation_init_script()])
for class_filter in test_filter.split(","):
@ -1044,7 +1052,7 @@ class GradleStrategy(BuildToolStrategy):
raise ValueError(msg)
gradle = self.find_executable(project_root) or "gradle"
cmd = [gradle, "test", "--no-daemon"]
cmd = [gradle, "test", "--no-daemon", "--configure-on-demand"]
if test_classes:
for cls in test_classes:
cmd.extend(["--tests", cls])

View file

@ -18,17 +18,19 @@ logger = logging.getLogger(__name__)
GRACEFUL_SHUTDOWN_WAIT = 5 # seconds to wait after SIGTERM before SIGKILL
def _run_java_with_graceful_timeout(
java_command: list[str], env: dict[str, str], timeout: int, stage_name: str
) -> None:
def _run_java_with_graceful_timeout(java_command: list[str], env: dict[str, str], timeout: int, stage_name: str) -> int:
"""Run a Java command with graceful timeout handling.
Sends SIGTERM first (allowing JFR dump and shutdown hooks to run),
then SIGKILL if the process doesn't exit within GRACEFUL_SHUTDOWN_WAIT seconds.
Returns the process exit code, or -1 if the process was killed due to timeout.
"""
if not timeout:
subprocess.run(java_command, env=env, check=False)
return
result = subprocess.run(java_command, env=env, check=False)
if result.returncode != 0:
logger.warning("%s exited with code %d", stage_name, result.returncode)
return result.returncode
import signal
@ -46,6 +48,11 @@ def _run_java_with_graceful_timeout(
logger.warning("%s stage did not exit after SIGTERM, sending SIGKILL", stage_name)
proc.kill()
proc.wait()
return -1
if proc.returncode != 0:
logger.warning("%s exited with code %d", stage_name, proc.returncode)
return proc.returncode
# --add-opens flags needed for Kryo serialization on Java 16+
@ -85,12 +92,23 @@ class JavaTracer:
combined_env = self.build_combined_env(jfr_file, config_path)
logger.info("Running combined JFR profiling + argument capture...")
_run_java_with_graceful_timeout(java_command, combined_env, timeout, "Combined tracing")
exit_code = _run_java_with_graceful_timeout(java_command, combined_env, timeout, "Combined tracing")
if not trace_db_path.exists():
msg = (
f"Combined tracing failed with exit code {exit_code} — trace database was not created at "
f"{trace_db_path}. Cannot proceed without trace data."
)
raise RuntimeError(msg)
if exit_code != 0:
logger.warning(
"Combined tracing exited with code %d but trace database was created — proceeding with partial data",
exit_code,
)
if not jfr_file.exists():
logger.warning("JFR file was not created at %s", jfr_file)
if not trace_db_path.exists():
logger.error("Trace database was not created at %s", trace_db_path)
return trace_db_path, jfr_file

View file

@ -30,6 +30,14 @@
"getting-started/java-installation"
]
},
{
"group": "Claude Code Plugin",
"pages": [
"claude-code-plugin/getting-started",
"claude-code-plugin/usage-guide",
"claude-code-plugin/troubleshooting"
]
},
{
"group": "Using Codeflash",
"pages": [
@ -67,19 +75,15 @@
"editor-plugins/vscode/configuration",
"editor-plugins/vscode/troubleshooting"
]
},
{
"group": "Claude Code Plugin",
"pages": [
"claude-code-plugin/getting-started",
"claude-code-plugin/usage-guide",
"claude-code-plugin/troubleshooting"
]
}
]
}
]
},
"banner": {
"content": "New: Use Codeflash directly inside Claude Code with the [Codeflash Claude Code Plugin](/claude-code-plugin/getting-started).",
"dismissible": true
},
"logo": {
"light": "/images/codeflash_light.svg",
"dark": "/images/codeflash_darkmode.svg"

View file

@ -0,0 +1,76 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from codeflash.languages.java.gradle_strategy import GradleStrategy
if TYPE_CHECKING:
from pathlib import Path
COD_FLAG = "--configure-on-demand"
MOCK_TARGET = "codeflash.languages.java.test_runner._run_cmd_kill_pg_on_timeout"
class TestConfigureOnDemand:
def test_compile_tests_includes_configure_on_demand(self, tmp_path: Path) -> None:
strategy = GradleStrategy()
with patch.object(strategy, "find_executable", return_value="gradlew"), patch(MOCK_TARGET) as mock_run:
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
strategy.compile_tests(tmp_path, {}, test_module=None)
cmd = mock_run.call_args[0][0]
assert COD_FLAG in cmd
def test_compile_tests_multimodule_includes_configure_on_demand(self, tmp_path: Path) -> None:
strategy = GradleStrategy()
with patch.object(strategy, "find_executable", return_value="gradlew"), patch(MOCK_TARGET) as mock_run:
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
strategy.compile_tests(tmp_path, {}, test_module="core")
cmd = mock_run.call_args[0][0]
assert COD_FLAG in cmd
assert ":core:testClasses" in cmd
def test_compile_source_only_includes_configure_on_demand(self, tmp_path: Path) -> None:
strategy = GradleStrategy()
with patch.object(strategy, "find_executable", return_value="gradlew"), patch(MOCK_TARGET) as mock_run:
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
strategy.compile_source_only(tmp_path, {}, test_module=None)
cmd = mock_run.call_args[0][0]
assert COD_FLAG in cmd
def test_get_test_run_command_includes_configure_on_demand(self, tmp_path: Path) -> None:
strategy = GradleStrategy()
with patch.object(strategy, "find_executable", return_value="gradlew"):
cmd = strategy.get_test_run_command(tmp_path)
assert COD_FLAG in cmd
def test_install_multi_module_deps_includes_configure_on_demand(self, tmp_path: Path) -> None:
strategy = GradleStrategy()
with (
patch.object(strategy, "find_executable", return_value="gradlew"),
patch(MOCK_TARGET) as mock_run,
patch("codeflash.languages.java.gradle_strategy._multimodule_deps_installed", set()),
):
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
strategy.install_multi_module_deps(tmp_path, test_module="core", env={})
cmd = mock_run.call_args[0][0]
assert COD_FLAG in cmd
assert ":core:testClasses" in cmd
def test_run_tests_via_build_tool_includes_configure_on_demand(self, tmp_path: Path) -> None:
strategy = GradleStrategy()
reports_dir = tmp_path / "build" / "test-results" / "test"
reports_dir.mkdir(parents=True, exist_ok=True)
with patch.object(strategy, "find_executable", return_value="gradlew"), patch(MOCK_TARGET) as mock_run:
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
strategy.run_tests_via_build_tool(
build_root=tmp_path,
test_paths=["com.example.TestFoo"],
env={},
timeout=60,
mode="behavior",
test_module=None,
)
cmd = mock_run.call_args[0][0]
assert COD_FLAG in cmd

View file

@ -0,0 +1,151 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
if TYPE_CHECKING:
from pathlib import Path
import pytest
from codeflash.languages.java.tracer import JavaTracer, _run_java_with_graceful_timeout
class TestRunJavaWithGracefulTimeout:
def test_returns_zero_on_success(self) -> None:
mock_result = MagicMock()
mock_result.returncode = 0
with patch("codeflash.languages.java.tracer.subprocess.run", return_value=mock_result):
rc = _run_java_with_graceful_timeout(["java", "-version"], {}, 0, "test")
assert rc == 0
def test_returns_nonzero_on_failure(self) -> None:
mock_result = MagicMock()
mock_result.returncode = 1
with patch("codeflash.languages.java.tracer.subprocess.run", return_value=mock_result):
rc = _run_java_with_graceful_timeout(["java", "-version"], {}, 0, "test")
assert rc == 1
def test_returns_exit_code_137_oom_kill(self) -> None:
mock_result = MagicMock()
mock_result.returncode = 137
with patch("codeflash.languages.java.tracer.subprocess.run", return_value=mock_result):
rc = _run_java_with_graceful_timeout(["java", "-version"], {}, 0, "test")
assert rc == 137
def test_timeout_path_returns_zero_on_success(self) -> None:
mock_proc = MagicMock()
mock_proc.returncode = 0
with patch("codeflash.languages.java.tracer.subprocess.Popen", return_value=mock_proc):
rc = _run_java_with_graceful_timeout(["java", "-version"], {}, 60, "test")
assert rc == 0
def test_timeout_path_returns_nonzero_on_failure(self) -> None:
mock_proc = MagicMock()
mock_proc.returncode = 1
with patch("codeflash.languages.java.tracer.subprocess.Popen", return_value=mock_proc):
rc = _run_java_with_graceful_timeout(["java", "-version"], {}, 60, "test")
assert rc == 1
def test_timeout_returns_negative_one(self) -> None:
import subprocess
mock_proc = MagicMock()
# First wait() times out, SIGTERM wait succeeds
mock_proc.wait.side_effect = [
subprocess.TimeoutExpired(cmd="java", timeout=60),
None, # SIGTERM wait succeeds
]
with patch("codeflash.languages.java.tracer.subprocess.Popen", return_value=mock_proc):
rc = _run_java_with_graceful_timeout(["java", "-version"], {}, 60, "test")
assert rc == -1
def test_timeout_sends_sigterm_then_sigkill(self) -> None:
import signal
import subprocess
mock_proc = MagicMock()
# First wait() times out, SIGTERM wait also times out
mock_proc.wait.side_effect = [
subprocess.TimeoutExpired(cmd="java", timeout=60),
subprocess.TimeoutExpired(cmd="java", timeout=5),
None,
]
with patch("codeflash.languages.java.tracer.subprocess.Popen", return_value=mock_proc):
rc = _run_java_with_graceful_timeout(["java", "-version"], {}, 60, "test")
assert rc == -1
mock_proc.send_signal.assert_called_once_with(signal.SIGTERM)
mock_proc.kill.assert_called_once()
class TestJavaTracerExitCodeHandling:
def test_success_with_trace_db_created(self, tmp_path: Path) -> None:
trace_db_path = (tmp_path / "trace.db").resolve()
tracer = JavaTracer()
def mock_run_timeout(java_command: list[str], env: dict, timeout: int, stage_name: str) -> int:
trace_db_path.write_bytes(b"fake-db")
return 0
with (
patch("codeflash.languages.java.tracer._run_java_with_graceful_timeout", side_effect=mock_run_timeout),
patch.object(tracer, "build_combined_env", return_value={}),
patch.object(tracer, "create_tracer_config", return_value=tmp_path / "config.json"),
):
trace_db, _jfr_file = tracer.trace(
java_command=["java", "-cp", ".", "Main"], trace_db_path=trace_db_path, packages=["com.example"]
)
assert trace_db == trace_db_path
def test_failure_without_trace_db_raises(self, tmp_path: Path) -> None:
trace_db_path = (tmp_path / "trace.db").resolve()
tracer = JavaTracer()
def mock_run_timeout(java_command: list[str], env: dict, timeout: int, stage_name: str) -> int:
return 1
with (
patch("codeflash.languages.java.tracer._run_java_with_graceful_timeout", side_effect=mock_run_timeout),
patch.object(tracer, "build_combined_env", return_value={}),
patch.object(tracer, "create_tracer_config", return_value=tmp_path / "config.json"),
pytest.raises(RuntimeError, match="Combined tracing failed with exit code 1"),
):
tracer.trace(
java_command=["java", "-cp", ".", "Main"], trace_db_path=trace_db_path, packages=["com.example"]
)
def test_nonzero_exit_with_trace_db_continues(self, tmp_path: Path) -> None:
trace_db_path = (tmp_path / "trace.db").resolve()
tracer = JavaTracer()
def mock_run_timeout(java_command: list[str], env: dict, timeout: int, stage_name: str) -> int:
trace_db_path.write_bytes(b"fake-db")
return 1
with (
patch("codeflash.languages.java.tracer._run_java_with_graceful_timeout", side_effect=mock_run_timeout),
patch.object(tracer, "build_combined_env", return_value={}),
patch.object(tracer, "create_tracer_config", return_value=tmp_path / "config.json"),
):
trace_db, _jfr_file = tracer.trace(
java_command=["java", "-cp", ".", "Main"], trace_db_path=trace_db_path, packages=["com.example"]
)
assert trace_db == trace_db_path
def test_timeout_without_trace_db_raises(self, tmp_path: Path) -> None:
trace_db_path = (tmp_path / "trace.db").resolve()
tracer = JavaTracer()
def mock_run_timeout(java_command: list[str], env: dict, timeout: int, stage_name: str) -> int:
return -1
with (
patch("codeflash.languages.java.tracer._run_java_with_graceful_timeout", side_effect=mock_run_timeout),
patch.object(tracer, "build_combined_env", return_value={}),
patch.object(tracer, "create_tracer_config", return_value=tmp_path / "config.json"),
pytest.raises(RuntimeError, match="Combined tracing failed with exit code -1"),
):
tracer.trace(
java_command=["java", "-cp", ".", "Main"], trace_db_path=trace_db_path, packages=["com.example"]
)