fixed the tests and done some frontend shit

This commit is contained in:
2025-08-12 13:46:48 +02:00
parent 5fe140c4f9
commit 1ac0a13fc3
9 changed files with 777 additions and 142 deletions

10
app.py
View File

@@ -1,5 +1,5 @@
from markupsafe import Markup from markupsafe import Markup
from flask import Flask, render_template, request, redirect, url_for from flask import Flask, render_template, request, redirect, url_for, send_from_directory
import markdown as md import markdown as md
from models import db, Problem, Solution from models import db, Problem, Solution
@@ -28,6 +28,14 @@ def setup():
# Start the background thread to scan problems # Start the background thread to scan problems
start_problem_scanner() start_problem_scanner()
@app.route("/script.js")
def script():
return send_from_directory("templates", "script.js")
@app.route('/favicon.ico')
def favicon():
return send_from_directory("templates", "favicon", "favicon.ico")
@app.route('/') @app.route('/')
def index(): def index():
db_path = Path(__file__).parent / 'problems.sqlite3' db_path = Path(__file__).parent / 'problems.sqlite3'

View File

@@ -4,8 +4,12 @@ import json
import sqlite3 import sqlite3
import threading import threading
import random import random
from pathlib import Path import tempfile
import subprocess
import sys import sys
import traceback
import io
from pathlib import Path
try: try:
from watchdog.observers import Observer from watchdog.observers import Observer
@@ -37,6 +41,10 @@ class ProblemScannerThread(threading.Thread):
def scan(self): def scan(self):
problems = [] problems = []
if not PROBLEMS_DIR.exists():
print(f"Problems directory does not exist: {PROBLEMS_DIR}")
return problems
for folder in PROBLEMS_DIR.iterdir(): for folder in PROBLEMS_DIR.iterdir():
if folder.is_dir(): if folder.is_dir():
# Dynamically find manifest file (manifest.json or manifets.json) # Dynamically find manifest file (manifest.json or manifets.json)
@@ -46,73 +54,323 @@ class ProblemScannerThread(threading.Thread):
if candidate_path.exists(): if candidate_path.exists():
manifest_path = candidate_path manifest_path = candidate_path
break break
desc_path = folder / 'description.md' desc_path = folder / 'description.md'
test_path = folder / 'test.py' test_path = folder / 'test.py'
if manifest_path and test_path.exists():
with open(desc_path, 'r') as f: # Check if required files exist
description = f.read() if manifest_path and desc_path.exists() and test_path.exists():
with open(test_path, 'r') as f: try:
test_code = f.read() with open(desc_path, 'r', encoding='utf-8') as f:
problems.append({ description = f.read()
'folder': folder.name, with open(test_path, 'r', encoding='utf-8') as f:
'description': description, test_code = f.read()
'test_code': test_code
}) problems.append({
'folder': folder.name,
'description': description,
'test_code': test_code
})
print(f"Found problem: {folder.name}")
except Exception as e:
print(f"Error reading problem files for {folder.name}: {e}")
else:
missing_files = []
if not manifest_path:
missing_files.append("manifest.json/manifets.json")
if not desc_path.exists():
missing_files.append("description.md")
if not test_path.exists():
missing_files.append("test.py")
print(f"Skipping {folder.name}: missing {', '.join(missing_files)}")
print(f"Total problems found: {len(problems)}")
return problems return problems
def update_db(self, problems, retries=5): def update_db(self, problems, retries=5):
for attempt in range(retries): for attempt in range(retries):
try: try:
conn = sqlite3.connect(DB_PATH, timeout=2) conn = sqlite3.connect(DB_PATH, timeout=5)
c = conn.cursor() c = conn.cursor()
c.execute('PRAGMA journal_mode=WAL;') c.execute('PRAGMA journal_mode=WAL;')
# Clear existing problems
c.execute('DELETE FROM problems') c.execute('DELETE FROM problems')
# Insert new problems
for p in problems: for p in problems:
c.execute('INSERT INTO problems (folder, description, test_code) VALUES (?, ?, ?)', c.execute('INSERT INTO problems (folder, description, test_code) VALUES (?, ?, ?)',
(p['folder'], p['description'], p['test_code'])) (p['folder'], p['description'], p['test_code']))
conn.commit() conn.commit()
print(f"Updated database with {len(problems)} problems")
conn.close() conn.close()
return return
except sqlite3.OperationalError as e: except sqlite3.OperationalError as e:
if 'locked' in str(e): if 'locked' in str(e).lower():
time.sleep(0.2 + random.random() * 0.3) wait_time = 0.2 + random.random() * 0.3
print(f"Database locked, retrying in {wait_time:.2f}s (attempt {attempt + 1})")
time.sleep(wait_time)
else: else:
print(f"Database error: {e}")
raise raise
except Exception as e:
print(f"Unexpected error updating database: {e}")
raise
print('Failed to update problems DB after several retries due to lock.') print('Failed to update problems DB after several retries due to lock.')
def rescan_and_update(self): def rescan_and_update(self):
print("Scanning for problems...")
problems = self.scan() problems = self.scan()
self.update_db(problems) self.update_db(problems)
def run(self): def run(self):
print("Starting problem scanner...")
# Initial scan and table creation # Initial scan and table creation
conn = sqlite3.connect(DB_PATH) try:
self.create_table(conn) conn = sqlite3.connect(DB_PATH)
conn.close() self.create_table(conn)
conn.close()
print("Database initialized")
except Exception as e:
print(f"Failed to initialize database: {e}")
return
# Initial scan
self.rescan_and_update() self.rescan_and_update()
if WATCHDOG_AVAILABLE: if WATCHDOG_AVAILABLE:
print("Using watchdog for file monitoring")
class Handler(FileSystemEventHandler): class Handler(FileSystemEventHandler):
def __init__(self, scanner): def __init__(self, scanner):
self.scanner = scanner self.scanner = scanner
self.last_event_time = 0
def on_any_event(self, event): def on_any_event(self, event):
self.scanner.rescan_and_update() # Debounce events to avoid too many rescans
now = time.time()
if now - self.last_event_time > 1: # Wait at least 1 second between rescans
self.last_event_time = now
print(f"File system event: {event.event_type} - {event.src_path}")
self.scanner.rescan_and_update()
event_handler = Handler(self) event_handler = Handler(self)
self.observer = Observer() self.observer = Observer()
self.observer.schedule(event_handler, str(PROBLEMS_DIR), recursive=True) self.observer.schedule(event_handler, str(PROBLEMS_DIR), recursive=True)
self.observer.start() self.observer.start()
try: try:
while True: while True:
time.sleep(1) time.sleep(1)
except KeyboardInterrupt:
print("Stopping problem scanner...")
finally: finally:
self.observer.stop() self.observer.stop()
self.observer.join() self.observer.join()
else: else:
print(f"Watchdog not available, using polling every {self.scan_interval}s")
# Fallback: poll every scan_interval seconds # Fallback: poll every scan_interval seconds
while True: try:
time.sleep(self.scan_interval) while True:
self.rescan_and_update() time.sleep(self.scan_interval)
self.rescan_and_update()
except KeyboardInterrupt:
print("Stopping problem scanner...")
def start_problem_scanner(): def start_problem_scanner():
scanner = ProblemScannerThread() scanner = ProblemScannerThread()
scanner.start() scanner.start()
return scanner return scanner
# Flask model loading functions
def load_problems_from_json(json_path):
"""Load problems from JSON file into Flask database"""
if not os.path.exists(json_path):
print(f"Problem JSON file not found: {json_path}")
return
try:
with open(json_path, 'r', encoding='utf-8') as f:
problems = json.load(f)
except Exception as e:
print(f"Error reading JSON file: {e}")
return
# This assumes you have imported the necessary Flask/SQLAlchemy components
try:
from models import db, Problem
for p in problems:
# Check if problem already exists by title
existing = Problem.query.filter_by(title=p['title']).first()
# Load test code from solution file if provided
test_code = ''
if 'solution' in p and os.path.exists(p['solution']):
try:
with open(p['solution'], 'r', encoding='utf-8') as sf:
test_code = sf.read()
except Exception as e:
print(f"Error reading solution file for {p['title']}: {e}")
if existing:
existing.description = p['description']
existing.test_code = test_code
print(f"Updated problem: {p['title']}")
else:
new_problem = Problem(title=p['title'], description=p['description'], test_code=test_code)
db.session.add(new_problem)
print(f"Added new problem: {p['title']}")
db.session.commit()
print("Successfully updated problems from JSON")
except ImportError:
print("Flask models not available - skipping JSON load")
except Exception as e:
print(f"Error loading problems from JSON: {e}")
def schedule_problem_reload(app, json_path, interval_hours=10):
"""Schedule periodic reloading of problems from JSON"""
def reload_loop():
while True:
try:
with app.app_context():
load_problems_from_json(json_path)
time.sleep(interval_hours * 3600)
except Exception as e:
print(f"Error in problem reload loop: {e}")
time.sleep(60) # Wait 1 minute before retrying
t = threading.Thread(target=reload_loop, daemon=True)
t.start()
def run_code_against_tests(user_code, test_code, timeout=10):
"""
Execute user code against test code with proper error handling.
Args:
user_code: The user's solution code
test_code: The test code to validate the solution
timeout: Maximum execution time in seconds
Returns:
dict: Result with passed, output, runtime, and error fields
"""
if not user_code or not user_code.strip():
return {
'passed': False,
'output': '',
'runtime': 0,
'error': 'No code provided'
}
if not test_code or not test_code.strip():
return {
'passed': False,
'output': '',
'runtime': 0,
'error': 'No test code available'
}
start_time = time.perf_counter()
output = ''
error = None
passed = False
temp_file = None
try:
# Check if unittest is used in test_code
if 'unittest' in test_code:
# Create temporary file with user code + test code
with tempfile.NamedTemporaryFile('w+', suffix='.py', delete=False, encoding='utf-8') as f:
# Combine user code and test code
combined_code = f"{user_code}\n\n{test_code}"
f.write(combined_code)
f.flush()
temp_file = f.name
try:
# Run the file as a subprocess with timeout
proc = subprocess.run(
[sys.executable, temp_file],
capture_output=True,
text=True,
timeout=timeout,
encoding='utf-8'
)
output = proc.stdout
if proc.stderr:
output += f"\nSTDERR:\n{proc.stderr}"
passed = proc.returncode == 0
if not passed:
error = f"Tests failed. Return code: {proc.returncode}\n{output}"
except subprocess.TimeoutExpired:
passed = False
error = f"Code execution timed out after {timeout} seconds"
output = "Execution timed out"
else:
# Direct execution approach for simple assert-based tests
local_ns = {}
# Capture stdout
old_stdout = sys.stdout
captured_output = io.StringIO()
sys.stdout = captured_output
try:
# Execute user code first
exec(user_code, {}, local_ns)
# Execute test code in the same namespace
exec(test_code, local_ns, local_ns)
# If we get here without exceptions, tests passed
passed = True
except AssertionError as e:
passed = False
error = f"Assertion failed: {str(e)}"
except Exception as e:
passed = False
error = f"Runtime error: {traceback.format_exc()}"
finally:
output = captured_output.getvalue()
sys.stdout = old_stdout
except Exception as e:
passed = False
error = f"Execution error: {traceback.format_exc()}"
finally:
# Clean up temporary file
if temp_file and os.path.exists(temp_file):
try:
os.unlink(temp_file)
except Exception as e:
print(f"Warning: Could not delete temp file {temp_file}: {e}")
runtime = time.perf_counter() - start_time
result = {
'passed': passed,
'output': output.strip() if output else '',
'runtime': runtime,
'error': error if not passed else None
}
print(f"Test execution result: passed={passed}, runtime={runtime:.3f}s")
if error:
print(f"Error: {error}")
return result

