blob: d186ce8556b665c56f9239c0d893b9d4d1218d41 [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 Corpus
*/
const program = require('commander');
const fs = require('fs');
const path = require('path');
const exceptions = require('./exceptions.js');
const random = require('./random.js');
const sourceHelpers = require('./source_helpers.js');
function* walkDirectory(directory, filter) {
// Generator for recursively walk a directory.
for (const filePath of fs.readdirSync(directory)) {
const currentPath = path.join(directory, filePath);
const stat = fs.lstatSync(currentPath);
if (stat.isFile()) {
if (!filter || filter(currentPath)) {
yield currentPath;
}
continue;
}
if (stat.isDirectory()) {
for (let childFilePath of walkDirectory(currentPath, filter)) {
yield childFilePath;
}
}
}
}
class Corpus {
// Input corpus.
constructor(inputDir, corpusName, extraStrict=false) {
this.inputDir = inputDir;
this.extraStrict = extraStrict;
// Filter for permitted JS files.
function isPermittedJSFile(absPath) {
return (absPath.endsWith('.js') &&
!exceptions.isTestSkippedAbs(absPath));
}
// Cache relative paths of all files in corpus.
this.skippedFiles = [];
this.softSkippedFiles = [];
this.permittedFiles = [];
const directory = path.join(inputDir, corpusName);
for (const absPath of walkDirectory(directory, isPermittedJSFile)) {
const relPath = path.relative(this.inputDir, absPath);
if (exceptions.isTestSkippedRel(relPath)) {
this.skippedFiles.push(relPath);
} else if (exceptions.isTestSoftSkippedAbs(absPath) ||
exceptions.isTestSoftSkippedRel(relPath)) {
this.softSkippedFiles.push(relPath);
} else {
this.permittedFiles.push(relPath);
}
}
random.shuffle(this.softSkippedFiles);
random.shuffle(this.permittedFiles);
}
// Relative paths of all files in corpus.
*relFiles() {
for (const relPath of this.permittedFiles) {
yield relPath;
}
for (const relPath of this.softSkippedFiles) {
yield relPath;
}
}
// Relative paths of all files in corpus including generated skipped.
*relFilesForGenSkipped() {
for (const relPath of this.relFiles()) {
yield relPath;
}
for (const relPath of this.skippedFiles) {
yield relPath;
}
}
/**
* Returns "count" relative test paths, randomly selected from soft-skipped
* and permitted files. Permitted files have a 4 times higher chance to
* be chosen.
*/
getRandomTestcasePaths(count) {
return random.twoBucketSample(
this.softSkippedFiles, this.permittedFiles, 4, count);
}
loadTestcase(relPath, strict, label) {
const start = Date.now();
try {
const source = sourceHelpers.loadSource(this.inputDir, relPath, strict);
if (program.verbose) {
const duration = Date.now() - start;
console.log(`Parsing ${relPath} ${label} took ${duration} ms.`);
}
return source;
} catch (e) {
console.log(`WARNING: failed to ${label} parse ${relPath}`);
console.log(e);
}
return undefined;
}
*loadTestcases(relPaths) {
for (const relPath of relPaths) {
if (this.extraStrict) {
// When re-generating the files marked sloppy, we additionally test if
// the file parses in strict mode.
this.loadTestcase(relPath, true, 'strict');
}
const source = this.loadTestcase(relPath, false, 'sloppy');
if (source) {
yield source;
}
}
}
getRandomTestcases(count) {
return Array.from(this.loadTestcases(this.getRandomTestcasePaths(count)));
}
getAllTestcases() {
return this.loadTestcases(this.relFilesForGenSkipped());
}
}
module.exports = {
Corpus: Corpus,
walkDirectory: walkDirectory,
}