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