View File

@@ -1,19 +1,26 @@
import unittest import unittest
#<!-- The Function the User needs to write -->
def revstring(x):
return x[::-1]
#<!-- This Test, test if the function works -->
class TestSolution(unittest.TestCase): class TestSolution(unittest.TestCase):
def test_simple(self): def test_simple(self):
x = "Hello World" test_cases = [
self.assertEqual(revstring(x), "dlroW olleH") # Test simple string reversal ("Hello World", "dlroW olleH"),
self.assertEqual(revstring(""), "") # Test empty string ("", ""),
self.assertEqual(revstring("a"), "a") # Test single character ("a", "a"),
self.assertEqual(revstring("racecar"), "racecar") # Test palindrome ("racecar", "racecar"),
self.assertEqual(revstring("12345"), "54321") # Test numbers as string ("12345", "54321"),
self.assertEqual(revstring("!@# $%"), "%$ #@!") # Test special chars and spaces ("!@# $%", "%$ #@!")
]
print("\n=== Function Output Test Results ===")
for input_val, expected in test_cases:
try:
actual = revstring(input_val) # pyright: ignore[reportUndefinedVariable]
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: '{input_val}' -> Got: '{actual}' | Expected: '{expected}'")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: '{input_val}' -> Exception: {e}")
raise
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main(verbosity=2)

