/** * 🐍 PYTHON LANGUAGE RUNNER ADAPTER (Pyodide WebAssembly) 🐍 * ========================================================== * Implementiert das standardisierte LanguageRunnerAdapter-Interface: * - Pyodide WASM Engine mit In-Memory Python 3.12 * - CODE_GUARD Sicherheits-Prüfung & Sandbox-Bootstrap * - Timeout-Schutz gegen Endlosschleifen * - Unittest-Runner für aufgabe.py & test_aufgabe.py * - Fehler-Dolmetscher Anbindung */ (function () { let pyodideInstance = null; let pyodideLoading = false; const PythonAdapter = { id: "python", displayName: "Python 3.12 (Pyodide WebAssembly)", icon: "🐍", monacoLanguage: "python", fileExtension: ".py", testFileExtension: "test_aufgabe.py", /** * Lädt und initialisiert die Pyodide WebAssembly Engine */ async loadEngine(logCallback) { if (pyodideInstance) return pyodideInstance; if (pyodideLoading) { while (pyodideLoading) { await new Promise((r) => setTimeout(r, 100)); } return pyodideInstance; } pyodideLoading = true; if (logCallback) logCallback("⏳ Initialisiere Python WebAssembly Engine (Pyodide)... Bitte kurz warten.\n", "info"); try { if (!window.loadPyodide) { await new Promise((resolve, reject) => { const script = document.createElement("script"); script.src = "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } pyodideInstance = await window.loadPyodide(); // Sandbox-Härtung im Pyodide-Interpreter aktivieren if (window.CODE_GUARD && window.CODE_GUARD.getSandboxBootstrap) { await pyodideInstance.runPythonAsync(window.CODE_GUARD.getSandboxBootstrap()); } if (logCallback) logCallback("✅ Python WebAssembly Sandbox bereitgestellt!\n\n", "success"); } catch (err) { if (logCallback) logCallback(`❌ Fehler beim Laden der Python Sandbox: ${err.message}\n`, "error"); throw err; } finally { pyodideLoading = false; } return pyodideInstance; }, /** * Sicherheitsprüfung vor Code-Ausführung */ validateSecurity(code) { if (window.CODE_GUARD && window.CODE_GUARD.validate) { return window.CODE_GUARD.validate(code); } return { safe: true }; }, /** * Führt Python-Code aus (▶ Code ausführen) */ async runCode(code, logCallback) { const securityCheck = this.validateSecurity(code); if (!securityCheck.safe) { logCallback(`\n${securityCheck.error}\n\n`, "error"); return { success: false, error: securityCheck.error }; } const py = await this.loadEngine(logCallback); if (!py) return { success: false, error: "Pyodide nicht verfügbar" }; // Umleitung von stdout & stderr py.setStdout({ batched: (text) => logCallback(text + "\n", "stdout") }); py.setStderr({ batched: (text) => logCallback(text + "\n", "stderr") }); try { let timeoutHandle; const timeoutPromise = new Promise((_, reject) => { timeoutHandle = setTimeout(() => { reject(new Error("Zeitüberschreitung: Dein Code lief länger als 5 Sekunden (Endlosschleife vermutet).")); }, 5000); }); const execPromise = py.runPythonAsync(code); await Promise.race([execPromise, timeoutPromise]); clearTimeout(timeoutHandle); return { success: true }; } catch (err) { logCallback(`\n❌ Ausführungsfehler:\n${err.message}\n`, "error"); return { success: false, error: err.message }; } }, /** * Führt automatisierte Unittests aus (🧪 Tests starten) */ async runTests(userCode, testCode, logCallback) { const securityCheck = this.validateSecurity(userCode); if (!securityCheck.safe) { logCallback(`\n${securityCheck.error}\n\n`, "error"); return { success: false, error: securityCheck.error }; } const py = await this.loadEngine(logCallback); if (!py) return { success: false, error: "Pyodide nicht verfügbar" }; try { py.FS.writeFile("aufgabe.py", userCode); const testRunnerScript = ` import unittest, io, sys from unittest import TextTestRunner for mod in list(sys.modules.keys()): if mod in ('aufgabe', 'test_aufgabe'): del sys.modules[mod] import aufgabe ${testCode} suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) stream = io.StringIO() runner = TextTestRunner(stream=stream, verbosity=2) result = runner.run(suite) output_text = stream.getvalue() success = result.wasSuccessful() total_runs = result.testsRun failures = len(result.failures) errors = len(result.errors) (success, total_runs, failures, errors, output_text) `; let timeoutHandle; const timeoutPromise = new Promise((_, reject) => { timeoutHandle = setTimeout(() => { reject(new Error("Zeitüberschreitung: Unittest lief länger als 6 Sekunden.")); }, 6000); }); const [isSuccess, totalRuns, fails, errs, outputText] = await Promise.race([ py.runPythonAsync(testRunnerScript), timeoutPromise ]); clearTimeout(timeoutHandle); logCallback(outputText + "\n", "stdout"); return { success: Boolean(isSuccess), total: totalRuns, passed: totalRuns - (fails + errs), failures: fails, errors: errs, rawOutput: outputText }; } catch (err) { logCallback(`\n❌ Fehler beim Ausführen der Testsuite:\n${err.message}\n`, "error"); return { success: false, error: err.message, rawOutput: err.message }; } }, /** * Übersetzt Python-Tracebacks */ translateError(traceback) { if (window.INTERPRETER && window.INTERPRETER.translate) { return window.INTERPRETER.translate(traceback); } return null; } }; // In Registry registrieren if (window.RunnerRegistry) { window.RunnerRegistry.register(PythonAdapter); } else { document.addEventListener("DOMContentLoaded", () => { if (window.RunnerRegistry) window.RunnerRegistry.register(PythonAdapter); }); } })();