solidityguard-openenv / validate_submission.py
tanaymitra98
Fix grader format in openenv.yaml and add grader validation
524b429
Raw
History Blame Contribute Delete
17.4 kB
#!/usr/bin/env python3
"""
Pre-Submission Validation Script
Meta x PyTorch Hackathon - Round 1
Validates all mandatory requirements before submission.
"""
from __future__ import annotations
import json
import os
import sys
import time
from pathlib import Path
from typing import Any, Dict, List
# Colors for output
GREEN = "\033[92m"
RED = "\033[91m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
RESET = "\033[0m"
def print_header(text: str) -> None:
"""Print section header."""
print(f"\n{'=' * 70}")
print(f"{BLUE}{text}{RESET}")
print("=" * 70)
def print_success(text: str) -> None:
"""Print success message."""
print(f"{GREEN}✓ PASS{RESET} - {text}")
def print_error(text: str) -> None:
"""Print error message."""
print(f"{RED}✗ FAIL{RESET} - {text}")
def print_warning(text: str) -> None:
"""Print warning message."""
print(f"{YELLOW}⚠ WARN{RESET} - {text}")
def print_info(text: str) -> None:
"""Print info message."""
print(f" {text}")
class ValidationResult:
def __init__(self):
self.passed = []
self.failed = []
self.warnings = []
def add_pass(self, message: str):
self.passed.append(message)
print_success(message)
def add_fail(self, message: str):
self.failed.append(message)
print_error(message)
def add_warning(self, message: str):
self.warnings.append(message)
print_warning(message)
def is_passing(self) -> bool:
return len(self.failed) == 0
def validate_hf_space_config(result: ValidationResult) -> None:
"""Check 1: HF Space Configuration."""
print_header("CHECK 1: HF SPACE DEPLOYMENT CONFIGURATION")
# Check README.md exists
if not os.path.exists("README.md"):
result.add_fail("README.md not found")
return
# Read README and check for HF frontmatter
with open("README.md", "r") as f:
content = f.read()
if not content.startswith("---"):
result.add_fail("README.md missing HF Spaces YAML frontmatter")
return
# Extract frontmatter
parts = content.split("---", 2)
if len(parts) < 3:
result.add_fail("README.md has invalid YAML frontmatter")
return
frontmatter = parts[1]
# Check required fields
required_fields = ["title:", "sdk:", "docker"]
for field in required_fields:
if field in frontmatter.lower():
result.add_pass(f"README.md has '{field}' configured")
else:
result.add_fail(f"README.md missing '{field}' in frontmatter")
# Check Dockerfile exists
if os.path.exists("Dockerfile"):
result.add_pass("Dockerfile present")
# Check Dockerfile content
with open("Dockerfile", "r") as f:
dockerfile = f.read()
if "7860" in dockerfile:
result.add_pass("Dockerfile exposes port 7860 (HF standard)")
else:
result.add_warning("Dockerfile should expose port 7860 for HF Spaces")
if "uvicorn" in dockerfile.lower() or "CMD" in dockerfile:
result.add_pass("Dockerfile has proper CMD configuration")
else:
result.add_fail("Dockerfile missing CMD to start server")
else:
result.add_fail("Dockerfile not found")
def validate_openenv_spec(result: ValidationResult) -> None:
"""Check 2: OpenEnv Spec Compliance."""
print_header("CHECK 2: OPENENV SPEC COMPLIANCE")
# Check openenv.yaml exists
if not os.path.exists("openenv.yaml"):
result.add_fail("openenv.yaml not found")
return
result.add_pass("openenv.yaml exists")
# Parse openenv.yaml
import yaml
try:
with open("openenv.yaml", "r") as f:
spec = yaml.safe_load(f)
result.add_pass("openenv.yaml is valid YAML")
except Exception as e:
result.add_fail(f"openenv.yaml parse error: {e}")
return
# Check required fields
required_fields = [
"name",
"version",
"description",
"entrypoint",
"tasks",
"schemas",
]
for field in required_fields:
if field in spec:
result.add_pass(f"openenv.yaml has '{field}'")
else:
result.add_fail(f"openenv.yaml missing '{field}'")
# Check tasks
if "tasks" in spec and isinstance(spec["tasks"], list):
task_count = len(spec["tasks"])
if task_count >= 3:
result.add_pass(f"Has {task_count} tasks (minimum 3 required)")
else:
result.add_fail(f"Only {task_count} tasks (minimum 3 required)")
# Check task difficulties
difficulties = [t.get("difficulty") for t in spec["tasks"]]
print_info(f"Task difficulties: {difficulties}")
# Check graders for each task
tasks_with_graders = 0
for task in spec["tasks"]:
if task.get("grader"):
tasks_with_graders += 1
if tasks_with_graders >= 3:
result.add_pass(
f"{tasks_with_graders} tasks have graders (minimum 3 required)"
)
else:
result.add_fail(
f"Only {tasks_with_graders} tasks have graders (minimum 3 required)"
)
# Check schemas
if "schemas" in spec:
required_schemas = ["observation", "action", "state"]
for schema in required_schemas:
if schema in spec["schemas"]:
result.add_pass(f"Schema '{schema}' defined")
else:
result.add_fail(f"Schema '{schema}' missing")
# Check environment.py exists and has required methods
if os.path.exists("environment.py"):
result.add_pass("environment.py exists")
with open("environment.py", "r") as f:
env_code = f.read()
required_methods = ["reset", "step", "state"]
for method in required_methods:
if f"def {method}" in env_code:
result.add_pass(f"environment.py has {method}() method")
else:
result.add_fail(f"environment.py missing {method}() method")
else:
result.add_fail("environment.py not found")
def validate_dockerfile_builds(result: ValidationResult) -> None:
"""Check 3: Dockerfile Builds."""
print_header("CHECK 3: DOCKERFILE BUILD CHECK")
if not os.path.exists("Dockerfile"):
result.add_fail("Dockerfile not found")
return
result.add_pass("Dockerfile exists")
# Check requirements.txt
if os.path.exists("requirements.txt"):
result.add_pass("requirements.txt exists")
with open("requirements.txt", "r") as f:
reqs = f.read()
required_deps = ["fastapi", "uvicorn", "openai", "pydantic"]
for dep in required_deps:
if dep in reqs.lower():
result.add_pass(f"requirements.txt has '{dep}'")
else:
result.add_fail(f"requirements.txt missing '{dep}'")
else:
result.add_fail("requirements.txt not found")
print_info("Note: Actual Docker build test requires Docker daemon")
result.add_warning("Docker build not tested (requires Docker installed)")
def validate_inference_script(result: ValidationResult) -> None:
"""Check 4: Baseline Inference Script."""
print_header("CHECK 4: INFERENCE SCRIPT VALIDATION")
# Check inference.py exists in root
if not os.path.exists("inference.py"):
result.add_fail("inference.py not found in root directory")
return
result.add_pass("inference.py exists in root directory")
# Read inference.py
with open("inference.py", "r") as f:
inference_code = f.read()
# Check for required imports
if (
"from openai import OpenAI" in inference_code
or "import openai" in inference_code
):
result.add_pass("inference.py uses OpenAI client")
else:
result.add_fail("inference.py must use OpenAI client for LLM calls")
# Check for environment variables
env_vars = ["API_BASE_URL", "MODEL_NAME", "HF_TOKEN"]
for var in env_vars:
if var in inference_code:
result.add_pass(f"inference.py loads {var} environment variable")
else:
result.add_fail(f"inference.py missing {var} environment variable")
# Check for logging format
has_start = '"START"' in inference_code or "'START'" in inference_code
has_step = '"STEP"' in inference_code or "'STEP'" in inference_code
has_end = '"END"' in inference_code or "'END'" in inference_code
if has_start and has_step and has_end:
result.add_pass("inference.py has [START], [STEP], [END] logging")
else:
missing = []
if not has_start:
missing.append("START")
if not has_step:
missing.append("STEP")
if not has_end:
missing.append("END")
result.add_fail(f"inference.py missing logging tags: {missing}")
# Check for proper logging function
if "_log(" in inference_code or 'print(f"[' in inference_code:
result.add_pass("inference.py has logging function")
else:
result.add_warning("inference.py logging implementation unclear")
# Try to run inference.py (will fail on missing env vars, but checks syntax)
print_info("Testing inference.py syntax...")
import subprocess
try:
proc = subprocess.run(
[sys.executable, "-m", "py_compile", "inference.py"],
capture_output=True,
text=True,
timeout=10,
)
if proc.returncode == 0:
result.add_pass("inference.py has valid Python syntax")
else:
result.add_fail(f"inference.py syntax error: {proc.stderr}")
except Exception as e:
result.add_warning(f"Could not validate syntax: {e}")
def validate_tasks_and_graders(result: ValidationResult) -> None:
"""Check 5: Tasks and Grading System."""
print_header("CHECK 5: TASKS AND GRADING SYSTEM")
# Check graders.py exists
if not os.path.exists("graders.py"):
result.add_fail("graders.py not found")
return
result.add_pass("graders.py exists")
# Import and test graders
try:
from graders import grade_action
result.add_pass("graders.py imports successfully")
# Test grading with empty action
score, details = grade_action([], [])
if 0.0 <= score <= 1.0:
result.add_pass(f"Grading returns score in 0.0-1.0 range: {score}")
else:
result.add_fail(f"Grading score out of range: {score}")
# Test with sample data
sample_action = [
{
"issue_type": "test",
"line_number": 1,
"description": "test",
"severity": "Low",
}
]
sample_expected = [
{
"issue_type": "test",
"line_number": 1,
"description": "test",
"severity": "Low",
}
]
score, details = grade_action(sample_action, sample_expected)
if 0.0 <= score <= 1.0:
result.add_pass(f"Grading with perfect match: {score}")
else:
result.add_fail(f"Grading score invalid: {score}")
except Exception as e:
result.add_fail(f"Grading system error: {e}")
# Check environment and tasks
try:
from environment import SolidityGuardEnv
result.add_pass("Environment imports successfully")
env = SolidityGuardEnv()
# Test each task
tasks = ["task_1_best_practices", "task_2_gas_optimization", "task_3_security"]
for task_id in tasks:
try:
obs = env.reset(task_id=task_id)
result.add_pass(f"Task '{task_id}' resets successfully")
# Test step
step_result = env.step([])
reward = step_result.get("reward", -1)
if 0.0 <= reward <= 1.0:
result.add_pass(f"Task '{task_id}' reward in valid range: {reward}")
else:
result.add_fail(f"Task '{task_id}' reward out of range: {reward}")
except Exception as e:
result.add_fail(f"Task '{task_id}' error: {e}")
except Exception as e:
result.add_fail(f"Environment error: {e}")
def validate_api_endpoints(result: ValidationResult) -> None:
"""Check 6: API Endpoints."""
print_header("CHECK 6: API ENDPOINTS")
if not os.path.exists("app.py"):
result.add_fail("app.py not found")
return
result.add_pass("app.py exists")
# Import app
try:
from app import app
result.add_pass("FastAPI app imports successfully")
# Check routes
routes = {route.path: route for route in app.routes if hasattr(route, "path")}
required_endpoints = ["/health", "/reset", "/step", "/state"]
for endpoint in required_endpoints:
if endpoint in routes:
result.add_pass(f"Endpoint '{endpoint}' exists")
else:
result.add_fail(f"Endpoint '{endpoint}' missing")
except Exception as e:
result.add_fail(f"FastAPI app error: {e}")
def validate_dataset(result: ValidationResult) -> None:
"""Check 7: Dataset and Manifest."""
print_header("CHECK 7: DATASET VALIDATION")
# Check manifest.json
manifest_path = "data/manifest.json"
if not os.path.exists(manifest_path):
result.add_fail("data/manifest.json not found")
return
result.add_pass("data/manifest.json exists")
# Parse manifest
try:
with open(manifest_path, "r") as f:
manifest = json.load(f)
if not isinstance(manifest, list):
result.add_fail("manifest.json must be a list")
return
result.add_pass(f"manifest.json has {len(manifest)} samples")
if len(manifest) >= 12:
result.add_pass(
f"Dataset has {len(manifest)} samples (minimum 12 recommended)"
)
else:
result.add_warning(
f"Dataset has only {len(manifest)} samples (12-20 recommended)"
)
# Check sample structure
for i, sample in enumerate(manifest[:3]): # Check first 3
required_fields = ["task_id", "source_path", "labels"]
missing = [f for f in required_fields if f not in sample]
if not missing:
result.add_pass(f"Sample {i + 1} has required fields")
else:
result.add_fail(f"Sample {i + 1} missing fields: {missing}")
# Check source file exists
if "source_path" in sample:
if os.path.exists(sample["source_path"]):
result.add_pass(f"Sample {i + 1} source file exists")
else:
result.add_fail(
f"Sample {i + 1} source file missing: {sample['source_path']}"
)
except Exception as e:
result.add_fail(f"manifest.json error: {e}")
def validate_runtime_requirements(result: ValidationResult) -> None:
"""Check 8: Runtime Requirements."""
print_header("CHECK 8: RUNTIME REQUIREMENTS")
print_info("Runtime constraints:")
print_info(" - Max runtime: 20 minutes")
print_info(" - CPU: 2 vCPU")
print_info(" - Memory: 8 GB")
result.add_pass("Runtime requirements documented")
result.add_warning(
"Actual runtime test requires LLM credentials and full execution"
)
def generate_report(result: ValidationResult) -> None:
"""Generate final report."""
print_header("VALIDATION SUMMARY")
print(f"\n{GREEN}Passed: {len(result.passed)}{RESET}")
print(f"{RED}Failed: {len(result.failed)}{RESET}")
print(f"{YELLOW}Warnings: {len(result.warnings)}{RESET}")
if result.failed:
print(f"\n{RED}FAILURES:{RESET}")
for i, fail in enumerate(result.failed, 1):
print(f" {i}. {fail}")
if result.warnings:
print(f"\n{YELLOW}WARNINGS:{RESET}")
for i, warn in enumerate(result.warnings, 1):
print(f" {i}. {warn}")
print("\n" + "=" * 70)
if result.is_passing():
print(f"{GREEN}✓ VALIDATION PASSED - READY FOR SUBMISSION{RESET}")
print("=" * 70)
return 0
else:
print(f"{RED}✗ VALIDATION FAILED - FIX ISSUES BEFORE SUBMISSION{RESET}")
print("=" * 70)
return 1
def main() -> int:
"""Run all validation checks."""
print(f"\n{BLUE}{'=' * 70}")
print("PRE-SUBMISSION VALIDATION SCRIPT")
print("Meta x PyTorch Hackathon - Round 1")
print(f"{'=' * 70}{RESET}\n")
result = ValidationResult()
try:
validate_hf_space_config(result)
validate_openenv_spec(result)
validate_dockerfile_builds(result)
validate_inference_script(result)
validate_tasks_and_graders(result)
validate_api_endpoints(result)
validate_dataset(result)
validate_runtime_requirements(result)
except Exception as e:
print_error(f"Validation error: {e}")
import traceback
traceback.print_exc()
return 1
return generate_report(result)
if __name__ == "__main__":
sys.exit(main())