mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 16:01:52 +00:00

PR-URL: https://github.com/nodejs/node/pull/35415 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Myles Borins <myles.borins@gmail.com>
197 lines
6.1 KiB
JavaScript
197 lines
6.1 KiB
JavaScript
// 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();
|