Spaces:
Sleeping
Sleeping
| """ | |
| Graders for Git Conflict Resolution Environment. | |
| Each grader scores a resolved set of files from 0.0 to 1.0. | |
| """ | |
| import ast | |
| import sys | |
| import types | |
| import importlib | |
| import tempfile | |
| import os | |
| CONFLICT_MARKERS = ["<<<<<<<", "=======", ">>>>>>>"] | |
| def has_conflict_markers(content: str) -> bool: | |
| return any(marker in content for marker in CONFLICT_MARKERS) | |
| def load_module_from_string(source: str, module_name: str = "resolved"): | |
| """Dynamically load a Python module from a source string.""" | |
| module = types.ModuleType(module_name) | |
| exec(compile(source, module_name, "exec"), module.__dict__) | |
| return module | |
| def score_easy(resolved_files: dict) -> tuple[float, str]: | |
| """ | |
| Score the easy task: calculate_discount must handle both | |
| the order_total > 100 and is_premium discounts together. | |
| Returns (score 0.0-1.0, feedback string). | |
| """ | |
| if "store.py" not in resolved_files: | |
| return 0.0, "Missing store.py in resolved files." | |
| content = resolved_files["store.py"] | |
| if has_conflict_markers(content): | |
| return 0.0, "File still contains conflict markers." | |
| try: | |
| mod = load_module_from_string(content) | |
| except SyntaxError as e: | |
| return 0.0, f"Syntax error in resolved file: {e}" | |
| except Exception as e: | |
| return 0.0, f"Error loading module: {e}" | |
| if not hasattr(mod, "calculate_discount"): | |
| return 0.0, "Function calculate_discount not found." | |
| fn = mod.calculate_discount | |
| test_cases = [ | |
| ((50, False), 50.0, "No discount for small non-premium order"), | |
| ((150, False), 135.0, "10% discount for large non-premium order"), | |
| ((50, True), 40.0, "20% discount for small premium order"), | |
| ((150, True), 105.0, "30% combined discount for large premium order"), | |
| ] | |
| passed = 0 | |
| feedback_lines = [] | |
| for args, expected, description in test_cases: | |
| try: | |
| result = fn(*args) | |
| if abs(result - expected) < 0.01: | |
| passed += 1 | |
| else: | |
| feedback_lines.append(f"FAIL [{description}]: got {result}, expected {expected}") | |
| except Exception as e: | |
| feedback_lines.append(f"ERROR [{description}]: {e}") | |
| score = passed / len(test_cases) | |
| feedback = f"Passed {passed}/{len(test_cases)} tests." | |
| if feedback_lines: | |
| feedback += " Issues: " + "; ".join(feedback_lines) | |
| return round(score, 2), feedback | |
| def score_medium(resolved_files: dict) -> tuple[float, str]: | |
| """ | |
| Score the medium task: process_order must validate inputs, | |
| log, and compute loyalty points. | |
| """ | |
| if "orders.py" not in resolved_files: | |
| return 0.0, "Missing orders.py in resolved files." | |
| content = resolved_files["orders.py"] | |
| if has_conflict_markers(content): | |
| return 0.0, "File still contains conflict markers." | |
| try: | |
| mod = load_module_from_string(content) | |
| except SyntaxError as e: | |
| return 0.0, f"Syntax error: {e}" | |
| except Exception as e: | |
| return 0.0, f"Error loading module: {e}" | |
| if not hasattr(mod, "process_order"): | |
| return 0.0, "Function process_order not found." | |
| fn = mod.process_order | |
| passed = 0 | |
| total = 4 | |
| feedback_lines = [] | |
| # Test 1: normal order with loyalty points | |
| try: | |
| result = fn(1, ["apple"], 50) | |
| if (result.get("loyalty_points") == 500 and | |
| result.get("status") == "processed"): | |
| passed += 1 | |
| else: | |
| feedback_lines.append(f"FAIL [loyalty points]: got {result}") | |
| except Exception as e: | |
| feedback_lines.append(f"ERROR [normal order]: {e}") | |
| # Test 2: fractional total | |
| try: | |
| result = fn(2, ["book", "pen"], 30.5) | |
| if result.get("loyalty_points") == 305: | |
| passed += 1 | |
| else: | |
| feedback_lines.append(f"FAIL [fractional total]: loyalty_points={result.get('loyalty_points')}") | |
| except Exception as e: | |
| feedback_lines.append(f"ERROR [fractional total]: {e}") | |
| # Test 3: empty items raises ValueError | |
| try: | |
| fn(1, [], 50) | |
| feedback_lines.append("FAIL [empty items]: should raise ValueError") | |
| except ValueError: | |
| passed += 1 | |
| except Exception as e: | |
| feedback_lines.append(f"FAIL [empty items]: raised {type(e).__name__} instead of ValueError") | |
| # Test 4: zero total raises ValueError | |
| try: | |
| fn(1, ["x"], 0) | |
| feedback_lines.append("FAIL [zero total]: should raise ValueError") | |
| except ValueError: | |
| passed += 1 | |
| except Exception as e: | |
| feedback_lines.append(f"FAIL [zero total]: raised {type(e).__name__} instead of ValueError") | |
| score = passed / total | |
| feedback = f"Passed {passed}/{total} tests." | |
| if feedback_lines: | |
| feedback += " Issues: " + "; ".join(feedback_lines) | |
| return round(score, 2), feedback | |
| def score_hard(resolved_files: dict) -> tuple[float, str]: | |
| """ | |
| Score the hard task: all three files must work together. | |
| User must have both role and session_token. Auth and API must be consistent. | |
| """ | |
| required = ["models.py", "auth.py", "api.py"] | |
| for f in required: | |
| if f not in resolved_files: | |
| return 0.0, f"Missing {f} in resolved files." | |
| if has_conflict_markers(resolved_files[f]): | |
| return 0.0, f"{f} still contains conflict markers." | |
| # Write files to a temp dir so they can import each other | |
| with tempfile.TemporaryDirectory() as tmpdir: | |
| for fname, content in resolved_files.items(): | |
| with open(os.path.join(tmpdir, fname), "w") as f: | |
| f.write(content) | |
| sys.path.insert(0, tmpdir) | |
| try: | |
| # Clear cached modules | |
| for mod_name in ["models", "auth", "api"]: | |
| if mod_name in sys.modules: | |
| del sys.modules[mod_name] | |
| import models as mod_models | |
| import auth as mod_auth | |
| import api as mod_api | |
| except Exception as e: | |
| sys.path.pop(0) | |
| return 0.0, f"Import error: {e}" | |
| finally: | |
| sys.path.pop(0) | |
| passed = 0 | |
| total = 7 | |
| feedback_lines = [] | |
| # Test 1: User has role attribute | |
| try: | |
| u = mod_models.User(1, "test") | |
| assert hasattr(u, "role") | |
| passed += 1 | |
| except Exception as e: | |
| feedback_lines.append(f"FAIL [user has role]: {e}") | |
| # Test 2: User has session_token attribute | |
| try: | |
| u = mod_models.User(1, "test") | |
| assert hasattr(u, "session_token") | |
| passed += 1 | |
| except Exception as e: | |
| feedback_lines.append(f"FAIL [user has session_token]: {e}") | |
| # Test 3: make_admin works | |
| try: | |
| u = mod_auth.login(1, "test") | |
| u = mod_auth.make_admin(u) | |
| assert u.is_admin() | |
| passed += 1 | |
| except Exception as e: | |
| feedback_lines.append(f"FAIL [make_admin]: {e}") | |
| # Test 4: logout clears session | |
| try: | |
| u = mod_auth.login(1, "test") | |
| u = mod_auth.logout(u) | |
| assert not u.has_session() | |
| passed += 1 | |
| except Exception as e: | |
| feedback_lines.append(f"FAIL [logout]: {e}") | |
| # Test 5: api login returns user with session | |
| try: | |
| u = mod_api.handle_request("login", 1, "test") | |
| assert u is not None and u.has_session() | |
| passed += 1 | |
| except Exception as e: | |
| feedback_lines.append(f"FAIL [api login]: {e}") | |
| # Test 6: api make_admin returns admin user | |
| try: | |
| u = mod_api.handle_request("make_admin", 1, "test") | |
| assert u is not None and u.is_admin() | |
| passed += 1 | |
| except Exception as e: | |
| feedback_lines.append(f"FAIL [api make_admin]: {e}") | |
| # Test 7: api logout returns user with no session | |
| try: | |
| u = mod_api.handle_request("logout", 1, "test") | |
| assert u is not None and not u.has_session() | |
| passed += 1 | |
| except Exception as e: | |
| feedback_lines.append(f"FAIL [api logout]: {e}") | |
| score = passed / total | |
| feedback = f"Passed {passed}/{total} tests." | |
| if feedback_lines: | |
| feedback += " Issues: " + "; ".join(feedback_lines) | |
| return round(score, 2), feedback | |
| GRADERS = { | |
| "easy": score_easy, | |
| "medium": score_medium, | |
| "hard": score_hard, | |
| } | |
| def grade(task_id: str, resolved_files: dict) -> tuple[float, str]: | |
| if task_id not in GRADERS: | |
| return 0.0, f"Unknown task: {task_id}" | |
| return GRADERS[task_id](resolved_files) | |