View File

@@ -2,15 +2,16 @@ import unittest
# This is the function the user is expected to write. # This is the function the user is expected to write.
# Its a really simple one, the user can choose not to type tho. # Its a really simple one, the user can choose not to type tho.
def sortlist(lst = [4,3,2,1]) -> list:
return sorted(lst) #def sortlist(lst = [4,3,2,1]) -> list:
#return sorted(lst)
class TestSolution(unittest.TestCase): class TestSolution(unittest.TestCase):
def test_sort(self): def test_sort(self):
# define x as a empty array. # define x as a empty array.
# this will be used for the functiun ; a empty var does not work. # this will be used for the functiun ; a empty var does not work.
self.x = [] self.x = []
self.assertEqual(sortlist(self.x), sorted(self.x)) ## sort self.assertEqual(sortlist(self.x), sorted(self.x)) # pyright: ignore[reportUndefinedVariable]
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

160
static/index.css Normal file
View File

@@ -0,0 +1,160 @@
:root {
--bg: #f6f8fb;
--card: #fff;
--muted: #6b7280;
--accent: #2563eb;
--shadow: 0 4px 12px rgba(16, 24, 40, 0.06);
--radius: 8px;
--mono: 'JetBrains Mono', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
}
body {
font-family: Inter, sans-serif;
background: var(--bg);
color: #0f172a;
padding: 16px;
display: flex;
justify-content: center;
align-items: center;
}
.wrap {
width: 100%;
max-width: 1100px;
}
header {
margin-bottom: 14px;
}
header h1 {
font-size: 1.6rem;
color: #111827;
}
header p {
color: var(--muted);
font-size: 0.9rem;
}
.content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.content.single-column {
grid-template-columns: 1fr;
}
.card {
background: var(--card);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 12px;
}
/* Search/filter controls */
.search-controls {
margin-bottom: 12px;
display: flex;
gap: 8px;
}
.search-input {
flex: 1;
padding: 6px 10px;
border: 1px solid #e5e7eb;
border-radius: 4px;
font-size: 0.9rem;
}
.filter-select {
padding: 6px 8px;
border: 1px solid #e5e7eb;
border-radius: 4px;
font-size: 0.9rem;
background: white;
}
/* Problems list */
.problems-list .problem-item {
padding: 8px;
border-bottom: 1px solid #e5e7eb;
}
.problem-item:last-child {
border-bottom: none;
}
.problem-item a {
text-decoration: none;
color: #0077ff;
font-weight: 600;
}
/* Leaderboard */
.leaderboard-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.leaderboard-controls {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.leaderboard-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.leaderboard-table th,
.leaderboard-table td {
padding: 6px 8px;
border-bottom: 1px solid #e5e7eb;
text-align: left;
}
.leaderboard-table th {
background: #f9fafb;
font-weight: 600;
color: var(--muted);
}
.leaderboard-table tr:hover {
background: #f3f4f6;
}
/* Sort indicators */
.sortable {
cursor: pointer;
position: relative;
padding-right: 16px;
}
.sortable::after {
content: "↕";
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
font-size: 0.8em;
opacity: 0.5;
}
.sort-asc::after {
content: "↑";
opacity: 1;
}
.sort-desc::after {
content: "↓";
opacity: 1;
}
/* Toggle button */
.btn {
border: none;
background: transparent;
cursor: pointer;
color: var(--accent);
font-size: 0.85rem;
padding: 4px 6px;
border-radius: 4px;
}
.btn:hover {
background: rgba(37, 99, 235, 0.08);
}
.btn.active {
background: rgba(37, 99, 235, 0.15);
}
@media (max-width: 800px) {
.content { grid-template-columns: 1fr; }
.leaderboard-controls {
flex-direction: column;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,101 +1,102 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Quick Problem Platform</title> <title>Quick Problem Platform</title>
<link rel="stylesheet" href="/static/style.css"> <!--<link rel="favicon" href="/favicon/favicon.ico">-->
<style> <script src="script.js" async defer></script>
.leaderboard-table { <link rel="stylesheet" href="/static/index.css">
width: auto;
min-width: 400px;
max-width: 600px;
border-collapse: collapse;
margin: 2em 0;
background: #fff;
box-shadow: 0 2px 8px #0001;
border-radius: 8px;
overflow: hidden;
}
.leaderboard-table th, .leaderboard-table td {
padding: 0.7em 1em;
text-align: center;
}
.leaderboard-table th {
background: #f5f5f5;
font-weight: 600;
color: #333;
}
.leaderboard-table tr:nth-child(even) {
background: #fafbfc;
}
.leaderboard-table tr:nth-child(odd) {
background: #f0f2f5;
}
.leaderboard-table td {
font-family: 'JetBrains Mono', monospace;
font-size: 1em;
}
.leaderboard-table tr:hover {
background: #e0e7ff;
}
.problems-list {
max-width: 600px;
min-width: 400px;
margin: 0;
padding: 0;
}
.problems-list ul {
list-style: none;
padding: 0;
margin: 0;
}
.problems-list li {
padding: 0.5em 0;
}
</style>
</head> </head>
<body> <body>
<div class="wrap">
<header>
<h1>Quick Problem Platform</h1> <h1>Quick Problem Platform</h1>
<!--<a href="/problem/new">Submit New Problem</a>--> </header>
<section class="problems-list">
<h2>Problems</h2> <div class="content" id="contentContainer">
<ul> <!-- Problems -->
{% for folder, description, test_code in problems %} <section class="card problems-list">
<li> <div class="search-controls">
<a href="/problem/{{ folder }}"><b>{{ folder.replace('_', ' ').title() }}</b></a> <input
</li> type="text"
{% else %} class="search-input"
<li>No problems yet.</li> id="problemSearch"
{% endfor %} placeholder="Search problems..."
</ul> />
</div>
<h2 style="margin-bottom:6px;font-size:1.1rem">Problems</h2>
<div id="problemsContainer">
{% for folder, description, test_code in problems %}
<div class="problem-item" data-name="{{ folder.replace('_',' ').title() }}" data-desc="{{ description }}">
<a href="/problem/{{ folder }}">{{ folder.replace('_',' ').title() }}</a>
</div>
{% else %}
<div class="problem-item">No problems yet.</div>
{% endfor %}
</div>
</section> </section>
<section>
<h2>Leaderboard</h2> <!-- Leaderboard -->
<section class="card" id="leaderboardSection">
<div class="leaderboard-head">
<h2 style="font-size:1.1rem;margin:0">Leaderboard</h2>
<button class="btn" id="toggleLeaderboard">Hide</button>
</div>
<div class="leaderboard-controls">
<input
type="text"
class="search-input"
id="userSearch"
placeholder="Filter by user..."
/>
<input
type="text"
class="search-input"
id="problemFilter"
placeholder="Filter by problem..."
/>
<select class="filter-select" id="runtimeFilter">
<option value="">All runtimes</option>
<option value="best">Best runtime</option>
<option value="worst">Worst runtime</option>
</select>
</div>
<div id="leaderboardContainer">
<table class="leaderboard-table"> <table class="leaderboard-table">
<thead>
<tr> <tr>
<th>Rank</th> <th class="sortable" data-sort="rank">Rank</th>
<th>User</th> <th class="sortable" data-sort="user">User</th>
<th>Problem</th> <th class="sortable" data-sort="problem">Problem</th>
<th>Runtime (s)</th> <th class="sortable" data-sort="runtime">Runtime (s)</th>
<th>Memory (KB)</th> <th class="sortable" data-sort="memory">Memory (KB)</th>
<th>Line Number</th> <th>Line</th>
<th>Timestamp</th> <th class="sortable" data-sort="timestamp">Timestamp</th>
</tr> </tr>
</thead>
<tbody id="leaderboardBody">
{% for entry in leaderboard %} {% for entry in leaderboard %}
<tr> <tr data-user="{{ entry[0] }}" data-problem="{{ problem_titles.get(entry[1], 'Unknown') }}"
<td>{{ loop.index }}</td> data-runtime="{{ '%.4f'|format(entry[2]) }}" data-memory="{{ entry[3] }}"
<td>{{ entry[0] }}</td> data-timestamp="{{ entry[5] }}">
<td>{{ problem_titles.get(entry[1], 'Unknown') }}</td> <td>{{ loop.index }}</td>
<td>{{ '%.4f'|format(entry[2]) }}</td> <td>{{ entry[0] }}</td>
<td>{{ entry[3] }}</td> <td>{{ problem_titles.get(entry[1], 'Unknown') }}</td>
<td>{{ entry[4] if entry[4] else '-' }}</td> <td>{{ '%.4f'|format(entry[2]) }}</td>
<td>{{ entry[5] }}</td> <td>{{ entry[3] }}</td>
<td>{{ entry[4] if entry[4] else '-' }}</td>
<td>{{ entry[5] }}</td>
</tr> </tr>
{% else %} {% else %}
<tr><td colspan="7">No leaderboard entries yet.</td></tr> <tr><td colspan="7">No leaderboard entries yet.</td></tr>
{% endfor %} {% endfor %}
</tbody>
</table> </table>
</div>
</section> </section>
</div>
</div>
</body> </body>
</html> </html>

152
templates/script.js Normal file
View File

@@ -0,0 +1,152 @@
// Toggle leaderboard visibility
const toggleBtn = document.getElementById('toggleLeaderboard');
const leaderboardSection = document.getElementById('leaderboardSection');
const contentContainer = document.getElementById('contentContainer');
toggleBtn.addEventListener('click', () => {
if (leaderboardSection.style.display === 'none') {
leaderboardSection.style.display = '';
toggleBtn.textContent = 'Hide';
contentContainer.classList.remove('single-column');
} else {
leaderboardSection.style.display = 'none';
toggleBtn.textContent = 'Show';
contentContainer.classList.add('single-column');
}
});
// Problem search functionality
const problemSearch = document.getElementById('problemSearch');
const problemsContainer = document.getElementById('problemsContainer');
const problemItems = problemsContainer.querySelectorAll('.problem-item');
problemSearch.addEventListener('input', () => {
const searchTerm = problemSearch.value.toLowerCase();
problemItems.forEach(item => {
const name = item.dataset.name.toLowerCase();
const desc = item.dataset.desc?.toLowerCase() || '';
if (name.includes(searchTerm) || desc.includes(searchTerm)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
});
// Leaderboard filtering and sorting
const userSearch = document.getElementById('userSearch');
const problemFilter = document.getElementById('problemFilter');
const runtimeFilter = document.getElementById('runtimeFilter');
const leaderboardBody = document.getElementById('leaderboardBody');
const leaderboardRows = Array.from(leaderboardBody.querySelectorAll('tr'));
const sortableHeaders = document.querySelectorAll('.sortable');
// Current sort state
let currentSort = {
column: null,
direction: 'asc'
};
// Filter leaderboard
function filterLeaderboard() {
const userTerm = userSearch.value.toLowerCase();
const problemTerm = problemFilter.value.toLowerCase();
const runtimeType = runtimeFilter.value;
leaderboardRows.forEach(row => {
const user = row.dataset.user.toLowerCase();
const problem = row.dataset.problem.toLowerCase();
const runtime = parseFloat(row.dataset.runtime);
const showUser = user.includes(userTerm);
const showProblem = problem.includes(problemTerm);
let showRuntime = true;
if (runtimeType === 'best') {
// Find if this is the best runtime for this user+problem combo
const userProblemRows = leaderboardRows.filter(r =>
r.dataset.user === row.dataset.user &&
r.dataset.problem === row.dataset.problem
);
const bestRuntime = Math.min(...userProblemRows.map(r => parseFloat(r.dataset.runtime)));
showRuntime = runtime === bestRuntime;
} else if (runtimeType === 'worst') {
// Find if this is the worst runtime for this user+problem combo
const userProblemRows = leaderboardRows.filter(r =>
r.dataset.user === row.dataset.user &&
r.dataset.problem === row.dataset.problem
);
const worstRuntime = Math.max(...userProblemRows.map(r => parseFloat(r.dataset.runtime)));
showRuntime = runtime === worstRuntime;
}
if (showUser && showProblem && showRuntime) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
// Sort leaderboard
function sortLeaderboard(column, direction) {
const rows = Array.from(leaderboardBody.querySelectorAll('tr'));
const index = Array.from(document.querySelectorAll('th')).findIndex(th => th.dataset.sort === column);
rows.sort((a, b) => {
let aValue = a.cells[index].textContent;
let bValue = b.cells[index].textContent;
// Special handling for numeric columns
if (column === 'runtime' || column === 'memory' || column === 'rank') {
aValue = parseFloat(aValue) || 0;
bValue = parseFloat(bValue) || 0;
return direction === 'asc' ? aValue - bValue : bValue - aValue;
}
// Special handling for timestamps
if (column === 'timestamp') {
aValue = new Date(aValue).getTime();
bValue = new Date(bValue).getTime();
return direction === 'asc' ? aValue - bValue : bValue - aValue;
}
// Default string comparison
aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase();
if (aValue < bValue) return direction === 'asc' ? -1 : 1;
if (aValue > bValue) return direction === 'asc' ? 1 : -1;
return 0;
});
// Re-append rows in sorted order
rows.forEach(row => leaderboardBody.appendChild(row));
}
// Set up event listeners
userSearch.addEventListener('input', filterLeaderboard);
problemFilter.addEventListener('input', filterLeaderboard);
runtimeFilter.addEventListener('change', filterLeaderboard);
// Set up sorting
sortableHeaders.forEach(header => {
header.addEventListener('click', () => {
const column = header.dataset.sort;
// Reset all sort indicators
sortableHeaders.forEach(h => {
h.classList.remove('sort-asc', 'sort-desc');
});
// Determine new sort direction
if (currentSort.column === column) {
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
} else {
currentSort.column = column;
currentSort.direction = 'asc';
}
// Apply new sort
header.classList.add(`sort-${currentSort.direction}`);
sortLeaderboard(column, currentSort.direction);
});
});

View File

@@ -1,51 +1,99 @@
import sys import sys
import traceback import traceback
import time import time
import io import io
import tempfile
import subprocess
import os
def run_code_against_tests(user_code, test_code): def run_code_against_tests(user_code, test_code):
import tempfile
import subprocess
local_ns = {} local_ns = {}
output = '' output = ''
start = time.perf_counter() start = time.perf_counter()
error = None error = None
passed = False passed = False
temp_file = None
try: try:
# Check if unittest is used in test_code # Check if unittest is used in test_code
if 'unittest' in test_code: if 'unittest' in test_code:
# Write user code + test code to a temp file # Write user code + test code to a temp file
with tempfile.NamedTemporaryFile('w+', suffix='.py', delete=False) as f: with tempfile.NamedTemporaryFile('w+', suffix='.py', delete=False, encoding='utf-8') as f:
f.write(user_code + '\n' + test_code) combined_code = f"{user_code}\n\n{test_code}"
f.write(combined_code)
f.flush() f.flush()
f_name = f.name temp_file = f.name
# Run the file as a subprocess # Run the file as a subprocess
proc = subprocess.run([sys.executable, f_name], capture_output=True, text=True, timeout=10) try:
output = proc.stdout + proc.stderr proc = subprocess.run(
passed = proc.returncode == 0 [sys.executable, temp_file],
error = None if passed else output capture_output=True,
text=True,
timeout=10,
encoding='utf-8'
)
output = proc.stdout
if proc.stderr:
output += f"\n{proc.stderr}"
passed = proc.returncode == 0
if not passed:
error = f"Tests failed. Return code: {proc.returncode}\n{output}"
else:
# For successful unittest runs, the stderr contains the test results
if proc.stderr and "OK" in proc.stderr:
output = proc.stderr # Use stderr as the main output for unittest
except subprocess.TimeoutExpired:
passed = False
error = "Code execution timed out after 10 seconds"
output = "Execution timed out"
else: else:
# Capture stdout # Capture stdout
old_stdout = sys.stdout old_stdout = sys.stdout
sys.stdout = mystdout = io.StringIO() captured_output = io.StringIO()
# Execute user code sys.stdout = captured_output
exec(user_code, {}, local_ns)
# Execute test code (should raise AssertionError if fail) try:
exec(test_code, local_ns, local_ns) # Execute user code
passed = True exec(user_code, {}, local_ns)
# Execute test code (should raise AssertionError if fail)
exec(test_code, local_ns, local_ns)
passed = True
except AssertionError as e:
passed = False
error = f"Assertion failed: {str(e)}"
except Exception as e:
passed = False
error = f"Runtime error: {traceback.format_exc()}"
finally:
output = captured_output.getvalue()
sys.stdout = old_stdout
except Exception as e: except Exception as e:
passed = False passed = False
error = traceback.format_exc() error = f"Execution error: {traceback.format_exc()}"
finally: finally:
if 'mystdout' in locals(): # Clean up temporary file
output = mystdout.getvalue() or output if temp_file and os.path.exists(temp_file):
sys.stdout = old_stdout if 'old_stdout' in locals() else sys.stdout try:
os.unlink(temp_file)
except Exception as e:
print(f"Warning: Could not delete temp file {temp_file}: {e}")
runtime = time.perf_counter() - start runtime = time.perf_counter() - start
result = { result = {
'passed': passed, 'passed': passed,
'output': output, 'output': output.strip() if output else '',
'runtime': runtime, 'runtime': runtime,
'error': error if not passed else None 'error': error if not passed else None
} }
return result return result