Remove RWC runner and related infrastructure (#55187)

This commit is contained in:
Jake Bailey 2023-08-07 14:35:15 -07:00 committed by GitHub
parent 9a771d54e8
commit 7eece9f798
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 4 additions and 954 deletions

View File

@ -14,7 +14,7 @@ import util from "util";
import { localizationDirectories } from "./scripts/build/localization.mjs";
import cmdLineOptions from "./scripts/build/options.mjs";
import { buildProject, cleanProject, watchProject } from "./scripts/build/projects.mjs";
import { localBaseline, localRwcBaseline, refBaseline, refRwcBaseline, runConsoleTests } from "./scripts/build/tests.mjs";
import { localBaseline, refBaseline, runConsoleTests } from "./scripts/build/tests.mjs";
import { Debouncer, Deferred, exec, getDiffTool, memoize, needsUpdate, readJson } from "./scripts/build/utils.mjs";
const glob = util.promisify(_glob);
@ -740,12 +740,6 @@ export const diff = task({
run: () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true, waitForExit: false }),
});
export const diffRwc = task({
name: "diff-rwc",
description: "Diffs the RWC baselines using the diff tool specified by the 'DIFF' environment variable",
run: () => exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], { ignoreExitCode: true, waitForExit: false }),
});
/**
* @param {string} localBaseline Path to the local copy of the baselines
* @param {string} refBaseline Path to the reference copy of the baselines
@ -780,12 +774,6 @@ export const baselineAccept = task({
run: baselineAcceptTask(localBaseline, refBaseline),
});
export const baselineAcceptRwc = task({
name: "baseline-accept-rwc",
description: "Makes the most recent rwc test results the new baseline, overwriting the old baseline",
run: baselineAcceptTask(localRwcBaseline, refRwcBaseline),
});
// TODO(rbuckton): Determine if we still need this task. Depending on a relative
// path here seems like a bad idea.
export const updateSublime = task({
@ -799,13 +787,6 @@ export const updateSublime = task({
}
});
// TODO(rbuckton): Should the path to DefinitelyTyped be configurable via an environment variable?
export const importDefinitelyTypedTests = task({
name: "importDefinitelyTypedTests",
description: "Runs the importDefinitelyTypedTests script to copy DT's tests to the TS-internal RWC tests",
run: () => exec(process.execPath, ["scripts/importDefinitelyTypedTests.mjs", "./", "../DefinitelyTyped"]),
});
export const produceLKG = task({
name: "LKG",

View File

@ -12,8 +12,6 @@ import { exec, ExecError } from "./utils.mjs";
const mochaJs = path.resolve(findUpRoot(), "node_modules", "mocha", "bin", "_mocha");
export const localBaseline = "tests/baselines/local/";
export const refBaseline = "tests/baselines/reference/";
export const localRwcBaseline = "internal/baselines/rwc/local";
export const refRwcBaseline = "internal/baselines/rwc/reference";
export const coverageDir = "coverage";
/**
@ -25,7 +23,7 @@ export const coverageDir = "coverage";
* @param {boolean} [options.watching]
*/
export async function runConsoleTests(runJs, defaultReporter, runInParallel, options = {}) {
let testTimeout = cmdLineOptions.timeout;
const testTimeout = cmdLineOptions.timeout;
const tests = cmdLineOptions.tests;
const inspect = cmdLineOptions.break || cmdLineOptions.inspect;
const runners = cmdLineOptions.runners;
@ -67,10 +65,6 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel, opt
workerCount = cmdLineOptions.workers;
}
if (tests && tests.toLocaleLowerCase() === "rwc") {
testTimeout = 400000;
}
if (options.watching) {
console.log(chalk.yellowBright(`[watch] running tests...`));
}
@ -174,8 +168,7 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel, opt
}
export async function cleanTestDirs() {
await del([localBaseline, localRwcBaseline]);
await fs.promises.mkdir(localRwcBaseline, { recursive: true });
await del([localBaseline]);
await fs.promises.mkdir(localBaseline, { recursive: true });
}

View File

