UNPKG

@firfi/quint-connect

Version:

Model-based testing framework connecting Quint specifications to TypeScript implementations

71 lines 3.95 kB
import spawn from "cross-spawn"; import { Effect, Schema } from "effect"; import { QuintError, QuintNotFoundError } from "./errors.js"; import { runManagedProcess } from "./managed-process.js"; import { platformProcess } from "./platform-process.js"; import { readTraceFiles } from "./trace-files.js"; import { resolveTraceGenerationPolicy } from "./trace-generation-policy.js"; export { buildRunArgs, buildTestArgs } from "./trace-generation-policy.js"; const QuintBinary = Schema.UndefinedOr(Schema.NonEmptyString); export const makeRunQuintProcess = (spawnProcess = (cmd, cmdArgs, options) => { const proc = spawn(cmd, [...cmdArgs], options); if (proc.stdout === null || proc.stderr === null) { throw new Error("Quint process was spawned without piped output"); } return { pid: proc.pid, stdout: proc.stdout, stderr: proc.stderr, on: proc.on.bind(proc) }; }, processBoundary = platformProcess, getEnvironment = () => process.env) => (args, verbose, quintBin) => Effect.gen(function* () { const baseEnv = getEnvironment(); const env = verbose ? { ...baseEnv, QUINT_VERBOSE: "true" } : baseEnv; const configuredQuintBin = yield* Schema.decodeUnknown(QuintBinary)(quintBin ?? baseEnv["QUINT_BIN"]).pipe(Effect.mapError((error) => new QuintNotFoundError({ message: `Invalid Quint executable configuration: ${error}` }))); // Intentional extension over Rust: upstream selects only `quint.cmd`/`quint`. // TypeScript additionally supports an injected executable, QUINT_BIN, and an npx // fallback because JS consumers commonly pin tools through different package runners. // https://github.com/informalsystems/quint-connect/blob/4f018f54fc7dd4cef341d10111427bab59d3b307/connect/src/trace/generator/utils.rs#L3-L9 const defaultQuintCommand = processBoundary.commandName("quint"); const runAttempt = (cmd, cmdArgs) => runManagedProcess({ processBoundary, spawn: () => spawnProcess(cmd, cmdArgs, { env, detached: processBoundary.detached }), captureResult: (proc) => { let stderr = ""; proc.stdout.resume(); proc.stderr.on("data", (chunk) => { stderr += chunk.toString(); }); return (exitCode) => ({ exitCode, stderr }); } }); const primary = runAttempt(configuredQuintBin ?? defaultQuintCommand, [...args]); return yield* primary.pipe(Effect.catchTag("ProcessStartError", (error) => { if (configuredQuintBin === undefined && error.code === "ENOENT") { console.warn("[quint-connect] 'quint' not found on PATH, falling back to npx (slower). Install globally: npm i -g @informalsystems/quint"); return runAttempt(processBoundary.commandName("npx"), ["@informalsystems/quint", ...args]); } return Effect.fail(error); }), Effect.mapError((error) => new QuintNotFoundError({ message: `Failed to start quint: ${error.message}` }))); }); const runQuintProcess = makeRunQuintProcess(); export const makeQuintCliTraceAdapter = (deps = { runQuintProcess }) => ({ canGenerate: () => true, generate: (opts, outDir) => Effect.gen(function* () { const policy = resolveTraceGenerationPolicy(opts); const args = yield* policy.buildCliArgs(outDir); const { exitCode, stderr } = yield* deps.runQuintProcess(args, opts.verbose === true, opts.quintBin); if (exitCode !== 0) { return yield* new QuintError({ message: stderr ? `${policy.command} failed with exit code ${exitCode}:\n${stderr.trim()}` : `${policy.command} failed with exit code ${exitCode}`, stderr, exitCode }); } return yield* readTraceFiles(outDir); }) }); export const quintCliTraceAdapter = makeQuintCliTraceAdapter(); //# sourceMappingURL=quint-cli-adapter.js.map