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