UNPKG

testeranto

Version:

the AI powered BDD test framework for typescript projects

1,084 lines (1,083 loc) 39.9 kB
"use strict"; // /* eslint-disable @typescript-eslint/ban-ts-comment */ // /* eslint-disable no-async-promise-executor */ // /* eslint-disable @typescript-eslint/no-explicit-any */ // /* eslint-disable @typescript-eslint/no-unused-vars */ // import http from "http"; // import fs, { watch } from "fs"; // import path from "path"; // import puppeteer, { executablePath } from "puppeteer-core"; // import ansiC from "ansi-colors"; // import { IBuiltConfig, IRunTime, ISummary } from "../../Types.js"; // import { // createLogStreams, // isValidUrl, // pollForFile, // writeFileAndCreateDir, // puppeteerConfigs, // filesHash, // IOutputs, // } from "../../PM/utils.js"; // import { getRunnables } from "./utils.js"; // import { PM_1 } from "./PM_1.js"; // import { makePrompt } from "./makePrompt.js"; // import { RawData, WebSocketServer } from "ws"; // import { ChildProcess } from "child_process"; // import { WebSocketMessage } from "../../PM/types.js"; // const changes: Record<string, string> = {}; // export abstract class PM_WithProcesses extends PM_1 { // // summary: ISummary = {}; // // webMetafileWatcher: fs.FSWatcher; // // nodeMetafileWatcher: fs.FSWatcher; // // importMetafileWatcher: fs.FSWatcher; // // pitonoMetafileWatcher: fs.FSWatcher; // // golangMetafileWatcher: fs.FSWatcher; // // ports: Record<number, string>; // // queue: string[]; // // logStreams: Record<string, ReturnType<typeof createLogStreams>> = {}; // // launchers: Record<string, () => void>; // // configs: any; // // abstract launchNode(src: string, dest: string); // // abstract launchWeb(src: string, dest: string); // // abstract launchPure(src: string, dest: string); // // abstract launchPython(src: string, dest: string); // // abstract launchGolang(src: string, dest: string); // // abstract startBuildProcesses(): Promise<void>; // // runningProcesses: Map<string, ChildProcess | Promise<any>> = new Map(); // // allProcesses: Map< // // string, // // { // // child?: ChildProcess; // // promise?: Promise<any>; // // status: "running" | "exited" | "error" | "completed"; // // exitCode?: number; // // error?: string; // // command: string; // // pid?: number; // // timestamp: string; // // type: "process" | "promise"; // // category: "aider" | "bdd-test" | "build-time" | "other"; // // testName?: string; // // platform?: "node" | "web" | "pure" | "python" | "golang"; // // } // // > = new Map(); // // processLogs: Map<string, string[]> = new Map(); // // clients: Set<any> = new Set(); // constructor(configs: IBuiltConfig, name, mode) { // super(configs, name, mode); // // this.configs.tests.forEach(([t, rt, tr, sidecars]) => { // // this.ensureSummaryEntry(t); // // sidecars.forEach(([sidecarName]) => { // // this.ensureSummaryEntry(sidecarName, true); // // }); // // }); // // this.launchers = {}; // // this.ports = {}; // // this.queue = []; // // this.configs.ports.forEach((element) => { // // this.ports[element] = ""; // set ports as open // // }); // // this.httpServer = http.createServer(this.handleHttpRequest.bind(this)); // // this.wss = new WebSocketServer({ server: this.httpServer }); // // this.wss.on("connection", (ws) => { // // this.clients.add(ws); // // console.log("Client connected"); // // ws.on("message", (data) => { // // try { // // this.websocket(data, ws); // // } catch (error) { // // console.error("Error handling WebSocket message:", error); // // } // // }); // // ws.on("close", () => { // // this.clients.delete(ws); // // console.log("Client disconnected"); // // }); // // ws.on("error", (error) => { // // console.error("WebSocket error:", error); // // this.clients.delete(ws); // // }); // // }); // // const httpPort = Number(process.env.HTTP_PORT) || 3000; // // this.httpServer.on("error", (error) => { // // console.error("HTTP server error:", error); // // if ((error as any).code === "EADDRINUSE") { // // console.error( // // `Port ${httpPort} is already in use. Please use a different port.` // // ); // // } // // process.exit(-1); // // }); // // this.httpServer.listen(httpPort, "0.0.0.0", () => { // // console.log(`HTTP server running on http://localhost:${httpPort}`); // // }); // } // writeBigBoard = () => { // // note: this path is different from the one used by front end // const summaryPath = `./testeranto/reports/${this.projectName}/summary.json`; // const summaryData = JSON.stringify(this.summary, null, 2); // fs.writeFileSync(summaryPath, summaryData); // // Broadcast the update // this.webSocketBroadcastMessage({ // type: "summaryUpdate", // data: this.summary, // }); // }; // addPromiseProcess( // processId: string, // promise: Promise<any>, // command: string, // category: "aider" | "bdd-test" | "build-time" | "other" = "other", // testName?: string, // platform?: "node" | "web" | "pure" | "python" | "golang", // onResolve?: (result: any) => void, // onReject?: (error: any) => void // ) { // this.runningProcesses.set(processId, promise); // this.allProcesses.set(processId, { // promise, // status: "running", // command, // timestamp: new Date().toISOString(), // type: "promise", // category, // testName, // platform, // }); // // Initialize logs for this process // this.processLogs.set(processId, []); // const startMessage = `Starting: ${command}`; // const logs = this.processLogs.get(processId) || []; // logs.push(startMessage); // this.processLogs.set(processId, logs); // this.webSocketBroadcastMessage({ // type: "processStarted", // processId, // command, // timestamp: new Date().toISOString(), // logs: [startMessage], // }); // promise // .then((result) => { // this.runningProcesses.delete(processId); // const processInfo = this.allProcesses.get(processId); // if (processInfo) { // this.allProcesses.set(processId, { // ...processInfo, // status: "completed", // exitCode: 0, // }); // } // // Add log entry for process completion // const successMessage = `Completed successfully with result: ${JSON.stringify( // result // )}`; // const currentLogs = this.processLogs.get(processId) || []; // currentLogs.push(successMessage); // this.processLogs.set(processId, currentLogs); // this.webSocketBroadcastMessage({ // type: "processExited", // processId, // exitCode: 0, // timestamp: new Date().toISOString(), // logs: [successMessage], // }); // if (onResolve) onResolve(result); // }) // .catch((error) => { // this.runningProcesses.delete(processId); // const processInfo = this.allProcesses.get(processId); // if (processInfo) { // this.allProcesses.set(processId, { // ...processInfo, // status: "error", // error: error.message, // }); // } // const errorMessage = `Failed with error: ${error.message}`; // const currentLogs = this.processLogs.get(processId) || []; // currentLogs.push(errorMessage); // this.processLogs.set(processId, currentLogs); // this.webSocketBroadcastMessage({ // type: "processError", // processId, // error: error.message, // timestamp: new Date().toISOString(), // logs: [errorMessage], // }); // if (onReject) onReject(error); // }); // return processId; // } // getProcessesByCategory( // category: "aider" | "bdd-test" | "build-time" | "other" // ) { // return Array.from(this.allProcesses.entries()) // .filter(([id, procInfo]) => procInfo.category === category) // .map(([id, procInfo]) => ({ // processId: id, // command: procInfo.command, // pid: procInfo.pid, // status: procInfo.status, // exitCode: procInfo.exitCode, // error: procInfo.error, // timestamp: procInfo.timestamp, // category: procInfo.category, // testName: procInfo.testName, // platform: procInfo.platform, // logs: this.processLogs.get(id) || [], // })); // } // getBDDTestProcesses() { // return this.getProcessesByCategory("bdd-test"); // } // getBuildTimeProcesses() { // return this.getProcessesByCategory("build-time"); // } // getAiderProcesses() { // return this.getProcessesByCategory("aider"); // } // getProcessesByTestName(testName: string) { // return Array.from(this.allProcesses.entries()) // .filter(([id, procInfo]) => procInfo.testName === testName) // .map(([id, procInfo]) => ({ // processId: id, // command: procInfo.command, // pid: procInfo.pid, // status: procInfo.status, // exitCode: procInfo.exitCode, // error: procInfo.error, // timestamp: procInfo.timestamp, // category: procInfo.category, // testName: procInfo.testName, // platform: procInfo.platform, // logs: this.processLogs.get(id) || [], // })); // } // getProcessesByPlatform( // platform: "node" | "web" | "pure" | "pitono" | "golang" // ) { // return Array.from(this.allProcesses.entries()) // .filter(([id, procInfo]) => procInfo.platform === platform) // .map(([id, procInfo]) => ({ // processId: id, // command: procInfo.command, // pid: procInfo.pid, // status: procInfo.status, // exitCode: procInfo.exitCode, // error: procInfo.error, // timestamp: procInfo.timestamp, // category: procInfo.category, // testName: procInfo.testName, // platform: procInfo.platform, // logs: this.processLogs.get(id) || [], // })); // } // bddTestIsRunning(src: string) { // // @ts-ignore // this.summary[src] = { // prompt: "?", // runTimeErrors: "?", // staticErrors: "?", // typeErrors: "?", // failingFeatures: {}, // }; // } // abstract tscCheck({ // entrypoint, // addableFiles, // platform, // }: { // entrypoint: string; // addableFiles: string[]; // platform: IRunTime; // }); // abstract eslintCheck({ // entrypoint, // addableFiles, // platform, // }: { // entrypoint: string; // addableFiles: string[]; // platform: IRunTime; // }); // async metafileOutputs(platform: IRunTime) { // let metafilePath: string; // if (platform === "python") { // metafilePath = `./testeranto/metafiles/python/core.json`; // } else { // metafilePath = `./testeranto/metafiles/${platform}/${this.projectName}.json`; // } // // Ensure the metafile exists // if (!fs.existsSync(metafilePath)) { // console.log( // ansiC.yellow(`Metafile not found at ${metafilePath}, skipping`) // ); // return; // } // let metafile; // try { // const fileContent = fs.readFileSync(metafilePath).toString(); // const parsedData = JSON.parse(fileContent); // // Handle different metafile structures // if (platform === "python") { // // Pitono metafile might be the entire content or have a different structure // metafile = parsedData.metafile || parsedData; // } else { // metafile = parsedData.metafile; // } // if (!metafile) { // console.log( // ansiC.yellow(ansiC.inverse(`No metafile found in ${metafilePath}`)) // ); // return; // } // // console.log( // // ansiC.blue( // // `Found metafile for ${platform} with ${ // // Object.keys(metafile.outputs || {}).length // // } outputs` // // ) // // ); // } catch (error) { // console.error(`Error reading metafile at ${metafilePath}:`, error); // return; // } // const outputs: IOutputs = metafile.outputs; // // Check if outputs exists and is an object // // if (!outputs || typeof outputs !== "object") { // // console.log( // // ansiC.yellow( // // ansiC.inverse(`No outputs found in metafile at ${metafilePath}`) // // ) // // ); // // return; // // } // // @ts-ignore // Object.keys(outputs).forEach(async (k) => { // const pattern = `testeranto/bundles/${platform}/${this.projectName}/${this.configs.src}`; // if (!k.startsWith(pattern)) { // return; // } // // // @ts-ignore // const output = outputs[k]; // // Check if the output entry exists and has inputs // // if (!output || !output.inputs) { // // return; // // } // // @ts-ignore // const addableFiles = Object.keys(output.inputs).filter((i) => { // if (!fs.existsSync(i)) return false; // if (i.startsWith("node_modules")) return false; // if (i.startsWith("./node_modules")) return false; // return true; // }); // const f = `${k.split(".").slice(0, -1).join(".")}/`; // if (!fs.existsSync(f)) { // fs.mkdirSync(f, { recursive: true }); // } // // @ts-ignore // let entrypoint = output.entryPoint; // if (entrypoint) { // // Normalize the entrypoint path to ensure consistent comparison // entrypoint = path.normalize(entrypoint); // const changeDigest = await filesHash(addableFiles); // if (changeDigest === changes[entrypoint]) { // // skip // } else { // changes[entrypoint] = changeDigest; // // Run appropriate static analysis based on platform // if ( // platform === "node" || // platform === "web" || // platform === "pure" // ) { // this.tscCheck({ entrypoint, addableFiles, platform }); // this.eslintCheck({ entrypoint, addableFiles, platform }); // } else if (platform === "python") { // this.pythonLintCheck(entrypoint, addableFiles); // this.pythonTypeCheck(entrypoint, addableFiles); // } // makePrompt( // this.summary, // this.projectName, // entrypoint, // addableFiles, // platform // ); // const testName = this.findTestNameByEntrypoint(entrypoint, platform); // if (testName) { // console.log( // ansiC.green( // ansiC.inverse( // `Source files changed, re-queueing test: ${testName}` // ) // ) // ); // this.addToQueue(testName, platform); // } else { // console.error( // `Could not find test for entrypoint: ${entrypoint} (${platform})` // ); // process.exit(-1); // } // } // } // }); // } // private findTestNameByEntrypoint( // entrypoint: string, // platform: IRunTime // ): string | null { // const runnables = getRunnables(this.configs.tests, this.projectName); // let entryPointsMap: Record<string, string>; // switch (platform) { // case "node": // entryPointsMap = runnables.nodeEntryPoints; // break; // case "web": // entryPointsMap = runnables.webEntryPoints; // break; // case "pure": // entryPointsMap = runnables.pureEntryPoints; // break; // case "python": // entryPointsMap = runnables.pythonEntryPoints; // break; // case "golang": // entryPointsMap = runnables.golangEntryPoints; // break; // default: // throw "wtf"; // } // if (!entryPointsMap) { // console.error("idk"); // } // if (!entryPointsMap[entrypoint]) { // console.error(`${entrypoint} not found`); // } // return entryPointsMap[entrypoint]; // } // async pythonLintCheck(entrypoint: string, addableFiles: string[]) { // const reportDest = `testeranto/reports/${this.projectName}/${entrypoint // .split(".") // .slice(0, -1) // .join(".")}/python`; // if (!fs.existsSync(reportDest)) { // fs.mkdirSync(reportDest, { recursive: true }); // } // const lintErrorsPath = `${reportDest}/lint_errors.txt`; // try { // // Use flake8 for Python linting // const { spawn } = await import("child_process"); // const child = spawn("flake8", [entrypoint, "--max-line-length=88"], { // stdio: ["pipe", "pipe", "pipe"], // }); // let stderr = ""; // child.stderr.on("data", (data) => { // stderr += data.toString(); // }); // let stdout = ""; // child.stdout.on("data", (data) => { // stdout += data.toString(); // }); // return new Promise<void>((resolve) => { // child.on("close", () => { // const output = stdout + stderr; // if (output.trim()) { // fs.writeFileSync(lintErrorsPath, output); // this.summary[entrypoint].staticErrors = output.split("\n").length; // } else { // if (fs.existsSync(lintErrorsPath)) { // fs.unlinkSync(lintErrorsPath); // } // this.summary[entrypoint].staticErrors = 0; // } // resolve(); // }); // }); // } catch (error) { // console.error(`Error running flake8 on ${entrypoint}:`, error); // fs.writeFileSync( // lintErrorsPath, // `Error running flake8: ${error.message}` // ); // this.summary[entrypoint].staticErrors = -1; // } // } // async pythonTypeCheck(entrypoint: string, addableFiles: string[]) { // const reportDest = `testeranto/reports/${this.projectName}/${entrypoint // .split(".") // .slice(0, -1) // .join(".")}/python`; // if (!fs.existsSync(reportDest)) { // fs.mkdirSync(reportDest, { recursive: true }); // } // const typeErrorsPath = `${reportDest}/type_errors.txt`; // try { // // Use mypy for Python type checking // const { spawn } = await import("child_process"); // const child = spawn("mypy", [entrypoint], { // stdio: ["pipe", "pipe", "pipe"], // }); // let stderr = ""; // child.stderr.on("data", (data) => { // stderr += data.toString(); // }); // let stdout = ""; // child.stdout.on("data", (data) => { // stdout += data.toString(); // }); // return new Promise<void>((resolve) => { // child.on("close", () => { // const output = stdout + stderr; // if (output.trim()) { // fs.writeFileSync(typeErrorsPath, output); // this.summary[entrypoint].typeErrors = output.split("\n").length; // } else { // if (fs.existsSync(typeErrorsPath)) { // fs.unlinkSync(typeErrorsPath); // } // this.summary[entrypoint].typeErrors = 0; // } // resolve(); // }); // }); // } catch (error) { // console.error(`Error running mypy on ${entrypoint}:`, error); // fs.writeFileSync(typeErrorsPath, `Error running mypy: ${error.message}`); // this.summary[entrypoint].typeErrors = -1; // } // } // abstract onBuildDone(): void; // async start() { // // Wait for build processes to complete first // try { // await this.startBuildProcesses(); // // Generate Python metafile if there are Python tests // const pythonTests = this.configs.tests.filter( // (test) => test[1] === "python" // ); // if (pythonTests.length > 0) { // const { generatePitonoMetafile, writePitonoMetafile } = await import( // "../../utils/pitonoMetafile.js" // ); // const entryPoints = pythonTests.map((test) => test[0]); // const metafile = await generatePitonoMetafile( // this.projectName, // entryPoints // ); // writePitonoMetafile(this.projectName, metafile); // } // this.onBuildDone(); // } catch (error) { // console.error("Build processes failed:", error); // return; // } // // Continue with the rest of the setup after builds are done // this.mapping().forEach(async ([command, func]) => { // globalThis[command] = func; // }); // if (!fs.existsSync(`testeranto/reports/${this.projectName}`)) { // fs.mkdirSync(`testeranto/reports/${this.projectName}`); // } // try { // this.browser = await puppeteer.launch(puppeteerConfigs); // } catch (e) { // console.error(e); // console.error( // "could not start chrome via puppeter. Check this path: ", // executablePath // ); // } // const runnables = getRunnables(this.configs.tests, this.projectName); // const { // nodeEntryPoints, // webEntryPoints, // pureEntryPoints, // pythonEntryPoints, // golangEntryPoints, // } = runnables; // // Add all tests to the queue // [ // ["node", nodeEntryPoints], // ["web", webEntryPoints], // ["pure", pureEntryPoints], // ["python", pythonEntryPoints], // ["golang", golangEntryPoints], // ].forEach(([runtime, entryPoints]: [IRunTime, Record<string, string>]) => { // Object.keys(entryPoints).forEach((entryPoint) => { // // Create the report directory // const reportDest = `testeranto/reports/${this.projectName}/${entryPoint // .split(".") // .slice(0, -1) // .join(".")}/${runtime}`; // if (!fs.existsSync(reportDest)) { // fs.mkdirSync(reportDest, { recursive: true }); // } // // Add to the processing queue // this.addToQueue(entryPoint, runtime); // }); // }); // // Set up metafile watchers for each runtime // const runtimeConfigs = [ // ["node", nodeEntryPoints], // ["web", webEntryPoints], // ["pure", pureEntryPoints], // ["python", pythonEntryPoints], // ["golang", golangEntryPoints], // ]; // for (const [runtime, entryPoints] of runtimeConfigs) { // if (Object.keys(entryPoints).length === 0) continue; // // For python, the metafile path is different // let metafile: string; // if (runtime === "python") { // metafile = `./testeranto/metafiles/${runtime}/core.json`; // } else { // metafile = `./testeranto/metafiles/${runtime}/${this.projectName}.json`; // } // // Ensure the directory exists // const metafileDir = metafile.split("/").slice(0, -1).join("/"); // if (!fs.existsSync(metafileDir)) { // fs.mkdirSync(metafileDir, { recursive: true }); // } // try { // // For python, we may need to generate the metafile first // if (runtime === "python" && !fs.existsSync(metafile)) { // const { generatePitonoMetafile, writePitonoMetafile } = await import( // "../../utils/pitonoMetafile.js" // ); // const entryPointList = Object.keys(entryPoints); // if (entryPointList.length > 0) { // const metafileData = await generatePitonoMetafile( // this.projectName, // entryPointList // ); // writePitonoMetafile(this.projectName, metafileData); // } // } // await pollForFile(metafile); // // console.log("Found metafile for", runtime, metafile); // // Set up watcher for the metafile with debouncing // let timeoutId: NodeJS.Timeout; // const watcher = watch(metafile, async (e, filename) => { // // Debounce to avoid multiple rapid triggers // clearTimeout(timeoutId); // timeoutId = setTimeout(async () => { // console.log( // ansiC.yellow(ansiC.inverse(`< ${e} ${filename} (${runtime})`)) // ); // try { // await this.metafileOutputs(runtime as IRunTime); // // After processing metafile changes, check the queue to run tests // console.log( // ansiC.blue( // `Metafile processed, checking queue for tests to run` // ) // ); // this.checkQueue(); // } catch (error) { // console.error(`Error processing metafile changes:`, error); // } // }, 300); // 300ms debounce // }); // // Store the watcher based on runtime // switch (runtime) { // case "node": // this.nodeMetafileWatcher = watcher; // break; // case "web": // this.webMetafileWatcher = watcher; // break; // case "pure": // this.importMetafileWatcher = watcher; // break; // case "python": // this.pitonoMetafileWatcher = watcher; // break; // case "golang": // this.golangMetafileWatcher = watcher; // break; // } // // Read the metafile immediately // await this.metafileOutputs(runtime as IRunTime); // } catch (error) { // console.error(`Error setting up watcher for ${runtime}:`, error); // } // } // } // async stop() { // console.log(ansiC.inverse("Testeranto-Run is shutting down gracefully...")); // this.mode = "once"; // this.nodeMetafileWatcher.close(); // this.webMetafileWatcher.close(); // this.importMetafileWatcher.close(); // if (this.pitonoMetafileWatcher) { // this.pitonoMetafileWatcher.close(); // } // // if (this.gitWatcher) { // // this.gitWatcher.close(); // // } // // if (this.gitWatchTimeout) { // // clearTimeout(this.gitWatchTimeout); // // } // Object.values(this.logStreams || {}).forEach((logs) => logs.closeAll()); // if (this.wss) { // this.wss.close(() => { // console.log("WebSocket server closed"); // }); // } // this.clients.forEach((client) => { // client.terminate(); // }); // this.clients.clear(); // if (this.httpServer) { // this.httpServer.close(() => { // console.log("HTTP server closed"); // }); // } // this.checkForShutdown(); // } // receiveFeaturesV2 = ( // reportDest: string, // srcTest: string, // platform: IRunTime // ) => { // const featureDestination = path.resolve( // process.cwd(), // "reports", // "features", // "strings", // srcTest.split(".").slice(0, -1).join(".") + ".features.txt" // ); // const testReportPath = `${reportDest}/tests.json`; // if (!fs.existsSync(testReportPath)) { // console.error(`tests.json not found at: ${testReportPath}`); // return; // } // const testReport = JSON.parse(fs.readFileSync(testReportPath, "utf8")); // // Add full path information to each test // if (testReport.tests) { // testReport.tests.forEach((test) => { // // Add the full path to each test // test.fullPath = path.resolve(process.cwd(), srcTest); // }); // } // // Add full path to the report itself // testReport.fullPath = path.resolve(process.cwd(), srcTest); // // Write the modified report back // fs.writeFileSync(testReportPath, JSON.stringify(testReport, null, 2)); // testReport.features // .reduce(async (mm, featureStringKey) => { // const accum = await mm; // const isUrl = isValidUrl(featureStringKey); // if (isUrl) { // const u = new URL(featureStringKey); // if (u.protocol === "file:") { // accum.files.push(u.pathname); // } else if (u.protocol === "http:" || u.protocol === "https:") { // const newPath = `${process.cwd()}/testeranto/features/external/${ // u.hostname // }${u.pathname}`; // const body = await this.configs.featureIngestor(featureStringKey); // writeFileAndCreateDir(newPath, body); // accum.files.push(newPath); // } // } else { // await fs.promises.mkdir(path.dirname(featureDestination), { // recursive: true, // }); // accum.strings.push(featureStringKey); // } // return accum; // }, Promise.resolve({ files: [] as string[], strings: [] as string[] })) // .then(({ files }: { files: string[]; strings: string[] }) => { // // Markdown files must be referenced in the prompt but string style features are already present in the tests.json file // fs.writeFileSync( // `testeranto/reports/${this.projectName}/${srcTest // .split(".") // .slice(0, -1) // .join(".")}/${platform}/featurePrompt.txt`, // files // .map((f) => { // return `/read ${f}`; // }) // .join("\n") // ); // }); // testReport.givens.forEach((g) => { // if (g.failed === true) { // // @ts-ignore // this.summary[srcTest].failingFeatures[g.key] = g.features; // } // }); // this.writeBigBoard(); // }; // addToQueue(src: string, runtime: IRunTime) { // // Ensure we're using the original test source path, not a bundle path // // The src parameter might be a bundle path from metafile changes // // We need to find the corresponding test source path // // First, check if this looks like a bundle path (contains 'testeranto/bundles') // if (src.includes("testeranto/bundles")) { // // Try to find the original test name that corresponds to this bundle // const runnables = getRunnables(this.configs.tests, this.projectName); // const allEntryPoints = [ // ...Object.entries(runnables.nodeEntryPoints), // ...Object.entries(runnables.webEntryPoints), // ...Object.entries(runnables.pureEntryPoints), // ...Object.entries(runnables.pythonEntryPoints), // ...Object.entries(runnables.golangEntryPoints), // ]; // // Normalize the source path for comparison // const normalizedSrc = path.normalize(src); // for (const [testName, bundlePath] of allEntryPoints) { // const normalizedBundlePath = path.normalize(bundlePath as string); // // Check if the source path ends with the bundle path // if (normalizedSrc.endsWith(normalizedBundlePath)) { // // Use the original test name instead of the bundle path // src = testName; // break; // } // } // } // // First, clean up any existing processes for this test // this.cleanupTestProcesses(src); // // Add the test to the queue (using the original test source path) // // Make sure we don't add duplicates // if (!this.queue.includes(src)) { // this.queue.push(src); // console.log( // ansiC.green( // ansiC.inverse(`Added ${src} (${runtime}) to the processing queue`) // ) // ); // // Try to process the queue // this.checkQueue(); // } else { // console.log( // ansiC.yellow( // ansiC.inverse(`Test ${src} is already in the queue, skipping`) // ) // ); // } // } // private cleanupTestProcesses(testName: string) { // // Find and clean up any running processes for this test // const processesToCleanup: string[] = []; // // Find all process IDs that match this test name // for (const [processId, processInfo] of this.allProcesses.entries()) { // if ( // processInfo.testName === testName && // processInfo.status === "running" // ) { // processesToCleanup.push(processId); // } // } // // Clean up each process // processesToCleanup.forEach((processId) => { // const processInfo = this.allProcesses.get(processId); // if (processInfo) { // // Kill child process if it exists // if (processInfo.child) { // try { // processInfo.child.kill(); // } catch (error) { // console.error(`Error killing process ${processId}:`, error); // } // } // // Update process status // this.allProcesses.set(processId, { // ...processInfo, // status: "exited", // exitCode: -1, // error: "Killed due to source file change", // }); // // Remove from running processes // this.runningProcesses.delete(processId); // // Broadcast process exit // this.webSocketBroadcastMessage({ // type: "processExited", // processId, // exitCode: -1, // timestamp: new Date().toISOString(), // logs: ["Process killed due to source file change"], // }); // } // }); // } // checkQueue() { // // Process all items in the queue // while (this.queue.length > 0) { // const x = this.queue.pop(); // if (!x) continue; // // Check if this test is already running // let isRunning = false; // for (const processInfo of this.allProcesses.values()) { // if (processInfo.testName === x && processInfo.status === "running") { // isRunning = true; // break; // } // } // if (isRunning) { // console.log( // ansiC.yellow( // `Skipping ${x} - already running, will be re-queued when current run completes` // ) // ); // continue; // } // const test = this.configs.tests.find((t) => t[0] === x); // if (!test) { // console.error(`test is undefined ${x}`); // continue; // } // // Get the appropriate launcher based on the runtime type // const runtime = test[1]; // const runnables = getRunnables(this.configs.tests, this.projectName); // let dest: string; // switch (runtime) { // case "node": // dest = runnables.nodeEntryPoints[x]; // if (dest) { // this.launchNode(x, dest); // } else { // console.error(`No destination found for node test: ${x}`); // } // break; // case "web": // dest = runnables.webEntryPoints[x]; // if (dest) { // this.launchWeb(x, dest); // } else { // console.error(`No destination found for web test: ${x}`); // } // break; // case "pure": // dest = runnables.pureEntryPoints[x]; // if (dest) { // this.launchPure(x, dest); // } else { // console.error(`No destination found for pure test: ${x}`); // } // break; // case "python": // dest = runnables.pythonEntryPoints[x]; // if (dest) { // this.launchPython(x, dest); // } else { // console.error(`No destination found for python test: ${x}`); // } // break; // case "golang": // dest = runnables.golangEntryPoints[x]; // if (dest) { // // For Golang, we need to build and run the executable // // The dest is the path to the generated wrapper file // this.launchGolang(x, dest); // } else { // console.error(`No destination found for golang test: ${x}`); // } // break; // default: // console.error(`Unknown runtime: ${runtime} for test ${x}`); // break; // } // } // if (this.queue.length === 0) { // console.log(ansiC.inverse(`The queue is empty`)); // } // } // checkForShutdown = () => { // this.checkQueue(); // console.log( // ansiC.inverse( // `The following jobs are awaiting resources: ${JSON.stringify( // this.queue // )}` // ) // ); // console.log( // ansiC.inverse(`The status of ports: ${JSON.stringify(this.ports)}`) // ); // this.writeBigBoard(); // if (this.mode === "dev") return; // let inflight = false; // Object.keys(this.summary).forEach((k) => { // if (this.summary[k].prompt === "?") { // console.log(ansiC.blue(ansiC.inverse(`🕕 prompt ${k}`))); // inflight = true; // } // }); // Object.keys(this.summary).forEach((k) => { // if (this.summary[k].runTimeErrors === "?") { // console.log(ansiC.blue(ansiC.inverse(`🕕 runTimeError ${k}`))); // inflight = true; // } // }); // Object.keys(this.summary).forEach((k) => { // if (this.summary[k].staticErrors === "?") { // console.log(ansiC.blue(ansiC.inverse(`🕕 staticErrors ${k}`))); // inflight = true; // } // }); // Object.keys(this.summary).forEach((k) => { // if (this.summary[k].typeErrors === "?") { // console.log(ansiC.blue(ansiC.inverse(`🕕 typeErrors ${k}`))); // inflight = true; // } // }); // this.writeBigBoard(); // if (!inflight) { // if (this.browser) { // if (this.browser) { // this.browser.disconnect().then(() => { // console.log( // ansiC.inverse(`${this.projectName} has been tested. Goodbye.`) // ); // process.exit(); // }); // } // } // } // }; // private ensureSummaryEntry(src: string, isSidecar = false) { // if (!this.summary[src]) { // // @ts-ignore // this.summary[src] = { // typeErrors: undefined, // staticErrors: undefined, // runTimeErrors: undefined, // prompt: undefined, // failingFeatures: {}, // }; // if (isSidecar) { // // Sidecars don't need all fields // // delete this.summary[src].runTimeError; // // delete this.summary[src].prompt; // } // } // return this.summary[src]; // } // websocket(data: RawData) { // const message: WebSocketMessage = JSON.parse(data.toString()); // if (message.type === "chatMessage") { // // Always record the message, even if aider is not available // console.log(`Received chat message: ${message.content}`); // // Pass to PM_WithHelpo if available // if ((this as any).handleChatMessage) { // (this as any).handleChatMessage(message.content); // } else { // console.log("PM_WithHelpo not available - message not processed"); // } // return; // } // super.websocket(data); // } // } // // onBuildDone(): void { // // console.log("Build processes completed"); // // // The builds are done, which means the files are ready to be watched // // // This matches the original behavior where builds completed before PM_Main started // // // Start Git watcher for development mode // // this.startGitWatcher(); // // }