UNPKG

@firfi/quint-connect

Version:

Model-based testing framework connecting Quint specifications to TypeScript implementations

113 lines 5.17 kB
import { Config, Effect, Schema } from "effect"; import { QuintError } from "./errors.js"; import { DEFAULT_MAX_SAMPLES, DEFAULT_N_TRACES, DEFAULT_TEST_MAX_SAMPLES, isQuintTestGeneration } from "./run-options.js"; // TypeScript-only extension: Rust constructs the Quint command directly and has no // environment-selected backend. Validate this untrusted setting before either the CLI // or compiled evaluator starts, while an explicit API option remains authoritative. // Compare upstream command construction: // https://github.com/informalsystems/quint-connect/blob/4f018f54fc7dd4cef341d10111427bab59d3b307/connect/src/trace/generator/run.rs#L27-L52 const BackendEnvironment = Config.literal("typescript", "rust")("QUINT_BACKEND").pipe(Config.withDefault("typescript")); const resolveBackend = (opts) => opts.backend !== undefined ? Effect.succeed(opts.backend) : BackendEnvironment.pipe(Effect.mapError((error) => new QuintError({ message: `Invalid QUINT_BACKEND: expected "typescript" or "rust": ${error}` }))); export const validateTraceGenerationConfiguration = (opts) => Effect.as(resolveBackend(opts), undefined); const buildRunArgsWithBackend = (opts, outDir, backend) => { const nTraces = opts.nTraces ?? DEFAULT_N_TRACES; const args = [ "run", opts.spec, "--mbt", "--n-traces", String(nTraces), "--out-itf", `${outDir}/trace_{seq}.itf.json` ]; args.push("--backend", backend); if (opts.seed !== undefined) { args.push("--seed", opts.seed); } if (opts.maxSteps !== undefined) { args.push("--max-steps", String(opts.maxSteps)); } if (opts.maxSamples !== undefined) { args.push("--max-samples", String(opts.maxSamples)); } else if (opts.seed !== undefined) { args.push("--max-samples", String(DEFAULT_MAX_SAMPLES)); } if (opts.init !== undefined) { args.push("--init", opts.init); } if (opts.step !== undefined) { args.push("--step", opts.step); } if (opts.main !== undefined) { args.push("--main", opts.main); } if (opts.invariants !== undefined && opts.invariants.length > 0) { args.push("--invariants", ...opts.invariants); } if (opts.witnesses !== undefined && opts.witnesses.length > 0) { args.push("--witnesses", ...opts.witnesses); } return args; }; /** Backwards-compatible synchronous argument builder. Invalid environment config throws immediately. */ export const buildRunArgs = (opts, outDir) => buildRunArgsWithBackend(opts, outDir, Effect.runSync(resolveBackend(opts))); const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const resolveTestName = (test) => Schema.decodeUnknown(Schema.NonEmptyString)(test).pipe(Effect.mapError((error) => new QuintError({ message: `Invalid Quint test name: ${error}` }))); const buildTestArgsWithBackend = (opts, outDir, backend, testName) => { const maxSamples = opts.maxSamples ?? DEFAULT_TEST_MAX_SAMPLES; const args = [ "test", opts.spec, "--match", `^${escapeRegex(testName)}$`, "--max-samples", String(maxSamples), "--out-itf", `${outDir}/trace_{seq}.itf.json`, "--verbosity", "0" ]; args.push("--backend", backend); if (opts.seed !== undefined) { args.push("--seed", opts.seed); } if (opts.main !== undefined) { args.push("--main", opts.main); } return args; }; /** Backwards-compatible synchronous argument builder. Invalid environment config throws immediately. */ export const buildTestArgs = (opts, outDir) => Effect.runSync(Effect.gen(function* () { const backend = yield* resolveBackend(opts); const testName = yield* resolveTestName(opts.generation.test); return buildTestArgsWithBackend(opts, outDir, backend, testName); })); export const resolveTraceGenerationPolicy = (opts) => { if (isQuintTestGeneration(opts)) { return { mode: "test", command: "quint test", options: opts, buildCliArgs: (outDir) => Effect.gen(function* () { const backend = yield* resolveBackend(opts); const testName = yield* resolveTestName(opts.generation.test); return buildTestArgsWithBackend(opts, outDir, backend, testName); }) }; } return { mode: "run", command: "quint run", options: opts, buildCliArgs: (outDir) => Effect.map(resolveBackend(opts), (backend) => buildRunArgsWithBackend(opts, outDir, backend)) }; }; // TypeScript-only optimization: upstream Rust always builds and executes a Quint // command. A compiled input may bypass the CLI only for run mode, so test mode keeps // the same `quint test` semantics as upstream. // https://github.com/informalsystems/quint-connect/blob/4f018f54fc7dd4cef341d10111427bab59d3b307/connect/src/trace/generator/mod.rs#L23-L26 export const isCompiledEvaluatorPolicy = (policy) => policy.mode === "run" && policy.options.compiledInput !== undefined; //# sourceMappingURL=trace-generation-policy.js.map