diff --git a/pi-coder-judge-extension.ts b/pi-coder-judge-extension.ts index f79cd24..a3d5c41 100644 --- a/pi-coder-judge-extension.ts +++ b/pi-coder-judge-extension.ts @@ -452,10 +452,16 @@ async function detectTestCommands( async function runTestsParallel( pi: ExtensionAPI, ctx: ExtensionCommandContext, - cmds: string[] + cmds: string[], + timeoutSecs: number = 120 ): Promise { const results = await Promise.all( - cmds.map(cmd => pi.exec("bash", ["-c", cmd], { cwd: ctx.cwd })) + // timeout-Wrapper: verhindert hängende Tests (Exit 124 = Timeout) + cmds.map(cmd => pi.exec( + "bash", + ["-c", `timeout ${timeoutSecs} bash -c ${JSON.stringify(cmd)}`], + { cwd: ctx.cwd } + )) ); const MAX_PER = Math.max(1000, Math.floor(6000 / cmds.length)); return results.map((r, i) => { @@ -463,7 +469,9 @@ async function runTestsParallel( const out = raw.length > MAX_PER ? raw.slice(0, MAX_PER) + `\n[… gekürzt, ${raw.length} Zeichen]` : raw || "(kein Output)"; - const status = r.code === 0 ? "✓ OK" : `✗ Exit ${r.code}`; + const status = r.code === 0 ? "✓ OK" + : r.code === 124 ? `✗ Timeout (>${timeoutSecs}s)` + : `✗ Exit ${r.code}`; return `=== ${cmds[i]} [${status}] ===\n${out}`; }).join("\n\n"); } @@ -706,17 +714,19 @@ export default function (pi: ExtensionAPI) { // ── Automatische Optimierungsschleife ──────────────────────────────────── pi.registerCommand("optimize", { - description: "Coder→Judge→Fix-Schleife bis PASS. Tests werden automatisch erkannt und parallel ausgeführt. /optimize [--rounds N] [--with-doku] [--continue] [--test-cmd \"override\"]", + description: "Coder→Judge→Fix-Schleife bis PASS. Tests werden automatisch erkannt und parallel ausgeführt. /optimize [--rounds N] [--with-doku] [--continue] [--test-cmd \"override\"] [--test-timeout N]", handler: async function (args: string, ctx: ExtensionCommandContext) { const roundsMatch = (args || "").match(/--rounds\s+(\d+)/); const maxRounds = roundsMatch ? Math.max(1, parseInt(roundsMatch[1], 10)) : 3; const withDoku = /--with-doku/.test(args || ""); const continueMode = /--continue/.test(args || ""); - // --test-cmd "befehl" oder --test-cmd befehl (ohne Leerzeichen im Befehl) const testCmdMatch = (args || "").match(/--test-cmd\s+"([^"]+)"|--test-cmd\s+(\S+)/); const testCmd: string | null = testCmdMatch ? (testCmdMatch[1] ?? testCmdMatch[2]) : null; + const testTimeoutMatch = (args || "").match(/--test-timeout\s+(\d+)/); + const testTimeout = testTimeoutMatch ? parseInt(testTimeoutMatch[1], 10) : 120; const task = (args || "") .replace(/--rounds\s+\d+/, "") + .replace(/--test-timeout\s+\d+/, "") .replace(/--with-doku/, "") .replace(/--continue/, "") .replace(/--test-cmd\s+"[^"]*"/, "") @@ -793,8 +803,8 @@ export default function (pi: ExtensionAPI) { const label = autoTestCmds.length === 1 ? autoTestCmds[0].split(" ")[0] : `${autoTestCmds.length} Suiten parallel`; - ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Tests laufen (${label})…`); - const testOutput = await runTestsParallel(pi, ctx, autoTestCmds); + ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Tests laufen (${label}, max. ${testTimeout}s)…`); + const testOutput = await runTestsParallel(pi, ctx, autoTestCmds, testTimeout); ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Judge analysiert Test-Ergebnis…`); await sendAndWait(pi, ctx, judgeWithTestsPrompt(testOutput, "")); } else {