85742ab165
CPU Test / Lint - next (push) Waiting to run
Dashboard / Chromatic (push) Waiting to run
CPU Test / Lint - fast (push) Waiting to run
CPU Test / Build documentation (push) Waiting to run
CPU Test / Test (Store, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Weave, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Others, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Store, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Utilities, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Weave, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Others, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Store, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Utilities, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Weave, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (Others, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (Store, latest, Python 3.13) (push) Waiting to run
CPU Test / Lint - slow (push) Waiting to run
CPU Test / Lint - JavaScript (push) Waiting to run
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Others, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Utilities, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (Weave, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (JavaScript) (push) Waiting to run
Deploy Documentation / deploy (push) Has been cancelled
259 lines
9.5 KiB
Python
259 lines
9.5 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
"""Evaluation module for SWE-bench instance testing and grading.
|
|
|
|
This module provides core functionality for evaluating model predictions on SWE-bench
|
|
instances. It handles containerized execution of test scripts, patch application,
|
|
and generation of evaluation reports. The module orchestrates the complete evaluation
|
|
process including container management, patch application, test execution, and result
|
|
grading.
|
|
"""
|
|
|
|
import json
|
|
import traceback
|
|
from pathlib import Path, PurePosixPath
|
|
from typing import Any, Dict, Optional
|
|
|
|
from docker.models.containers import ExecResult
|
|
from swebench.harness.constants import (
|
|
APPLY_PATCH_FAIL,
|
|
APPLY_PATCH_PASS,
|
|
DOCKER_PATCH,
|
|
DOCKER_USER,
|
|
DOCKER_WORKDIR,
|
|
INSTANCE_IMAGE_BUILD_DIR,
|
|
KEY_MODEL,
|
|
KEY_PREDICTION,
|
|
LOG_INSTANCE,
|
|
LOG_REPORT,
|
|
LOG_TEST_OUTPUT,
|
|
RUN_EVALUATION_LOG_DIR,
|
|
UTF8,
|
|
SWEbenchInstance,
|
|
)
|
|
from swebench.harness.docker_build import close_logger # type: ignore
|
|
from swebench.harness.docker_build import (
|
|
BuildImageError,
|
|
build_container,
|
|
setup_logger,
|
|
)
|
|
from swebench.harness.docker_utils import cleanup_container # type: ignore
|
|
from swebench.harness.docker_utils import exec_run_with_timeout # type: ignore
|
|
from swebench.harness.docker_utils import remove_image # type: ignore
|
|
from swebench.harness.docker_utils import should_remove # type: ignore
|
|
from swebench.harness.docker_utils import (
|
|
copy_to_container,
|
|
)
|
|
from swebench.harness.grading import get_eval_report
|
|
from swebench.harness.test_spec.test_spec import TestSpec, make_test_spec
|
|
from swebench.harness.utils import EvaluationError
|
|
|
|
import docker
|
|
|
|
GIT_APPLY_CMDS = [
|
|
"git apply --verbose",
|
|
"git apply --verbose --reject",
|
|
"patch --batch --fuzz=5 -p1 -i",
|
|
]
|
|
|
|
|
|
def run_instance(
|
|
test_spec: TestSpec,
|
|
pred: Dict[str, Any],
|
|
rm_image: bool,
|
|
force_rebuild: bool,
|
|
client: docker.DockerClient,
|
|
run_id: str,
|
|
timeout: int | None = None,
|
|
rewrite_reports: bool = False,
|
|
):
|
|
"""
|
|
Run a single instance with the given prediction.
|
|
|
|
Args:
|
|
test_spec (TestSpec): TestSpec instance
|
|
pred (dict): Prediction w/ model_name_or_path, model_patch, instance_id
|
|
rm_image (bool): Whether to remove the image after running
|
|
force_rebuild (bool): Whether to force rebuild the image
|
|
client (docker.DockerClient): Docker client
|
|
run_id (str): Run ID
|
|
timeout (int): Timeout for running tests
|
|
rewrite_reports (bool): True if eval run is just to reformat existing report
|
|
"""
|
|
# Set up logging directory
|
|
instance_id = test_spec.instance_id
|
|
model_name_or_path = pred.get(KEY_MODEL, "None").replace("/", "__")
|
|
log_dir = RUN_EVALUATION_LOG_DIR / run_id / model_name_or_path / instance_id
|
|
|
|
# Set up report file
|
|
report_path = log_dir / LOG_REPORT
|
|
if rewrite_reports:
|
|
test_output_path = log_dir / LOG_TEST_OUTPUT
|
|
if not test_output_path.exists():
|
|
raise ValueError(f"Test output file {test_output_path} does not exist")
|
|
report = get_eval_report(
|
|
test_spec=test_spec,
|
|
prediction=pred,
|
|
test_log_path=test_output_path,
|
|
include_tests_status=True,
|
|
)
|
|
# Write report to report.json
|
|
with open(report_path, "w") as f:
|
|
f.write(json.dumps(report, indent=4))
|
|
return instance_id, report
|
|
if report_path.exists():
|
|
return instance_id, json.loads(report_path.read_text())
|
|
|
|
if not test_spec.is_remote_image:
|
|
# Link the image build dir in the log dir
|
|
build_dir = INSTANCE_IMAGE_BUILD_DIR / test_spec.instance_image_key.replace(":", "__")
|
|
image_build_link = log_dir / "image_build_dir"
|
|
if not image_build_link.exists():
|
|
try:
|
|
# link the image build dir in the log dir
|
|
image_build_link.symlink_to(build_dir.absolute(), target_is_directory=True)
|
|
except:
|
|
# some error, idk why
|
|
pass
|
|
|
|
# Set up logger
|
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
log_file = log_dir / LOG_INSTANCE
|
|
logger = setup_logger(instance_id, log_file)
|
|
|
|
# Run the instance
|
|
container = None
|
|
try:
|
|
# Build + start instance container (instance image should already be built)
|
|
container = build_container(test_spec, client, run_id, logger, rm_image, force_rebuild)
|
|
container.start()
|
|
logger.info(f"Container for {instance_id} started: {container.id}")
|
|
|
|
# Copy model prediction as patch file to container
|
|
patch_file = Path(log_dir / "patch.diff")
|
|
patch_file.write_text(pred[KEY_PREDICTION] or "")
|
|
logger.info(f"Intermediate patch for {instance_id} written to {patch_file}, now applying to container...")
|
|
copy_to_container(container, patch_file, PurePosixPath(DOCKER_PATCH)) # type: ignore
|
|
|
|
# Attempt to apply patch to container (TODO: FIX THIS)
|
|
val: Optional[ExecResult] = None
|
|
for git_apply_cmd in GIT_APPLY_CMDS:
|
|
val = container.exec_run( # type: ignore
|
|
f"{git_apply_cmd} {DOCKER_PATCH}",
|
|
workdir=DOCKER_WORKDIR,
|
|
user=DOCKER_USER,
|
|
)
|
|
if val.exit_code == 0:
|
|
logger.info(f"{APPLY_PATCH_PASS}:\n{val.output.decode(UTF8)}")
|
|
break
|
|
else:
|
|
logger.info(f"Failed to apply patch to container: {git_apply_cmd}")
|
|
if val is not None:
|
|
logger.info(f"{APPLY_PATCH_FAIL}:\n{val.output.decode(UTF8)}")
|
|
raise EvaluationError(
|
|
instance_id,
|
|
f"{APPLY_PATCH_FAIL}:\n{val.output.decode(UTF8)}",
|
|
logger,
|
|
)
|
|
|
|
# Get git diff before running eval script
|
|
git_diff_output_before = (
|
|
container.exec_run("git -c core.fileMode=false diff", workdir=DOCKER_WORKDIR).output.decode(UTF8).strip() # type: ignore
|
|
)
|
|
logger.info(f"Git diff before:\n{git_diff_output_before}")
|
|
|
|
eval_file = Path(log_dir / "eval.sh")
|
|
eval_file.write_text(test_spec.eval_script)
|
|
logger.info(f"Eval script for {instance_id} written to {eval_file}; copying to container...")
|
|
copy_to_container(container, eval_file, PurePosixPath("/eval.sh")) # type: ignore
|
|
|
|
# Run eval script, write output to logs
|
|
test_output, timed_out, total_runtime = exec_run_with_timeout(container, "/bin/bash /eval.sh", timeout)
|
|
test_output_path = log_dir / LOG_TEST_OUTPUT
|
|
logger.info(f"Test runtime: {total_runtime:_.2f} seconds")
|
|
with open(test_output_path, "w") as f:
|
|
f.write(test_output)
|
|
logger.info(f"Test output for {instance_id} written to {test_output_path}")
|
|
if timed_out:
|
|
f.write(f"\n\nTimeout error: {timeout} seconds exceeded.")
|
|
raise EvaluationError(
|
|
instance_id,
|
|
f"Test timed out after {timeout} seconds.",
|
|
logger,
|
|
)
|
|
|
|
# Get git diff after running eval script (ignore permission changes)
|
|
git_diff_output_after = (
|
|
container.exec_run("git -c core.fileMode=false diff", workdir=str(DOCKER_WORKDIR)).output.decode(UTF8).strip() # type: ignore
|
|
)
|
|
|
|
# Check if git diff changed after running eval script
|
|
logger.info(f"Git diff after:\n{git_diff_output_after}")
|
|
if git_diff_output_after != git_diff_output_before:
|
|
logger.info("Git diff changed after running eval script")
|
|
|
|
# Get report from test output
|
|
logger.info(f"Grading answer for {instance_id}...")
|
|
report = get_eval_report(
|
|
test_spec=test_spec,
|
|
prediction=pred,
|
|
test_log_path=test_output_path,
|
|
include_tests_status=True,
|
|
)
|
|
logger.info(f"report: {report}\n" f"Result for {instance_id}: resolved: {report[instance_id]['resolved']}")
|
|
|
|
# Write report to report.json
|
|
with open(report_path, "w") as f:
|
|
f.write(json.dumps(report, indent=4))
|
|
return instance_id, report
|
|
except EvaluationError as e:
|
|
error_msg = traceback.format_exc()
|
|
logger.info(error_msg)
|
|
print(e)
|
|
except BuildImageError as e:
|
|
error_msg = traceback.format_exc()
|
|
logger.info(error_msg)
|
|
print(e)
|
|
except Exception as e:
|
|
error_msg = f"Error in evaluating model for {instance_id}: {e}\n" f"{traceback.format_exc()}"
|
|
logger.error(error_msg)
|
|
finally:
|
|
# Remove instance container + image, close logger
|
|
cleanup_container(client, container, logger)
|
|
if rm_image:
|
|
remove_image(client, test_spec.instance_image_key, logger)
|
|
close_logger(logger)
|
|
return
|
|
|
|
|
|
def evaluate(
|
|
prediction: Dict[str, Any],
|
|
instance: SWEbenchInstance,
|
|
cache_level: str,
|
|
clean: bool,
|
|
force_rebuild: bool,
|
|
run_id: str,
|
|
timeout: Optional[int],
|
|
namespace: Optional[str],
|
|
instance_image_tag: str,
|
|
rewrite_reports: bool,
|
|
):
|
|
client = docker.from_env()
|
|
test_spec = make_test_spec(instance, namespace=namespace, instance_image_tag=instance_image_tag)
|
|
|
|
instance_image_ids = {
|
|
test_spec.instance_image_key,
|
|
}
|
|
existing_images = {tag for i in client.images.list(all=True) for tag in i.tags if tag in instance_image_ids}
|
|
|
|
return run_instance(
|
|
test_spec,
|
|
prediction,
|
|
should_remove(test_spec.instance_image_key, cache_level, clean, existing_images),
|
|
force_rebuild,
|
|
client,
|
|
run_id,
|
|
timeout,
|
|
rewrite_reports,
|
|
)
|