// 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, }