Spaces:
Sleeping
Sleeping
| import os | |
| import re | |
| import sys | |
| import uuid | |
| import stat | |
| import shutil | |
| import subprocess | |
| from typing import Tuple | |
| def remove_readonly(func, path, excinfo): | |
| """ | |
| On Windows, files can sometimes be marked read-only or locked, | |
| preventing shutil.rmtree from working. This helper removes | |
| the read-only attribute and retries the removal. | |
| """ | |
| try: | |
| os.chmod(path, stat.S_IWRITE) | |
| func(path) | |
| except Exception: | |
| pass | |
| def run_tests(language: str, code: str, tests: str) -> Tuple[bool, str]: | |
| """ | |
| Writes the wrapper code and unit test code to an isolated temporary sandbox directory, | |
| runs the language-specific test suite via a subprocess, and returns a tuple | |
| of (test_passed, console_logs). | |
| """ | |
| # Create a unique sandbox directory under a root 'temp' folder | |
| root_temp_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "temp")) | |
| os.makedirs(root_temp_dir, exist_ok=True) | |
| run_id = str(uuid.uuid4()) | |
| sandbox_dir = os.path.join(root_temp_dir, f"run_{run_id}") | |
| os.makedirs(sandbox_dir, exist_ok=True) | |
| language = language.lower().strip() | |
| timeout_val = 30 if language in ["go", "golang", "java"] else 15 | |
| code_filename = "" | |
| test_filename = "" | |
| run_args = [] | |
| env = os.environ.copy() | |
| # Enable Java assertions by default | |
| if "JAVA_TOOL_OPTIONS" not in env: | |
| env["JAVA_TOOL_OPTIONS"] = "-ea" | |
| else: | |
| env["JAVA_TOOL_OPTIONS"] += " -ea" | |
| try: | |
| if language in ["python", "py"]: | |
| code_filename = "client.py" | |
| test_filename = "test_client.py" | |
| # Add sandbox directory to PYTHONPATH so pytest can import the client module | |
| env["PYTHONPATH"] = f"{sandbox_dir}{os.pathsep}{env.get('PYTHONPATH', '')}" | |
| # Use sys.executable to ensure we use the virtual environment's interpreter | |
| run_args = [sys.executable, "-m", "pytest", "--tb=short", test_filename] | |
| elif language in ["javascript", "js"]: | |
| code_filename = "client.js" | |
| test_filename = "test_client.test.js" | |
| run_args = ["node", test_filename] | |
| elif language in ["typescript", "ts"]: | |
| code_filename = "client.ts" | |
| test_filename = "test_client.test.ts" | |
| # Write a basic tsconfig.json so ts-node works consistently | |
| tsconfig_path = os.path.join(sandbox_dir, "tsconfig.json") | |
| tsconfig_content = """{ | |
| "compilerOptions": { | |
| "target": "es2020", | |
| "module": "commonjs", | |
| "esModuleInterop": true, | |
| "strict": false, | |
| "skipLibCheck": true | |
| } | |
| }""" | |
| with open(tsconfig_path, "w", encoding="utf-8") as f: | |
| f.write(tsconfig_content) | |
| run_args = ["npx", "ts-node", "--transpile-only", test_filename] | |
| elif language in ["go", "golang"]: | |
| code_filename = "client.go" | |
| test_filename = "client_test.go" | |
| # Initialize a temporary go module to prevent module loading errors | |
| subprocess.run( | |
| ["go", "mod", "init", "sandbox"], | |
| cwd=sandbox_dir, | |
| capture_output=True, | |
| timeout=5 | |
| ) | |
| run_args = ["go", "test", "-v", code_filename, test_filename] | |
| elif language in ["java"]: | |
| # Strip package declarations to avoid compile/runtime classpath resolution issues | |
| code = re.sub(r"^\s*package\s+[\w\.]+;\s*", "", code, flags=re.MULTILINE) | |
| tests = re.sub(r"^\s*package\s+[\w\.]+;\s*", "", tests, flags=re.MULTILINE) | |
| def find_class_name(source: str, default: str) -> str: | |
| # Matches public/non-public class names safely | |
| match = re.search(r"(?:public\s+)?class\s+(\w+)", source) | |
| return match.group(1) if match else default | |
| code_classname = find_class_name(code, "MyAPIClient") | |
| test_classname = find_class_name(tests, "TestClient") | |
| code_filename = f"{code_classname}.java" | |
| test_filename = f"{test_classname}.java" | |
| else: | |
| raise ValueError(f"Unsupported language for self-healing execution: {language}") | |
| # Write files to sandbox | |
| code_path = os.path.join(sandbox_dir, code_filename) | |
| test_path = os.path.join(sandbox_dir, test_filename) | |
| with open(code_path, "w", encoding="utf-8") as f: | |
| f.write(code) | |
| with open(test_path, "w", encoding="utf-8") as f: | |
| f.write(tests) | |
| # Execute the subprocess | |
| if language == "java": | |
| # 1. Compile Java files | |
| compile_process = subprocess.run( | |
| ["javac", code_filename, test_filename], | |
| cwd=sandbox_dir, | |
| capture_output=True, | |
| text=True, | |
| timeout=timeout_val | |
| ) | |
| if compile_process.returncode != 0: | |
| return False, f"Compilation Error:\n{compile_process.stderr}\nStdout:\n{compile_process.stdout}" | |
| # 2. Execute Java test entrypoint | |
| test_class = test_filename[:-5] | |
| run_process = subprocess.run( | |
| ["java", "-ea", test_class], | |
| cwd=sandbox_dir, | |
| capture_output=True, | |
| text=True, | |
| timeout=timeout_val, | |
| env=env | |
| ) | |
| success = (run_process.returncode == 0) | |
| logs = f"Stdout:\n{run_process.stdout}\nStderr:\n{run_process.stderr}" | |
| return success, logs | |
| else: | |
| # On Windows, prepend cmd.exe /c for CMD-based tools like npx | |
| actual_args = run_args | |
| if os.name == "nt" and run_args and run_args[0] == "npx": | |
| actual_args = ["cmd.exe", "/c"] + run_args | |
| # Run the command with strict timeout to prevent CPU hung states | |
| process = subprocess.run( | |
| actual_args, | |
| cwd=sandbox_dir, | |
| capture_output=True, | |
| text=True, | |
| timeout=timeout_val, | |
| env=env | |
| ) | |
| success = (process.returncode == 0) | |
| logs = f"Stdout:\n{process.stdout}\nStderr:\n{process.stderr}" | |
| return success, logs | |
| except subprocess.TimeoutExpired as e: | |
| return False, f"Execution timed out after 15 seconds.\nStdout:\n{e.stdout}\nStderr:\n{e.stderr}" | |
| except Exception as e: | |
| return False, f"Executor encountered an internal exception: {str(e)}" | |
| finally: | |
| # Clean up sandbox directory using the Windows-resilient onerror handler | |
| try: | |
| shutil.rmtree(sandbox_dir, onerror=remove_readonly) | |
| except Exception: | |
| pass | |