File size: 3,937 Bytes
2ce1061
8485798
 
2ce1061
 
2785b89
2ce1061
 
 
2785b89
8485798
2785b89
 
2ce1061
8485798
2ce1061
 
 
 
 
 
8485798
2ce1061
 
 
8485798
2ce1061
 
 
 
 
2785b89
 
 
 
8485798
2785b89
ceba2ab
2ce1061
ceba2ab
 
 
 
 
2ce1061
 
2785b89
 
8485798
2785b89
 
 
2ce1061
8485798
2785b89
8485798
2ce1061
2785b89
 
 
 
2ce1061
 
 
 
 
 
 
 
 
 
 
 
 
8485798
 
2ce1061
 
 
 
 
 
 
 
 
 
 
 
 
 
8485798
ceba2ab
2ce1061
 
8485798
ceba2ab
2ce1061
8485798
ceba2ab
2ce1061
29c44a0
 
 
 
 
 
 
 
 
2ce1061
8485798
2ce1061
8485798
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# server/graders/grader_easy.py
# Grades easy and medium tasks: runs code against test cases.
# Reward is proportional to tests passed (0.33, 0.67, 1.0).

import traceback
import signal
from typing import Tuple, List


def _timeout_handler(signum, frame):
    raise TimeoutError("Code timed out — likely infinite loop. Check for missing visited set in graph traversal.")


def _run_code_safely(code: str, func_name: str, test_input):
    """Run submitted code safely with 5s timeout. Returns (result, error)."""
    namespace = {}
    try:
        exec(compile(code, "<submitted>", "exec"), namespace)
    except SyntaxError as e:
        return None, f"SyntaxError: {e}"
    except Exception as e:
        return None, f"CompileError: {e}"

    func = namespace.get(func_name)
    if func is None:
        funcs = [v for v in namespace.values() if callable(v) and not str(v.__name__).startswith("_")]
        if not funcs:
            return None, "No callable function found in submitted code."
        func = funcs[0]

    try:
        try:
            signal.signal(signal.SIGALRM, _timeout_handler)
            signal.alarm(5)
        except (AttributeError, OSError):
            pass  # Windows has no SIGALRM

        if isinstance(test_input, list) and len(test_input) > 0 and isinstance(test_input[0], list):
            result = func(*test_input)
        elif isinstance(test_input, list):
            try:
                result = func(test_input)
            except TypeError:
                result = func(*test_input)
        else:
            result = func(test_input)

        try:
            signal.alarm(0)
        except (AttributeError, OSError):
            pass

        return result, None

    except TimeoutError as e:
        return None, str(e)
    except Exception as e:
        try:
            signal.alarm(0)
        except (AttributeError, OSError):
            pass
        return None, f"RuntimeError: {traceback.format_exc(limit=2)}"


def _extract_func_name(code: str) -> str:
    for line in code.splitlines():
        line = line.strip()
        if line.startswith("def "):
            return line.split("(")[0].replace("def ", "").strip()
    return "unknown"


def grade_easy(fixed_code: str, task: dict) -> Tuple[float, int, int, str, List[dict]]:
    """
    Grade submission against test cases.
    Returns: (reward, passed, total, feedback, results)
    """
    test_cases = task["test_cases"]
    total = len(test_cases)
    passed = 0
    results = []
    func_name = _extract_func_name(fixed_code)
    feedback_lines = []

    for i, tc in enumerate(test_cases):
        inp = tc["input"]
        expected = tc["expected"]
        got, error = _run_code_safely(fixed_code, func_name, inp)

        if error:
            results.append({"test_id": i+1, "passed": False, "expected": str(expected), "got": f"ERROR"})
            feedback_lines.append(f"Test {i+1}: ❌ Error\n   Input    : {inp!r}\n   Expected : {expected!r}\n   Error    : {error}")
        elif got == expected:
            passed += 1
            results.append({"test_id": i+1, "passed": True, "expected": str(expected), "got": str(got)})
            feedback_lines.append(f"Test {i+1}: ✅ Passed\n   Input    : {inp!r}\n   Expected : {expected!r}\n   Got      : {got!r}")
        else:
            results.append({"test_id": i+1, "passed": False, "expected": str(expected), "got": str(got)})
            feedback_lines.append(f"Test {i+1}: ❌ Failed\n   Input    : {inp!r}\n   Expected : {expected!r}\n   Got      : {got!r}")

    reward = passed / total

    # ensure strict (0,1) range
    if reward <= 0:
        reward = 0.01
    elif reward >= 1:
        reward = 0.99

    reward = round(reward, 2)
    feedback = "\n".join(feedback_lines)
    feedback += "\n🎉 All tests passed! Full reward." if passed == total else f"\n{passed}/{total} tests passed."

    return reward, passed, total, feedback, results