UNPKG

testeranto

Version:

the AI powered BDD test framework for typescript projects

151 lines (134 loc) 4.36 kB
/* eslint-disable @typescript-eslint/no-unused-vars */ import chokidar from "chokidar"; import type { FSWatcher } from "chokidar"; import { generatePitonoMetafile, writePitonoMetafile } from "./pitonoMetafile"; import path from "path"; import fs from "fs"; export class PitonoWatcher { private watcher: FSWatcher | null = null; private testName: string; private entryPoints: string[]; private onChangeCallback: (() => void) | null = null; constructor(testName: string, entryPoints: string[]) { this.testName = testName; this.entryPoints = entryPoints; } async start() { // Watch source Python files to trigger metafile regeneration const pythonFilesPattern = "**/*.py"; this.watcher = chokidar.watch(pythonFilesPattern, { persistent: true, ignoreInitial: true, cwd: process.cwd(), ignored: [ "**/node_modules/**", "**/.git/**", "**/testeranto/bundles/**", "**/testeranto/reports/**", ], usePolling: true, interval: 1000, binaryInterval: 1000, depth: 99, followSymlinks: false, atomic: false, }); // Add event listeners for source file changes this.watcher .on("add", (filePath) => { this.handleFileChange("add", filePath); }) .on("change", (filePath) => { this.handleFileChange("change", filePath); }) .on("unlink", (filePath) => { this.handleFileChange("unlink", filePath); }) .on("error", (error) => { console.error(`Source watcher error: ${error}`); }) .on("ready", () => { console.log( "Initial python source file scan complete. Ready for changes." ); }); // Second watcher: watches bundle files to schedule tests when they change const outputDir = path.join( process.cwd(), `testeranto/bundles/python/${this.testName}` ); // Track the last seen signatures to detect changes const lastSignatures = new Map<string, string>(); // Create a separate watcher for bundle files const bundleWatcher = chokidar.watch(path.join(outputDir, "*.py"), { persistent: true, ignoreInitial: false, awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50, }, }); bundleWatcher .on("add", (filePath) => { this.readAndCheckSignature(filePath, lastSignatures); }) .on("change", (filePath) => { this.readAndCheckSignature(filePath, lastSignatures); }) .on("error", (error) => console.error(`Bundle watcher error: ${error}`)); // Initial metafile generation await this.regenerateMetafile(); } private async handleFileChange(event: string, filePath: string) { // Add a small delay to ensure the file is fully written await new Promise((resolve) => setTimeout(resolve, 100)); await this.regenerateMetafile(); if (this.onChangeCallback) { this.onChangeCallback(); } } private readAndCheckSignature( filePath: string, lastSignatures: Map<string, string> ) { try { const content = fs.readFileSync(filePath, "utf-8"); // Extract the signature from the comment const signatureMatch = content.match(/# Signature: (\w+)/); if (signatureMatch && signatureMatch[1]) { const currentSignature = signatureMatch[1]; const lastSignature = lastSignatures.get(filePath); if (lastSignature === undefined) { lastSignatures.set(filePath, currentSignature); } else if (lastSignature !== currentSignature) { lastSignatures.set(filePath, currentSignature); if (this.onChangeCallback) { this.onChangeCallback(); } } } } catch (error) { console.error(`Error reading bundle file ${filePath}:`, error); } } async regenerateMetafile() { try { const metafile = await generatePitonoMetafile( this.testName, this.entryPoints ); writePitonoMetafile(this.testName, metafile); } catch (error) { console.error("Error regenerating pitono metafile:", error); } } onMetafileChange(callback: () => void) { this.onChangeCallback = callback; } stop() { if (this.watcher) { this.watcher.close(); this.watcher = null; } } }