@firfi/quint-connect
Version:
Model-based testing framework connecting Quint specifications to TypeScript implementations
71 lines • 3.95 kB
JavaScript
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