diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 63c58b8d6f9f8..e64d52a57c05b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2,31 +2,8 @@ /* @internal */ namespace ts { - /** - * Returns the native Map implementation if it is available and compatible (i.e. supports iteration). - */ - export function tryGetNativeMap(): MapConstructor | undefined { - // Internet Explorer's Map doesn't support iteration, so don't use it. - // Natives - // NOTE: TS doesn't strictly allow in-line declares, but if we suppress the error, the declaration - // is still used for typechecking _and_ correctly elided, which is out goal, as this prevents us from - // needing to pollute an outer scope with a declaration of `Map` just to satisfy the checks in this function - //@ts-ignore - declare const Map: (new () => Map) | undefined; - // eslint-disable-next-line no-in-operator - return typeof Map !== "undefined" && "entries" in Map.prototype ? Map : undefined; - } - export const emptyArray: never[] = [] as never[]; - export const Map: MapConstructor = tryGetNativeMap() || (() => { - // NOTE: createMapShim will be defined for typescriptServices.js but not for tsc.js, so we must test for it. - if (typeof createMapShim === "function") { - return createMapShim(); - } - throw new Error("TypeScript requires an environment that provides a compatible native Map implementation."); - })(); - /** Create a new map. */ export function createMap(): Map { return new Map(); diff --git a/src/compiler/corePublic.ts b/src/compiler/corePublic.ts index 7ea17fef24af2..c1c1c97b950aa 100644 --- a/src/compiler/corePublic.ts +++ b/src/compiler/corePublic.ts @@ -46,6 +46,31 @@ namespace ts { new (): Map; } + /** + * Returns the native Map implementation if it is available and compatible (i.e. supports iteration). + */ + /* @internal */ + export function tryGetNativeMap(): MapConstructor | undefined { + // Internet Explorer's Map doesn't support iteration, so don't use it. + // Natives + // NOTE: TS doesn't strictly allow in-line declares, but if we suppress the error, the declaration + // is still used for typechecking _and_ correctly elided, which is out goal, as this prevents us from + // needing to pollute an outer scope with a declaration of `Map` just to satisfy the checks in this function + //@ts-ignore + declare const Map: (new () => Map) | undefined; + // eslint-disable-next-line no-in-operator + return typeof Map !== "undefined" && "entries" in Map.prototype ? Map : undefined; + } + + /* @internal */ + export const Map: MapConstructor = tryGetNativeMap() || (() => { + // NOTE: createMapShim will be defined for typescriptServices.js but not for tsc.js, so we must test for it. + if (typeof createMapShim === "function") { + return createMapShim(); + } + throw new Error("TypeScript requires an environment that provides a compatible native Map implementation."); + })(); + /** ES6 Iterator type. */ export interface Iterator { next(): { value: T, done?: false } | { value: never, done: true }; diff --git a/src/harness/collections.ts b/src/harness/collectionsImpl.ts similarity index 100% rename from src/harness/collections.ts rename to src/harness/collectionsImpl.ts diff --git a/src/harness/compiler.ts b/src/harness/compilerImpl.ts similarity index 100% rename from src/harness/compiler.ts rename to src/harness/compilerImpl.ts diff --git a/src/harness/documents.ts b/src/harness/documentsUtil.ts similarity index 100% rename from src/harness/documents.ts rename to src/harness/documentsUtil.ts diff --git a/src/harness/evaluator.ts b/src/harness/evaluatorImpl.ts similarity index 100% rename from src/harness/evaluator.ts rename to src/harness/evaluatorImpl.ts diff --git a/src/harness/fakes.ts b/src/harness/fakesHosts.ts similarity index 96% rename from src/harness/fakes.ts rename to src/harness/fakesHosts.ts index d14d8dd2a718f..832e87408282b 100644 --- a/src/harness/fakes.ts +++ b/src/harness/fakesHosts.ts @@ -39,7 +39,7 @@ namespace fakes { public readFile(path: string) { try { const content = this.vfs.readFileSync(path, "utf8"); - return content === undefined ? undefined : utils.removeByteOrderMark(content); + return content === undefined ? undefined : Utils.removeByteOrderMark(content); } catch { return undefined; @@ -48,7 +48,7 @@ namespace fakes { public writeFile(path: string, data: string, writeByteOrderMark?: boolean): void { this.vfs.mkdirpSync(vpath.dirname(path)); - this.vfs.writeFileSync(path, writeByteOrderMark ? utils.addUTF8ByteOrderMark(data) : data); + this.vfs.writeFileSync(path, writeByteOrderMark ? Utils.addUTF8ByteOrderMark(data) : data); } public deleteFile(path: string) { @@ -289,7 +289,7 @@ namespace fakes { } public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) { - if (writeByteOrderMark) content = utils.addUTF8ByteOrderMark(content); + if (writeByteOrderMark) content = Utils.addUTF8ByteOrderMark(content); this.sys.writeFile(fileName, content); const document = new documents.TextDocument(fileName, content); diff --git a/src/harness/fourslash.ts b/src/harness/fourslashImpl.ts similarity index 98% rename from src/harness/fourslash.ts rename to src/harness/fourslashImpl.ts index 4895a553bb459..f413341dcc721 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslashImpl.ts @@ -1,6 +1,4 @@ namespace FourSlash { - ts.disableIncrementalParsing = false; - import ArrayOrSingle = FourSlashInterface.ArrayOrSingle; export const enum FourSlashTestType { diff --git a/src/harness/fourslashInterface.ts b/src/harness/fourslashInterfaceImpl.ts similarity index 100% rename from src/harness/fourslashInterface.ts rename to src/harness/fourslashInterfaceImpl.ts diff --git a/src/harness/harnessGlobals.ts b/src/harness/harnessGlobals.ts index 5dd74d6ece9cd..ace692ba536d8 100644 --- a/src/harness/harnessGlobals.ts +++ b/src/harness/harnessGlobals.ts @@ -2,8 +2,9 @@ /* eslint-disable no-var */ // this will work in the browser via browserify -var _chai: typeof chai = require("chai"); -var assert: typeof _chai.assert = _chai.assert; +declare var assert: typeof _chai.assert; +var _chai: typeof import("chai") = require("chai"); +globalThis.assert = _chai.assert; { // chai's builtin `assert.isFalse` is featureful but slow - we don't use those features, // so we'll just overwrite it as an alterative to migrating a bunch of code off of chai @@ -27,4 +28,7 @@ var assert: typeof _chai.assert = _chai.assert; } }; } -/* eslint-enable no-var */ \ No newline at end of file +/* eslint-enable no-var */ +// empty ts namespace so this file is included in the `ts.ts` namespace file generated by the module swapover +// This way, everything that ends up importing `ts` downstream also imports this file and picks up its augmentation +namespace ts {} \ No newline at end of file diff --git a/src/harness/harnessIO.ts b/src/harness/harnessIO.ts index 56bcc60122f9e..e81e76dbc9f88 100644 --- a/src/harness/harnessIO.ts +++ b/src/harness/harnessIO.ts @@ -29,6 +29,9 @@ namespace Harness { } export let IO: IO; + export function setHarnessIO(io: IO) { + IO = io; + } // harness always uses one kind of new line // But note that `parseTestData` in `fourslash.ts` uses "\n" @@ -185,6 +188,9 @@ namespace Harness { export let userSpecifiedRoot = ""; export let lightMode = false; /* eslint-enable prefer-const */ + export function setLightMode(flag: boolean) { + lightMode = flag; + } /** Functionality for compiling TypeScript code */ export namespace Compiler { @@ -467,7 +473,7 @@ namespace Harness { else if (vpath.isTypeScript(file.unitName) || (vpath.isJavaScript(file.unitName) && options.allowJs)) { const declFile = findResultCodeFile(file.unitName); if (declFile && !findUnit(declFile.file, declInputFiles) && !findUnit(declFile.file, declOtherFiles)) { - dtsFiles.push({ unitName: declFile.file, content: utils.removeByteOrderMark(declFile.text) }); + dtsFiles.push({ unitName: declFile.file, content: Utils.removeByteOrderMark(declFile.text) }); } } } @@ -557,7 +563,7 @@ namespace Harness { function outputErrorText(error: ts.Diagnostic) { const message = ts.flattenDiagnosticMessageText(error.messageText, IO.newLine()); - const errLines = utils.removeTestPathPrefixes(message) + const errLines = Utils.removeTestPathPrefixes(message) .split("\n") .map(s => s.length > 0 && s.charAt(s.length - 1) === "\r" ? s.substr(0, s.length - 1) : s) .filter(s => s.length > 0) @@ -580,7 +586,7 @@ namespace Harness { } } - yield [diagnosticSummaryMarker, utils.removeTestPathPrefixes(minimalDiagnosticsToString(diagnostics, options && options.pretty)) + IO.newLine() + IO.newLine(), diagnostics.length]; + yield [diagnosticSummaryMarker, Utils.removeTestPathPrefixes(minimalDiagnosticsToString(diagnostics, options && options.pretty)) + IO.newLine() + IO.newLine(), diagnostics.length]; // Report global errors const globalErrors = diagnostics.filter(err => !err.file); @@ -595,7 +601,7 @@ namespace Harness { // Filter down to the errors in the file const fileErrors = diagnostics.filter((e): e is ts.DiagnosticWithLocation => { const errFn = e.file; - return !!errFn && ts.comparePaths(utils.removeTestPathPrefixes(errFn.fileName), utils.removeTestPathPrefixes(inputFile.unitName), options && options.currentDirectory || "", !(options && options.caseSensitive)) === ts.Comparison.EqualTo; + return !!errFn && ts.comparePaths(Utils.removeTestPathPrefixes(errFn.fileName), Utils.removeTestPathPrefixes(inputFile.unitName), options && options.currentDirectory || "", !(options && options.caseSensitive)) === ts.Comparison.EqualTo; }); @@ -812,7 +818,7 @@ namespace Harness { } typeLines += "\r\n"; } - yield [checkDuplicatedFileName(unitName, dupeCase), utils.removeTestPathPrefixes(typeLines)]; + yield [checkDuplicatedFileName(unitName, dupeCase), Utils.removeTestPathPrefixes(typeLines)]; } } } @@ -901,8 +907,8 @@ namespace Harness { } function fileOutput(file: documents.TextDocument, harnessSettings: TestCaseParser.CompilerSettings): string { - const fileName = harnessSettings.fullEmitPaths ? utils.removeTestPathPrefixes(file.file) : ts.getBaseFileName(file.file); - return "//// [" + fileName + "]\r\n" + utils.removeTestPathPrefixes(file.text); + const fileName = harnessSettings.fullEmitPaths ? Utils.removeTestPathPrefixes(file.file) : ts.getBaseFileName(file.file); + return "//// [" + fileName + "]\r\n" + Utils.removeTestPathPrefixes(file.text); } export function collateOutputs(outputFiles: readonly documents.TextDocument[]): string { @@ -928,7 +934,7 @@ namespace Harness { const dupeCase = ts.createMap(); // Yield them for (const outputFile of files) { - yield [checkDuplicatedFileName(outputFile.file, dupeCase), "/*====== " + outputFile.file + " ======*/\r\n" + utils.removeByteOrderMark(outputFile.text)]; + yield [checkDuplicatedFileName(outputFile.file, dupeCase), "/*====== " + outputFile.file + " ======*/\r\n" + Utils.removeByteOrderMark(outputFile.text)]; } function cleanName(fn: string) { diff --git a/src/harness/loggedIO.ts b/src/harness/loggedIO.ts index 5fad3405e0ee9..240af2e879d38 100644 --- a/src/harness/loggedIO.ts +++ b/src/harness/loggedIO.ts @@ -1,86 +1,86 @@ -interface FileInformation { - contents?: string; - contentsPath?: string; - codepage: number; - bom?: string; -} - -interface FindFileResult { -} - -interface IoLogFile { - path: string; - codepage: number; - result?: FileInformation; -} - -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; +namespace Playback { + interface FileInformation { 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: { + codepage: number; + bom?: string; + } + + interface FindFileResult { + } + + interface IoLogFile { 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; -} + codepage: number; + result?: FileInformation; + } -interface PlaybackControl { - startReplayFromFile(logFileName: string): void; - startReplayFromString(logContents: string): void; - startReplayFromData(log: IoLog): void; - endReplay(): void; - startRecord(logFileName: string): void; - endRecord(): void; -} + 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; + } -namespace Playback { let recordLog: IoLog | undefined; let replayLog: IoLog | undefined; let replayFilesRead: ts.Map | undefined; diff --git a/src/harness/runnerbase.ts b/src/harness/runnerbase.ts index c3380c9883915..1af3a903ad5a8 100644 --- a/src/harness/runnerbase.ts +++ b/src/harness/runnerbase.ts @@ -1,53 +1,63 @@ -type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" | "test262" | "user" | "dt" | "docker"; -type CompilerTestKind = "conformance" | "compiler"; -type FourslashTestKind = "fourslash" | "fourslash-shims" | "fourslash-shims-pp" | "fourslash-server"; - -/* eslint-disable prefer-const */ -let shards = 1; -let shardId = 1; -/* eslint-enable prefer-const */ - -abstract class RunnerBase { - // contains the tests to run - public tests: (string | Harness.FileBasedTest)[] = []; - - /** Add a source file to the runner's list of tests that need to be initialized with initializeTests */ - public addTest(fileName: string) { - this.tests.push(fileName); +namespace Harness { + export type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" | "test262" | "user" | "dt" | "docker"; + export type CompilerTestKind = "conformance" | "compiler"; + export type FourslashTestKind = "fourslash" | "fourslash-shims" | "fourslash-shims-pp" | "fourslash-server"; + + /* eslint-disable prefer-const */ + export let shards = 1; + export let shardId = 1; + /* eslint-enable prefer-const */ + + // The following have setters as while they're read here in the harness, they're only set in the runner + export function setShards(count: number) { + shards = count; } - - public enumerateFiles(folder: string, regex?: RegExp, options?: { recursive: boolean }): string[] { - return ts.map(Harness.IO.listFiles(Harness.userSpecifiedRoot + folder, regex, { recursive: (options ? options.recursive : false) }), ts.normalizeSlashes); + export function setShardId(id: number) { + shardId = id; } - abstract kind(): TestRunnerKind; + export abstract class RunnerBase { + // contains the tests to run + public tests: (string | FileBasedTest)[] = []; - abstract enumerateTestFiles(): (string | Harness.FileBasedTest)[]; + /** Add a source file to the runner's list of tests that need to be initialized with initializeTests */ + public addTest(fileName: string) { + this.tests.push(fileName); + } - getTestFiles(): ReturnType { - const all = this.enumerateTestFiles(); - if (shards === 1) { - return all as ReturnType; + public enumerateFiles(folder: string, regex?: RegExp, options?: { recursive: boolean }): string[] { + return ts.map(IO.listFiles(userSpecifiedRoot + folder, regex, { recursive: (options ? options.recursive : false) }), ts.normalizeSlashes); } - return all.filter((_val, idx) => idx % shards === (shardId - 1)) as ReturnType; - } - /** The working directory where tests are found. Needed for batch testing where the input path will differ from the output path inside baselines */ - public workingDirectory = ""; + abstract kind(): TestRunnerKind; + + abstract enumerateTestFiles(): (string | FileBasedTest)[]; - /** 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 abstract initializeTests(): void; + getTestFiles(): ReturnType { + const all = this.enumerateTestFiles(); + if (shards === 1) { + return all as ReturnType; + } + return all.filter((_val, idx) => idx % shards === (shardId - 1)) as ReturnType; + } + + /** The working directory where tests are found. Needed for batch testing where the input path will differ from the output path inside baselines */ + public workingDirectory = ""; - /** Replaces instances of full paths with fileNames only */ - static removeFullPaths(path: string) { - // If its a full path (starts with "C:" or "/") replace with just the filename - let fixedPath = /^(\w:|\/)/.test(path) ? ts.getBaseFileName(path) : path; + /** 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 abstract initializeTests(): void; - // when running in the browser the 'full path' is the host name, shows up in error baselines - const localHost = /http:\/localhost:\d+/g; - fixedPath = fixedPath.replace(localHost, ""); - return fixedPath; + /** Replaces instances of full paths with fileNames only */ + static removeFullPaths(path: string) { + // If its a full path (starts with "C:" or "/") replace with just the filename + let fixedPath = /^(\w:|\/)/.test(path) ? ts.getBaseFileName(path) : path; + + // when running in the browser the 'full path' is the host name, shows up in error baselines + const localHost = /http:\/localhost:\d+/g; + fixedPath = fixedPath.replace(localHost, ""); + return fixedPath; + } } -} +} \ No newline at end of file diff --git a/src/harness/sourceMapRecorder.ts b/src/harness/sourceMapRecorder.ts index e0d6765bcf716..e1df9b4b76969 100644 --- a/src/harness/sourceMapRecorder.ts +++ b/src/harness/sourceMapRecorder.ts @@ -157,7 +157,7 @@ namespace Harness.SourceMapRecorder { const startPos = lineMap[line]; const endPos = lineMap[line + 1]; const text = code.substring(startPos, endPos); - return line === 0 ? utils.removeByteOrderMark(text) : text; + return line === 0 ? Utils.removeByteOrderMark(text) : text; } function writeJsFileLines(endJsLine: number) { diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 309e8b0118b10..70ccbb36b92c4 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -19,14 +19,14 @@ ], "files": [ - "collections.ts", - "utils.ts", - "documents.ts", - "vpath.ts", - "vfs.ts", - "compiler.ts", - "evaluator.ts", - "fakes.ts", + "collectionsImpl.ts", + "util.ts", + "documentsUtil.ts", + "vpathUtil.ts", + "vfsUtil.ts", + "compilerImpl.ts", + "evaluatorImpl.ts", + "fakesHosts.ts", "client.ts", "runnerbase.ts", @@ -36,8 +36,8 @@ "harnessIO.ts", "harnessLanguageService.ts", "virtualFileSystemWithWatch.ts", - "fourslash.ts", - "fourslashInterface.ts", + "fourslashImpl.ts", + "fourslashInterfaceImpl.ts", "typeWriter.ts", "loggedIO.ts" ] diff --git a/src/harness/utils.ts b/src/harness/util.ts similarity index 97% rename from src/harness/utils.ts rename to src/harness/util.ts index 5938db7d62c58..6e0ab3cfb7df3 100644 --- a/src/harness/utils.ts +++ b/src/harness/util.ts @@ -1,7 +1,7 @@ /** * Common utilities */ -namespace utils { +namespace Utils { const testPathPrefixRegExp = /(?:(file:\/{3})|\/)\.(ts|lib|src)\//g; export function removeTestPathPrefixes(text: string, retainTrailingDirectorySeparator?: boolean): string { return text !== undefined ? text.replace(testPathPrefixRegExp, (_, scheme) => scheme || (retainTrailingDirectorySeparator ? "/" : "")) : undefined!; // TODO: GH#18217 diff --git a/src/harness/vfs.ts b/src/harness/vfsUtil.ts similarity index 100% rename from src/harness/vfs.ts rename to src/harness/vfsUtil.ts diff --git a/src/harness/vpath.ts b/src/harness/vpathUtil.ts similarity index 100% rename from src/harness/vpath.ts rename to src/harness/vpathUtil.ts diff --git a/src/server/types.ts b/src/server/types.ts index ce68a33d3b790..222dd0fdf2e46 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -5,7 +5,7 @@ declare namespace ts.server { data: any; } - type RequireResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message?: string } }; + export type RequireResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message?: string } }; export interface ServerHost extends System { watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; diff --git a/src/services/services.ts b/src/services/services.ts index 5f020969274b6..72f1eed4ef88f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1025,59 +1025,54 @@ namespace ts { return sourceFile; } - export let disableIncrementalParsing = false; // eslint-disable-line prefer-const - export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile { // If we were given a text change range, and our version or open-ness changed, then // incrementally parse this file. if (textChangeRange) { if (version !== sourceFile.version) { - // Once incremental parsing is ready, then just call into this function. - if (!disableIncrementalParsing) { - let newText: string; - - // grab the fragment from the beginning of the original text to the beginning of the span - const prefix = textChangeRange.span.start !== 0 - ? sourceFile.text.substr(0, textChangeRange.span.start) - : ""; - - // grab the fragment from the end of the span till the end of the original text - const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length - ? sourceFile.text.substr(textSpanEnd(textChangeRange.span)) - : ""; - - if (textChangeRange.newLength === 0) { - // edit was a deletion - just combine prefix and suffix - newText = prefix && suffix ? prefix + suffix : prefix || suffix; - } - else { - // it was actual edit, fetch the fragment of new text that correspond to new span - const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength); - // combine prefix, changed text and suffix - newText = prefix && suffix - ? prefix + changedText + suffix - : prefix - ? (prefix + changedText) - : (changedText + suffix); - } + let newText: string; - const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); - setSourceFileFields(newSourceFile, scriptSnapshot, version); - // after incremental parsing nameTable might not be up-to-date - // drop it so it can be lazily recreated later - newSourceFile.nameTable = undefined; + // grab the fragment from the beginning of the original text to the beginning of the span + const prefix = textChangeRange.span.start !== 0 + ? sourceFile.text.substr(0, textChangeRange.span.start) + : ""; - // dispose all resources held by old script snapshot - if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) { - if (sourceFile.scriptSnapshot.dispose) { - sourceFile.scriptSnapshot.dispose(); - } + // grab the fragment from the end of the span till the end of the original text + const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length + ? sourceFile.text.substr(textSpanEnd(textChangeRange.span)) + : ""; - sourceFile.scriptSnapshot = undefined; + if (textChangeRange.newLength === 0) { + // edit was a deletion - just combine prefix and suffix + newText = prefix && suffix ? prefix + suffix : prefix || suffix; + } + else { + // it was actual edit, fetch the fragment of new text that correspond to new span + const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength); + // combine prefix, changed text and suffix + newText = prefix && suffix + ? prefix + changedText + suffix + : prefix + ? (prefix + changedText) + : (changedText + suffix); + } + + const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); + setSourceFileFields(newSourceFile, scriptSnapshot, version); + // after incremental parsing nameTable might not be up-to-date + // drop it so it can be lazily recreated later + newSourceFile.nameTable = undefined; + + // dispose all resources held by old script snapshot + if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) { + if (sourceFile.scriptSnapshot.dispose) { + sourceFile.scriptSnapshot.dispose(); } - return newSourceFile; + sourceFile.scriptSnapshot = undefined; } + + return newSourceFile; } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index b1144daa19077..5034fe0dc752e 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -6,7 +6,7 @@ interface PromiseConstructor { all(values: (T | PromiseLike)[]): Promise; } /* @internal */ -declare let Promise: PromiseConstructor; +declare var Promise: PromiseConstructor; // eslint-disable-line no-var /* @internal */ namespace ts { diff --git a/src/shims/mapShim.ts b/src/shims/mapShim.ts index e0468ad43f26f..23391150941b7 100644 --- a/src/shims/mapShim.ts +++ b/src/shims/mapShim.ts @@ -1,13 +1,21 @@ /* @internal */ namespace ts { - // NOTE: Due to how the project-reference merging ends up working, `T` isn't considered referenced until `Map` merges with the definition - // in src/compiler/core.ts - // @ts-ignore - export interface Map { - // full type defined in ~/src/compiler/core.ts + interface IteratorShim { + next(): { value: T, done?: false } | { value: never, done: true }; } - - export function createMapShim(): new () => Map { + interface MapShim { + readonly size: number; + get(key: string): T | undefined; + set(key: string, value: T): this; + has(key: string): boolean; + delete(key: string): boolean; + clear(): void; + keys(): IteratorShim; + values(): IteratorShim; + entries(): IteratorShim<[string, T]>; + forEach(action: (value: T, key: string) => void): void; + } + export function createMapShim(): new () => MapShim { /** Create a MapLike with good performance. */ function createDictionaryObject(): Record { const map = Object.create(/*prototype*/ null); // eslint-disable-line no-null/no-null @@ -46,7 +54,7 @@ namespace ts { this.selector = selector; } - public next(): { value: U, done: false } | { value: never, done: true } { + public next(): { value: U, done?: false } | { value: never, done: true } { // Navigate to the next entry. while (this.currentEntry) { const skipNext = !!this.currentEntry.skipNext; @@ -66,7 +74,7 @@ namespace ts { } } - return class implements Map { + return class implements MapShim { private data = createDictionaryObject>(); public size = 0; @@ -183,15 +191,15 @@ namespace ts { this.lastEntry = firstEntry; } - keys(): Iterator { + keys(): IteratorShim { return new MapIterator(this.firstEntry, key => key); } - values(): Iterator { + values(): IteratorShim { return new MapIterator(this.firstEntry, (_key, value) => value); } - entries(): Iterator<[string, T]> { + entries(): IteratorShim<[string, T]> { return new MapIterator(this.firstEntry, (key, value) => [key, value] as [string, T]); } diff --git a/src/testRunner/compilerRef.ts b/src/testRunner/compilerRef.ts new file mode 100644 index 0000000000000..b76e4e72fbb88 --- /dev/null +++ b/src/testRunner/compilerRef.ts @@ -0,0 +1,2 @@ +// empty ref to compiler so it can be referenced by unittests +namespace compiler {} \ No newline at end of file diff --git a/src/testRunner/compilerRunner.ts b/src/testRunner/compilerRunner.ts index 2a99b34d5a041..ef0abb1c45699 100644 --- a/src/testRunner/compilerRunner.ts +++ b/src/testRunner/compilerRunner.ts @@ -1,333 +1,335 @@ -const enum CompilerTestType { - Conformance, - Regressions, - Test262 -} - -interface CompilerFileBasedTest extends Harness.FileBasedTest { - readonly content?: string; -} - -class CompilerBaselineRunner extends RunnerBase { - private basePath = "tests/cases"; - private testSuiteName: TestRunnerKind; - private emit: boolean; - - public options: string | undefined; - - constructor(public testType: CompilerTestType) { - super(); - this.emit = true; - if (testType === CompilerTestType.Conformance) { - this.testSuiteName = "conformance"; - } - else if (testType === CompilerTestType.Regressions) { - this.testSuiteName = "compiler"; - } - else if (testType === CompilerTestType.Test262) { - this.testSuiteName = "test262"; - } - else { - this.testSuiteName = "compiler"; // default to this for historical reasons - } - this.basePath += "/" + this.testSuiteName; +namespace Harness { + export const enum CompilerTestType { + Conformance, + Regressions, + Test262 } - public kind() { - return this.testSuiteName; + interface CompilerFileBasedTest extends FileBasedTest { + readonly content?: string; } - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true }).map(CompilerTest.getConfigurations); - } + export class CompilerBaselineRunner extends RunnerBase { + private basePath = "tests/cases"; + private testSuiteName: TestRunnerKind; + private emit: boolean; - public initializeTests() { - describe(this.testSuiteName + " tests", () => { - describe("Setup compiler for compiler baselines", () => { - this.parseOptions(); - }); + public options: string | undefined; - // this will set up a series of describe/it blocks to run between the setup and cleanup phases - const files = this.tests.length > 0 ? this.tests : Harness.IO.enumerateTestFiles(this); - files.forEach(test => { - const file = typeof test === "string" ? test : test.file; - this.checkTestCodeOutput(vpath.normalizeSeparators(file), typeof test === "string" ? CompilerTest.getConfigurations(test) : test); - }); - }); - } + constructor(public testType: CompilerTestType) { + super(); + this.emit = true; + if (testType === CompilerTestType.Conformance) { + this.testSuiteName = "conformance"; + } + else if (testType === CompilerTestType.Regressions) { + this.testSuiteName = "compiler"; + } + else if (testType === CompilerTestType.Test262) { + this.testSuiteName = "test262"; + } + else { + this.testSuiteName = "compiler"; // default to this for historical reasons + } + this.basePath += "/" + this.testSuiteName; + } - public checkTestCodeOutput(fileName: string, test?: CompilerFileBasedTest) { - if (test && ts.some(test.configurations)) { - test.configurations.forEach(configuration => { - describe(`${this.testSuiteName} tests for ${fileName}${configuration ? ` (${Harness.getFileBasedTestConfigurationDescription(configuration)})` : ``}`, () => { - this.runSuite(fileName, test, configuration); - }); - }); + public kind() { + return this.testSuiteName; + } + + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true }).map(CompilerTest.getConfigurations); } - else { - describe(`${this.testSuiteName} tests for ${fileName}`, () => { - this.runSuite(fileName, test); + + public initializeTests() { + describe(this.testSuiteName + " tests", () => { + describe("Setup compiler for compiler baselines", () => { + this.parseOptions(); + }); + + // this will set up a series of describe/it blocks to run between the setup and cleanup phases + const files = this.tests.length > 0 ? this.tests : IO.enumerateTestFiles(this); + files.forEach(test => { + const file = typeof test === "string" ? test : test.file; + this.checkTestCodeOutput(vpath.normalizeSeparators(file), typeof test === "string" ? CompilerTest.getConfigurations(test) : test); + }); }); } - } - private runSuite(fileName: string, test?: CompilerFileBasedTest, configuration?: Harness.FileBasedTestConfiguration) { - // Mocha holds onto the closure environment of the describe callback even after the test is done. - // Everything declared here should be cleared out in the "after" callback. - let compilerTest!: CompilerTest; - before(() => { - let payload; - if (test && test.content) { - const rootDir = test.file.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(test.file) + "/"; - payload = Harness.TestCaseParser.makeUnitsFromTest(test.content, test.file, rootDir); + public checkTestCodeOutput(fileName: string, test?: CompilerFileBasedTest) { + if (test && ts.some(test.configurations)) { + test.configurations.forEach(configuration => { + describe(`${this.testSuiteName} tests for ${fileName}${configuration ? ` (${getFileBasedTestConfigurationDescription(configuration)})` : ``}`, () => { + this.runSuite(fileName, test, configuration); + }); + }); } - compilerTest = new CompilerTest(fileName, payload, configuration); - }); - it(`Correct errors for ${fileName}`, () => { compilerTest.verifyDiagnostics(); }); - it(`Correct module resolution tracing for ${fileName}`, () => { compilerTest.verifyModuleResolution(); }); - it(`Correct sourcemap content for ${fileName}`, () => { compilerTest.verifySourceMapRecord(); }); - it(`Correct JS output for ${fileName}`, () => { if (this.emit) compilerTest.verifyJavaScriptOutput(); }); - it(`Correct Sourcemap output for ${fileName}`, () => { compilerTest.verifySourceMapOutput(); }); - it(`Correct type/symbol baselines for ${fileName}`, () => { compilerTest.verifyTypesAndSymbols(); }); - after(() => { compilerTest = undefined!; }); - } + else { + describe(`${this.testSuiteName} tests for ${fileName}`, () => { + this.runSuite(fileName, test); + }); + } + } + + private runSuite(fileName: string, test?: CompilerFileBasedTest, configuration?: FileBasedTestConfiguration) { + // Mocha holds onto the closure environment of the describe callback even after the test is done. + // Everything declared here should be cleared out in the "after" callback. + let compilerTest!: CompilerTest; + before(() => { + let payload; + if (test && test.content) { + const rootDir = test.file.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(test.file) + "/"; + payload = TestCaseParser.makeUnitsFromTest(test.content, test.file, rootDir); + } + compilerTest = new CompilerTest(fileName, payload, configuration); + }); + it(`Correct errors for ${fileName}`, () => { compilerTest.verifyDiagnostics(); }); + it(`Correct module resolution tracing for ${fileName}`, () => { compilerTest.verifyModuleResolution(); }); + it(`Correct sourcemap content for ${fileName}`, () => { compilerTest.verifySourceMapRecord(); }); + it(`Correct JS output for ${fileName}`, () => { if (this.emit) compilerTest.verifyJavaScriptOutput(); }); + it(`Correct Sourcemap output for ${fileName}`, () => { compilerTest.verifySourceMapOutput(); }); + it(`Correct type/symbol baselines for ${fileName}`, () => { compilerTest.verifyTypesAndSymbols(); }); + after(() => { compilerTest = undefined!; }); + } - private parseOptions() { - if (this.options && this.options.length > 0) { - this.emit = false; - - const opts = this.options.split(","); - for (const opt of opts) { - switch (opt) { - case "emit": - this.emit = true; - break; - default: - throw new Error("unsupported flag"); + private parseOptions() { + if (this.options && this.options.length > 0) { + this.emit = false; + + const opts = this.options.split(","); + for (const opt of opts) { + switch (opt) { + case "emit": + this.emit = true; + break; + default: + throw new Error("unsupported flag"); + } } } } } -} - -class CompilerTest { - private static varyBy: readonly string[] = [ - "module", - "target", - "jsx", - "removeComments", - "importHelpers", - "importHelpers", - "downlevelIteration", - "isolatedModules", - "strict", - "noImplicitAny", - "strictNullChecks", - "strictFunctionTypes", - "strictBindCallApply", - "strictPropertyInitialization", - "noImplicitThis", - "alwaysStrict", - "allowSyntheticDefaultImports", - "esModuleInterop", - "emitDecoratorMetadata", - "skipDefaultLibCheck", - "preserveConstEnums", - "skipLibCheck", - ]; - private fileName: string; - private justName: string; - private configuredName: string; - private lastUnit: Harness.TestCaseParser.TestUnitData; - private harnessSettings: Harness.TestCaseParser.CompilerSettings; - private hasNonDtsFiles: boolean; - private result: compiler.CompilationResult; - private options: ts.CompilerOptions; - private tsConfigFiles: Harness.Compiler.TestFile[]; - // equivalent to the files that will be passed on the command line - private toBeCompiled: Harness.Compiler.TestFile[]; - // equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files) - private otherFiles: Harness.Compiler.TestFile[]; - - constructor(fileName: string, testCaseContent?: Harness.TestCaseParser.TestCaseContent, configurationOverrides?: Harness.TestCaseParser.CompilerSettings) { - this.fileName = fileName; - this.justName = vpath.basename(fileName); - this.configuredName = this.justName; - if (configurationOverrides) { - let configuredName = ""; - const keys = Object - .keys(configurationOverrides) - .map(k => k.toLowerCase()) - .sort(); - for (const key of keys) { + + class CompilerTest { + private static varyBy: readonly string[] = [ + "module", + "target", + "jsx", + "removeComments", + "importHelpers", + "importHelpers", + "downlevelIteration", + "isolatedModules", + "strict", + "noImplicitAny", + "strictNullChecks", + "strictFunctionTypes", + "strictBindCallApply", + "strictPropertyInitialization", + "noImplicitThis", + "alwaysStrict", + "allowSyntheticDefaultImports", + "esModuleInterop", + "emitDecoratorMetadata", + "skipDefaultLibCheck", + "preserveConstEnums", + "skipLibCheck", + ]; + private fileName: string; + private justName: string; + private configuredName: string; + private lastUnit: TestCaseParser.TestUnitData; + private harnessSettings: TestCaseParser.CompilerSettings; + private hasNonDtsFiles: boolean; + private result: compiler.CompilationResult; + private options: ts.CompilerOptions; + private tsConfigFiles: Compiler.TestFile[]; + // equivalent to the files that will be passed on the command line + private toBeCompiled: Compiler.TestFile[]; + // equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files) + private otherFiles: Compiler.TestFile[]; + + constructor(fileName: string, testCaseContent?: TestCaseParser.TestCaseContent, configurationOverrides?: TestCaseParser.CompilerSettings) { + this.fileName = fileName; + this.justName = vpath.basename(fileName); + this.configuredName = this.justName; + if (configurationOverrides) { + let configuredName = ""; + const keys = Object + .keys(configurationOverrides) + .map(k => k.toLowerCase()) + .sort(); + for (const key of keys) { + if (configuredName) { + configuredName += ","; + } + configuredName += `${key}=${configurationOverrides[key].toLowerCase()}`; + } if (configuredName) { - configuredName += ","; + const extname = vpath.extname(this.justName); + const basename = vpath.basename(this.justName, extname, /*ignoreCase*/ true); + this.configuredName = `${basename}(${configuredName})${extname}`; } - configuredName += `${key}=${configurationOverrides[key].toLowerCase()}`; } - if (configuredName) { - const extname = vpath.extname(this.justName); - const basename = vpath.basename(this.justName, extname, /*ignoreCase*/ true); - this.configuredName = `${basename}(${configuredName})${extname}`; + + const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/"; + + if (testCaseContent === undefined) { + testCaseContent = TestCaseParser.makeUnitsFromTest(IO.readFile(fileName)!, fileName, rootDir); } - } - const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/"; + if (configurationOverrides) { + testCaseContent = { ...testCaseContent, settings: { ...testCaseContent.settings, ...configurationOverrides } }; + } - if (testCaseContent === undefined) { - testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(Harness.IO.readFile(fileName)!, fileName, rootDir); - } + const units = testCaseContent.testUnitData; + this.harnessSettings = testCaseContent.settings; + let tsConfigOptions: ts.CompilerOptions | undefined; + this.tsConfigFiles = []; + if (testCaseContent.tsConfig) { + assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`); + assert.equal(testCaseContent.tsConfig.raw.exclude, undefined, `exclude in tsconfig is not currently supported`); - if (configurationOverrides) { - testCaseContent = { ...testCaseContent, settings: { ...testCaseContent.settings, ...configurationOverrides } }; - } + tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options); + this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData!, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath))); + } + else { + const baseUrl = this.harnessSettings.baseUrl; + if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) { + this.harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir); + } + } - const units = testCaseContent.testUnitData; - this.harnessSettings = testCaseContent.settings; - let tsConfigOptions: ts.CompilerOptions | undefined; - this.tsConfigFiles = []; - if (testCaseContent.tsConfig) { - assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`); - assert.equal(testCaseContent.tsConfig.raw.exclude, undefined, `exclude in tsconfig is not currently supported`); + this.lastUnit = units[units.length - 1]; + this.hasNonDtsFiles = units.some(unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts)); + // We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test) + // If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references, + // otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another. + this.toBeCompiled = []; + this.otherFiles = []; + + if (testCaseContent.settings.noImplicitReferences || /require\(/.test(this.lastUnit.content) || /reference\spath/.test(this.lastUnit.content)) { + this.toBeCompiled.push(this.createHarnessTestFile(this.lastUnit, rootDir)); + units.forEach(unit => { + if (unit.name !== this.lastUnit.name) { + this.otherFiles.push(this.createHarnessTestFile(unit, rootDir)); + } + }); + } + else { + this.toBeCompiled = units.map(unit => { + return this.createHarnessTestFile(unit, rootDir); + }); + } - tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options); - this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData!, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath))); - } - else { - const baseUrl = this.harnessSettings.baseUrl; - if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) { - this.harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir); + if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) { + tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath); + tsConfigOptions.configFile!.fileName = tsConfigOptions.configFilePath; } - } - this.lastUnit = units[units.length - 1]; - this.hasNonDtsFiles = units.some(unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts)); - // We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test) - // If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references, - // otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another. - this.toBeCompiled = []; - this.otherFiles = []; - - if (testCaseContent.settings.noImplicitReferences || /require\(/.test(this.lastUnit.content) || /reference\spath/.test(this.lastUnit.content)) { - this.toBeCompiled.push(this.createHarnessTestFile(this.lastUnit, rootDir)); - units.forEach(unit => { - if (unit.name !== this.lastUnit.name) { - this.otherFiles.push(this.createHarnessTestFile(unit, rootDir)); - } - }); - } - else { - this.toBeCompiled = units.map(unit => { - return this.createHarnessTestFile(unit, rootDir); - }); - } + this.result = Compiler.compileFiles( + this.toBeCompiled, + this.otherFiles, + this.harnessSettings, + /*options*/ tsConfigOptions, + /*currentDirectory*/ this.harnessSettings.currentDirectory, + testCaseContent.symlinks + ); - if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) { - tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath); - tsConfigOptions.configFile!.fileName = tsConfigOptions.configFilePath; + this.options = this.result.options; } - this.result = Harness.Compiler.compileFiles( - this.toBeCompiled, - this.otherFiles, - this.harnessSettings, - /*options*/ tsConfigOptions, - /*currentDirectory*/ this.harnessSettings.currentDirectory, - testCaseContent.symlinks - ); - - this.options = this.result.options; - } + public static getConfigurations(file: string): CompilerFileBasedTest { + // also see `parseCompilerTestConfigurations` in tests/webTestServer.ts + const content = IO.readFile(file)!; + const settings = TestCaseParser.extractCompilerSettings(content); + const configurations = getFileBasedTestConfigurations(settings, CompilerTest.varyBy); + return { file, configurations, content }; + } - public static getConfigurations(file: string): CompilerFileBasedTest { - // also see `parseCompilerTestConfigurations` in tests/webTestServer.ts - const content = Harness.IO.readFile(file)!; - const settings = Harness.TestCaseParser.extractCompilerSettings(content); - const configurations = Harness.getFileBasedTestConfigurations(settings, CompilerTest.varyBy); - return { file, configurations, content }; - } + public verifyDiagnostics() { + // check errors + Compiler.doErrorBaseline( + this.configuredName, + this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles), + this.result.diagnostics, + !!this.options.pretty); + } - public verifyDiagnostics() { - // check errors - Harness.Compiler.doErrorBaseline( - this.configuredName, - this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles), - this.result.diagnostics, - !!this.options.pretty); - } + public verifyModuleResolution() { + if (this.options.traceResolution) { + Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".trace.json"), + JSON.stringify(this.result.traces.map(Utils.sanitizeTraceResolutionLogEntry), undefined, 4)); + } + } - public verifyModuleResolution() { - if (this.options.traceResolution) { - Harness.Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".trace.json"), - JSON.stringify(this.result.traces.map(utils.sanitizeTraceResolutionLogEntry), undefined, 4)); + public verifySourceMapRecord() { + if (this.options.sourceMap || this.options.inlineSourceMap || this.options.declarationMap) { + const record = Utils.removeTestPathPrefixes(this.result.getSourceMapRecord()!); + const baseline = (this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined + // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required. + ? null // eslint-disable-line no-null/no-null + : record; + Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".sourcemap.txt"), baseline); + } } - } - public verifySourceMapRecord() { - if (this.options.sourceMap || this.options.inlineSourceMap || this.options.declarationMap) { - const record = utils.removeTestPathPrefixes(this.result.getSourceMapRecord()!); - const baseline = (this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined - // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required. - ? null // eslint-disable-line no-null/no-null - : record; - Harness.Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".sourcemap.txt"), baseline); + public verifyJavaScriptOutput() { + if (this.hasNonDtsFiles) { + Compiler.doJsEmitBaseline( + this.configuredName, + this.fileName, + this.options, + this.result, + this.tsConfigFiles, + this.toBeCompiled, + this.otherFiles, + this.harnessSettings); + } } - } - public verifyJavaScriptOutput() { - if (this.hasNonDtsFiles) { - Harness.Compiler.doJsEmitBaseline( + public verifySourceMapOutput() { + Compiler.doSourcemapBaseline( this.configuredName, - this.fileName, this.options, this.result, - this.tsConfigFiles, - this.toBeCompiled, - this.otherFiles, this.harnessSettings); } - } - public verifySourceMapOutput() { - Harness.Compiler.doSourcemapBaseline( - this.configuredName, - this.options, - this.result, - this.harnessSettings); - } + public verifyTypesAndSymbols() { + if (this.fileName.indexOf("APISample") >= 0) { + return; + } - public verifyTypesAndSymbols() { - if (this.fileName.indexOf("APISample") >= 0) { - return; - } + const noTypesAndSymbols = + this.harnessSettings.noTypesAndSymbols && + this.harnessSettings.noTypesAndSymbols.toLowerCase() === "true"; + if (noTypesAndSymbols) { + return; + } - const noTypesAndSymbols = - this.harnessSettings.noTypesAndSymbols && - this.harnessSettings.noTypesAndSymbols.toLowerCase() === "true"; - if (noTypesAndSymbols) { - return; + Compiler.doTypeAndSymbolBaseline( + this.configuredName, + this.result.program!, + this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program!.getSourceFile(file.unitName)), + /*opts*/ undefined, + /*multifile*/ undefined, + /*skipTypeBaselines*/ undefined, + /*skipSymbolBaselines*/ undefined, + !!ts.length(this.result.diagnostics) + ); } - Harness.Compiler.doTypeAndSymbolBaseline( - this.configuredName, - this.result.program!, - this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program!.getSourceFile(file.unitName)), - /*opts*/ undefined, - /*multifile*/ undefined, - /*skipTypeBaselines*/ undefined, - /*skipSymbolBaselines*/ undefined, - !!ts.length(this.result.diagnostics) - ); - } - - private makeUnitName(name: string, root: string) { - const path = ts.toPath(name, root, ts.identity); - const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", ts.identity); - return pathStart ? path.replace(pathStart, "/") : path; - } + private makeUnitName(name: string, root: string) { + const path = ts.toPath(name, root, ts.identity); + const pathStart = ts.toPath(IO.getCurrentDirectory(), "", ts.identity); + return pathStart ? path.replace(pathStart, "/") : path; + } - private createHarnessTestFile(lastUnit: Harness.TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Harness.Compiler.TestFile { - return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions }; + private createHarnessTestFile(lastUnit: TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Compiler.TestFile { + return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions }; + } } -} +} \ No newline at end of file diff --git a/src/testRunner/documentsRef.ts b/src/testRunner/documentsRef.ts new file mode 100644 index 0000000000000..d3d92746b4ed7 --- /dev/null +++ b/src/testRunner/documentsRef.ts @@ -0,0 +1,2 @@ +// empty ref to documents so it can be referenced by unittests +namespace documents {} \ No newline at end of file diff --git a/src/testRunner/evaluatorRef.ts b/src/testRunner/evaluatorRef.ts new file mode 100644 index 0000000000000..cc81ec2402db5 --- /dev/null +++ b/src/testRunner/evaluatorRef.ts @@ -0,0 +1,2 @@ +// empty ref to evaluator so it can be referenced by unittests +namespace evaluator {} \ No newline at end of file diff --git a/src/testRunner/externalCompileRunner.ts b/src/testRunner/externalCompileRunner.ts index 8fb4715b30c39..77eb21c0a7bee 100644 --- a/src/testRunner/externalCompileRunner.ts +++ b/src/testRunner/externalCompileRunner.ts @@ -1,338 +1,340 @@ -const fs: typeof import("fs") = require("fs"); -const path: typeof import("path") = require("path"); -const del: typeof import("del") = require("del"); - -interface ExecResult { - stdout: Buffer; - stderr: Buffer; - status: number | null; -} - -interface UserConfig { - types: string[]; - cloneUrl: string; - path?: string; -} - -abstract class ExternalCompileRunnerBase extends RunnerBase { - abstract testDir: string; - abstract report(result: ExecResult, cwd: string): string | null; - enumerateTestFiles() { - return Harness.IO.getDirectories(this.testDir); +namespace Harness { + const fs: typeof import("fs") = require("fs"); + const path: typeof import("path") = require("path"); + const del: typeof import("del") = require("del"); + + interface ExecResult { + stdout: Buffer; + stderr: Buffer; + status: number | null; } - /** 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 - */ - initializeTests(): void { - // Read in and evaluate the test list - const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const cls = this; - describe(`${this.kind()} code samples`, function(this: Mocha.ISuiteCallbackContext) { - this.timeout(600_000); // 10 minutes - for (const test of testList) { - cls.runTest(typeof test === "string" ? test : test.file); - } - }); - } - private runTest(directoryName: string) { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const cls = this; - const timeout = 600_000; // 10 minutes - describe(directoryName, function(this: Mocha.ISuiteCallbackContext) { - this.timeout(timeout); - const cp: typeof import("child_process") = require("child_process"); - it("should build successfully", () => { - let cwd = path.join(Harness.IO.getWorkspaceRoot(), cls.testDir, directoryName); - const originalCwd = cwd; - const stdio = isWorker ? "pipe" : "inherit"; - let types: string[] | undefined; - if (fs.existsSync(path.join(cwd, "test.json"))) { - const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig; - ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present."); - ts.Debug.assert(!!config.cloneUrl, "Bad format from test.json: cloneUrl field must be present."); - const submoduleDir = path.join(cwd, directoryName); - if (!fs.existsSync(submoduleDir)) { - exec("git", ["clone", config.cloneUrl, directoryName], { cwd }); - } - exec("git", ["reset", "HEAD", "--hard"], { cwd: submoduleDir }); - exec("git", ["clean", "-f"], { cwd: submoduleDir }); - exec("git", ["pull", "-f"], { cwd: submoduleDir }); - - types = config.types; + interface UserConfig { + types: string[]; + cloneUrl: string; + path?: string; + } - cwd = config.path ? path.join(cwd, config.path) : submoduleDir; + abstract class ExternalCompileRunnerBase extends RunnerBase { + abstract testDir: string; + abstract report(result: ExecResult, cwd: string): string | null; + enumerateTestFiles() { + return IO.getDirectories(this.testDir); + } + /** 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 + */ + initializeTests(): void { + // Read in and evaluate the test list + const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const cls = this; + describe(`${this.kind()} code samples`, function(this: Mocha.ISuiteCallbackContext) { + this.timeout(600_000); // 10 minutes + for (const test of testList) { + cls.runTest(typeof test === "string" ? test : test.file); } - if (fs.existsSync(path.join(cwd, "package.json"))) { - if (fs.existsSync(path.join(cwd, "package-lock.json"))) { - fs.unlinkSync(path.join(cwd, "package-lock.json")); + }); + } + private runTest(directoryName: string) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const cls = this; + const timeout = 600_000; // 10 minutes + describe(directoryName, function(this: Mocha.ISuiteCallbackContext) { + this.timeout(timeout); + const cp: typeof import("child_process") = require("child_process"); + + it("should build successfully", () => { + let cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directoryName); + const originalCwd = cwd; + const stdio = isWorker ? "pipe" : "inherit"; + let types: string[] | undefined; + if (fs.existsSync(path.join(cwd, "test.json"))) { + const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig; + ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present."); + ts.Debug.assert(!!config.cloneUrl, "Bad format from test.json: cloneUrl field must be present."); + const submoduleDir = path.join(cwd, directoryName); + if (!fs.existsSync(submoduleDir)) { + exec("git", ["clone", config.cloneUrl, directoryName], { cwd }); + } + exec("git", ["reset", "HEAD", "--hard"], { cwd: submoduleDir }); + exec("git", ["clean", "-f"], { cwd: submoduleDir }); + exec("git", ["pull", "-f"], { cwd: submoduleDir }); + + types = config.types; + + cwd = config.path ? path.join(cwd, config.path) : submoduleDir; } - if (fs.existsSync(path.join(cwd, "node_modules"))) { - del.sync(path.join(cwd, "node_modules"), { force: true }); + if (fs.existsSync(path.join(cwd, "package.json"))) { + if (fs.existsSync(path.join(cwd, "package-lock.json"))) { + fs.unlinkSync(path.join(cwd, "package-lock.json")); + } + if (fs.existsSync(path.join(cwd, "node_modules"))) { + del.sync(path.join(cwd, "node_modules"), { force: true }); + } + exec("npm", ["i", "--ignore-scripts"], { cwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure } - exec("npm", ["i", "--ignore-scripts"], { cwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure - } - const args = [path.join(Harness.IO.getWorkspaceRoot(), "built/local/tsc.js")]; - if (types) { - args.push("--types", types.join(",")); - // Also actually install those types (for, eg, the js projects which need node) - exec("npm", ["i", ...types.map(t => `@types/${t}`), "--no-save", "--ignore-scripts"], { cwd: originalCwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure - } - args.push("--noEmit"); - Harness.Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }), cwd)); - - function exec(command: string, args: string[], options: { cwd: string, timeout?: number }): void { - const res = cp.spawnSync(command, args, { timeout, shell: true, stdio, ...options }); - if (res.status !== 0) { - throw new Error(`${command} ${args.join(" ")} for ${directoryName} failed: ${res.stderr && res.stderr.toString()}`); + const args = [path.join(IO.getWorkspaceRoot(), "built/local/tsc.js")]; + if (types) { + args.push("--types", types.join(",")); + // Also actually install those types (for, eg, the js projects which need node) + exec("npm", ["i", ...types.map(t => `@types/${t}`), "--no-save", "--ignore-scripts"], { cwd: originalCwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure } - } + args.push("--noEmit"); + Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }), cwd)); + + function exec(command: string, args: string[], options: { cwd: string, timeout?: number }): void { + const res = cp.spawnSync(command, args, { timeout, shell: true, stdio, ...options }); + if (res.status !== 0) { + throw new Error(`${command} ${args.join(" ")} for ${directoryName} failed: ${res.stderr && res.stderr.toString()}`); + } + } + }); }); - }); + } } -} -class UserCodeRunner extends ExternalCompileRunnerBase { - readonly testDir = "tests/cases/user/"; - kind(): TestRunnerKind { - return "user"; - } - report(result: ExecResult) { - // eslint-disable-next-line no-null/no-null - return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} -Standard output: -${sortErrors(stripAbsoluteImportPaths(result.stdout.toString().replace(/\r\n/g, "\n")))} + export class UserCodeRunner extends ExternalCompileRunnerBase { + readonly testDir = "tests/cases/user/"; + kind(): TestRunnerKind { + return "user"; + } + report(result: ExecResult) { + // eslint-disable-next-line no-null/no-null + return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} + Standard output: + ${sortErrors(stripAbsoluteImportPaths(result.stdout.toString().replace(/\r\n/g, "\n")))} -Standard error: -${stripAbsoluteImportPaths(result.stderr.toString().replace(/\r\n/g, "\n"))}`; + Standard error: + ${stripAbsoluteImportPaths(result.stderr.toString().replace(/\r\n/g, "\n"))}`; + } } -} -class DockerfileRunner extends ExternalCompileRunnerBase { - readonly testDir = "tests/cases/docker/"; - kind(): TestRunnerKind { - return "docker"; - } - initializeTests(): void { - // Read in and evaluate the test list - const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const cls = this; - describe(`${this.kind()} code samples`, function(this: Mocha.ISuiteCallbackContext) { - this.timeout(cls.timeout); // 20 minutes - before(() => { - cls.exec("docker", ["build", ".", "-t", "typescript/typescript"], { cwd: Harness.IO.getWorkspaceRoot() }); // cached because workspace is hashed to determine cacheability - }); - for (const test of testList) { - const directory = typeof test === "string" ? test : test.file; - const cwd = path.join(Harness.IO.getWorkspaceRoot(), cls.testDir, directory); - it(`should build ${directory} successfully`, () => { - const imageName = `tstest/${directory}`; - cls.exec("docker", ["build", "--no-cache", ".", "-t", imageName], { cwd }); // --no-cache so the latest version of the repos referenced is always fetched - const cp: typeof import("child_process") = require("child_process"); - Harness.Baseline.runBaseline(`${cls.kind()}/${directory}.log`, cls.report(cp.spawnSync(`docker`, ["run", imageName], { cwd, timeout: cls.timeout, shell: true }))); + export class DockerfileRunner extends ExternalCompileRunnerBase { + readonly testDir = "tests/cases/docker/"; + kind(): TestRunnerKind { + return "docker"; + } + initializeTests(): void { + // Read in and evaluate the test list + const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const cls = this; + describe(`${this.kind()} code samples`, function(this: Mocha.ISuiteCallbackContext) { + this.timeout(cls.timeout); // 20 minutes + before(() => { + cls.exec("docker", ["build", ".", "-t", "typescript/typescript"], { cwd: IO.getWorkspaceRoot() }); // cached because workspace is hashed to determine cacheability }); + for (const test of testList) { + const directory = typeof test === "string" ? test : test.file; + const cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directory); + it(`should build ${directory} successfully`, () => { + const imageName = `tstest/${directory}`; + cls.exec("docker", ["build", "--no-cache", ".", "-t", imageName], { cwd }); // --no-cache so the latest version of the repos referenced is always fetched + const cp: typeof import("child_process") = require("child_process"); + Baseline.runBaseline(`${cls.kind()}/${directory}.log`, cls.report(cp.spawnSync(`docker`, ["run", imageName], { cwd, timeout: cls.timeout, shell: true }))); + }); + } + }); + } + + private timeout = 1_200_000; // 20 minutes; + private exec(command: string, args: string[], options: { cwd: string, timeout?: number }): void { + const cp: typeof import("child_process") = require("child_process"); + const stdio = isWorker ? "pipe" : "inherit"; + const res = cp.spawnSync(command, args, { timeout: this.timeout, shell: true, stdio, ...options }); + if (res.status !== 0) { + throw new Error(`${command} ${args.join(" ")} for ${options.cwd} failed: ${res.stderr && res.stderr.toString()}`); } - }); - } + } + report(result: ExecResult) { + // eslint-disable-next-line no-null/no-null + return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} + Standard output: + ${sanitizeDockerfileOutput(result.stdout.toString())} - private timeout = 1_200_000; // 20 minutes; - private exec(command: string, args: string[], options: { cwd: string, timeout?: number }): void { - const cp: typeof import("child_process") = require("child_process"); - const stdio = isWorker ? "pipe" : "inherit"; - const res = cp.spawnSync(command, args, { timeout: this.timeout, shell: true, stdio, ...options }); - if (res.status !== 0) { - throw new Error(`${command} ${args.join(" ")} for ${options.cwd} failed: ${res.stderr && res.stderr.toString()}`); + + Standard error: + ${sanitizeDockerfileOutput(result.stderr.toString())}`; } } - report(result: ExecResult) { - // eslint-disable-next-line no-null/no-null - return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} -Standard output: -${sanitizeDockerfileOutput(result.stdout.toString())} + function sanitizeDockerfileOutput(result: string): string { + return [ + normalizeNewlines, + stripANSIEscapes, + stripRushStageNumbers, + stripWebpackHash, + sanitizeVersionSpecifiers, + sanitizeTimestamps, + sanitizeSizes, + sanitizeUnimportantGulpOutput, + stripAbsoluteImportPaths, + ].reduce((result, f) => f(result), result); + } + + function normalizeNewlines(result: string): string { + return result.replace(/\r\n/g, "\n"); + } + + function stripANSIEscapes(result: string): string { + return result.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ""); + } + + function stripRushStageNumbers(result: string): string { + return result.replace(/\d+ of \d+:/g, "XX of XX:"); + } -Standard error: -${sanitizeDockerfileOutput(result.stderr.toString())}`; + function stripWebpackHash(result: string): string { + return result.replace(/Hash: \w+/g, "Hash: [redacted]"); } -} - -function sanitizeDockerfileOutput(result: string): string { - return [ - normalizeNewlines, - stripANSIEscapes, - stripRushStageNumbers, - stripWebpackHash, - sanitizeVersionSpecifiers, - sanitizeTimestamps, - sanitizeSizes, - sanitizeUnimportantGulpOutput, - stripAbsoluteImportPaths, - ].reduce((result, f) => f(result), result); -} - -function normalizeNewlines(result: string): string { - return result.replace(/\r\n/g, "\n"); -} - -function stripANSIEscapes(result: string): string { - return result.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ""); -} - -function stripRushStageNumbers(result: string): string { - return result.replace(/\d+ of \d+:/g, "XX of XX:"); -} - -function stripWebpackHash(result: string): string { - return result.replace(/Hash: \w+/g, "Hash: [redacted]"); -} - -function sanitizeSizes(result: string): string { - return result.replace(/\d+(\.\d+)? ((Ki|M)B|bytes)/g, "X KiB"); -} - -/** - * Gulp's output order within a `parallel` block is nondeterministic (and there's no way to configure it to execute in series), - * so we purge as much of the gulp output as we can - */ -function sanitizeUnimportantGulpOutput(result: string): string { - return result.replace(/^.*(\] (Starting)|(Finished)).*$/gm, "") // "gulp" task start/end messages (nondeterministic order) - .replace(/^.*(\] . (finished)|(started)).*$/gm, "") // "just" task start/end messages (nondeterministic order) - .replace(/^.*\] Respawned to PID: \d+.*$/gm, "") // PID of child is OS and system-load dependent (likely stableish in a container but still dangerous) - .replace(/\n+/g, "\n"); -} - -function sanitizeTimestamps(result: string): string { - return result.replace(/\[\d?\d:\d\d:\d\d (A|P)M\]/g, "[XX:XX:XX XM]") - .replace(/\[\d?\d:\d\d:\d\d\]/g, "[XX:XX:XX]") - .replace(/\/\d+-\d+-[\d_TZ]+-debug.log/g, "\/XXXX-XX-XXXXXXXXX-debug.log") - .replace(/\d+\/\d+\/\d+ \d+:\d+:\d+ (AM|PM)/g, "XX/XX/XX XX:XX:XX XM") - .replace(/\d+(\.\d+)? sec(onds?)?/g, "? seconds") - .replace(/\d+(\.\d+)? min(utes?)?/g, "") - .replace(/\d+(\.\d+)? ?m?s/g, "?s") - .replace(/ \(\?s\)/g, ""); -} - -function sanitizeVersionSpecifiers(result: string): string { - return result - .replace(/\d+.\d+.\d+-insiders.\d\d\d\d\d\d\d\d/g, "X.X.X-insiders.xxxxxxxx") - .replace(/Rush Multi-Project Build Tool (\d+)\.\d+\.\d+/g, "Rush Multi-Project Build Tool $1.X.X") - .replace(/([@v\()])\d+\.\d+\.\d+/g, "$1X.X.X") - .replace(/webpack (\d+)\.\d+\.\d+/g, "webpack $1.X.X") - .replace(/Webpack version: (\d+)\.\d+\.\d+/g, "Webpack version: $1.X.X"); -} - -/** - * Import types and some other error messages use absolute paths in errors as they have no context to be written relative to; - * This is problematic for error baselines, so we grep for them and strip them out. - */ -function stripAbsoluteImportPaths(result: string) { - const workspaceRegexp = new RegExp(Harness.IO.getWorkspaceRoot().replace(/\\/g, "\\\\"), "g"); - return result - .replace(/import\(".*?\/tests\/cases\/user\//g, `import("/`) - .replace(/Module '".*?\/tests\/cases\/user\//g, `Module '"/`) - .replace(workspaceRegexp, "../../.."); -} - -function sortErrors(result: string) { - return ts.flatten(splitBy(result.split("\n"), s => /^\S+/.test(s)).sort(compareErrorStrings)).join("\n"); -} - -const errorRegexp = /^(.+\.[tj]sx?)\((\d+),(\d+)\)(: error TS.*)/; -function compareErrorStrings(a: string[], b: string[]) { - ts.Debug.assertGreaterThanOrEqual(a.length, 1); - ts.Debug.assertGreaterThanOrEqual(b.length, 1); - const matchA = a[0].match(errorRegexp); - if (!matchA) { - return -1; + + function sanitizeSizes(result: string): string { + return result.replace(/\d+(\.\d+)? ((Ki|M)B|bytes)/g, "X KiB"); } - const matchB = b[0].match(errorRegexp); - if (!matchB) { - return 1; + + /** + * Gulp's output order within a `parallel` block is nondeterministic (and there's no way to configure it to execute in series), + * so we purge as much of the gulp output as we can + */ + function sanitizeUnimportantGulpOutput(result: string): string { + return result.replace(/^.*(\] (Starting)|(Finished)).*$/gm, "") // "gulp" task start/end messages (nondeterministic order) + .replace(/^.*(\] . (finished)|(started)).*$/gm, "") // "just" task start/end messages (nondeterministic order) + .replace(/^.*\] Respawned to PID: \d+.*$/gm, "") // PID of child is OS and system-load dependent (likely stableish in a container but still dangerous) + .replace(/\n+/g, "\n"); } - const [, errorFileA, lineNumberStringA, columnNumberStringA, remainderA] = matchA; - const [, errorFileB, lineNumberStringB, columnNumberStringB, remainderB] = matchB; - return ts.comparePathsCaseSensitive(errorFileA, errorFileB) || - ts.compareValues(parseInt(lineNumberStringA), parseInt(lineNumberStringB)) || - ts.compareValues(parseInt(columnNumberStringA), parseInt(columnNumberStringB)) || - ts.compareStringsCaseSensitive(remainderA, remainderB) || - ts.compareStringsCaseSensitive(a.slice(1).join("\n"), b.slice(1).join("\n")); -} - -class DefinitelyTypedRunner extends ExternalCompileRunnerBase { - readonly testDir = "../DefinitelyTyped/types/"; - workingDirectory = this.testDir; - kind(): TestRunnerKind { - return "dt"; + + function sanitizeTimestamps(result: string): string { + return result.replace(/\[\d?\d:\d\d:\d\d (A|P)M\]/g, "[XX:XX:XX XM]") + .replace(/\[\d?\d:\d\d:\d\d\]/g, "[XX:XX:XX]") + .replace(/\/\d+-\d+-[\d_TZ]+-debug.log/g, "\/XXXX-XX-XXXXXXXXX-debug.log") + .replace(/\d+\/\d+\/\d+ \d+:\d+:\d+ (AM|PM)/g, "XX/XX/XX XX:XX:XX XM") + .replace(/\d+(\.\d+)? sec(onds?)?/g, "? seconds") + .replace(/\d+(\.\d+)? min(utes?)?/g, "") + .replace(/\d+(\.\d+)? ?m?s/g, "?s") + .replace(/ \(\?s\)/g, ""); } - report(result: ExecResult, cwd: string) { - const stdout = removeExpectedErrors(result.stdout.toString(), cwd); - const stderr = result.stderr.toString(); - // eslint-disable-next-line no-null/no-null - return !stdout.length && !stderr.length ? null : `Exit Code: ${result.status} -Standard output: -${stdout.replace(/\r\n/g, "\n")} + function sanitizeVersionSpecifiers(result: string): string { + return result + .replace(/\d+.\d+.\d+-insiders.\d\d\d\d\d\d\d\d/g, "X.X.X-insiders.xxxxxxxx") + .replace(/Rush Multi-Project Build Tool (\d+)\.\d+\.\d+/g, "Rush Multi-Project Build Tool $1.X.X") + .replace(/([@v\()])\d+\.\d+\.\d+/g, "$1X.X.X") + .replace(/webpack (\d+)\.\d+\.\d+/g, "webpack $1.X.X") + .replace(/Webpack version: (\d+)\.\d+\.\d+/g, "Webpack version: $1.X.X"); + } + /** + * Import types and some other error messages use absolute paths in errors as they have no context to be written relative to; + * This is problematic for error baselines, so we grep for them and strip them out. + */ + function stripAbsoluteImportPaths(result: string) { + const workspaceRegexp = new RegExp(IO.getWorkspaceRoot().replace(/\\/g, "\\\\"), "g"); + return result + .replace(/import\(".*?\/tests\/cases\/user\//g, `import("/`) + .replace(/Module '".*?\/tests\/cases\/user\//g, `Module '"/`) + .replace(workspaceRegexp, "../../.."); + } -Standard error: -${stderr.replace(/\r\n/g, "\n")}`; + function sortErrors(result: string) { + return ts.flatten(splitBy(result.split("\n"), s => /^\S+/.test(s)).sort(compareErrorStrings)).join("\n"); } -} - -function removeExpectedErrors(errors: string, cwd: string): string { - return ts.flatten(splitBy(errors.split("\n"), s => /^\S+/.test(s)).filter(isUnexpectedError(cwd))).join("\n"); -} -/** - * Returns true if the line that caused the error contains '$ExpectError', - * or if the line before that one contains '$ExpectError'. - * '$ExpectError' is a marker used in Definitely Typed tests, - * meaning that the error should not contribute toward our error baslines. - */ -function isUnexpectedError(cwd: string) { - return (error: string[]) => { - ts.Debug.assertGreaterThanOrEqual(error.length, 1); - const match = error[0].match(/(.+\.tsx?)\((\d+),\d+\): error TS/); - if (!match) { - return true; + + const errorRegexp = /^(.+\.[tj]sx?)\((\d+),(\d+)\)(: error TS.*)/; + function compareErrorStrings(a: string[], b: string[]) { + ts.Debug.assertGreaterThanOrEqual(a.length, 1); + ts.Debug.assertGreaterThanOrEqual(b.length, 1); + const matchA = a[0].match(errorRegexp); + if (!matchA) { + return -1; } - const [, errorFile, lineNumberString] = match; - const lines = fs.readFileSync(path.join(cwd, errorFile), { encoding: "utf8" }).split("\n"); - const lineNumber = parseInt(lineNumberString) - 1; - ts.Debug.assertGreaterThanOrEqual(lineNumber, 0); - ts.Debug.assertLessThan(lineNumber, lines.length); - const previousLine = lineNumber - 1 > 0 ? lines[lineNumber - 1] : ""; - return !ts.stringContains(lines[lineNumber], "$ExpectError") && !ts.stringContains(previousLine, "$ExpectError"); - }; -} -/** - * Split an array into multiple arrays whenever `isStart` returns true. - * @example - * splitBy([1,2,3,4,5,6], isOdd) - * ==> [[1, 2], [3, 4], [5, 6]] - * where - * const isOdd = n => !!(n % 2) - */ -function splitBy(xs: T[], isStart: (x: T) => boolean): T[][] { - const result = []; - let group: T[] = []; - for (const x of xs) { - if (isStart(x)) { - if (group.length) { - result.push(group); - } - group = [x]; + const matchB = b[0].match(errorRegexp); + if (!matchB) { + return 1; + } + const [, errorFileA, lineNumberStringA, columnNumberStringA, remainderA] = matchA; + const [, errorFileB, lineNumberStringB, columnNumberStringB, remainderB] = matchB; + return ts.comparePathsCaseSensitive(errorFileA, errorFileB) || + ts.compareValues(parseInt(lineNumberStringA), parseInt(lineNumberStringB)) || + ts.compareValues(parseInt(columnNumberStringA), parseInt(columnNumberStringB)) || + ts.compareStringsCaseSensitive(remainderA, remainderB) || + ts.compareStringsCaseSensitive(a.slice(1).join("\n"), b.slice(1).join("\n")); + } + + export class DefinitelyTypedRunner extends ExternalCompileRunnerBase { + readonly testDir = "../DefinitelyTyped/types/"; + workingDirectory = this.testDir; + kind(): TestRunnerKind { + return "dt"; } - else { - group.push(x); + report(result: ExecResult, cwd: string) { + const stdout = removeExpectedErrors(result.stdout.toString(), cwd); + const stderr = result.stderr.toString(); + + // eslint-disable-next-line no-null/no-null + return !stdout.length && !stderr.length ? null : `Exit Code: ${result.status} + Standard output: + ${stdout.replace(/\r\n/g, "\n")} + + + Standard error: + ${stderr.replace(/\r\n/g, "\n")}`; } } - if (group.length) { - result.push(group); + + function removeExpectedErrors(errors: string, cwd: string): string { + return ts.flatten(splitBy(errors.split("\n"), s => /^\S+/.test(s)).filter(isUnexpectedError(cwd))).join("\n"); + } + /** + * Returns true if the line that caused the error contains '$ExpectError', + * or if the line before that one contains '$ExpectError'. + * '$ExpectError' is a marker used in Definitely Typed tests, + * meaning that the error should not contribute toward our error baslines. + */ + function isUnexpectedError(cwd: string) { + return (error: string[]) => { + ts.Debug.assertGreaterThanOrEqual(error.length, 1); + const match = error[0].match(/(.+\.tsx?)\((\d+),\d+\): error TS/); + if (!match) { + return true; + } + const [, errorFile, lineNumberString] = match; + const lines = fs.readFileSync(path.join(cwd, errorFile), { encoding: "utf8" }).split("\n"); + const lineNumber = parseInt(lineNumberString) - 1; + ts.Debug.assertGreaterThanOrEqual(lineNumber, 0); + ts.Debug.assertLessThan(lineNumber, lines.length); + const previousLine = lineNumber - 1 > 0 ? lines[lineNumber - 1] : ""; + return !ts.stringContains(lines[lineNumber], "$ExpectError") && !ts.stringContains(previousLine, "$ExpectError"); + }; + } + /** + * Split an array into multiple arrays whenever `isStart` returns true. + * @example + * splitBy([1,2,3,4,5,6], isOdd) + * ==> [[1, 2], [3, 4], [5, 6]] + * where + * const isOdd = n => !!(n % 2) + */ + function splitBy(xs: T[], isStart: (x: T) => boolean): T[][] { + const result = []; + let group: T[] = []; + for (const x of xs) { + if (isStart(x)) { + if (group.length) { + result.push(group); + } + group = [x]; + } + else { + group.push(x); + } + } + if (group.length) { + result.push(group); + } + return result; } - return result; -} +} \ No newline at end of file diff --git a/src/testRunner/fakesRef.ts b/src/testRunner/fakesRef.ts new file mode 100644 index 0000000000000..b19d4cc8c80ed --- /dev/null +++ b/src/testRunner/fakesRef.ts @@ -0,0 +1,2 @@ +// empty ref to fakes so it can be referenced by unittests +namespace fakes {} \ No newline at end of file diff --git a/src/testRunner/fourslashRef.ts b/src/testRunner/fourslashRef.ts new file mode 100644 index 0000000000000..23a50810a46af --- /dev/null +++ b/src/testRunner/fourslashRef.ts @@ -0,0 +1,2 @@ +// empty ref to FourSlash so it can be referenced by unittests +namespace FourSlash {} \ No newline at end of file diff --git a/src/testRunner/fourslashRunner.ts b/src/testRunner/fourslashRunner.ts index f7d7bf621d553..4916ec6693f6e 100644 --- a/src/testRunner/fourslashRunner.ts +++ b/src/testRunner/fourslashRunner.ts @@ -1,70 +1,72 @@ -class FourSlashRunner extends RunnerBase { - protected basePath: string; - protected testSuiteName: TestRunnerKind; +namespace Harness { + export class FourSlashRunner extends RunnerBase { + protected basePath: string; + protected testSuiteName: TestRunnerKind; - constructor(private testType: FourSlash.FourSlashTestType) { - super(); - switch (testType) { - case FourSlash.FourSlashTestType.Native: - this.basePath = "tests/cases/fourslash"; - this.testSuiteName = "fourslash"; - break; - case FourSlash.FourSlashTestType.Shims: - this.basePath = "tests/cases/fourslash/shims"; - this.testSuiteName = "fourslash-shims"; - break; - case FourSlash.FourSlashTestType.ShimsWithPreprocess: - this.basePath = "tests/cases/fourslash/shims-pp"; - this.testSuiteName = "fourslash-shims-pp"; - break; - case FourSlash.FourSlashTestType.Server: - this.basePath = "tests/cases/fourslash/server"; - this.testSuiteName = "fourslash-server"; - break; - default: - throw ts.Debug.assertNever(testType); + constructor(private testType: FourSlash.FourSlashTestType) { + super(); + switch (testType) { + case FourSlash.FourSlashTestType.Native: + this.basePath = "tests/cases/fourslash"; + this.testSuiteName = "fourslash"; + break; + case FourSlash.FourSlashTestType.Shims: + this.basePath = "tests/cases/fourslash/shims"; + this.testSuiteName = "fourslash-shims"; + break; + case FourSlash.FourSlashTestType.ShimsWithPreprocess: + this.basePath = "tests/cases/fourslash/shims-pp"; + this.testSuiteName = "fourslash-shims-pp"; + break; + case FourSlash.FourSlashTestType.Server: + this.basePath = "tests/cases/fourslash/server"; + this.testSuiteName = "fourslash-server"; + break; + default: + throw ts.Debug.assertNever(testType); + } } - } - - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false }); - } - public kind() { - return this.testSuiteName; - } + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false }); + } - public initializeTests() { - if (this.tests.length === 0) { - this.tests = Harness.IO.enumerateTestFiles(this); + public kind() { + return this.testSuiteName; } - describe(this.testSuiteName + " tests", () => { - this.tests.forEach(test => { - const file = typeof test === "string" ? test : test.file; - describe(file, () => { - let fn = ts.normalizeSlashes(file); - const justName = fn.replace(/^.*[\\\/]/, ""); + public initializeTests() { + if (this.tests.length === 0) { + this.tests = IO.enumerateTestFiles(this); + } - // Convert to relative path - const testIndex = fn.indexOf("tests/"); - if (testIndex >= 0) fn = fn.substr(testIndex); + describe(this.testSuiteName + " tests", () => { + this.tests.forEach(test => { + const file = typeof test === "string" ? test : test.file; + describe(file, () => { + let fn = ts.normalizeSlashes(file); + const justName = fn.replace(/^.*[\\\/]/, ""); - if (justName && !justName.match(/fourslash\.ts$/i) && !justName.match(/\.d\.ts$/i)) { - it(this.testSuiteName + " test " + justName + " runs correctly", () => { - FourSlash.runFourSlashTest(this.basePath, this.testType, fn); - }); - } + // Convert to relative path + const testIndex = fn.indexOf("tests/"); + if (testIndex >= 0) fn = fn.substr(testIndex); + + if (justName && !justName.match(/fourslash\.ts$/i) && !justName.match(/\.d\.ts$/i)) { + it(this.testSuiteName + " test " + justName + " runs correctly", () => { + FourSlash.runFourSlashTest(this.basePath, this.testType, fn); + }); + } + }); }); }); - }); + } } -} -class GeneratedFourslashRunner extends FourSlashRunner { - constructor(testType: FourSlash.FourSlashTestType) { - super(testType); - this.basePath += "/generated/"; + export class GeneratedFourslashRunner extends FourSlashRunner { + constructor(testType: FourSlash.FourSlashTestType) { + super(testType); + this.basePath += "/generated/"; + } } } diff --git a/src/testRunner/parallel/host.ts b/src/testRunner/parallel/host.ts index c2d338479a65b..bb4b31b9f058d 100644 --- a/src/testRunner/parallel/host.ts +++ b/src/testRunner/parallel/host.ts @@ -240,7 +240,7 @@ namespace Harness.Parallel.Host { let totalPassing = 0; const startDate = new Date(); - const progressBars = new ProgressBars({ noColors }); + const progressBars = new ProgressBars({ noColors: Harness.noColors }); // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier const progressUpdateInterval = 1 / progressBars._options.width; let nextProgress = progressUpdateInterval; @@ -250,7 +250,7 @@ namespace Harness.Parallel.Host { let closedWorkers = 0; for (let i = 0; i < workerCount; i++) { // TODO: Just send the config over the IPC channel or in the command line arguments - const config: TestConfig = { light: lightMode, listenForWork: true, runUnitTests, stackTraceLimit, timeout: globalTimeout }; + const config: TestConfig = { light: lightMode, listenForWork: true, runUnitTests: Harness.runUnitTests, stackTraceLimit: Harness.stackTraceLimit, timeout: globalTimeout }; // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier const configPath = ts.combinePaths(taskConfigsFolder, `task-config${i}.json`); IO.writeFile(configPath, JSON.stringify(config)); const worker: Worker = { @@ -549,7 +549,7 @@ namespace Harness.Parallel.Host { failedTestReporter = new FailedTestReporter(replayRunner, { reporterOptions: { file: path.resolve(".failed-tests"), - keepFailed + keepFailed: Harness.keepFailed // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier } }); } diff --git a/src/testRunner/playbackRef.ts b/src/testRunner/playbackRef.ts new file mode 100644 index 0000000000000..8fcb9965e0923 --- /dev/null +++ b/src/testRunner/playbackRef.ts @@ -0,0 +1,2 @@ +// empty ref to Playback so it can be referenced by unittests +namespace Playback {} \ No newline at end of file diff --git a/src/testRunner/projectsRunner.ts b/src/testRunner/projectsRunner.ts index eeb9e076f0cd9..d90972fe09a76 100644 --- a/src/testRunner/projectsRunner.ts +++ b/src/testRunner/projectsRunner.ts @@ -30,16 +30,16 @@ namespace project { outputFiles?: readonly documents.TextDocument[]; } - export class ProjectRunner extends RunnerBase { + export class ProjectRunner extends Harness.RunnerBase { public enumerateTestFiles() { const all = this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true }); - if (shards === 1) { + if (Harness.shards === 1) { return all; } - return all.filter((_val, idx) => idx % shards === (shardId - 1)); + return all.filter((_val, idx) => idx % Harness.shards === (Harness.shardId - 1)); } - public kind(): TestRunnerKind { + public kind(): Harness.TestRunnerKind { return "project"; } @@ -207,13 +207,13 @@ namespace project { const resolutionInfo: ProjectRunnerTestCaseResolutionInfo & ts.CompilerOptions = JSON.parse(JSON.stringify(this.testCase)); resolutionInfo.resolvedInputFiles = this.compilerResult.program!.getSourceFiles() .map(({ fileName: input }) => - vpath.beneath(vfs.builtFolder, input, this.vfs.ignoreCase) || vpath.beneath(vfs.testLibFolder, input, this.vfs.ignoreCase) ? utils.removeTestPathPrefixes(input) : + vpath.beneath(vfs.builtFolder, input, this.vfs.ignoreCase) || vpath.beneath(vfs.testLibFolder, input, this.vfs.ignoreCase) ? Utils.removeTestPathPrefixes(input) : vpath.isAbsolute(input) ? vpath.relative(cwd, input, ignoreCase) : input); resolutionInfo.emittedFiles = this.compilerResult.outputFiles! .map(output => output.meta.get("fileName") || output.file) - .map(output => utils.removeTestPathPrefixes(vpath.isAbsolute(output) ? vpath.relative(cwd, output, ignoreCase) : output)); + .map(output => Utils.removeTestPathPrefixes(vpath.isAbsolute(output) ? vpath.relative(cwd, output, ignoreCase) : output)); const content = JSON.stringify(resolutionInfo, undefined, " "); Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".json", content); @@ -246,7 +246,7 @@ namespace project { nonSubfolderDiskFiles++; } - const content = utils.removeTestPathPrefixes(output.text, /*retainTrailingDirectorySeparator*/ true); + const content = Utils.removeTestPathPrefixes(output.text, /*retainTrailingDirectorySeparator*/ true); Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + diskRelativeName, content as string | null); // TODO: GH#18217 } catch (e) { @@ -412,7 +412,7 @@ namespace project { const inputFiles = inputSourceFiles.map(sourceFile => ({ unitName: ts.isRootedDiskPath(sourceFile.fileName) ? - RunnerBase.removeFullPaths(sourceFile.fileName) : + Harness.RunnerBase.removeFullPaths(sourceFile.fileName) : sourceFile.fileName, content: sourceFile.text })); diff --git a/src/testRunner/runner.ts b/src/testRunner/runner.ts index 11b4b88a0dbd0..6c85e0c53199d 100644 --- a/src/testRunner/runner.ts +++ b/src/testRunner/runner.ts @@ -1,257 +1,259 @@ -/* eslint-disable prefer-const */ -let runners: RunnerBase[] = []; -let iterations = 1; -/* eslint-enable prefer-const */ +namespace Harness { + /* eslint-disable prefer-const */ + export let runners: RunnerBase[] = []; + export let iterations = 1; + /* eslint-enable prefer-const */ -function runTests(runners: RunnerBase[]) { - for (let i = iterations; i > 0; i--) { - for (const runner of runners) { - runner.initializeTests(); + function runTests(runners: RunnerBase[]) { + for (let i = iterations; i > 0; i--) { + for (const runner of runners) { + runner.initializeTests(); + } } } -} - -function tryGetConfig(args: string[]) { - const prefix = "--config="; - const configPath = ts.forEach(args, arg => arg.lastIndexOf(prefix, 0) === 0 && arg.substr(prefix.length)); - // strip leading and trailing quotes from the path (necessary on Windows since shell does not do it automatically) - return configPath && configPath.replace(/(^[\"'])|([\"']$)/g, ""); -} -function createRunner(kind: TestRunnerKind): RunnerBase { - switch (kind) { - case "conformance": - return new CompilerBaselineRunner(CompilerTestType.Conformance); - case "compiler": - return new CompilerBaselineRunner(CompilerTestType.Regressions); - case "fourslash": - return new FourSlashRunner(FourSlash.FourSlashTestType.Native); - case "fourslash-shims": - return new FourSlashRunner(FourSlash.FourSlashTestType.Shims); - case "fourslash-shims-pp": - return new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess); - case "fourslash-server": - return new FourSlashRunner(FourSlash.FourSlashTestType.Server); - case "project": - return new project.ProjectRunner(); - case "rwc": - return new RWCRunner(); - case "test262": - return new Test262BaselineRunner(); - case "user": - return new UserCodeRunner(); - case "dt": - return new DefinitelyTypedRunner(); - case "docker": - return new DockerfileRunner(); + function tryGetConfig(args: string[]) { + const prefix = "--config="; + const configPath = ts.forEach(args, arg => arg.lastIndexOf(prefix, 0) === 0 && arg.substr(prefix.length)); + // strip leading and trailing quotes from the path (necessary on Windows since shell does not do it automatically) + return configPath && configPath.replace(/(^[\"'])|([\"']$)/g, ""); } - return ts.Debug.fail(`Unknown runner kind ${kind}`); -} -// users can define tests to run in mytest.config that will override cmd line args, otherwise use cmd line args (test.config), otherwise no options + export function createRunner(kind: TestRunnerKind): RunnerBase { + switch (kind) { + case "conformance": + return new CompilerBaselineRunner(CompilerTestType.Conformance); + case "compiler": + return new CompilerBaselineRunner(CompilerTestType.Regressions); + case "fourslash": + return new FourSlashRunner(FourSlash.FourSlashTestType.Native); + case "fourslash-shims": + return new FourSlashRunner(FourSlash.FourSlashTestType.Shims); + case "fourslash-shims-pp": + return new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess); + case "fourslash-server": + return new FourSlashRunner(FourSlash.FourSlashTestType.Server); + case "project": + return new project.ProjectRunner(); + case "rwc": + return new RWC.RWCRunner(); + case "test262": + return new Test262BaselineRunner(); + case "user": + return new UserCodeRunner(); + case "dt": + return new DefinitelyTypedRunner(); + case "docker": + return new DockerfileRunner(); + } + return ts.Debug.fail(`Unknown runner kind ${kind}`); + } -const mytestconfigFileName = "mytest.config"; -const testconfigFileName = "test.config"; + // users can define tests to run in mytest.config that will override cmd line args, otherwise use cmd line args (test.config), otherwise no options -const customConfig = tryGetConfig(Harness.IO.args()); -const testConfigContent = - customConfig && Harness.IO.fileExists(customConfig) - ? Harness.IO.readFile(customConfig)! - : Harness.IO.fileExists(mytestconfigFileName) - ? Harness.IO.readFile(mytestconfigFileName)! - : Harness.IO.fileExists(testconfigFileName) ? Harness.IO.readFile(testconfigFileName)! : ""; + const mytestconfigFileName = "mytest.config"; + const testconfigFileName = "test.config"; -let taskConfigsFolder: string; -let workerCount: number; -let runUnitTests: boolean | undefined; -let stackTraceLimit: number | "full" | undefined; -let noColors = false; -let keepFailed = false; + const customConfig = tryGetConfig(IO.args()); + const testConfigContent = + customConfig && IO.fileExists(customConfig) + ? IO.readFile(customConfig)! + : IO.fileExists(mytestconfigFileName) + ? IO.readFile(mytestconfigFileName)! + : IO.fileExists(testconfigFileName) ? IO.readFile(testconfigFileName)! : ""; -interface TestConfig { - light?: boolean; - taskConfigsFolder?: string; - listenForWork?: boolean; - workerCount?: number; - stackTraceLimit?: number | "full"; - test?: string[]; - runners?: string[]; - runUnitTests?: boolean; - noColors?: boolean; - timeout?: number; - keepFailed?: boolean; - shardId?: number; - shards?: number; -} + export let taskConfigsFolder: string; + export let workerCount: number; + export let runUnitTests: boolean | undefined; + export let stackTraceLimit: number | "full" | undefined; + export let noColors = false; + export let keepFailed = false; -interface TaskSet { - runner: TestRunnerKind; - files: string[]; -} + export interface TestConfig { + light?: boolean; + taskConfigsFolder?: string; + listenForWork?: boolean; + workerCount?: number; + stackTraceLimit?: number | "full"; + test?: string[]; + runners?: string[]; + runUnitTests?: boolean; + noColors?: boolean; + timeout?: number; + keepFailed?: boolean; + shardId?: number; + shards?: number; + } -let configOption: string; -let globalTimeout: number; -function handleTestConfig() { - if (testConfigContent !== "") { - const testConfig = JSON.parse(testConfigContent); - if (testConfig.light) { - Harness.lightMode = true; - } - if (testConfig.timeout) { - globalTimeout = testConfig.timeout; - } - runUnitTests = testConfig.runUnitTests; - if (testConfig.workerCount) { - workerCount = +testConfig.workerCount; - } - if (testConfig.taskConfigsFolder) { - taskConfigsFolder = testConfig.taskConfigsFolder; - } - if (testConfig.noColors !== undefined) { - noColors = testConfig.noColors; - } - if (testConfig.keepFailed) { - keepFailed = true; - } - if (testConfig.shardId) { - shardId = testConfig.shardId; - } - if (testConfig.shards) { - shards = testConfig.shards; - } + export interface TaskSet { + runner: TestRunnerKind; + files: string[]; + } - if (testConfig.stackTraceLimit === "full") { - (Error).stackTraceLimit = Infinity; - stackTraceLimit = testConfig.stackTraceLimit; - } - else if ((+testConfig.stackTraceLimit! | 0) > 0) { - (Error).stackTraceLimit = +testConfig.stackTraceLimit! | 0; - stackTraceLimit = +testConfig.stackTraceLimit! | 0; - } - if (testConfig.listenForWork) { - return true; - } + export let configOption: string; + export let globalTimeout: number; + function handleTestConfig() { + if (testConfigContent !== "") { + const testConfig = JSON.parse(testConfigContent); + if (testConfig.light) { + setLightMode(true); + } + if (testConfig.timeout) { + globalTimeout = testConfig.timeout; + } + runUnitTests = testConfig.runUnitTests; + if (testConfig.workerCount) { + workerCount = +testConfig.workerCount; + } + if (testConfig.taskConfigsFolder) { + taskConfigsFolder = testConfig.taskConfigsFolder; + } + if (testConfig.noColors !== undefined) { + noColors = testConfig.noColors; + } + if (testConfig.keepFailed) { + keepFailed = true; + } + if (testConfig.shardId) { + setShardId(testConfig.shardId); + } + if (testConfig.shards) { + setShards(testConfig.shards); + } - const runnerConfig = testConfig.runners || testConfig.test; - if (runnerConfig && runnerConfig.length > 0) { - if (testConfig.runners) { - runUnitTests = runnerConfig.indexOf("unittest") !== -1; + if (testConfig.stackTraceLimit === "full") { + (Error).stackTraceLimit = Infinity; + stackTraceLimit = testConfig.stackTraceLimit; + } + else if ((+testConfig.stackTraceLimit! | 0) > 0) { + (Error).stackTraceLimit = +testConfig.stackTraceLimit! | 0; + stackTraceLimit = +testConfig.stackTraceLimit! | 0; + } + if (testConfig.listenForWork) { + return true; } - for (const option of runnerConfig) { - if (!option) { - continue; - } - if (!configOption) { - configOption = option; - } - else { - configOption += "+" + option; + const runnerConfig = testConfig.runners || testConfig.test; + if (runnerConfig && runnerConfig.length > 0) { + if (testConfig.runners) { + runUnitTests = runnerConfig.indexOf("unittest") !== -1; } + for (const option of runnerConfig) { + if (!option) { + continue; + } + + if (!configOption) { + configOption = option; + } + else { + configOption += "+" + option; + } - switch (option) { - case "compiler": - runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); - runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); - break; - case "conformance": - runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); - break; - case "project": - runners.push(new project.ProjectRunner()); - break; - case "fourslash": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); - break; - case "fourslash-shims": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); - break; - case "fourslash-shims-pp": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); - break; - case "fourslash-server": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); - break; - case "fourslash-generated": - runners.push(new GeneratedFourslashRunner(FourSlash.FourSlashTestType.Native)); - break; - case "rwc": - runners.push(new RWCRunner()); - break; - case "test262": - runners.push(new Test262BaselineRunner()); - break; - case "user": - runners.push(new UserCodeRunner()); - break; - case "dt": - runners.push(new DefinitelyTypedRunner()); - break; - case "docker": - runners.push(new DockerfileRunner()); - break; + switch (option) { + case "compiler": + runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); + runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); + break; + case "conformance": + runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); + break; + case "project": + runners.push(new project.ProjectRunner()); + break; + case "fourslash": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); + break; + case "fourslash-shims": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); + break; + case "fourslash-shims-pp": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); + break; + case "fourslash-server": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); + break; + case "fourslash-generated": + runners.push(new GeneratedFourslashRunner(FourSlash.FourSlashTestType.Native)); + break; + case "rwc": + runners.push(new RWC.RWCRunner()); + break; + case "test262": + runners.push(new Test262BaselineRunner()); + break; + case "user": + runners.push(new UserCodeRunner()); + break; + case "dt": + runners.push(new DefinitelyTypedRunner()); + break; + case "docker": + runners.push(new DockerfileRunner()); + break; + } } } } - } - if (runners.length === 0) { - // compiler - runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); - runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); + if (runners.length === 0) { + // compiler + runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); + runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); - runners.push(new project.ProjectRunner()); + runners.push(new project.ProjectRunner()); - // language services - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); - // runners.push(new GeneratedFourslashRunner()); + // language services + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); + // runners.push(new GeneratedFourslashRunner()); - // CRON-only tests - if (process.env.TRAVIS_EVENT_TYPE === "cron") { - runners.push(new UserCodeRunner()); - runners.push(new DockerfileRunner()); + // CRON-only tests + if (process.env.TRAVIS_EVENT_TYPE === "cron") { + runners.push(new UserCodeRunner()); + runners.push(new DockerfileRunner()); + } } + if (runUnitTests === undefined) { + runUnitTests = runners.length !== 1; // Don't run unit tests when running only one runner if unit tests were not explicitly asked for + } + return false; } - if (runUnitTests === undefined) { - runUnitTests = runners.length !== 1; // Don't run unit tests when running only one runner if unit tests were not explicitly asked for - } - return false; -} -function beginTests() { - if (ts.Debug.isDebugging) { - ts.Debug.enableDebugInfo(); - } + function beginTests() { + if (ts.Debug.isDebugging) { + ts.Debug.enableDebugInfo(); + } - // run tests in en-US by default. - let savedUILocale: string | undefined; - beforeEach(() => { - savedUILocale = ts.getUILocale(); - ts.setUILocale("en-US"); - }); - afterEach(() => ts.setUILocale(savedUILocale)); + // run tests in en-US by default. + let savedUILocale: string | undefined; + beforeEach(() => { + savedUILocale = ts.getUILocale(); + ts.setUILocale("en-US"); + }); + afterEach(() => ts.setUILocale(savedUILocale)); - runTests(runners); + runTests(runners); - if (!runUnitTests) { - // patch `describe` to skip unit tests - (global as any).describe = ts.noop; + if (!runUnitTests) { + // patch `describe` to skip unit tests + (global as any).describe = ts.noop; + } } -} -let isWorker: boolean; -function startTestEnvironment() { - isWorker = handleTestConfig(); - if (isWorker) { - return Harness.Parallel.Worker.start(); - } - else if (taskConfigsFolder && workerCount && workerCount > 1) { - return Harness.Parallel.Host.start(); + export let isWorker: boolean; + function startTestEnvironment() { + isWorker = handleTestConfig(); + if (isWorker) { + return Parallel.Worker.start(); + } + else if (taskConfigsFolder && workerCount && workerCount > 1) { + return Parallel.Host.start(); + } + beginTests(); } - beginTests(); -} -startTestEnvironment(); + startTestEnvironment(); +} \ No newline at end of file diff --git a/src/testRunner/rwcRunner.ts b/src/testRunner/rwcRunner.ts index 3ae381c6ab4eb..0a3e0e090b7f2 100644 --- a/src/testRunner/rwcRunner.ts +++ b/src/testRunner/rwcRunner.ts @@ -1,18 +1,18 @@ // In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. namespace RWC { - function runWithIOLog(ioLog: IoLog, fn: (oldIO: Harness.IO) => void) { + function runWithIOLog(ioLog: Playback.IoLog, fn: (oldIO: Harness.IO) => void) { const oldIO = Harness.IO; const wrappedIO = Playback.wrapIO(oldIO); wrappedIO.startReplayFromData(ioLog); - Harness.IO = wrappedIO; + Harness.setHarnessIO(wrappedIO); try { fn(oldIO); } finally { wrappedIO.endReplay(); - Harness.IO = oldIO; + Harness.setHarnessIO(oldIO); } } @@ -51,7 +51,7 @@ namespace RWC { this.timeout(800_000); // Allow long timeouts for RWC compilations let opts!: ts.ParsedCommandLine; - const ioLog: IoLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)!), Harness.IO, `internal/cases/rwc/${baseName}`); + 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, () => { @@ -207,29 +207,29 @@ namespace RWC { }); }); } -} -class RWCRunner extends RunnerBase { - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return Harness.IO.getDirectories("internal/cases/rwc/"); - } + export class RWCRunner extends Harness.RunnerBase { + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return Harness.IO.getDirectories("internal/cases/rwc/"); + } - public kind(): TestRunnerKind { - return "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); + /** 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) { - RWC.runRWCTest(jsonFileName); + private runTest(jsonFileName: string) { + runRWCTest(jsonFileName); + } } } diff --git a/src/testRunner/test262Runner.ts b/src/testRunner/test262Runner.ts index 030b5b7b10263..728d3fb322790 100644 --- a/src/testRunner/test262Runner.ts +++ b/src/testRunner/test262Runner.ts @@ -1,108 +1,110 @@ -// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. -class Test262BaselineRunner extends RunnerBase { - private static readonly basePath = "internal/cases/test262"; - private static readonly helpersFilePath = "tests/cases/test262-harness/helpers.d.ts"; - private static readonly helperFile: Harness.Compiler.TestFile = { - unitName: Test262BaselineRunner.helpersFilePath, - content: Harness.IO.readFile(Test262BaselineRunner.helpersFilePath)!, - }; - private static readonly testFileExtensionRegex = /\.js$/; - private static readonly options: ts.CompilerOptions = { - allowNonTsExtensions: true, - target: ts.ScriptTarget.Latest, - module: ts.ModuleKind.CommonJS - }; - private static readonly baselineOptions: Harness.Baseline.BaselineOptions = { - Subfolder: "test262", - Baselinefolder: "internal/baselines" - }; +namespace Harness { + // In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. + export class Test262BaselineRunner extends RunnerBase { + private static readonly basePath = "internal/cases/test262"; + private static readonly helpersFilePath = "tests/cases/test262-harness/helpers.d.ts"; + private static readonly helperFile: Compiler.TestFile = { + unitName: Test262BaselineRunner.helpersFilePath, + content: IO.readFile(Test262BaselineRunner.helpersFilePath)!, + }; + private static readonly testFileExtensionRegex = /\.js$/; + private static readonly options: ts.CompilerOptions = { + allowNonTsExtensions: true, + target: ts.ScriptTarget.Latest, + module: ts.ModuleKind.CommonJS + }; + private static readonly baselineOptions: Baseline.BaselineOptions = { + Subfolder: "test262", + Baselinefolder: "internal/baselines" + }; - private static getTestFilePath(filename: string): string { - return Test262BaselineRunner.basePath + "/" + filename; - } + private static getTestFilePath(filename: string): string { + return Test262BaselineRunner.basePath + "/" + filename; + } - private runTest(filePath: string) { - describe("test262 test for " + filePath, () => { - // Mocha holds onto the closure environment of the describe callback even after the test is done. - // Everything declared here should be cleared out in the "after" callback. - let testState: { - filename: string; - compilerResult: compiler.CompilationResult; - inputFiles: Harness.Compiler.TestFile[]; - }; + private runTest(filePath: string) { + describe("test262 test for " + filePath, () => { + // Mocha holds onto the closure environment of the describe callback even after the test is done. + // Everything declared here should be cleared out in the "after" callback. + let testState: { + filename: string; + compilerResult: compiler.CompilationResult; + inputFiles: Compiler.TestFile[]; + }; - before(() => { - const content = Harness.IO.readFile(filePath)!; - const testFilename = ts.removeFileExtension(filePath).replace(/\//g, "_") + ".test"; - const testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, testFilename); + before(() => { + const content = IO.readFile(filePath)!; + const testFilename = ts.removeFileExtension(filePath).replace(/\//g, "_") + ".test"; + const testCaseContent = TestCaseParser.makeUnitsFromTest(content, testFilename); - const inputFiles: Harness.Compiler.TestFile[] = testCaseContent.testUnitData.map(unit => { - const unitName = Test262BaselineRunner.getTestFilePath(unit.name); - return { unitName, content: unit.content }; - }); + const inputFiles: Compiler.TestFile[] = testCaseContent.testUnitData.map(unit => { + const unitName = Test262BaselineRunner.getTestFilePath(unit.name); + return { unitName, content: unit.content }; + }); - // Emit the results - testState = { - filename: testFilename, - inputFiles, - compilerResult: undefined!, // TODO: GH#18217 - }; + // Emit the results + testState = { + filename: testFilename, + inputFiles, + compilerResult: undefined!, // TODO: GH#18217 + }; - testState.compilerResult = Harness.Compiler.compileFiles( - [Test262BaselineRunner.helperFile].concat(inputFiles), - /*otherFiles*/ [], - /* harnessOptions */ undefined, - Test262BaselineRunner.options, - /* currentDirectory */ undefined); - }); + testState.compilerResult = Compiler.compileFiles( + [Test262BaselineRunner.helperFile].concat(inputFiles), + /*otherFiles*/ [], + /* harnessOptions */ undefined, + Test262BaselineRunner.options, + /* currentDirectory */ undefined); + }); - after(() => { - testState = undefined!; - }); + after(() => { + testState = undefined!; + }); - it("has the expected emitted code", () => { - const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath); - Harness.Baseline.runBaseline(testState.filename + ".output.js", Harness.Compiler.collateOutputs(files), Test262BaselineRunner.baselineOptions); - }); + it("has the expected emitted code", () => { + const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath); + Baseline.runBaseline(testState.filename + ".output.js", Compiler.collateOutputs(files), Test262BaselineRunner.baselineOptions); + }); - it("has the expected errors", () => { - const errors = testState.compilerResult.diagnostics; - // eslint-disable-next-line no-null/no-null - const baseline = errors.length === 0 ? null : Harness.Compiler.getErrorBaseline(testState.inputFiles, errors); - Harness.Baseline.runBaseline(testState.filename + ".errors.txt", baseline, Test262BaselineRunner.baselineOptions); - }); + it("has the expected errors", () => { + const errors = testState.compilerResult.diagnostics; + // eslint-disable-next-line no-null/no-null + const baseline = errors.length === 0 ? null : Compiler.getErrorBaseline(testState.inputFiles, errors); + Baseline.runBaseline(testState.filename + ".errors.txt", baseline, Test262BaselineRunner.baselineOptions); + }); - it("satisfies invariants", () => { - const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename)); - Utils.assertInvariants(sourceFile, /*parent:*/ undefined); - }); + it("satisfies invariants", () => { + const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename)); + Utils.assertInvariants(sourceFile, /*parent:*/ undefined); + }); - it("has the expected AST", () => { - const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename))!; - Harness.Baseline.runBaseline(testState.filename + ".AST.txt", Utils.sourceFileToJSON(sourceFile), Test262BaselineRunner.baselineOptions); + it("has the expected AST", () => { + const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename))!; + Baseline.runBaseline(testState.filename + ".AST.txt", Utils.sourceFileToJSON(sourceFile), Test262BaselineRunner.baselineOptions); + }); }); - }); - } - - public kind(): TestRunnerKind { - return "test262"; - } + } - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return ts.map(this.enumerateFiles(Test262BaselineRunner.basePath, Test262BaselineRunner.testFileExtensionRegex, { recursive: true }), ts.normalizePath); - } + public kind(): TestRunnerKind { + return "test262"; + } - public initializeTests() { - // this will set up a series of describe/it blocks to run between the setup and cleanup phases - if (this.tests.length === 0) { - const testFiles = this.getTestFiles(); - testFiles.forEach(fn => { - this.runTest(fn); - }); + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return ts.map(this.enumerateFiles(Test262BaselineRunner.basePath, Test262BaselineRunner.testFileExtensionRegex, { recursive: true }), ts.normalizePath); } - else { - this.tests.forEach(test => this.runTest(typeof test === "string" ? test : test.file)); + + public initializeTests() { + // this will set up a series of describe/it blocks to run between the setup and cleanup phases + if (this.tests.length === 0) { + const testFiles = this.getTestFiles(); + testFiles.forEach(fn => { + this.runTest(fn); + }); + } + else { + this.tests.forEach(test => this.runTest(typeof test === "string" ? test : test.file)); + } } } -} +} \ No newline at end of file diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 6c120e3034908..28c240ef8b8e3 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -24,6 +24,16 @@ ], "files": [ + "compilerRef.ts", + "evaluatorRef.ts", + "fakesRef.ts", + "vpathRef.ts", + "vfsRef.ts", + "fourslashRef.ts", + "playbackRef.ts", + "utilsRef.ts", + "documentsRef.ts", + "fourslashRunner.ts", "compilerRunner.ts", "projectsRunner.ts", diff --git a/src/testRunner/unittests/incrementalParser.ts b/src/testRunner/unittests/incrementalParser.ts index 5884067d9aa86..8e00feefc6eee 100644 --- a/src/testRunner/unittests/incrementalParser.ts +++ b/src/testRunner/unittests/incrementalParser.ts @@ -1,8 +1,4 @@ namespace ts { - // make clear this is a global mutation! - // eslint-disable-next-line @typescript-eslint/no-unnecessary-qualifier - ts.disableIncrementalParsing = false; - function withChange(text: IScriptSnapshot, start: number, length: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { const contents = getSnapshotText(text); const newContents = contents.substr(0, start) + newText + contents.substring(start + length); diff --git a/src/testRunner/unittests/semver.ts b/src/testRunner/unittests/semver.ts index 079bdc3d512ad..b3447ca77b09e 100644 --- a/src/testRunner/unittests/semver.ts +++ b/src/testRunner/unittests/semver.ts @@ -1,5 +1,5 @@ namespace ts { - import theory = utils.theory; + import theory = Utils.theory; describe("unittests:: semver", () => { describe("Version", () => { function assertVersion(version: Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) { diff --git a/src/testRunner/unittests/services/languageService.ts b/src/testRunner/unittests/services/languageService.ts index d9a69dcd2e66b..557ed9370c1d8 100644 --- a/src/testRunner/unittests/services/languageService.ts +++ b/src/testRunner/unittests/services/languageService.ts @@ -1,4 +1,6 @@ namespace ts { + const _chai: typeof import("chai") = require("chai"); + const expect: typeof _chai.expect = _chai.expect; describe("unittests:: services:: languageService", () => { const files: {[index: string]: string} = { "foo.ts": `import Vue from "./vue"; diff --git a/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts b/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts index fb580a14d45ad..433f5443098e0 100644 --- a/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts +++ b/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts @@ -4,14 +4,14 @@ namespace ts { scenario: "javascriptProjectEmit", subScenario: `loads js-based projects and emits them correctly`, fs: () => loadProjectFromFiles({ - "/src/common/nominal.js": utils.dedent` + "/src/common/nominal.js": Utils.dedent` /** * @template T, Name * @typedef {T & {[Symbol.species]: Name}} Nominal */ module.exports = {}; `, - "/src/common/tsconfig.json": utils.dedent` + "/src/common/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -19,14 +19,14 @@ namespace ts { }, "include": ["nominal.js"] }`, - "/src/sub-project/index.js": utils.dedent` + "/src/sub-project/index.js": Utils.dedent` import { Nominal } from '../common/nominal'; /** * @typedef {Nominal} MyNominal */ `, - "/src/sub-project/tsconfig.json": utils.dedent` + "/src/sub-project/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -37,7 +37,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/sub-project-2/index.js": utils.dedent` + "/src/sub-project-2/index.js": Utils.dedent` import { MyNominal } from '../sub-project/index'; const variable = { @@ -51,7 +51,7 @@ namespace ts { return 'key'; } `, - "/src/sub-project-2/tsconfig.json": utils.dedent` + "/src/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -62,7 +62,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/tsconfig.json": utils.dedent` + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true @@ -73,7 +73,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -93,13 +93,13 @@ namespace ts { let projFs: vfs.FileSystem; before(() => { projFs = loadProjectFromFiles({ - "/src/common/nominal.js": utils.dedent` + "/src/common/nominal.js": Utils.dedent` /** * @template T, Name * @typedef {T & {[Symbol.species]: Name}} Nominal */ `, - "/src/common/tsconfig.json": utils.dedent` + "/src/common/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -108,13 +108,13 @@ namespace ts { }, "include": ["nominal.js"] }`, - "/src/sub-project/index.js": utils.dedent` + "/src/sub-project/index.js": Utils.dedent` /** * @typedef {Nominal} MyNominal */ const c = /** @type {*} */(null); `, - "/src/sub-project/tsconfig.json": utils.dedent` + "/src/sub-project/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -126,7 +126,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/sub-project-2/index.js": utils.dedent` + "/src/sub-project-2/index.js": Utils.dedent` const variable = { key: /** @type {MyNominal} */('value'), }; @@ -138,7 +138,7 @@ namespace ts { return 'key'; } `, - "/src/sub-project-2/tsconfig.json": utils.dedent` + "/src/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -150,7 +150,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/tsconfig.json": utils.dedent` + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true, @@ -162,7 +162,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -200,15 +200,15 @@ namespace ts { scenario: "javascriptProjectEmit", subScenario: `loads js-based projects with non-moved json files and emits them correctly`, fs: () => loadProjectFromFiles({ - "/src/common/obj.json": utils.dedent` + "/src/common/obj.json": Utils.dedent` { "val": 42 }`, - "/src/common/index.ts": utils.dedent` + "/src/common/index.ts": Utils.dedent` import x = require("./obj.json"); export = x; `, - "/src/common/tsconfig.json": utils.dedent` + "/src/common/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -217,12 +217,12 @@ namespace ts { }, "include": ["index.ts", "obj.json"] }`, - "/src/sub-project/index.js": utils.dedent` + "/src/sub-project/index.js": Utils.dedent` import mod from '../common'; export const m = mod; `, - "/src/sub-project/tsconfig.json": utils.dedent` + "/src/sub-project/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -233,7 +233,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/sub-project-2/index.js": utils.dedent` + "/src/sub-project-2/index.js": Utils.dedent` import { m } from '../sub-project/index'; const variable = { @@ -244,7 +244,7 @@ namespace ts { return variable; } `, - "/src/sub-project-2/tsconfig.json": utils.dedent` + "/src/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -255,7 +255,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/tsconfig.json": utils.dedent` + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true @@ -266,7 +266,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, diff --git a/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts b/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts index 81d8e170e5d71..8494fd9498a36 100644 --- a/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts +++ b/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts @@ -5,12 +5,12 @@ namespace ts { scenario: "moduleSpecifiers", subScenario: `synthesized module specifiers resolve correctly`, fs: () => loadProjectFromFiles({ - "/src/solution/common/nominal.ts": utils.dedent` + "/src/solution/common/nominal.ts": Utils.dedent` export declare type Nominal = T & { [Symbol.species]: Name; }; `, - "/src/solution/common/tsconfig.json": utils.dedent` + "/src/solution/common/tsconfig.json": Utils.dedent` { "extends": "../../tsconfig.base.json", "compilerOptions": { @@ -18,12 +18,12 @@ namespace ts { }, "include": ["nominal.ts"] }`, - "/src/solution/sub-project/index.ts": utils.dedent` + "/src/solution/sub-project/index.ts": Utils.dedent` import { Nominal } from '../common/nominal'; export type MyNominal = Nominal; `, - "/src/solution/sub-project/tsconfig.json": utils.dedent` + "/src/solution/sub-project/tsconfig.json": Utils.dedent` { "extends": "../../tsconfig.base.json", "compilerOptions": { @@ -34,7 +34,7 @@ namespace ts { ], "include": ["./index.ts"] }`, - "/src/solution/sub-project-2/index.ts": utils.dedent` + "/src/solution/sub-project-2/index.ts": Utils.dedent` import { MyNominal } from '../sub-project/index'; const variable = { @@ -45,7 +45,7 @@ namespace ts { return 'key'; } `, - "/src/solution/sub-project-2/tsconfig.json": utils.dedent` + "/src/solution/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../../tsconfig.base.json", "compilerOptions": { @@ -56,7 +56,7 @@ namespace ts { ], "include": ["./index.ts"] }`, - "/src/solution/tsconfig.json": utils.dedent` + "/src/solution/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true @@ -67,7 +67,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -75,7 +75,7 @@ namespace ts { "outDir": "lib", } }`, - "/src/tsconfig.json": utils.dedent`{ + "/src/tsconfig.json": Utils.dedent`{ "compilerOptions": { "composite": true }, diff --git a/src/testRunner/unittests/tsc/declarationEmit.ts b/src/testRunner/unittests/tsc/declarationEmit.ts index 5577ad2202314..bad536ed60889 100644 --- a/src/testRunner/unittests/tsc/declarationEmit.ts +++ b/src/testRunner/unittests/tsc/declarationEmit.ts @@ -4,12 +4,12 @@ namespace ts { scenario: "declarationEmit", subScenario: "when same version is referenced through source and another symlinked package", fs: () => { - const fsaPackageJson = utils.dedent` + const fsaPackageJson = Utils.dedent` { "name": "typescript-fsa", "version": "3.0.0-beta-2" }`; - const fsaIndex = utils.dedent` + const fsaIndex = Utils.dedent` export interface Action { type: string; payload: Payload; @@ -24,7 +24,7 @@ namespace ts { export declare function actionCreatorFactory(prefix?: string | null): ActionCreatorFactory; export default actionCreatorFactory;`; return loadProjectFromFiles({ - "/src/plugin-two/index.d.ts": utils.dedent` + "/src/plugin-two/index.d.ts": Utils.dedent` declare const _default: { features: { featureOne: { @@ -48,16 +48,16 @@ namespace ts { export default _default;`, "/src/plugin-two/node_modules/typescript-fsa/package.json": fsaPackageJson, "/src/plugin-two/node_modules/typescript-fsa/index.d.ts": fsaIndex, - "/src/plugin-one/tsconfig.json": utils.dedent` + "/src/plugin-one/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", "declaration": true, }, }`, - "/src/plugin-one/index.ts": utils.dedent` + "/src/plugin-one/index.ts": Utils.dedent` import pluginTwo from "plugin-two"; // include this to add reference to symlink`, - "/src/plugin-one/action.ts": utils.dedent` + "/src/plugin-one/action.ts": Utils.dedent` import { actionCreatorFactory } from "typescript-fsa"; // Include version of shared lib const action = actionCreatorFactory("somekey"); const featureOne = action<{ route: string }>("feature-one"); @@ -74,9 +74,9 @@ namespace ts { scenario: "declarationEmit", subScenario: "when pkg references sibling package through indirect symlink", fs: () => loadProjectFromFiles({ - "/src/pkg1/dist/index.d.ts": utils.dedent` + "/src/pkg1/dist/index.d.ts": Utils.dedent` export * from './types';`, - "/src/pkg1/dist/types.d.ts": utils.dedent` + "/src/pkg1/dist/types.d.ts": Utils.dedent` export declare type A = { id: string; }; @@ -90,7 +90,7 @@ namespace ts { toString(): string; static create(key: string): MetadataAccessor; }`, - "/src/pkg1/package.json": utils.dedent` + "/src/pkg1/package.json": Utils.dedent` { "name": "@raymondfeng/pkg1", "version": "1.0.0", @@ -98,11 +98,11 @@ namespace ts { "main": "dist/index.js", "typings": "dist/index.d.ts" }`, - "/src/pkg2/dist/index.d.ts": utils.dedent` + "/src/pkg2/dist/index.d.ts": Utils.dedent` export * from './types';`, - "/src/pkg2/dist/types.d.ts": utils.dedent` + "/src/pkg2/dist/types.d.ts": Utils.dedent` export {MetadataAccessor} from '@raymondfeng/pkg1';`, - "/src/pkg2/package.json": utils.dedent` + "/src/pkg2/package.json": Utils.dedent` { "name": "@raymondfeng/pkg2", "version": "1.0.0", @@ -110,12 +110,12 @@ namespace ts { "main": "dist/index.js", "typings": "dist/index.d.ts" }`, - "/src/pkg3/src/index.ts": utils.dedent` + "/src/pkg3/src/index.ts": Utils.dedent` export * from './keys';`, - "/src/pkg3/src/keys.ts": utils.dedent` + "/src/pkg3/src/keys.ts": Utils.dedent` import {MetadataAccessor} from "@raymondfeng/pkg2"; export const ADMIN = MetadataAccessor.create('1');`, - "/src/pkg3/tsconfig.json": utils.dedent` + "/src/pkg3/tsconfig.json": Utils.dedent` { "compilerOptions": { "outDir": "dist", diff --git a/src/testRunner/unittests/tsc/incremental.ts b/src/testRunner/unittests/tsc/incremental.ts index 17cc7f49bdc54..9075bfb777f48 100644 --- a/src/testRunner/unittests/tsc/incremental.ts +++ b/src/testRunner/unittests/tsc/incremental.ts @@ -5,7 +5,7 @@ namespace ts { subScenario: "when passing filename for buildinfo on commandline", fs: () => loadProjectFromFiles({ "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": utils.dedent` + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -25,7 +25,7 @@ namespace ts { subScenario: "when passing rootDir from commandline", fs: () => loadProjectFromFiles({ "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": utils.dedent` + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "incremental": true, @@ -60,7 +60,7 @@ namespace ts { subScenario: "when passing rootDir is in the tsconfig", fs: () => loadProjectFromFiles({ "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": utils.dedent` + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "incremental": true, diff --git a/src/testRunner/unittests/tsc/listFilesOnly.ts b/src/testRunner/unittests/tsc/listFilesOnly.ts index 5655d3d76148f..97c9bd7d5e43c 100644 --- a/src/testRunner/unittests/tsc/listFilesOnly.ts +++ b/src/testRunner/unittests/tsc/listFilesOnly.ts @@ -4,7 +4,7 @@ namespace ts { scenario: "listFilesOnly", subScenario: "combined with watch", fs: () => loadProjectFromFiles({ - "/src/test.ts": utils.dedent` + "/src/test.ts": Utils.dedent` export const x = 1;`, }), commandLineArgs: ["/src/test.ts", "--watch", "--listFilesOnly"] @@ -14,7 +14,7 @@ namespace ts { scenario: "listFilesOnly", subScenario: "loose file", fs: () => loadProjectFromFiles({ - "/src/test.ts": utils.dedent` + "/src/test.ts": Utils.dedent` export const x = 1;`, }), commandLineArgs: ["/src/test.ts", "--listFilesOnly"] diff --git a/src/testRunner/unittests/tscWatch/incremental.ts b/src/testRunner/unittests/tscWatch/incremental.ts index 0217ed91ee346..d966b77005a4f 100644 --- a/src/testRunner/unittests/tscWatch/incremental.ts +++ b/src/testRunner/unittests/tscWatch/incremental.ts @@ -194,7 +194,7 @@ namespace ts.tscWatch { exportedModulesMap: {}, semanticDiagnosticsPerFile: [libFilePath, file1Path, file2Path] }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -221,7 +221,7 @@ namespace ts.tscWatch { exportedModulesMap: {}, semanticDiagnosticsPerFile: [libFilePath, file1Path, file2Path] }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -285,7 +285,7 @@ namespace ts.tscWatch { file2ReuasableError ] }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -315,7 +315,7 @@ namespace ts.tscWatch { file2ReuasableError ] }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -348,7 +348,7 @@ namespace ts.tscWatch { ] }, }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -420,7 +420,7 @@ namespace ts.tscWatch { exportedModulesMap: {}, semanticDiagnosticsPerFile: [libFilePath, file1Path, file2Path] }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -446,7 +446,7 @@ namespace ts.tscWatch { exportedModulesMap: {}, semanticDiagnosticsPerFile: [libFilePath, file1Path, file2Path] }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -506,7 +506,7 @@ namespace ts.tscWatch { file2ReuasableError ] }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -536,7 +536,7 @@ namespace ts.tscWatch { file1Path ] }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -620,7 +620,7 @@ namespace ts.tscWatch { ] }, }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -729,7 +729,7 @@ export { C } from "./c"; path: `${project}/tsconfig.tsbuildinfo`, content: getBuildInfoText({ program: initialProgram, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], @@ -753,7 +753,7 @@ export { C } from "./c"; }, ...rest }, - version + version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }) } ], diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index 71280330e8cd7..f5a7073869d11 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -276,7 +276,7 @@ namespace ts.projectSystem { configFileName: "tsconfig.json", projectType: "configured", languageServiceEnabled: true, - version, + version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier ...partial, }); } diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index a16c4f57817ae..61ce6001c2d51 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -1,6 +1,6 @@ -const expect: typeof _chai.expect = _chai.expect; - namespace ts.server { + const _chai: typeof import("chai") = require("chai"); + const expect: typeof _chai.expect = _chai.expect; let lastWrittenToHost: string; const noopFileWatcher: FileWatcher = { close: noop }; const mockHost: ServerHost = { @@ -179,7 +179,9 @@ namespace ts.server { type: "request" }; - const expected: protocol.StatusResponseBody = { version }; + const expected: protocol.StatusResponseBody = { + version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier + }; assert.deepEqual(session.executeCommand(req).response, expected); }); }); diff --git a/src/testRunner/utilsRef.ts b/src/testRunner/utilsRef.ts new file mode 100644 index 0000000000000..6d34691c828b0 --- /dev/null +++ b/src/testRunner/utilsRef.ts @@ -0,0 +1,2 @@ +// empty ref to Utils so it can be referenced by unittests +namespace Utils {} \ No newline at end of file diff --git a/src/testRunner/vfsRef.ts b/src/testRunner/vfsRef.ts new file mode 100644 index 0000000000000..232efaab17874 --- /dev/null +++ b/src/testRunner/vfsRef.ts @@ -0,0 +1,2 @@ +// empty ref to vfs so it can be referenced by unittests +namespace vfs {} \ No newline at end of file diff --git a/src/testRunner/vpathRef.ts b/src/testRunner/vpathRef.ts new file mode 100644 index 0000000000000..80291760bca1e --- /dev/null +++ b/src/testRunner/vpathRef.ts @@ -0,0 +1,2 @@ +// empty ref to vpath so it can be referenced by unittests +namespace vpath {} \ No newline at end of file diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 11a5f9c1aef46..07e5407d7060a 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5876,7 +5876,6 @@ declare namespace ts { function getDefaultCompilerOptions(): CompilerOptions; function getSupportedCodeFixes(): string[]; function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile; - let disableIncrementalParsing: boolean; function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile; function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService; /** diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 665a419ae57fa..5cec6985aa46c 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5876,7 +5876,6 @@ declare namespace ts { function getDefaultCompilerOptions(): CompilerOptions; function getSupportedCodeFixes(): string[]; function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile; - let disableIncrementalParsing: boolean; function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile; function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService; /**