blob: 19e40ed970d6d77bd102e4eab47625f2615e0a37 [file] [log] [blame]
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Generate exceptions from full corpus test report.
*/
const program = require('commander');
const assert = require('assert');
const babelGenerator = require('@babel/generator').default;
const babelTemplate = require('@babel/template').default;
const babelTypes = require('@babel/types');
const fs = require('fs');
const p = require('path');
const prettier = require("prettier");
const SPLIT_LINES_RE = /^.*([\n\r]+|$)/gm;
const PARSE_RE = /^Parsing (.*) sloppy took (\d+) ms\.\n$/;
const MUTATE_RE = /^Mutating (.*) took (\d+) ms\.\n$/;
const PARSE_FAILED_RE = /^WARNING: failed to sloppy parse (.*)\n$/;
const PARSE_STRICT_FAILED_RE = /^WARNING: failed to strict parse (.*)\n$/;
const MUTATE_FAILED_RE = /^ERROR: Exception during mutate: (.*)\n$/;
// Add tests matching error regexp to result array.
function matchError(regexp, line, resultArray){
const match = line.match(regexp);
if (!match) return false;
const relPath = match[1];
assert(relPath);
resultArray.push(relPath);
return true;
}
// Sum up total duration of tests matching the duration regexp and
// map test -> duration in result map.
function matchDuration(regexp, line, resultMap){
const match = line.match(regexp);
if (!match) return false;
const relPath = match[1];
assert(relPath);
resultMap[relPath] = (resultMap[relPath] || 0) + parseInt(match[2]);
return true;
}
// Create lists of failed and slow tests from stdout of a fuzzer run.
function processFuzzOutput(outputFile){
const text = fs.readFileSync(outputFile, 'utf-8');
const lines = text.match(SPLIT_LINES_RE);
const failedParse = [];
const failedParseStrict = [];
const failedMutate = [];
const durationsMap = {};
for (const line of lines) {
if (matchError(PARSE_FAILED_RE, line, failedParse))
continue;
if (matchError(PARSE_STRICT_FAILED_RE, line, failedParseStrict))
continue;
if (matchError(MUTATE_FAILED_RE, line, failedMutate))
continue;
if (matchDuration(PARSE_RE, line, durationsMap))
continue;
if (matchDuration(MUTATE_RE, line, durationsMap))
continue;
}
// Tuples (absPath, duration).
const total = Object.entries(durationsMap);
// Tuples (absPath, duration) with 2s < duration <= 10s.
const slow = total.filter(t => t[1] > 2000 && t[1] <= 10000);
// Tuples (absPath, duration) with 10s < duration.
const verySlow = total.filter(t => t[1] > 10000);
// Assert there's nothing horribly wrong with the results.
// We have at least 2500 tests in the output.
assert(total.length > 2500);
// No more than 5% parse/mutation errors.
assert(failedParse.length + failedMutate.length < total.length / 20);
// No more than 10% slow tests
assert(slow.length < total.length / 10);
// No more than 2% very slow tests.
assert(verySlow.length < total.length / 50);
// Sort everything.
failedParse.sort();
failedParseStrict.sort();
failedMutate.sort();
function slowestFirst(a, b) {
return b[1] - a[1];
}
slow.sort(slowestFirst);
verySlow.sort(slowestFirst);
return [failedParse, failedParseStrict, failedMutate, slow, verySlow];
}
// List of string literals of failed tests.
function getLiteralsForFailed(leadingComment, failedList) {
const result = failedList.map(path => babelTypes.stringLiteral(path));
if (result.length) {
babelTypes.addComment(result[0], 'leading', leadingComment);
}
return result;
}
// List of string literals of slow tests with duration comments.
function getLiteralsForSlow(leadingComment, slowList) {
const result = slowList.map(([path, duration]) => {
const literal = babelTypes.stringLiteral(path);
babelTypes.addComment(
literal, 'trailing', ` ${duration / 1000}s`, true);
return literal;
});
if (result.length) {
babelTypes.addComment(result[0], 'leading', leadingComment);
}
return result;
}
function main() {
program
.version('0.0.1')
.parse(process.argv);
if (!program.args.length) {
console.log('Need to specify stdout reports of fuzz runs.');
return;
}
let skipped = [];
let softSkipped = [];
let sloppy = [];
for (const outputFile of program.args) {
const [failedParse, failedParseStrict, failedMutate, slow, verySlow] = (
processFuzzOutput(outputFile));
const name = p.basename(outputFile, p.extname(outputFile));
// Skip tests that fail to parse/mutate or are very slow.
skipped = skipped.concat(getLiteralsForFailed(
` Tests with parse errors from ${name} `, failedParse));
skipped = skipped.concat(getLiteralsForFailed(
` Tests with mutation errors from ${name} `, failedMutate));
skipped = skipped.concat(getLiteralsForSlow(
` Very slow tests from ${name} `, verySlow));
// Soft-skip slow but not very slow tests.
softSkipped = softSkipped.concat(getLiteralsForSlow(
` Slow tests from ${name} `, slow));
// Mark sloppy tests.
sloppy = sloppy.concat(getLiteralsForFailed(
` Tests requiring sloppy mode from ${name} `, failedParseStrict));
}
const fileTemplate = babelTemplate(`
/**
* @fileoverview Autogenerated exceptions. Created with gen_exceptions.js.
*/
'use strict';
const skipped = SKIPPED;
const softSkipped = SOFTSKIPPED;
const sloppy = SLOPPY;
module.exports = {
generatedSkipped: new Set(skipped),
generatedSoftSkipped: new Set(softSkipped),
generatedSloppy: new Set(sloppy),
}
`, {preserveComments: true});
const skippedArray = babelTypes.arrayExpression(skipped);
const softSkippedArray = babelTypes.arrayExpression(softSkipped);
const sloppyArray = babelTypes.arrayExpression(sloppy);
const statements = fileTemplate({
SKIPPED: skippedArray,
SOFTSKIPPED: softSkippedArray,
SLOPPY: sloppyArray,
});
const resultProgram = babelTypes.program(statements);
const code = babelGenerator(resultProgram, { comments: true }).code;
const prettyCode = prettier.format(code, { parser: "babel" });
fs.writeFileSync('generated/exceptions.js', prettyCode);
}
main();