@ -1,178 +0,0 @@
import * as childProcess from "child_process";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import url from "url";
const __filename = url.fileURLToPath(new URL(import.meta.url));
const __dirname = path.dirname(__filename);
main();
function main() {
const [, progName, tscRoot, definitelyTypedRoot] = process.argv;
if (process.argv.length !== 4) {
if (process.argv.length < 2) {
throw new Error("Expected at least 2 argv elements.");
}
console.log("Usage:");
console.log(` node ${path.relative(__dirname, progName)} [TypeScript Repo Root] [DefinitelyTyped Repo Root]`);
return;
}
const tscPath = path.resolve(tscRoot, "built", "local", "tsc.js");
const rwcTestPath = path.resolve(tscRoot, "internal", "cases", "rwc");
const resolvedDefinitelyTypedRoot = path.resolve(definitelyTypedRoot);
console.log(`Resolved TypeScript Compiler Path: '${tscPath}'.`);
console.log(`Resolved TypeScript RWC Path: '${rwcTestPath}'.`);
console.log(`Resolved DefinitelyTyped Repo Root: '${resolvedDefinitelyTypedRoot}'.`);
importDefinitelyTypedTests(tscPath, rwcTestPath, resolvedDefinitelyTypedRoot);
}
/**
* @param {string} path
* @param {string} endingString
* @returns {boolean}
*/
function filePathEndsWith(path, endingString) {
const pathLen = path.length;
const extLen = endingString.length;
return pathLen > extLen && path.substr(pathLen - extLen, extLen).toLocaleLowerCase() === endingString.toLocaleLowerCase();
}
/**
* @param {string} source
* @param {string} destination
*/
function copyFileSync(source, destination) {
const text = fs.readFileSync(source);
fs.writeFileSync(destination, text);
}
/**
* @param {string} tscPath
* @param {string} rwcTestPath
* @param {string} testCaseName
* @param {string[]} testFiles
* @param {string | undefined} responseFile
*/
function importDefinitelyTypedTest(tscPath, rwcTestPath, testCaseName, testFiles, responseFile) {
let cmd = "node " + tscPath + " --module commonjs " + testFiles.join(" ");
if (responseFile) {
cmd += " @" + responseFile;
}
const testDirectoryName = testCaseName + "_" + Math.floor((Math.random() * 10000) + 1);
const testDirectoryPath = path.join(os.tmpdir(), testDirectoryName);
if (fs.existsSync(testDirectoryPath)) {
throw new Error("Could not create test directory");
}
fs.mkdirSync(testDirectoryPath);
childProcess.exec(cmd, {
maxBuffer: 1 * 1024 * 1024,
cwd: testDirectoryPath
}, (error, stdout, stderr) => {
console.log("importing " + testCaseName + " ...");
console.log(cmd);
if (error) {
console.log("importing " + testCaseName + " ...");
console.log(cmd);
console.log("==> error " + JSON.stringify(error));
console.log("==> stdout " + String(stdout));
console.log("==> stderr " + String(stderr));
console.log("\r\n");
return;
}
// copy generated file to output location
const outputFilePath = path.join(testDirectoryPath, "iocapture0.json");
const testCasePath = path.join(rwcTestPath, "DefinitelyTyped_" + testCaseName + ".json");
copyFileSync(outputFilePath, testCasePath);
//console.log("output generated at: " + outputFilePath);
if (!fs.existsSync(testCasePath)) {
throw new Error("could not find test case at: " + testCasePath);
}
else {
fs.unlinkSync(outputFilePath);
fs.rmdirSync(testDirectoryPath);
//console.log("testcase generated at: " + testCasePath);
//console.log("Done.");
}
//console.log("\r\n");
}).on("error", (error) => {
console.log("==> error " + JSON.stringify(error));
console.log("\r\n");
});
}
/**
* @param {string} tscPath
* @param {string} rwcTestPath
* @param {string} definitelyTypedRoot
*/
function importDefinitelyTypedTests(tscPath, rwcTestPath, definitelyTypedRoot) {
fs.readdir(definitelyTypedRoot, (err, subDirectories) => {
if (err) {
throw err;
}
// When you just want to test the script out on one or two files,
// just add a line like the following:
//
// .filter(d => d.indexOf("sipml") >= 0 )
subDirectories
.filter(d => ["_infrastructure", "node_modules", ".git"].indexOf(d) < 0)
.filter(i => fs.statSync(path.join(definitelyTypedRoot, i)).isDirectory())
.forEach(d => {
const directoryPath = path.join(definitelyTypedRoot, d);
fs.readdir(directoryPath, (err, files) => {
if (err) {
throw err;
}
/** @type {string[]} */
const tsFiles = [];
/** @type {string[]} */
const testFiles = [];
/** @type {string | undefined} */
let paramFile;
for (const filePath of files.map(f => path.join(directoryPath, f))) {
if (filePathEndsWith(filePath, ".ts")) {
tsFiles.push(filePath);
if (filePathEndsWith(filePath, "-tests.ts")) {
testFiles.push(filePath);
}
}
else if (filePathEndsWith(filePath, ".tscparams")) {
paramFile = filePath;
}
}
if (testFiles.length === 0) {
// no test files but multiple d.ts's, e.g. winjs
const regexp = new RegExp(d + "(([-][0-9])|(\\.d\\.ts))");
if (tsFiles.length > 1 && tsFiles.every(t => filePathEndsWith(t, ".d.ts") && regexp.test(t))) {
for (const fileName of tsFiles) {
importDefinitelyTypedTest(tscPath, rwcTestPath, path.basename(fileName, ".d.ts"), [fileName], paramFile);
}
}
else {
importDefinitelyTypedTest(tscPath, rwcTestPath, d, tsFiles, paramFile);
}
}
else {
for (const fileName of tsFiles) {
importDefinitelyTypedTest(tscPath, rwcTestPath, path.basename(fileName, "-tests.ts"), [fileName], paramFile);
}
}
});
});
});
}

