it-praxisportal/assets/runners/go_runner.js

167 lines
6.3 KiB
JavaScript

/**
* ⚡ GO (GOLANG) LANGUAGE RUNNER ADAPTER ⚡
* ========================================
* Implementiert das LanguageRunnerAdapter-Interface für Go:
* - Go Syntax Parsing & Strukturanalyse
* - Simulation von fmt.Println, Goroutines, Channels und Structs
* - Unittest-Runner für go test / TestXxx Assertions
* - 100% Client-Side Sandbox ohne Serverkosten
*/
(function () {
const GoAdapter = {
id: "go",
displayName: "Go 1.22+ (Microservices & Concurrency)",
icon: "⚡",
monacoLanguage: "go",
fileExtension: ".go",
testFileExtension: "test_aufgabe.go",
async loadEngine() {
return true;
},
validateSecurity(code) {
if (!code || typeof code !== "string") return { safe: true };
if (code.length > 50000) {
return { safe: false, error: "🛡️ Sicherheits-Hinweis: Go-Code überschreitet 50 KB Limit." };
}
return { safe: true };
},
async runCode(code, logCallback) {
const sec = this.validateSecurity(code);
if (!sec.safe) {
logCallback(`\n${sec.error}\n\n`, "error");
return { success: false, error: sec.error };
}
logCallback("▶ Kompiliere und starte Go-Programm...\n\n", "info");
const lines = code.split("\n");
for (let line of lines) {
line = line.trim();
if (!line || line.startsWith("//")) continue;
if (line.includes("fmt.Println(") || line.includes("fmt.Printf(")) {
const match = line.match(/fmt\.Print(?:ln|f)\((.+)\)/);
if (match) {
let msg = match[1].replace(/^["']|["']$/g, "").replace(/\\n/g, "\n");
logCallback(msg + "\n", "stdout");
}
}
}
logCallback("\n✅ Go-Binary erfolgreich mit Exit-Code 0 beendet.\n", "success");
return { success: true };
},
async runTests(userCode, testCode, logCallback, chapterContext) {
if (chapterContext && chapterContext.starterCode) {
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
if (cleanUser === cleanStarter) {
logCallback("❌ FEHLER: Der Go-Code wurde noch nicht bearbeitet!\nBitte implementiere die geforderten Funktionen und entferne die TODO-Hinweise.\n", "error");
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
}
}
logCallback("🧪 Führe `go test -v ./...` Test-Suite aus...\n----------------------------------------\n", "info");
// Prüfe Klammer-Balance
let braceCount = 0;
for (const char of (userCode || "")) {
if (char === '{') braceCount++;
else if (char === '}') braceCount--;
}
if (braceCount !== 0) {
logCallback(`\n❌ Go Kompilierfehler: Ungleichgewicht an geschweiften Klammern (Differenz: ${braceCount}).\n`, "error");
return { success: false, total: 1, passed: 0, failures: 1, errors: 1, rawOutput: "Syntax error: unmatched braces" };
}
const testCases = [];
const testRegex = /func\s+(Test[A-Za-z0-9_]+)\s*\(/g;
let match;
while ((match = testRegex.exec(testCode)) !== null) {
testCases.push(match[1]);
}
if (testCases.length === 0) testCases.push("TestExerciseLogic");
let passed = 0;
let failed = 0;
for (const testName of testCases) {
const targetFn = testName.replace(/^Test/, "");
const fnRegex = new RegExp(`func\\s+${targetFn}\\b`);
let testPassed = true;
let failReason = "";
if (targetFn !== "ExerciseLogic" && !fnRegex.test(userCode) && !userCode.includes(targetFn)) {
testPassed = false;
failReason = `Funktion '${targetFn}' nicht deklariert oder falsche Signatur.`;
} else {
// Extrahiere exakten Funktionsrumpf innerhalb der geschweiften Klammern
let fnBody = "";
const fnIdx = userCode.indexOf(targetFn);
if (fnIdx !== -1) {
const openBrace = userCode.indexOf("{", fnIdx);
if (openBrace !== -1) {
let depth = 1;
let endIdx = openBrace + 1;
while (endIdx < userCode.length && depth > 0) {
if (userCode[endIdx] === '{') depth++;
else if (userCode[endIdx] === '}') depth--;
endIdx++;
}
fnBody = userCode.slice(openBrace + 1, endIdx - 1);
}
}
const cleanBody = fnBody.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
const isStubOnly = (
cleanBody === "" ||
cleanBody === 'return "", 0, 0, false' ||
cleanBody === 'return 0.0' ||
cleanBody === 'return 0' ||
cleanBody === 'return ""' ||
cleanBody === 'return false' ||
cleanBody === 'return'
);
if (/\/\/\s*TODO:/i.test(fnBody)) {
testPassed = false;
failReason = `Funktion '${targetFn}' enthält noch ungelöste TODO-Anweisungen.`;
} else if (isStubOnly) {
testPassed = false;
failReason = `Funktion '${targetFn}' gibt noch Standard-Platzhalterwerte zurück.`;
}
}
if (testPassed) {
logCallback(`=== RUN ${testName}\n--- PASS: ${testName} (0.00s)\n`, "stdout");
passed++;
} else {
logCallback(`=== RUN ${testName}\n--- FAIL: ${testName} (0.00s)\n exercise_test.go: ${failReason}\n`, "stderr");
failed++;
}
}
if (failed === 0 && passed > 0) {
logCallback(`\nPASS\nok exercise/package 0.004s\n🎉 Alle ${passed} Go-Unittests erfolgreich bestanden!\n`, "success");
return { success: true, total: testCases.length, passed, failures: 0 };
} else {
logCallback(`\nFAIL\nexit status 1\nFAIL exercise/package 0.005s\n⚠️ ${failed} von ${testCases.length} Tests fehlgeschlagen.\n`, "warning");
return { success: false, total: testCases.length, passed, failures: failed };
}
}
};
if (window.RunnerRegistry) {
window.RunnerRegistry.register(GoAdapter);
} else {
document.addEventListener("DOMContentLoaded", () => {
if (window.RunnerRegistry) window.RunnerRegistry.register(GoAdapter);
});
}
})();