@informalsystems/quint
Version:
Core tool for the Quint specification language
300 lines • 13.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.printInductiveInvariantProgress = exports.outputTestErrors = exports.outputTestResults = exports.prepareOnTrace = exports.handleMainModuleError = exports.findMainModule = exports.cliErr = exports.writeToJson = exports.jsonStringOfOutputStage = exports.writeOutputToJson = exports.outputJson = exports.processVerifyResult = exports.printViolatedInvariants = exports.maybePrintWitnesses = exports.maybePrintCounterExample = void 0;
const chalk_1 = __importDefault(require("chalk"));
const graphics_1 = require("./graphics");
const verbosity_1 = require("./verbosity");
const trace_1 = require("./runtime/trace");
const json_bigint_1 = __importDefault(require("json-bigint"));
const evaluator_1 = require("./runtime/impl/evaluator");
const rng_1 = require("./rng");
const fs_1 = require("fs");
const path_1 = require("path");
const itf_1 = require("./itf");
const cliHelpers_1 = require("./cliHelpers");
const either_1 = require("@sweet-monads/either");
const process_1 = require("process");
const jsonHelper_1 = require("./jsonHelper");
const errorReporter_1 = require("./errorReporter");
/**
* Print a counterexample if the appropriate verbosity is set.
*
* @param verbosityLevel The verbosity level.
* @param states The states of the counterexample.
* @param frames The execution frames (optional).
* @param hideVars Variables to hide in the output (optional).
*/
function maybePrintCounterExample(verbosityLevel, states, frames = [], hideVars = []) {
if (verbosity_1.verbosity.hasStateOutput(verbosityLevel)) {
console.log(chalk_1.default.gray('An example execution:\n'));
const myConsole = {
width: (0, graphics_1.terminalWidth)(),
out: (s) => process.stdout.write(s),
};
(0, graphics_1.printTrace)(myConsole, states, frames, hideVars);
}
}
exports.maybePrintCounterExample = maybePrintCounterExample;
/**
* Print witnesses if the appropriate verbosity is set.
*
* @param verbosityLevel The verbosity level.
* @param outcome The simulation outcome.
* @param witnesses The list of witnesses.
*/
function maybePrintWitnesses(verbosityLevel, outcome, witnesses) {
if (verbosity_1.verbosity.hasWitnessesOutput(verbosityLevel)) {
if (outcome.witnessingTraces.length > 0) {
console.log(chalk_1.default.green('Witnesses:'));
}
outcome.witnessingTraces.forEach((n, i) => {
const percentage = chalk_1.default.gray(`(${(((1.0 * n) / outcome.samples) * 100).toFixed(2)}%)`);
console.log(`${chalk_1.default.yellow(witnesses[i])} was witnessed in ${chalk_1.default.green(n)} trace(s) out of ${outcome.samples} explored ${percentage}`);
});
}
}
exports.maybePrintWitnesses = maybePrintWitnesses;
/**
* Print violated invariants in the final state.
*
* @param state The final state.
* @param invariants The list of invariants.
* @param prev The previous stage context.
*/
function printViolatedInvariants(state, invariants, prev) {
if (invariants.length <= 1 && prev.args.inductiveInvariant === undefined) {
return;
}
const evaluator = new evaluator_1.Evaluator(prev.resolver.table, (0, trace_1.newTraceRecorder)(0, (0, rng_1.newRng)()), (0, rng_1.newRng)(), false);
for (const inv of invariants) {
const invExpr = (0, cliHelpers_1.toExpr)(prev, inv).unwrap();
evaluator.evaluate(invExpr);
evaluator.updateState(state);
const evalResult = evaluator.evaluate(invExpr);
if (evalResult.isRight() && evalResult.value.kind === 'bool' && !evalResult.value.value) {
console.log(chalk_1.default.red(` ❌ ${inv}`));
}
}
}
exports.printViolatedInvariants = printViolatedInvariants;
/**
* Process the result of a verification call.
*
* @param res The result of the verification.
* @param startMs The start time in milliseconds.
* @param verbosityLevel The verbosity level.
* @param verifying The current tracing stage.
* @param invariantsList The list of invariants.
* @param prev The previous stage context.
* @returns The processed result.
*/
function processVerifyResult(res, startMs, verbosityLevel, stage, invariantsList) {
const elapsedMs = Date.now() - startMs;
return res
.map(() => {
if (verbosity_1.verbosity.hasResults(verbosityLevel)) {
console.log(chalk_1.default.green('[ok]') + ' No violation found ' + chalk_1.default.gray(`(${elapsedMs}ms).`));
if (verbosity_1.verbosity.hasHints(verbosityLevel)) {
console.log(chalk_1.default.gray('You may increase --max-steps.'));
console.log(chalk_1.default.gray('Use --verbosity to produce more (or less) output.'));
}
}
return { ...stage, status: 'ok', errors: [] };
})
.mapLeft(err => {
const trace = err.traces ? (0, itf_1.ofItfNormalized)(err.traces[0]) : undefined;
const status = trace !== undefined ? 'violation' : 'failure';
if (trace !== undefined) {
maybePrintCounterExample(verbosityLevel, trace, [], stage.args.hide || []);
if (verbosity_1.verbosity.hasResults(verbosityLevel)) {
console.log(chalk_1.default.red(`[${status}]`) + ' Found an issue ' + chalk_1.default.gray(`(${elapsedMs}ms).`));
printViolatedInvariants(trace[trace.length - 1], invariantsList, stage);
}
if (stage.args.outItf && err.traces) {
writeToJson(stage.args.outItf, err.traces[0]);
}
}
return {
msg: err.explanation,
stage: { ...stage, status, errors: err.errors, trace },
};
});
}
exports.processVerifyResult = processVerifyResult;
function outputJson(stage) {
return jsonStringOfOutputStage(pickOutputStage(stage));
}
exports.outputJson = outputJson;
function writeOutputToJson(filename, stage) {
const path = (0, path_1.resolve)((0, process_1.cwd)(), filename);
(0, fs_1.writeFileSync)(path, outputJson(stage));
}
exports.writeOutputToJson = writeOutputToJson;
function jsonStringOfOutputStage(json) {
return json_bigint_1.default.stringify(json, jsonHelper_1.replacer);
}
exports.jsonStringOfOutputStage = jsonStringOfOutputStage;
/**
* Write json to a file.
*
* @param filename name of the file to write to
* @param json is an object tree to write
*/
function writeToJson(filename, json) {
const path = (0, path_1.resolve)((0, process_1.cwd)(), filename);
(0, fs_1.writeFileSync)(path, jsonStringOfOutputStage(json));
}
exports.writeToJson = writeToJson;
// Extract just the parts of a ProcedureStage that we use for the output
// See https://stackoverflow.com/a/39333479/1187277
const pickOutputStage = ({ stage, warnings, modules, table, types, effects, errors, documentation, passed, failed, ignored, status, trace, seed, main, }) => {
return {
stage,
warnings,
modules,
table,
types,
effects,
errors,
documentation,
passed,
failed,
ignored,
status,
trace,
seed,
main,
};
};
function cliErr(msg, stage) {
return (0, either_1.left)({ msg, stage });
}
exports.cliErr = cliErr;
/**
* Find the main module by name.
*/
function findMainModule(prev, mainName) {
return prev.modules.find(m => m.name === mainName);
}
exports.findMainModule = findMainModule;
/**
* Handle errors when the main module is not found.
*/
function handleMainModuleError(prev, mainName) {
const error = { code: 'QNT405', message: `Main module ${mainName} not found` };
return cliErr('Argument error', { ...prev, errors: [(0, cliHelpers_1.mkErrorMessage)(prev.sourceMap)(error)] });
}
exports.handleMainModuleError = handleMainModuleError;
function prepareOnTrace(source, outputTemplate, nTraces, metadata) {
return (index, status, vars, states, name) => {
if (outputTemplate) {
const filename = name
? (0, cliHelpers_1.expandNamedOutputTemplate)(outputTemplate, name, index, { autoAppend: nTraces > 1 })
: (0, cliHelpers_1.expandOutputTemplate)(outputTemplate, index, { autoAppend: nTraces > 1 });
const trace = (0, itf_1.toItf)(vars, states, metadata);
if (trace.isRight()) {
const jsonObj = (0, cliHelpers_1.addItfHeader)(source, status, trace.value);
writeToJson(filename, jsonObj);
}
else {
console.error(`ITF conversion failed on ${index}: ${trace.value}`);
}
}
};
}
exports.prepareOnTrace = prepareOnTrace;
/**
* Output test results.
*/
function outputTestResults(results, verbosityLevel, elapsedMs) {
const out = console.log;
let nFailures = 1;
if (verbosity_1.verbosity.hasResults(verbosityLevel)) {
results.forEach(res => {
if (res.status === 'passed') {
out(` ${chalk_1.default.green('ok')} ${res.name} passed ${res.nsamples} test(s)`);
}
if (res.status === 'failed') {
const errNo = chalk_1.default.red(nFailures);
out(` ${errNo}) ${res.name} failed after ${res.nsamples} test(s)`);
nFailures++;
}
});
const passed = results.filter(r => r.status === 'passed');
const failed = results.filter(r => r.status === 'failed');
const ignored = results.filter(r => r.status === 'ignored');
out('');
if (passed.length > 0) {
out(chalk_1.default.green(` ${passed.length} passing`) + chalk_1.default.gray(` (${elapsedMs}ms)`));
}
if (failed.length > 0) {
out(chalk_1.default.red(` ${failed.length} failed`));
}
if (ignored.length > 0) {
out(chalk_1.default.gray(` ${ignored.length} ignored`));
}
}
}
exports.outputTestResults = outputTestResults;
function outputTestErrors(prev, verbosityLevel, failed) {
const namedErrors = failed.reduce((acc, failure) => acc.concat(failure.errors.map(e => [failure, (0, cliHelpers_1.mkErrorMessage)(prev.sourceMap)(e)])), []);
const out = console.log;
// We know that there are errors, so report as required by the verbosity configuration
if (verbosity_1.verbosity.hasTestDetails(verbosityLevel)) {
const code = prev.sourceCode;
const finders = (0, errorReporter_1.createFinders)(code);
const columns = !prev.args.out ? (0, graphics_1.terminalWidth)() : 80;
out('');
namedErrors.forEach(([testResult, err], index) => {
const details = (0, errorReporter_1.formatError)(code, finders, err);
// output the header
out(` ${index + 1}) ${testResult.name}:`);
const lines = details.split('\n');
// output the error lines in red
lines.filter(l => l.trim().length > 0).forEach(l => out(chalk_1.default.red(' ' + l)));
if (verbosity_1.verbosity.hasActionTracking(verbosityLevel)) {
out('');
testResult.frames.forEach((f, index) => {
out(`[${chalk_1.default.bold('Frame ' + index)}]`);
const console = {
width: columns,
out: (s) => process.stdout.write(s),
};
(0, graphics_1.printExecutionFrameRec)(console, f, []);
out('');
});
if (testResult.frames.length == 0) {
out(' [No execution]');
}
}
// output the seed
out(chalk_1.default.gray(` Use --seed=0x${testResult.seed.toString(16)} --match=${testResult.name} to repeat.`));
});
out('');
}
if (verbosity_1.verbosity.hasHints(verbosityLevel) && !verbosity_1.verbosity.hasActionTracking(verbosityLevel)) {
out(chalk_1.default.gray(`\n Use --verbosity=3 to show executions.`));
out(chalk_1.default.gray(` Further debug with: quint test --verbosity=3 ${prev.args.input}`));
}
}
exports.outputTestErrors = outputTestErrors;
function printInductiveInvariantProgress(verbosityLevel, args, phase, nPhases, invariantsString = '') {
if (verbosity_1.verbosity.hasProgress(verbosityLevel)) {
switch (phase) {
case 1:
console.log(chalk_1.default.gray(`> [1/${nPhases}] Checking whether the inductive invariant '${args.inductiveInvariant}' holds in the initial state(s) defined by '${args.init}'...`));
break;
case 2:
console.log(chalk_1.default.gray(`> [2/${nPhases}] Checking whether '${args.step}' preserves the inductive invariant '${args.inductiveInvariant}'...`));
break;
case 3:
console.log(chalk_1.default.gray(`> [3/3] Checking whether the inductive invariant '${args.inductiveInvariant}' implies '${invariantsString}'...`));
break;
}
}
}
exports.printInductiveInvariantProgress = printInductiveInvariantProgress;
//# sourceMappingURL=cliReporting.js.map