View File

@ -5,7 +5,7 @@ import {
} from "./_namespaces/Harness";
import * as ts from "./_namespaces/ts";
export type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc";
export type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project";
export type CompilerTestKind = "conformance" | "compiler";
export type FourslashTestKind = "fourslash" | "fourslash-shims" | "fourslash-shims-pp" | "fourslash-server";

View File

@ -1,4 +0,0 @@
/* Generated file to emulate the Harness namespace. */
export * from "../../harness/_namespaces/Harness";
export * from "../loggedIO";

View File

@ -1,3 +0,0 @@
/* Generated file to emulate the Playback namespace. */
export * from "../loggedIO";

View File

@ -1,7 +0,0 @@
/* Generated file to emulate the ts.server namespace. */
export * from "../../jsTyping/_namespaces/ts.server";
export * from "../../server/_namespaces/ts.server";
export * from "../../typingsInstallerCore/_namespaces/ts.server";
export * from "../../harness/_namespaces/ts.server";
export * from "../loggedIO";

View File

@ -1,10 +0,0 @@
/* Generated file to emulate the ts namespace. */
export * from "../../compiler/_namespaces/ts";
export * from "../../services/_namespaces/ts";
export * from "../../jsTyping/_namespaces/ts";
export * from "../../server/_namespaces/ts";
export * from "../../typingsInstallerCore/_namespaces/ts";
export * from "../../harness/_namespaces/ts";
import * as server from "./ts.server";
export { server };

View File

