Yash030's picture
deploy: Hugging Face Space clean release
3ae68d6
Raw
History Blame Contribute Delete
7.03 kB
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