Spaces:
Running on Zero
Running on Zero
Download codestar/core/runner.py from dongyanggg/codestar-backend: direct link, hf CLI and curl.
- Browser
- Download file 30.2 kB
-
https://huggingface.co/spaces/dongyanggg/codestar-backend/resolve/main/codestar/core/runner.py
- Command line
-
hf download hf://spaces/dongyanggg/codestar-backend/codestar/core/runner.py
-
curl -L -o runner.py https://huggingface.co/spaces/dongyanggg/codestar-backend/resolve/main/codestar/core/runner.py
30.2 kB
| from __future__ import annotations | |
| import json | |
| import math | |
| import os | |
| import secrets | |
| import subprocess | |
| import sys | |
| import tempfile | |
| import time | |
| from pathlib import Path | |
| from typing import Dict, Any, List, Optional | |
| from codestar.core.schemas import ( | |
| JudgeResult, | |
| JudgeStatus, | |
| CaseResult, | |
| ProblemMeta, | |
| ) | |
| # Python interpreter to use for running harness | |
| PYTHON_EXE = ( | |
| r"C:\Users\hj\.workbuddy\binaries\python\envs\default\Scripts\python.exe" | |
| if os.path.exists(r"C:\Users\hj\.workbuddy\binaries\python\envs\default\Scripts\python.exe") | |
| else sys.executable | |
| ) | |
| HARNESS_TEMPLATE = """# -*- coding: utf-8 -*- | |
| import sys | |
| import time | |
| __HARNESS_T0__ = time.time() | |
| import json | |
| import math | |
| import traceback | |
| import tracemalloc | |
| import _thread | |
| import threading | |
| from typing import Optional, List, Tuple, Dict, Any | |
| # Setup environment & seeds | |
| try: | |
| import torch | |
| torch.manual_seed(42) | |
| if torch.cuda.is_available(): | |
| torch.cuda.manual_seed_all(42) | |
| except ImportError: | |
| pass | |
| try: | |
| import numpy as np | |
| np.random.seed(42) | |
| except ImportError: | |
| pass | |
| # Import comparator from codestar | |
| from codestar.core.comparator import compare_outputs, summarize_obj | |
| # Argument cloning to prevent in-place mutation side-effects between ref and user code | |
| def _clone_arg(arg): | |
| if "torch" in sys.modules and isinstance(arg, sys.modules["torch"].Tensor): | |
| return arg.clone() | |
| elif "numpy" in sys.modules and isinstance(arg, sys.modules["numpy"].ndarray): | |
| return arg.copy() | |
| elif isinstance(arg, list): | |
| return [_clone_arg(x) for x in arg] | |
| elif isinstance(arg, tuple): | |
| return tuple(_clone_arg(x) for x in arg) | |
| elif isinstance(arg, dict): | |
| return {k: _clone_arg(v) for k, v in arg.items()} | |
| return arg | |
| def _clone_args(args, kwargs): | |
| c_args = tuple(_clone_arg(a) for a in args) if args else () | |
| c_kwargs = {k: _clone_arg(v) for k, v in kwargs.items()} if kwargs else {} | |
| return c_args, c_kwargs | |
| def _safe_wrap(obj): | |
| if isinstance(obj, type): | |
| class WrappedClass(obj): | |
| __wrapped__ = obj | |
| def __init__(self, *args, **kwargs): | |
| c_args, c_kwargs = _clone_args(args, kwargs) | |
| super().__init__(*c_args, **c_kwargs) | |
| def __call__(self, *args, **kwargs): | |
| c_args, c_kwargs = _clone_args(args, kwargs) | |
| return super().__call__(*c_args, **c_kwargs) | |
| def forward(self, *args, **kwargs): | |
| c_args, c_kwargs = _clone_args(args, kwargs) | |
| return super().forward(*c_args, **c_kwargs) | |
| WrappedClass.__name__ = getattr(obj, "__name__", "WrappedClass") | |
| WrappedClass.__qualname__ = getattr(obj, "__qualname__", "WrappedClass") | |
| return WrappedClass | |
| elif callable(obj): | |
| def wrapped(*args, **kwargs): | |
| c_args, c_kwargs = _clone_args(args, kwargs) | |
| return obj(*c_args, **c_kwargs) | |
| wrapped.__name__ = getattr(obj, "__name__", "wrapped") | |
| wrapped.__wrapped__ = obj | |
| return wrapped | |
| return obj | |
| # Load Reference Implementation | |
| ref_ns = {} | |
| try: | |
| exec(__REF_CODE_STR__, ref_ns) | |
| except MemoryError: | |
| msg = "Memory Limit Exceeded: Out of Memory during tensor allocation" | |
| print("__ERROR_DELIM__") | |
| print(json.dumps({"error": "MLE", "message": msg, "traceback": msg})) | |
| sys.exit(0) | |
| except Exception as e: | |
| err = traceback.format_exc() | |
| lines = [line.strip() for line in err.splitlines() if line.strip()] | |
| concise = lines[-1] if lines else str(e) | |
| print("__ERROR_DELIM__") | |
| print(json.dumps({"error": "RE", "message": concise, "traceback": err})) | |
| sys.exit(0) | |
| # Load User Implementation | |
| user_ns = {} | |
| try: | |
| exec(__USER_CODE_STR__, user_ns) | |
| except MemoryError: | |
| msg = "Memory Limit Exceeded: Out of Memory during tensor allocation" | |
| print("__ERROR_DELIM__") | |
| print(json.dumps({"error": "MLE", "message": msg, "traceback": msg})) | |
| sys.exit(0) | |
| except Exception as e: | |
| err = traceback.format_exc() | |
| lines = [line.strip() for line in err.splitlines() if line.strip()] | |
| concise = lines[-1] if lines else str(e) | |
| print("__ERROR_DELIM__") | |
| print(json.dumps({"error": "RE", "message": concise, "traceback": err})) | |
| sys.exit(0) | |
| entry_point = "__ENTRY_POINT__" | |
| if entry_point not in user_ns: | |
| msg = f"Entry point '{entry_point}' not found in user submission." | |
| print("__ERROR_DELIM__") | |
| print(json.dumps({"error": "RE", "message": msg, "traceback": msg})) | |
| sys.exit(0) | |
| raw_user_obj = user_ns[entry_point] | |
| raw_ref_obj = ref_ns[entry_point] | |
| user_obj = _safe_wrap(raw_user_obj) | |
| ref_obj = _safe_wrap(raw_ref_obj) | |
| atol = __ATOL__ | |
| rtol = __RTOL__ | |
| # Start memory tracking | |
| tracemalloc.start() | |
| # Run problem-specific test runner or custom testcase | |
| cases_results = [] | |
| all_passed = True | |
| custom_input = __CUSTOM_TESTCASE_STR__ | |
| if custom_input.strip(): | |
| t0 = time.time() | |
| safe_builtins = { | |
| "abs": abs, "all": all, "any": any, "bool": bool, "dict": dict, | |
| "enumerate": enumerate, "filter": filter, "float": float, "int": int, | |
| "isinstance": isinstance, "len": len, "list": list, "map": map, | |
| "max": max, "min": min, "range": range, "reversed": reversed, | |
| "round": round, "set": set, "slice": slice, "sorted": sorted, | |
| "str": str, "sum": sum, "tuple": tuple, "zip": zip, | |
| "True": True, "False": False, "None": None, | |
| "__import__": __import__, | |
| } | |
| custom_ns = { | |
| "__builtins__": safe_builtins, | |
| "torch": sys.modules.get("torch"), | |
| "math": sys.modules.get("math"), | |
| "time": time, | |
| "np": sys.modules.get("numpy"), | |
| "List": list, | |
| "Tuple": tuple, | |
| "Dict": dict, | |
| "Optional": Optional, | |
| "user_obj": user_obj, | |
| "ref_obj": ref_obj, | |
| } | |
| if "torch" in sys.modules and hasattr(sys.modules["torch"], "nn"): | |
| custom_ns["nn"] = sys.modules["torch"].nn | |
| custom_ns["F"] = getattr(sys.modules["torch"].nn, "functional", None) | |
| input_text = custom_input.strip() | |
| args = None | |
| kwargs = {} | |
| try: | |
| # First try: direct expression evaluation (e.g. "0.1" or "torch.randn(2, 4)") | |
| try: | |
| val = eval(input_text, custom_ns) | |
| if isinstance(val, tuple): | |
| args = val | |
| else: | |
| args = (val,) | |
| except Exception: | |
| # Second try: statement block with 'args = ...' or local vars | |
| exec(input_text, custom_ns) | |
| if "args" in custom_ns: | |
| args = custom_ns["args"] if isinstance(custom_ns["args"], tuple) else (custom_ns["args"],) | |
| elif "kwargs" in custom_ns: | |
| kwargs = custom_ns["kwargs"] | |
| args = custom_ns.get("args", ()) | |
| else: | |
| excluded = {"torch", "math", "time", "np", "List", "Tuple", "Dict", "Optional", "user_obj", "ref_obj", "nn", "F", "__builtins__"} | |
| user_vars = [v for k, v in custom_ns.items() if not k.startswith("_") and k not in excluded] | |
| if user_vars: | |
| args = tuple(user_vars) | |
| else: | |
| raise ValueError(f"无法解析自定义用例输入: {input_text}") | |
| # Helper to coerce raw Python lists into torch.Tensor / np.ndarray when annotation expects it | |
| import inspect | |
| def _is_all_bools(seq): | |
| if isinstance(seq, bool): | |
| return True | |
| if isinstance(seq, (list, tuple)) and len(seq) > 0: | |
| return all(_is_all_bools(x) for x in seq) | |
| return False | |
| def _is_all_ints(seq): | |
| if isinstance(seq, bool): | |
| return False | |
| if isinstance(seq, int): | |
| return True | |
| if isinstance(seq, (list, tuple)) and len(seq) > 0: | |
| return all(_is_all_ints(x) for x in seq) | |
| return False | |
| def _coerce_val(val, param): | |
| if param is None or val is None: | |
| return val | |
| ann_str = str(param.annotation) if param.annotation != inspect.Parameter.empty else "" | |
| p_name = param.name.lower() | |
| if "Tensor" in ann_str and isinstance(val, (list, tuple)) and "torch" in sys.modules: | |
| _torch = sys.modules["torch"] | |
| if _is_all_bools(val): | |
| return _torch.tensor(val, dtype=_torch.bool) | |
| if _is_all_ints(val) and any(k in p_name for k in ("idx", "index", "indices", "token", "target", "label", "id", "pos", "seq")): | |
| return _torch.tensor(val, dtype=_torch.long) | |
| return _torch.tensor(val, dtype=_torch.float32) | |
| if "ndarray" in ann_str and isinstance(val, (list, tuple)) and "numpy" in sys.modules: | |
| _np = sys.modules["numpy"] | |
| return _np.asarray(val, dtype=_np.float64 if not _is_all_ints(val) else _np.int64) | |
| return val | |
| def _coerce_args_for_callable(fn_obj, raw_args, raw_kwargs): | |
| try: | |
| sig = inspect.signature(fn_obj) | |
| sig_params = [p for p in sig.parameters.values() if p.name not in ("self", "args", "kwargs")] | |
| new_args = [] | |
| for idx_a, a_val in enumerate(raw_args): | |
| p_obj = sig_params[idx_a] if idx_a < len(sig_params) else None | |
| new_args.append(_coerce_val(a_val, p_obj)) | |
| param_map = {p.name: p for p in sig_params} | |
| new_kwargs = {k: _coerce_val(v, param_map.get(k)) for k, v in raw_kwargs.items()} | |
| return tuple(new_args), new_kwargs | |
| except Exception: | |
| return raw_args, raw_kwargs | |
| # Check if entry point is a class (e.g. nn.Module) or a function | |
| is_class = isinstance(ref_obj, type) | |
| if is_class: | |
| target_cls = getattr(ref_obj, "__wrapped__", ref_obj) | |
| if not hasattr(target_cls, "forward") and hasattr(target_cls, "split_heads"): | |
| args, kwargs = _coerce_args_for_callable(target_cls.split_heads, args, kwargs) | |
| exp_s = ref_obj.split_heads(*args, **kwargs) | |
| act_s = user_obj.split_heads(*args, **kwargs) | |
| exp = ref_obj.merge_heads(exp_s) | |
| act = user_obj.merge_heads(act_s) | |
| else: | |
| init_sig = inspect.signature(target_cls.__init__) | |
| init_params = [ | |
| p for p in init_sig.parameters.values() | |
| if p.name not in ("self", "args", "kwargs") | |
| ] | |
| req_init_params = [p for p in init_params if p.default == inspect.Parameter.empty] | |
| req_init_count = len(req_init_params) | |
| # If args has only 1 element (or matches forward signature) and is a list when forward expects Tensor, coerce it first | |
| if hasattr(target_cls, "forward") and len(args) > 0: | |
| fwd_sig = inspect.signature(target_cls.forward) | |
| fwd_params = [p for p in fwd_sig.parameters.values() if p.name not in ("self", "args", "kwargs")] | |
| if len(args) <= len(fwd_params) and isinstance(args[0], (list, tuple)): | |
| args, kwargs = _coerce_args_for_callable(target_cls.forward, args, kwargs) | |
| has_tensor_arg = len(args) > 0 and ( | |
| ("torch" in sys.modules and isinstance(args[0], sys.modules["torch"].Tensor)) or | |
| ("numpy" in sys.modules and isinstance(args[0], sys.modules["numpy"].ndarray)) | |
| ) | |
| if has_tensor_arg: | |
| x0 = args[0] | |
| last_dim = x0.shape[-1] if hasattr(x0, "shape") and len(x0.shape) > 0 else 16 | |
| inferred_init = [] | |
| if req_init_count > 0: | |
| for p in req_init_params: | |
| p_name = p.name.lower() | |
| if any(k in p_name for k in ("dim", "size", "features", "shape", "channel")): | |
| inferred_init.append(last_dim) | |
| elif any(k in p_name for k in ("rank", "r")): | |
| inferred_init.append(min(4, last_dim)) | |
| elif "kv" in p_name and "head" in p_name: | |
| inferred_init.append(1 if last_dim % 2 != 0 else 2) | |
| elif any(k in p_name for k in ("head",)): | |
| inferred_init.append(4 if last_dim % 4 == 0 else (2 if last_dim % 2 == 0 else 1)) | |
| else: | |
| inferred_init.append(last_dim) | |
| elif len(init_params) > 0 and any(k in init_params[0].name.lower() for k in ("dim", "size", "features")): | |
| inferred_init.append(last_dim) | |
| init_slice = tuple(inferred_init) | |
| fwd_slice = args | |
| elif len(args) > req_init_count and len(init_params) > 0 and not has_tensor_arg: | |
| split_at = min(len(init_params), len(args) - 1) | |
| init_slice = args[:split_at] | |
| fwd_slice = args[split_at:] | |
| if hasattr(target_cls, "forward"): | |
| fwd_slice, _ = _coerce_args_for_callable(target_cls.forward, fwd_slice, {}) | |
| else: | |
| init_slice = args | |
| fwd_slice = () | |
| try: | |
| ref_inst = ref_obj(*init_slice) | |
| user_inst = user_obj(*init_slice) | |
| if hasattr(ref_inst, "state_dict") and hasattr(user_inst, "load_state_dict"): | |
| try: | |
| user_inst.load_state_dict(ref_inst.state_dict(), strict=False) | |
| except Exception: | |
| pass | |
| elif hasattr(ref_inst, "weight") and hasattr(user_inst, "weight"): | |
| try: | |
| user_inst.weight.data.copy_(ref_inst.weight.data) | |
| except Exception: | |
| pass | |
| if fwd_slice: | |
| if "torch" in sys.modules: | |
| sys.modules["torch"].manual_seed(42) | |
| exp = ref_inst(*fwd_slice) | |
| if "torch" in sys.modules: | |
| sys.modules["torch"].manual_seed(42) | |
| act = user_inst(*fwd_slice) | |
| else: | |
| exp = ref_inst | |
| act = user_inst | |
| except Exception: | |
| exp = ref_obj(*args, **kwargs) | |
| act = user_obj(*args, **kwargs) | |
| else: | |
| target_fn = getattr(ref_obj, "__wrapped__", ref_obj) | |
| args, kwargs = _coerce_args_for_callable(target_fn, args, kwargs) | |
| exp = ref_obj(*args, **kwargs) | |
| act = user_obj(*args, **kwargs) | |
| match, err, max_diff = compare_outputs(act, exp, atol=atol, rtol=rtol) | |
| passed, msg = match, err | |
| except MemoryError: | |
| passed, msg = False, "Memory Limit Exceeded: Out of Memory during tensor allocation" | |
| act, exp, max_diff = None, None, None | |
| except Exception as e: | |
| passed, msg = False, f"自定义用例运行异常: {str(e)}" | |
| act, exp, max_diff = None, None, None | |
| cases_results.append({ | |
| "case_id": 1, | |
| "case_name": "自定义用例自测 (Custom Input)", | |
| "passed": passed, | |
| "time_ms": round((time.time() - t0) * 1000, 2), | |
| "message": msg, | |
| "user_output_summary": summarize_obj(act) if act is not None else None, | |
| "expected_summary": summarize_obj(exp) if exp is not None else None, | |
| "max_diff": max_diff, | |
| "input_summary": input_text[:300], | |
| }) | |
| else: | |
| # Execute user-provided or standard test suite | |
| test_ns = { | |
| "user_obj": user_obj, | |
| "ref_obj": ref_obj, | |
| "compare_outputs": compare_outputs, | |
| "summarize_obj": summarize_obj, | |
| "atol": atol, | |
| "rtol": rtol, | |
| "cases_results": cases_results, | |
| "time": time, | |
| } | |
| __limit = float(__TIME_LIMIT__) | |
| __elapsed_init = time.time() - __HARNESS_T0__ | |
| __budget = max(0.6, min(__limit, 5.2 - __elapsed_init)) if __limit <= 3.5 else __limit | |
| _watchdog = threading.Timer(__budget, _thread.interrupt_main) | |
| _watchdog.daemon = True | |
| _watchdog.start() | |
| try: | |
| exec(__TEST_CODE_STR__, test_ns) | |
| except KeyboardInterrupt: | |
| msg = f"Time Limit Exceeded (> {__TIME_LIMIT__}s)" | |
| print("__ERROR_DELIM__") | |
| print(json.dumps({"error": "TLE", "message": msg, "traceback": msg})) | |
| sys.exit(0) | |
| except MemoryError: | |
| msg = "Memory Limit Exceeded: Out of Memory during tensor allocation" | |
| print("__ERROR_DELIM__") | |
| print(json.dumps({"error": "MLE", "message": msg, "traceback": msg})) | |
| sys.exit(0) | |
| except Exception as e: | |
| err = traceback.format_exc() | |
| lines = [line.strip() for line in err.splitlines() if line.strip()] | |
| concise = lines[-1] if lines else str(e) | |
| print("__ERROR_DELIM__") | |
| print(json.dumps({"error": "RE", "message": concise, "traceback": err})) | |
| sys.exit(0) | |
| finally: | |
| _watchdog.cancel() | |
| # Stop memory tracking | |
| _, peak_bytes = tracemalloc.get_traced_memory() | |
| tracemalloc.stop() | |
| peak_memory_mb = round(peak_bytes / (1024 * 1024), 2) | |
| # RFC 8259 Sanitization | |
| def _sanitize_for_json(val): | |
| if isinstance(val, float): | |
| if math.isnan(val) or math.isinf(val): | |
| return 0.0 | |
| return val | |
| elif isinstance(val, dict): | |
| return {k: _sanitize_for_json(v) for k, v in val.items()} | |
| elif isinstance(val, list): | |
| return [_sanitize_for_json(v) for v in val] | |
| elif isinstance(val, tuple): | |
| return [_sanitize_for_json(v) for v in val] | |
| return val | |
| clean_cases = _sanitize_for_json(cases_results) | |
| clean_peak = _sanitize_for_json(peak_memory_mb) | |
| # Output final result | |
| print("__START_DELIM__") | |
| print(json.dumps({"cases": clean_cases, "peak_memory_mb": clean_peak})) | |
| print("__END_DELIM__") | |
| """ | |
| def execute_submission( | |
| problem: ProblemMeta, | |
| user_code: str, | |
| custom_testcase: Optional[str] = None, | |
| python_exe: str = PYTHON_EXE, | |
| ) -> JudgeResult: | |
| """ | |
| Executes a submission against a problem using an isolated subprocess harness. | |
| """ | |
| # Check custom_testcase AST if provided | |
| if custom_testcase and custom_testcase.strip(): | |
| from codestar.core.ast_checker import check_code_ast | |
| c_safe, c_msg, c_status = check_code_ast( | |
| code=custom_testcase, | |
| extra_forbidden_modules=problem.forbidden_modules, | |
| extra_forbidden_calls=problem.forbidden_calls, | |
| ) | |
| if not c_safe: | |
| return JudgeResult( | |
| status=c_status or JudgeStatus.CE, | |
| message=f"Custom testcase error: {c_msg}", | |
| passed_count=0, | |
| total_count=0, | |
| execution_time_ms=0.0, | |
| ) | |
| delim_token = secrets.token_hex(8) | |
| start_delim = f"---JUDGE_OUTPUT_START_{delim_token}---" | |
| end_delim = f"---JUDGE_OUTPUT_END_{delim_token}---" | |
| error_delim = f"---JUDGE_ERROR_{delim_token}---" | |
| harness_content = ( | |
| HARNESS_TEMPLATE | |
| .replace("__REF_CODE_STR__", repr(problem.reference_code)) | |
| .replace("__USER_CODE_STR__", repr(user_code)) | |
| .replace("__ENTRY_POINT__", problem.entry_point) | |
| .replace("__TEST_CODE_STR__", repr(problem.test_code)) | |
| .replace("__CUSTOM_TESTCASE_STR__", repr(custom_testcase or "")) | |
| .replace("__ATOL__", str(problem.atol)) | |
| .replace("__RTOL__", str(problem.rtol)) | |
| .replace("__TIME_LIMIT__", str(problem.time_limit)) | |
| .replace("__START_DELIM__", start_delim) | |
| .replace("__END_DELIM__", end_delim) | |
| .replace("__ERROR_DELIM__", error_delim) | |
| ) | |
| # Write harness to temp file | |
| with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False, encoding="utf-8") as f: | |
| harness_path = f.name | |
| f.write(harness_content) | |
| t0 = time.time() | |
| try: | |
| # Clean subprocess environment: filter out sensitive environment variables | |
| clean_env = {} | |
| keep_keys = { | |
| "PATH", "PATHEXT", "SYSTEMROOT", "SYSTEMDRIVE", "TEMP", "TMP", | |
| "USERPROFILE", "HOMEDRIVE", "HOMEPATH", "WINDIR", "COMSPEC", | |
| "APPDATA", "LOCALAPPDATA", "PROGRAMDATA", "PROGRAMFILES", | |
| "PROGRAMFILES(X86)", "COMMONPROGRAMFILES", "COMMONPROGRAMFILES(X86)", | |
| "VIRTUAL_ENV", "PYTHONHOME", "CONDA_PREFIX", | |
| } | |
| for k, v in os.environ.items(): | |
| if k.upper() in keep_keys: | |
| clean_env[k] = v | |
| if "VIRTUAL_ENV" not in clean_env and sys.prefix: | |
| clean_env["VIRTUAL_ENV"] = sys.prefix | |
| repo_root = str(Path(__file__).resolve().parents[2]) | |
| clean_env["PYTHONPATH"] = repo_root + (os.pathsep + clean_env["PYTHONPATH"] if "PYTHONPATH" in clean_env else "") | |
| clean_env["PYTHONIOENCODING"] = "utf-8" | |
| clean_env["PYTHONUTF8"] = "1" | |
| # Subprocess timeout: outer safety ceiling allowing sufficient bootstrap overhead on Windows | |
| subproc_timeout = max(problem.time_limit * 2.5 + 8.0, 16.0) | |
| proc = subprocess.Popen( | |
| [python_exe, harness_path], | |
| stdin=subprocess.DEVNULL, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| text=True, | |
| cwd=repo_root, | |
| env=clean_env, | |
| encoding="utf-8", | |
| errors="replace", | |
| ) | |
| try: | |
| stdout, stderr = proc.communicate(timeout=subproc_timeout) | |
| except subprocess.TimeoutExpired: | |
| try: | |
| proc.kill() | |
| except Exception: | |
| pass | |
| try: | |
| proc.communicate(timeout=1.0) | |
| except Exception: | |
| pass | |
| return JudgeResult( | |
| status=JudgeStatus.TLE, | |
| message=f"Time Limit Exceeded (> {problem.time_limit}s)", | |
| execution_time_ms=round(problem.time_limit * 1000, 2), | |
| ) | |
| exec_time_ms = round((time.time() - t0) * 1000, 2) | |
| # Check for harness judge output | |
| if start_delim in stdout: | |
| # Extract raw user stdout prior to start_delim | |
| raw_user_stdout = stdout.split(start_delim)[0] | |
| if len(raw_user_stdout) > 8000: | |
| user_stdout = raw_user_stdout[:8000] + "\n[输出已截断: 超过 8000 字符限制]" | |
| else: | |
| user_stdout = raw_user_stdout | |
| user_stdout_val = user_stdout.strip() if user_stdout.strip() else None | |
| parts = stdout.split(start_delim)[1].split(end_delim)[0].strip() | |
| data = json.loads(parts) | |
| raw_cases = data.get("cases", []) | |
| peak_memory_mb = float(data.get("peak_memory_mb", 0.0)) | |
| if math.isnan(peak_memory_mb) or math.isinf(peak_memory_mb): | |
| peak_memory_mb = 0.0 | |
| cases = [] | |
| for c in raw_cases: | |
| if not c.get("input_summary"): | |
| c["input_summary"] = c.get("case_name") or f"Case #{c.get('case_id', 1)}" | |
| if c.get("max_diff") is not None: | |
| if math.isnan(c["max_diff"]) or math.isinf(c["max_diff"]): | |
| c["max_diff"] = 0.0 | |
| cases.append(CaseResult(**c)) | |
| passed_count = sum(1 for c in cases if c.passed) | |
| total_count = len(cases) | |
| # Check if actual user code evaluation time exceeded problem limit | |
| actual_cases_time_s = sum(c.time_ms for c in cases) / 1000.0 | |
| if actual_cases_time_s > problem.time_limit: | |
| return JudgeResult( | |
| status=JudgeStatus.TLE, | |
| message=f"Time Limit Exceeded: computation took {actual_cases_time_s:.2f}s > {problem.time_limit}s", | |
| passed_count=passed_count, | |
| total_count=total_count, | |
| execution_time_ms=exec_time_ms, | |
| peak_memory_mb=peak_memory_mb, | |
| cases=cases, | |
| stdout=user_stdout_val, | |
| ) | |
| if total_count > 0 and passed_count == total_count: | |
| status = JudgeStatus.AC | |
| message = f"All {total_count} test cases passed." | |
| else: | |
| first_failed = next((c for c in cases if not c.passed), None) | |
| fail_msg = first_failed.message if first_failed else "Assertion mismatch" | |
| if fail_msg and ("Memory Limit Exceeded" in fail_msg or "MemoryError" in fail_msg): | |
| status = JudgeStatus.MLE | |
| message = "Memory Limit Exceeded: Out of Memory during tensor allocation" | |
| elif fail_msg and ("Runtime Exception:" in fail_msg or "RuntimeError:" in fail_msg): | |
| status = JudgeStatus.RE | |
| clean_msg = fail_msg.replace("Runtime Exception:", "").strip() | |
| message = f"Runtime Error on case #{first_failed.case_id if first_failed else '?'}: {clean_msg}" | |
| else: | |
| status = JudgeStatus.WA | |
| message = f"Failed on case #{first_failed.case_id if first_failed else '?'}: {fail_msg}" | |
| return JudgeResult( | |
| status=status, | |
| message=message, | |
| passed_count=passed_count, | |
| total_count=total_count, | |
| execution_time_ms=exec_time_ms, | |
| peak_memory_mb=peak_memory_mb, | |
| cases=cases, | |
| stdout=user_stdout_val, | |
| ) | |
| # Check for harness runtime error delimiter | |
| if error_delim in stdout: | |
| raw_user_stdout = stdout.split(error_delim)[0] | |
| if len(raw_user_stdout) > 8000: | |
| user_stdout = raw_user_stdout[:8000] + "\n[输出已截断: 超过 8000 字符限制]" | |
| else: | |
| user_stdout = raw_user_stdout | |
| user_stdout_val = user_stdout.strip() if user_stdout.strip() else None | |
| payload_raw = stdout.split(error_delim)[1].strip() | |
| payload = {} | |
| for line in payload_raw.splitlines(): | |
| line = line.strip() | |
| if line.startswith("{") and line.endswith("}"): | |
| try: | |
| payload = json.loads(line) | |
| break | |
| except Exception: | |
| pass | |
| err_type = payload.get("error") | |
| msg = payload.get("message") or "Runtime Error" | |
| tb = payload.get("traceback") or msg | |
| if err_type == "TLE" or "Time Limit Exceeded" in msg: | |
| return JudgeResult( | |
| status=JudgeStatus.TLE, | |
| message=msg or f"Time Limit Exceeded (> {problem.time_limit}s)", | |
| error_detail=tb, | |
| stdout=user_stdout_val, | |
| execution_time_ms=exec_time_ms, | |
| ) | |
| if err_type == "MLE" or "Memory Limit Exceeded" in msg or "MemoryError" in tb: | |
| return JudgeResult( | |
| status=JudgeStatus.MLE, | |
| message=msg or "Memory Limit Exceeded: Out of Memory during tensor allocation", | |
| error_detail=tb, | |
| stdout=user_stdout_val, | |
| execution_time_ms=exec_time_ms, | |
| ) | |
| return JudgeResult( | |
| status=JudgeStatus.RE, | |
| message=msg, | |
| error_detail=tb, | |
| stdout=user_stdout_val, | |
| execution_time_ms=exec_time_ms, | |
| ) | |
| # Check if legacy error JSON was emitted | |
| for line in stdout.splitlines(): | |
| line = line.strip() | |
| if line.startswith("{") and line.endswith("}"): | |
| try: | |
| payload = json.loads(line) | |
| if payload.get("error") in ("RE", "MLE"): | |
| st = JudgeStatus.MLE if payload.get("error") == "MLE" else JudgeStatus.RE | |
| return JudgeResult( | |
| status=st, | |
| message=payload.get("message", "Runtime Error"), | |
| error_detail=payload.get("traceback"), | |
| execution_time_ms=exec_time_ms, | |
| ) | |
| except Exception: | |
| pass | |
| # Subprocess failed with return code or unhandled exception | |
| err_msg = stderr.strip() or stdout.strip() or f"Process exited with return code {proc.returncode}" | |
| lines = [l.strip() for l in err_msg.splitlines() if l.strip()] | |
| concise = lines[-1] if lines else err_msg | |
| user_stdout_val = stdout.strip()[:8000] if stdout.strip() else None | |
| # Check for Out of Memory | |
| status = JudgeStatus.RE | |
| if any(oom_sig in err_msg for oom_sig in ("MemoryError", "std::bad_alloc", "OutOfMemoryError")) or proc.returncode in (-9, 137): | |
| status = JudgeStatus.MLE | |
| concise = "Memory Limit Exceeded: Process terminated due to out-of-memory" | |
| return JudgeResult( | |
| status=status, | |
| message=concise[:500], | |
| error_detail=err_msg[:8000], | |
| stdout=user_stdout_val, | |
| execution_time_ms=exec_time_ms, | |
| ) | |
| except Exception as e: | |
| import traceback | |
| return JudgeResult( | |
| status=JudgeStatus.RE, | |
| message=f"Runner execution exception: {str(e)}", | |
| error_detail=traceback.format_exc(), | |
| execution_time_ms=round((time.time() - t0) * 1000, 2), | |
| ) | |
| finally: | |
| if os.path.exists(harness_path): | |
| try: | |
| os.remove(harness_path) | |
| except OSError: | |
| pass | |
| # Alias ExecutionResult to JudgeResult for backward compatibility and test verification | |
| ExecutionResult = JudgeResult | |