UNPKG

@firfi/quint-connect

Version:

Model-based testing framework connecting Quint specifications to TypeScript implementations

113 lines 5.62 kB
import spawn from "cross-spawn"; import { Effect } from "effect"; import { existsSync, readdirSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { cpus, homedir } from "node:os"; import { join } from "node:path"; import { decodeCompiledEvaluatorInput, makeRandomSeedHex, patchCompiledEvaluatorInput } from "./compiled-evaluator-input.js"; import { normalizeEvaluatorOutput } from "./compiled-evaluator-output.js"; import { QuintError, QuintNotFoundError } from "./errors.js"; import { runManagedProcess } from "./managed-process.js"; import { platformProcess } from "./platform-process.js"; import { readTraceFiles, writeTraceFiles } from "./trace-files.js"; import { isCompiledEvaluatorPolicy, resolveTraceGenerationPolicy, validateTraceGenerationConfiguration } from "./trace-generation-policy.js"; const getRustEvaluatorPath = (processBoundary = platformProcess) => { const quintDir = join(homedir(), ".quint"); if (!existsSync(quintDir)) { throw new Error(`Quint home directory not found: ${quintDir}`); } const preferredVersion = process.env["QUINT_EVALUATOR_VERSION"]; const dirs = readdirSync(quintDir).filter((dir) => dir.startsWith("rust-evaluator-")).sort(); if (dirs.length === 0) { throw new Error("No Rust evaluator found in ~/.quint/. Run `quint run` once with --backend rust to download it."); } const preferred = preferredVersion ? dirs.find((dir) => dir.includes(preferredVersion)) : undefined; const latest = preferred ?? dirs[dirs.length - 1]; const exePath = join(quintDir, latest, processBoundary.executableName("quint_evaluator")); if (!existsSync(exePath)) { throw new Error(`Rust evaluator binary not found: ${exePath}`); } return exePath; }; export const makeRunEvaluatorProcess = (spawnProcess = (evaluatorPath, args, options) => { const proc = spawn(evaluatorPath, [...args], options); if (proc.stdin === null || proc.stdout === null || proc.stderr === null) { throw new Error("Rust evaluator was spawned without piped stdio"); } return { pid: proc.pid, stdin: proc.stdin, stdout: proc.stdout, stderr: proc.stderr, on: proc.on.bind(proc) }; }, processBoundary = platformProcess) => (evaluatorPath, inputStr) => runManagedProcess({ processBoundary, spawn: () => spawnProcess(evaluatorPath, ["simulate-from-stdin"], { stdio: ["pipe", "pipe", "pipe"], detached: processBoundary.detached }), captureResult: (proc) => { let stdout = ""; let stderr = ""; proc.stdin.write(inputStr); proc.stdin.end(); proc.stdout.on("data", (chunk) => { stdout += chunk.toString(); }); proc.stderr.on("data", (chunk) => { stderr += chunk.toString(); }); return (exitCode) => ({ stdout, exitCode, stderr }); } }).pipe(Effect.mapError((error) => new QuintNotFoundError({ message: `Failed to start Rust evaluator: ${error.message}` }))); const runEvaluatorDirect = makeRunEvaluatorProcess(); const defaultDeps = { compiledInputExists: existsSync, cpuCount: () => cpus().length, getEvaluatorPath: getRustEvaluatorPath, randomSeedHex: makeRandomSeedHex, readCompiledInput: (compiledInputPath) => Effect.tryPromise({ try: () => readFile(compiledInputPath, "utf-8"), catch: (e) => new QuintError({ message: `Failed to read compiled input: ${e}` }) }), runEvaluator: runEvaluatorDirect }; export const makeCompiledEvaluatorTraceAdapter = (deps = defaultDeps) => ({ canGenerate: (opts) => { const policy = resolveTraceGenerationPolicy(opts); return isCompiledEvaluatorPolicy(policy) && deps.compiledInputExists(policy.options.compiledInput); }, generate: (opts, outDir) => Effect.gen(function* () { const policy = resolveTraceGenerationPolicy(opts); if (!isCompiledEvaluatorPolicy(policy)) { return yield* new QuintError({ message: "Compiled input path is required for compiled evaluator generation" }); } yield* validateTraceGenerationConfiguration(policy.options); const rawInput = yield* deps.readCompiledInput(policy.options.compiledInput); const compiledInput = yield* decodeCompiledEvaluatorInput(rawInput); const { input, seedHex } = patchCompiledEvaluatorInput(compiledInput, policy.options, deps.cpuCount(), deps.randomSeedHex()); console.error(`[quint-connect] seed: ${seedHex} (compiled-input path)`); const evaluatorPath = yield* Effect.try({ try: deps.getEvaluatorPath, catch: (e) => new QuintError({ message: `Failed to locate Rust evaluator: ${e}` }) }); const result = yield* deps.runEvaluator(evaluatorPath, input); if (result.exitCode !== 0) { return yield* new QuintError({ message: `Rust evaluator failed with exit code ${result.exitCode}:\n${result.stderr}`, stderr: result.stderr, exitCode: result.exitCode }); } const traces = yield* normalizeEvaluatorOutput(result.stdout); yield* writeTraceFiles(outDir, traces); return yield* readTraceFiles(outDir); }) }); export const compiledEvaluatorTraceAdapter = makeCompiledEvaluatorTraceAdapter(); export { decodeCompiledEvaluatorInput, patchCompiledEvaluatorInput } from "./compiled-evaluator-input.js"; export { normalizeEvaluatorOutput } from "./compiled-evaluator-output.js"; //# sourceMappingURL=compiled-evaluator-adapter.js.map