@ -1,450 +0,0 @@
import * as Harness from "./_namespaces/Harness";
import * as ts from "./_namespaces/ts";
interface FileInformation {
contents?: string;
contentsPath?: string;
codepage: number;
bom?: string;
}
interface FindFileResult {
}
interface IoLogFile {
path: string;
codepage: number;
result?: FileInformation;
}
export interface IoLog {
timestamp: string;
arguments: string[];
executingPath: string;
currentDirectory: string;
useCustomLibraryFile?: boolean;
filesRead: IoLogFile[];
filesWritten: {
path: string;
contents?: string;
contentsPath?: string;
bom: boolean;
}[];
filesDeleted: string[];
filesAppended: {
path: string;
contents?: string;
contentsPath?: string;
}[];
fileExists: {
path: string;
result?: boolean;
}[];
filesFound: {
path: string;
pattern: string;
result?: FindFileResult;
}[];
dirs: {
path: string;
re: string;
re_m: boolean;
re_g: boolean;
re_i: boolean;
opts: { recursive?: boolean; };
result?: string[];
}[];
dirExists: {
path: string;
result?: boolean;
}[];
dirsCreated: string[];
pathsResolved: {
path: string;
result?: string;
}[];
directoriesRead: {
path: string,
extensions: readonly string[] | undefined,
exclude: readonly string[] | undefined,
include: readonly string[] | undefined,
depth: number | undefined,
result: readonly string[],
}[];
useCaseSensitiveFileNames?: boolean;
}
interface PlaybackControl {
startReplayFromFile(logFileName: string): void;
startReplayFromString(logContents: string): void;
startReplayFromData(log: IoLog): void;
endReplay(): void;
startRecord(logFileName: string): void;
endRecord(): void;
}
let recordLog: IoLog | undefined;
let replayLog: IoLog | undefined;
let replayFilesRead: Map<string, IoLogFile> | undefined;
let recordLogFileNameBase = "";
interface Memoized<T> {
(s: string): T;
reset(): void;
}
function memoize<T>(func: (s: string) => T): Memoized<T> {
let lookup: { [s: string]: T } = {};
const run: Memoized<T> = ((s: string) => {
if (ts.hasProperty(lookup, s)) return lookup[s];
return lookup[s] = func(s);
}) as Memoized<T>;
run.reset = () => {
lookup = undefined!; // TODO: GH#18217
};
return run;
}
export interface PlaybackIO extends Harness.IO, PlaybackControl { }
export interface PlaybackSystem extends ts.System, PlaybackControl { }
function createEmptyLog(): IoLog {
return {
timestamp: (new Date()).toString(),
arguments: [],
currentDirectory: "",
filesRead: [],
directoriesRead: [],
filesWritten: [],
filesDeleted: [],
filesAppended: [],
fileExists: [],
filesFound: [],
dirs: [],
dirExists: [],
dirsCreated: [],
pathsResolved: [],
executingPath: ""
};
}
export function newStyleLogIntoOldStyleLog(log: IoLog, host: ts.System | Harness.IO, baseName: string) {
for (const file of log.filesAppended) {
if (file.contentsPath) {
file.contents = host.readFile(ts.combinePaths(baseName, file.contentsPath));
delete file.contentsPath;
}
}
for (const file of log.filesWritten) {
if (file.contentsPath) {
file.contents = host.readFile(ts.combinePaths(baseName, file.contentsPath));
delete file.contentsPath;
}
}
for (const file of log.filesRead) {
const result = file.result!; // TODO: GH#18217
if (result.contentsPath) {
// `readFile` strips away a BOM (and actually reinerprets the file contents according to the correct encoding)
// - but this has the unfortunate sideeffect of removing the BOM from any outputs based on the file, so we readd it here.
result.contents = (result.bom || "") + host.readFile(ts.combinePaths(baseName, result.contentsPath));
delete result.contentsPath;
}
}
return log;
}
const canonicalizeForHarness = ts.createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ false); // This is done so tests work on windows _and_ linux
function sanitizeTestFilePath(name: string) {
const path = ts.toPath(ts.normalizeSlashes(name.replace(/[\^<>:"|?*%]/g, "_")).replace(/\.\.\//g, "__dotdot/"), "", canonicalizeForHarness);
if (ts.startsWith(path, "/")) {
return path.substring(1);
}
return path;
}
export function oldStyleLogIntoNewStyleLog(log: IoLog, writeFile: typeof Harness.IO.writeFile, baseTestName: string) {
if (log.filesAppended) {
for (const file of log.filesAppended) {
if (file.contents !== undefined) {
file.contentsPath = ts.combinePaths("appended", sanitizeTestFilePath(file.path));
writeFile(ts.combinePaths(baseTestName, file.contentsPath), file.contents);
delete file.contents;
}
}
}
if (log.filesWritten) {
for (const file of log.filesWritten) {
if (file.contents !== undefined) {
file.contentsPath = ts.combinePaths("written", sanitizeTestFilePath(file.path));
writeFile(ts.combinePaths(baseTestName, file.contentsPath), file.contents);
delete file.contents;
}
}
}
if (log.filesRead) {
for (const file of log.filesRead) {
const result = file.result!; // TODO: GH#18217
const { contents } = result;
if (contents !== undefined) {
result.contentsPath = ts.combinePaths("read", sanitizeTestFilePath(file.path));
writeFile(ts.combinePaths(baseTestName, result.contentsPath), contents);
const len = contents.length;
if (len >= 2 && contents.charCodeAt(0) === 0xfeff) {
result.bom = "\ufeff";
}
if (len >= 2 && contents.charCodeAt(0) === 0xfffe) {
result.bom = "\ufffe";
}
if (len >= 3 && contents.charCodeAt(0) === 0xefbb && contents.charCodeAt(1) === 0xbf) {
result.bom = "\uefbb\xbf";
}
delete result.contents;
}
}
}
return log;
}
export function initWrapper(...[wrapper, underlying]: [PlaybackSystem, ts.System] | [PlaybackIO, Harness.IO]): void {
ts.forEach(Object.keys(underlying), prop => {
(wrapper as any)[prop] = (underlying as any)[prop];
});
wrapper.startReplayFromString = logString => {
wrapper.startReplayFromData(JSON.parse(logString));
};
wrapper.startReplayFromData = log => {
replayLog = log;
// Remove non-found files from the log (shouldn't really need them, but we still record them for diagnostic purposes)
replayLog.filesRead = replayLog.filesRead.filter(f => f.result!.contents !== undefined);
replayFilesRead = new Map();
for (const file of replayLog.filesRead) {
replayFilesRead.set(ts.normalizeSlashes(file.path).toLowerCase(), file);
}
};
wrapper.endReplay = () => {
replayLog = undefined;
replayFilesRead = undefined;
};
wrapper.startRecord = (fileNameBase) => {
recordLogFileNameBase = fileNameBase;
recordLog = createEmptyLog();
recordLog.useCaseSensitiveFileNames = typeof underlying.useCaseSensitiveFileNames === "function" ? underlying.useCaseSensitiveFileNames() : underlying.useCaseSensitiveFileNames;
if (typeof underlying.args !== "function") {
recordLog.arguments = underlying.args;
}
};
wrapper.startReplayFromFile = logFn => {
wrapper.startReplayFromString(underlying.readFile(logFn)!);
};
wrapper.endRecord = () => {
if (recordLog !== undefined) {
let i = 0;
const getBase = () => recordLogFileNameBase + i;
while (underlying.fileExists(ts.combinePaths(getBase(), "test.json"))) i++;
const newLog = oldStyleLogIntoNewStyleLog(recordLog, (path, str) => underlying.writeFile(path, str), getBase());
underlying.writeFile(ts.combinePaths(getBase(), "test.json"), JSON.stringify(newLog, null, 4)); // eslint-disable-line no-null/no-null
const syntheticTsconfig = generateTsconfig(newLog);
if (syntheticTsconfig) {
underlying.writeFile(ts.combinePaths(getBase(), "tsconfig.json"), JSON.stringify(syntheticTsconfig, null, 4)); // eslint-disable-line no-null/no-null
}
recordLog = undefined;
}
};
function generateTsconfig(newLog: IoLog): undefined | { compilerOptions: ts.CompilerOptions, files: string[] } {
if (newLog.filesRead.some(file => /tsconfig.+json$/.test(file.path))) {
return;
}
const files = [];
for (const file of newLog.filesRead) {
const result = file.result!;
if (result.contentsPath &&
Harness.isDefaultLibraryFile(result.contentsPath) &&
/\.[tj]s$/.test(result.contentsPath)) {
files.push(result.contentsPath);
}
}
return { compilerOptions: ts.parseCommandLine(newLog.arguments).options, files };
}
wrapper.fileExists = recordReplay(wrapper.fileExists, underlying)(
path => callAndRecord(underlying.fileExists(path), recordLog!.fileExists, { path }),
memoize(path => {
// If we read from the file, it must exist
if (findFileByPath(path, /*throwFileNotFoundError*/ false)) {
return true;
}
else {
return findResultByFields(replayLog!.fileExists, { path }, /*defaultValue*/ false)!;
}
})
);
wrapper.getExecutingFilePath = () => {
if (replayLog !== undefined) {
return replayLog.executingPath;
}
else if (recordLog !== undefined) {
return recordLog.executingPath = underlying.getExecutingFilePath();
}
else {
return underlying.getExecutingFilePath();
}
};
wrapper.getCurrentDirectory = () => {
if (replayLog !== undefined) {
return replayLog.currentDirectory || "";
}
else if (recordLog !== undefined) {
return recordLog.currentDirectory = underlying.getCurrentDirectory();
}
else {
return underlying.getCurrentDirectory();
}
};
wrapper.resolvePath = recordReplay(wrapper.resolvePath, underlying)(
path => callAndRecord(underlying.resolvePath(path), recordLog!.pathsResolved, { path }),
memoize(path => findResultByFields(replayLog!.pathsResolved, { path }, !ts.isRootedDiskPath(ts.normalizeSlashes(path)) && replayLog!.currentDirectory ? replayLog!.currentDirectory + "/" + path : ts.normalizeSlashes(path))));
wrapper.readFile = recordReplay(wrapper.readFile, underlying)(
(path: string) => {
const result = underlying.readFile(path);
const logEntry = { path, codepage: 0, result: { contents: result, codepage: 0 } };
recordLog!.filesRead.push(logEntry);
return result;
},
memoize(path => findFileByPath(path, /*throwFileNotFoundError*/ true)!.contents));
wrapper.readDirectory = recordReplay(wrapper.readDirectory, underlying)(
(path, extensions, exclude, include, depth) => {
const result = (underlying as ts.System).readDirectory(path, extensions, exclude, include, depth);
recordLog!.directoriesRead.push({ path, extensions, exclude, include, depth, result });
return result;
},
path => {
// Because extensions is an array of all allowed extension, we will want to merge each of the replayLog.directoriesRead into one
// if each of the directoriesRead has matched path with the given path (directory with same path but different extension will considered
// different entry).
// TODO (yuisu): We can certainly remove these once we recapture the RWC using new API
const normalizedPath = ts.normalizePath(path).toLowerCase();
return ts.flatMap(replayLog!.directoriesRead, directory => {
if (ts.normalizeSlashes(directory.path).toLowerCase() === normalizedPath) {
return directory.result;
}
});
});
wrapper.writeFile = recordReplay(wrapper.writeFile, underlying)(
(path: string, contents: string) => callAndRecord(underlying.writeFile(path, contents), recordLog!.filesWritten, { path, contents, bom: false }),
() => noOpReplay("writeFile"));
wrapper.exit = (exitCode) => {
if (recordLog !== undefined) {
wrapper.endRecord();
}
underlying.exit(exitCode);
};
wrapper.useCaseSensitiveFileNames = () => {
if (replayLog !== undefined) {
return !!replayLog.useCaseSensitiveFileNames;
}
return typeof underlying.useCaseSensitiveFileNames === "function" ? underlying.useCaseSensitiveFileNames() : underlying.useCaseSensitiveFileNames;
};
}
function recordReplay<T extends ts.AnyFunction>(original: T, underlying: any) {
function createWrapper(record: T, replay: T): T {
// eslint-disable-next-line local/only-arrow-functions
return (function () {
if (replayLog !== undefined) {
return replay.apply(undefined, arguments);
}
else if (recordLog !== undefined) {
return record.apply(undefined, arguments);
}
else {
return original.apply(underlying, arguments);
}
} as any);
}
return createWrapper;
}
function callAndRecord<T, U>(underlyingResult: T, logArray: U[], logEntry: U): T {
if (underlyingResult !== undefined) {
(logEntry as any).result = underlyingResult;
}
logArray.push(logEntry);
return underlyingResult;
}
function findResultByFields<T>(logArray: { result?: T }[], expectedFields: {}, defaultValue?: T): T | undefined {
const predicate = (entry: { result?: T }) => {
return Object.getOwnPropertyNames(expectedFields).every((name) => (entry as any)[name] === (expectedFields as any)[name]);
};
const results = logArray.filter(entry => predicate(entry));
if (results.length === 0) {
if (defaultValue !== undefined) {
return defaultValue;
}
else {
throw new Error("No matching result in log array for: " + JSON.stringify(expectedFields));
}
}
return results[0].result;
}
function findFileByPath(expectedPath: string, throwFileNotFoundError: boolean): FileInformation | undefined {
const normalizedName = ts.normalizePath(expectedPath).toLowerCase();
// Try to find the result through normal fileName
const result = replayFilesRead!.get(normalizedName);
if (result) {
return result.result;
}
// If we got here, we didn't find a match
if (throwFileNotFoundError) {
throw new Error("No matching result in log array for path: " + expectedPath);
}
else {
return undefined;
}
}
function noOpReplay(_name: string) {
// console.log("Swallowed write operation during replay: " + name);
}
export function wrapIO(underlying: Harness.IO): PlaybackIO {
const wrapper: PlaybackIO = {} as any;
initWrapper(wrapper, underlying);
wrapper.directoryName = notSupported;
wrapper.createDirectory = notSupported;
wrapper.directoryExists = notSupported;
wrapper.deleteFile = notSupported;
wrapper.listFiles = notSupported;
return wrapper;
function notSupported(): never {
throw new Error("NotSupported");
}
}
export function wrapSystem(underlying: ts.System): PlaybackSystem {
const wrapper: PlaybackSystem = {} as any;
initWrapper(wrapper, underlying);
return wrapper;
}
// empty modules for the module migration script

View File

@ -1,17 +0,0 @@
{
"extends": "../tsconfig-base",
"compilerOptions": {
"types": [
]
},
"references": [
{ "path": "../compiler" },
{ "path": "../services" },
{ "path": "../jsTyping" },
{ "path": "../server" },
{ "path": "../typingsInstallerCore" },
{ "path": "../harness" },
],
"include": ["**/*"]
}

View File

@ -1,7 +1,6 @@
/* Generated file to emulate the Harness namespace. */
export * from "../../harness/_namespaces/Harness";
export * from "../../loggedIO/_namespaces/Harness";
import * as Parallel from "./Harness.Parallel";
export { Parallel };

View File

@ -1,3 +0,0 @@
/* Generated file to emulate the Playback namespace. */
export * from "../../loggedIO/_namespaces/Playback";

View File

@ -1,3 +0,0 @@
/* Generated file to emulate the RWC namespace. */
export * from "../rwcRunner";

View File

@ -4,4 +4,3 @@ export * from "../../jsTyping/_namespaces/ts.server";
export * from "../../server/_namespaces/ts.server";
export * from "../../typingsInstallerCore/_namespaces/ts.server";
export * from "../../harness/_namespaces/ts.server";
export * from "../../loggedIO/_namespaces/ts.server";

View File

@ -8,6 +8,5 @@ export * from "../../server/_namespaces/ts";
export * from "../../typingsInstallerCore/_namespaces/ts";
export * from "../../deprecatedCompat/_namespaces/ts";
export * from "../../harness/_namespaces/ts";
export * from "../../loggedIO/_namespaces/ts";
import * as server from "./ts.server";
export { server };

View File

@ -13,7 +13,6 @@ import {
TestRunnerKind,
} from "./_namespaces/Harness";
import * as project from "./_namespaces/project";
import * as RWC from "./_namespaces/RWC";
import * as ts from "./_namespaces/ts";
import * as vpath from "./_namespaces/vpath";
@ -72,8 +71,6 @@ export function createRunner(kind: TestRunnerKind): RunnerBase {
return new FourSlashRunner(FourSlash.FourSlashTestType.Server);
case "project":
return new project.ProjectRunner();
case "rwc":
return new RWC.RWCRunner();
}
return ts.Debug.fail(`Unknown runner kind ${kind}`);
}
@ -205,8 +202,6 @@ function handleTestConfig() {
case "fourslash-generated":
runners.push(new GeneratedFourslashRunner(FourSlash.FourSlashTestType.Native));
break;
case "rwc":
runners.push(new RWC.RWCRunner());
break;
}
}

View File

@ -1,239 +0,0 @@
import * as compiler from "./_namespaces/compiler";
import * as Harness from "./_namespaces/Harness";
import * as Playback from "./_namespaces/Playback";
import * as ts from "./_namespaces/ts";
import * as vpath from "./_namespaces/vpath";
// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`.
function runWithIOLog(ioLog: Playback.IoLog, fn: (oldIO: Harness.IO) => void) {
const oldIO = Harness.IO;
const wrappedIO = Playback.wrapIO(oldIO);
wrappedIO.startReplayFromData(ioLog);
Harness.setHarnessIO(wrappedIO);
try {
fn(oldIO);
}
finally {
wrappedIO.endReplay();
Harness.setHarnessIO(oldIO);
}
}
export function runRWCTest(jsonPath: string) {
describe("Testing a rwc project: " + jsonPath, () => {
let inputFiles: Harness.Compiler.TestFile[] = [];
let otherFiles: Harness.Compiler.TestFile[] = [];
let tsconfigFiles: Harness.Compiler.TestFile[] = [];
let compilerResult: compiler.CompilationResult;
let compilerOptions: ts.CompilerOptions;
const baselineOpts: Harness.Baseline.BaselineOptions = {
Subfolder: "rwc",
Baselinefolder: "internal/baselines"
};
const baseName = ts.getBaseFileName(jsonPath);
let currentDirectory: string;
let useCustomLibraryFile: boolean;
let caseSensitive: boolean;
after(() => {
// Mocha holds onto the closure environment of the describe callback even after the test is done.
// Therefore we have to clean out large objects after the test is done.
inputFiles = [];
otherFiles = [];
tsconfigFiles = [];
compilerResult = undefined!;
compilerOptions = undefined!;
currentDirectory = undefined!;
// useCustomLibraryFile is a flag specified in the json object to indicate whether to use built/local/lib.d.ts
// or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file
// otherwise use the lib.d.ts from built/local
useCustomLibraryFile = false;
caseSensitive = false;
});
it("can compile", function (this: Mocha.Context) {
this.timeout(800_000); // Allow long timeouts for RWC compilations
let opts!: ts.ParsedCommandLine;
const ioLog: Playback.IoLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)!), Harness.IO, `internal/cases/rwc/${baseName}`);
currentDirectory = ioLog.currentDirectory;
useCustomLibraryFile = !!ioLog.useCustomLibraryFile;
runWithIOLog(ioLog, () => {
opts = ts.parseCommandLine(ioLog.arguments, fileName => Harness.IO.readFile(fileName));
assert.equal(opts.errors.length, 0);
// To provide test coverage of output javascript file,
// we will set noEmitOnError flag to be false.
opts.options.noEmitOnError = false;
});
let fileNames = opts.fileNames;
runWithIOLog(ioLog, () => {
const tsconfigFile = ts.forEach(ioLog.filesRead, f => vpath.isTsConfigFile(f.path) ? f : undefined);
if (tsconfigFile) {
const tsconfigFileContents = getHarnessCompilerInputUnit(tsconfigFile.path);
tsconfigFiles.push({ unitName: tsconfigFile.path, content: tsconfigFileContents.content });
const parsedTsconfigFileContents = ts.parseJsonText(tsconfigFile.path, tsconfigFileContents.content);
const configParseHost: ts.ParseConfigHost = {
useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(),
fileExists: Harness.IO.fileExists,
readDirectory: Harness.IO.readDirectory,
readFile: Harness.IO.readFile
};
const configParseResult = ts.parseJsonSourceFileConfigFileContent(parsedTsconfigFileContents, configParseHost, ts.getDirectoryPath(tsconfigFile.path));
fileNames = configParseResult.fileNames;
opts.options = ts.extend(opts.options, configParseResult.options);
ts.setConfigFileInOptions(opts.options, configParseResult.options.configFile);
}
// Deduplicate files so they are only printed once in baselines (they are deduplicated within the compiler already)
const uniqueNames = new Map<string, true>();
for (const fileName of fileNames) {
// Must maintain order, build result list while checking map
const normalized = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!);
if (!uniqueNames.has(normalized)) {
uniqueNames.set(normalized, true);
// Load the file
inputFiles.push(getHarnessCompilerInputUnit(fileName));
}
}
// Add files to compilation
for (const fileRead of ioLog.filesRead) {
const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileRead.path)!);
if (!uniqueNames.has(unitName) && !Harness.isDefaultLibraryFile(fileRead.path)) {
uniqueNames.set(unitName, true);
otherFiles.push(getHarnessCompilerInputUnit(fileRead.path));
}
else if (!opts.options.noLib && Harness.isDefaultLibraryFile(fileRead.path) && !uniqueNames.has(unitName) && useCustomLibraryFile) {
// If useCustomLibraryFile is true, we will use lib.d.ts from json object
// otherwise use the lib.d.ts from built/local
// Majority of RWC code will be using built/local/lib.d.ts instead of
// lib.d.ts inside json file. However, some RWC cases will still use
// their own version of lib.d.ts because they have customized lib.d.ts
uniqueNames.set(unitName, true);
inputFiles.push(getHarnessCompilerInputUnit(fileRead.path));
}
}
});
if (useCustomLibraryFile) {
// do not use lib since we already read it in above
opts.options.lib = undefined;
opts.options.noLib = true;
}
caseSensitive = ioLog.useCaseSensitiveFileNames || false;
// Emit the results
compilerResult = Harness.Compiler.compileFiles(
inputFiles,
otherFiles,
{ useCaseSensitiveFileNames: "" + caseSensitive },
opts.options,
// Since each RWC json file specifies its current directory in its json file, we need
// to pass this information in explicitly instead of acquiring it from the process.
currentDirectory);
compilerOptions = compilerResult.options;
function getHarnessCompilerInputUnit(fileName: string): Harness.Compiler.TestFile {
const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!);
let content: string;
try {
content = Harness.IO.readFile(unitName)!;
}
catch (e) {
content = Harness.IO.readFile(fileName)!;
}
return { unitName, content };
}
});
it("has the expected emitted code", function (this: Mocha.Context) {
this.timeout(100_000); // Allow longer timeouts for RWC js verification
Harness.Baseline.runMultifileBaseline(baseName, "", () => {
return Harness.Compiler.iterateOutputs(compilerResult.js.values());
}, baselineOpts, [".js", ".jsx"]);
});
it("has the expected declaration file content", () => {
Harness.Baseline.runMultifileBaseline(baseName, "", () => {
if (!compilerResult.dts.size) {
return null; // eslint-disable-line no-null/no-null
}
return Harness.Compiler.iterateOutputs(compilerResult.dts.values());
}, baselineOpts, [".d.ts"]);
});
it("has the expected source maps", () => {
Harness.Baseline.runMultifileBaseline(baseName, "", () => {
if (!compilerResult.maps.size) {
return null; // eslint-disable-line no-null/no-null
}
return Harness.Compiler.iterateOutputs(compilerResult.maps.values());
}, baselineOpts, [".map"]);
});
it("has the expected errors", () => {
Harness.Baseline.runMultifileBaseline(baseName, ".errors.txt", () => {
if (compilerResult.diagnostics.length === 0) {
return null; // eslint-disable-line no-null/no-null
}
// Do not include the library in the baselines to avoid noise
const baselineFiles = tsconfigFiles.concat(inputFiles, otherFiles).filter(f => !Harness.isDefaultLibraryFile(f.unitName));
const errors = compilerResult.diagnostics.filter(e => !e.file || !Harness.isDefaultLibraryFile(e.file.fileName));
return Harness.Compiler.iterateErrorBaseline(baselineFiles, errors, { caseSensitive, currentDirectory });
}, baselineOpts);
});
// Ideally, a generated declaration file will have no errors. But we allow generated
// declaration file errors as part of the baseline.
it("has the expected errors in generated declaration files", () => {
if (compilerOptions.declaration && !compilerResult.diagnostics.length) {
Harness.Baseline.runMultifileBaseline(baseName, ".dts.errors.txt", () => {
if (compilerResult.diagnostics.length === 0) {
return null; // eslint-disable-line no-null/no-null
}
const declContext = Harness.Compiler.prepareDeclarationCompilationContext(
inputFiles, otherFiles, compilerResult, /*harnessSettings*/ undefined!, compilerOptions, currentDirectory // TODO: GH#18217
);
// Reset compilerResult before calling into `compileDeclarationFiles` so the memory from the original compilation can be freed
const links = compilerResult.symlinks;
compilerResult = undefined!;
const declFileCompilationResult = Harness.Compiler.compileDeclarationFiles(declContext, links)!;
return Harness.Compiler.iterateErrorBaseline(tsconfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.diagnostics, { caseSensitive, currentDirectory });
}, baselineOpts);
}
});
});
}
export class RWCRunner extends Harness.RunnerBase {
public enumerateTestFiles() {
// see also: `enumerateTestFiles` in tests/webTestServer.ts
return Harness.IO.getDirectories("internal/cases/rwc/");
}
public kind(): Harness.TestRunnerKind {
return "rwc";
}
/** Setup the runner's tests so that they are ready to be executed by the harness
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
*/
public initializeTests(): void {
// Read in and evaluate the test list
for (const test of this.tests && this.tests.length ? this.tests : this.getTestFiles()) {
this.runTest(typeof test === "string" ? test : test.file);
}
}
private runTest(jsonFileName: string) {
runRWCTest(jsonFileName);
}
}

View File

@ -14,7 +14,6 @@
{ "path": "../typingsInstallerCore" },
{ "path": "../deprecatedCompat" },
{ "path": "../harness" },
{ "path": "../loggedIO" }
],
"include": ["**/*"]
}

View File

@ -8,7 +8,6 @@
{ "path": "./executeCommandLine" },
{ "path": "./harness" },
{ "path": "./jsTyping" },
{ "path": "./loggedIO" },
{ "path": "./server" },
{ "path": "./services" },
{ "path": "./testRunner" },