mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Tests build and run
But editorServices needs some fixes. At least, createDirectory needs a recursive version.
This commit is contained in:
@@ -2,19 +2,13 @@
|
||||
// support the eventual conversion of harness into a modular system.
|
||||
|
||||
namespace documents {
|
||||
export interface HarnessCompilerTestFile {
|
||||
unitName: string;
|
||||
content: string;
|
||||
fileOptions?: any;
|
||||
}
|
||||
|
||||
export class TextDocument {
|
||||
public readonly meta: Map<string, string>;
|
||||
public readonly file: string;
|
||||
public readonly text: string;
|
||||
|
||||
private _lineStarts: readonly number[] | undefined;
|
||||
private _testFile: HarnessCompilerTestFile | undefined;
|
||||
private _testFile: Harness.Compiler.TestFile | undefined;
|
||||
|
||||
constructor(file: string, text: string, meta?: Map<string, string>) {
|
||||
this.file = file;
|
||||
@@ -26,7 +20,7 @@ namespace documents {
|
||||
return this._lineStarts || (this._lineStarts = ts.computeLineStarts(this.text));
|
||||
}
|
||||
|
||||
public static fromTestFile(file: HarnessCompilerTestFile) {
|
||||
public static fromTestFile(file: Harness.Compiler.TestFile) {
|
||||
return new TextDocument(
|
||||
file.unitName,
|
||||
file.content,
|
||||
|
||||
+35
-197
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Fake implementations of various compiler dependencies.
|
||||
*/
|
||||
namespace fakes {
|
||||
const processExitSentinel = new Error("System exit");
|
||||
|
||||
@@ -211,172 +214,7 @@ namespace fakes {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @implements {server.ServerHost} but that would create a circular dependency
|
||||
*/
|
||||
export class FakeServerHost extends System {
|
||||
timeoutCallbacks = new Callbacks()
|
||||
immediateCallbacks = new Callbacks()
|
||||
readonly watchedFiles = ts.createMultiMap<ts.Path, TestFileWatcher>();
|
||||
readonly fsWatches = ts.createMultiMap<ts.Path, TestFsWatcher>();
|
||||
readonly fsWatchesRecursive = ts.createMultiMap<ts.Path, TestFsWatcher>();
|
||||
readonly currentDirectory: string // TODO: Needed? Probably not
|
||||
readonly toPath: (f: string) => ts.Path;
|
||||
watchFile: ts.HostWatchFile
|
||||
watchDirectory: ts.HostWatchDirectory
|
||||
constructor(vfs: vfs.FileSystem, options: SystemOptions = {}) {
|
||||
super(vfs, options)
|
||||
|
||||
this.currentDirectory = '/'; // THIS IS FINE
|
||||
this.toPath = s => ts.toPath(s, this.currentDirectory, s => s);
|
||||
|
||||
const { watchFile, watchDirectory } = ts.createSystemWatchFunctions({
|
||||
// We dont have polling watch file
|
||||
// it is essentially fsWatch but lets get that separate from fsWatch and
|
||||
// into watchedFiles for easier testing
|
||||
pollingWatchFile: /*tscWatchFile === Tsc_WatchFile.SingleFileWatcherPerName ?
|
||||
createSingleFileWatcherPerName(
|
||||
this.watchFileWorker.bind(this),
|
||||
this.useCaseSensitiveFileNames
|
||||
) :*/
|
||||
this.watchFileWorker.bind(this),
|
||||
getModifiedTime: this.getModifiedTime.bind(this),
|
||||
setTimeout: this.setTimeout.bind(this),
|
||||
clearTimeout: this.clearTimeout.bind(this),
|
||||
fsWatch: this.fsWatch.bind(this),
|
||||
fileExists: this.fileExists.bind(this),
|
||||
useCaseSensitiveFileNames: this.useCaseSensitiveFileNames,
|
||||
getCurrentDirectory: this.getCurrentDirectory.bind(this),
|
||||
// TODO: Tests usually set "run without recursive watches" to true, except for two tests
|
||||
// watchOptions/with excludeFiles option${...}
|
||||
// and the same, but excludeDirectories
|
||||
// .............guessing that it's true on a real FS
|
||||
fsSupportsRecursiveFsWatch: true, /*tscWatchDirectory ? false : !runWithoutRecursiveWatches */
|
||||
directoryExists: this.directoryExists.bind(this),
|
||||
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
|
||||
realpath: this.realpath.bind(this),
|
||||
tscWatchFile: undefined,
|
||||
tscWatchDirectory: undefined,
|
||||
defaultWatchFileKind: () => undefined, // () => this.defaultWatchFileKind?.(),
|
||||
})
|
||||
this.watchFile = watchFile
|
||||
this.watchDirectory = watchDirectory
|
||||
}
|
||||
setTimeout(callback: (...args: any[]) => void, _ms: number, ...args: any[]): any {
|
||||
return this.timeoutCallbacks.register(callback, args)
|
||||
}
|
||||
clearTimeout(timeoutId: any): void {
|
||||
this.timeoutCallbacks.unregister(timeoutId)
|
||||
}
|
||||
setImmediate(callback: (...args: any[]) => void, ...args: any[]): any {
|
||||
return this.immediateCallbacks.register(callback, args)
|
||||
}
|
||||
clearImmediate(timeoutId: any): void {
|
||||
this.immediateCallbacks.unregister(timeoutId)
|
||||
}
|
||||
watchFileWorker(fileName: string, cb: ts.FileWatcherCallback, pollingInterval: ts.PollingInterval) {
|
||||
console.log('watchfileworker')
|
||||
return createWatcher(
|
||||
this.watchedFiles,
|
||||
this.toFullPath(fileName),
|
||||
{ fileName, cb, pollingInterval }
|
||||
);
|
||||
}
|
||||
|
||||
fsWatch(
|
||||
fileOrDirectory: string,
|
||||
_entryKind: ts.FileSystemEntryKind,
|
||||
cb: ts.FsWatchCallback,
|
||||
recursive: boolean,
|
||||
fallbackPollingInterval: ts.PollingInterval,
|
||||
fallbackOptions: ts.WatchOptions | undefined): ts.FileWatcher {
|
||||
console.log('fswatch')
|
||||
/*return this.runWithFallbackPolling ?
|
||||
this.watchFile(
|
||||
fileOrDirectory,
|
||||
createFileWatcherCallback(cb),
|
||||
fallbackPollingInterval,
|
||||
fallbackOptions
|
||||
) :*/
|
||||
return createWatcher(
|
||||
recursive ? this.fsWatchesRecursive : this.fsWatches,
|
||||
this.toFullPath(fileOrDirectory),
|
||||
{
|
||||
directoryName: fileOrDirectory,
|
||||
cb,
|
||||
fallbackPollingInterval,
|
||||
fallbackOptions
|
||||
}
|
||||
);
|
||||
}
|
||||
toFullPath(s: string) {
|
||||
return this.toPath(this.toNormalizedAbsolutePath(s));
|
||||
}
|
||||
toNormalizedAbsolutePath(s: string) {
|
||||
return ts.getNormalizedAbsolutePath(s, this.currentDirectory);
|
||||
}
|
||||
}
|
||||
function createWatcher<T>(map: ts.MultiMap<ts.Path, T>, path: ts.Path, callback: T): ts.FileWatcher {
|
||||
map.add(path, callback);
|
||||
return { close: () => map.remove(path, callback) };
|
||||
}
|
||||
/** Copied from virtualFileSystemWithWatch */
|
||||
type TimeOutCallback = () => any;
|
||||
/** Copied from virtualFileSystemWithWatch */
|
||||
interface TestFileWatcher {
|
||||
cb: ts.FileWatcherCallback;
|
||||
fileName: string;
|
||||
pollingInterval: ts.PollingInterval;
|
||||
}
|
||||
/** Copied from virtualFileSystemWithWatch */
|
||||
interface TestFsWatcher {
|
||||
cb: ts.FsWatchCallback;
|
||||
directoryName: string;
|
||||
fallbackPollingInterval: ts.PollingInterval;
|
||||
fallbackOptions: ts.WatchOptions | undefined;
|
||||
}
|
||||
/** Copied from virtualFileSystemWithWatch */
|
||||
class Callbacks {
|
||||
private map: TimeOutCallback[] = [];
|
||||
private nextId = 1;
|
||||
getNextId() {
|
||||
return this.nextId;
|
||||
}
|
||||
register(cb: (...args: any[]) => void, args: any[]) {
|
||||
const timeoutId = this.nextId;
|
||||
this.nextId++;
|
||||
this.map[timeoutId] = cb.bind(/*this*/ undefined, ...args);
|
||||
return timeoutId;
|
||||
}
|
||||
unregister(id: any) {
|
||||
if (typeof id === "number") {
|
||||
delete this.map[id];
|
||||
}
|
||||
}
|
||||
count() {
|
||||
// ??????????????????????????????????????????????????????????????????
|
||||
let n = 0;
|
||||
for (const _ in this.map) {
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
invoke(invokeKey?: number) {
|
||||
if (invokeKey) {
|
||||
this.map[invokeKey]();
|
||||
delete this.map[invokeKey];
|
||||
return;
|
||||
}
|
||||
// Note: invoking a callback may result in new callbacks been queued,
|
||||
// so do not clear the entire callback list regardless. Only remove the
|
||||
// ones we have invoked.
|
||||
for (const key in this.map) {
|
||||
this.map[key]();
|
||||
delete this.map[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
/**
|
||||
* A fake `ts.CompilerHost` that leverages a virtual file system.
|
||||
*/
|
||||
export class CompilerHost implements ts.CompilerHost {
|
||||
@@ -386,8 +224,8 @@ namespace fakes {
|
||||
private readonly _outputsMap: collections.SortedMap<string, number>;
|
||||
public readonly traces: string[] = [];
|
||||
public readonly shouldAssertInvariants = !Harness.lightMode;
|
||||
protected _setParentNodes: boolean;
|
||||
protected _sourceFiles: collections.SortedMap<string, ts.SourceFile>;
|
||||
private _setParentNodes: boolean;
|
||||
private _sourceFiles: collections.SortedMap<string, ts.SourceFile>;
|
||||
private _parseConfigHost: ParseConfigHost | undefined;
|
||||
private _newLine: string;
|
||||
|
||||
@@ -546,7 +384,6 @@ namespace fakes {
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type ExpectedDiagnosticMessage = [ts.DiagnosticMessage, ...(string | number)[]];
|
||||
@@ -574,7 +411,7 @@ namespace fakes {
|
||||
|
||||
export type ExpectedDiagnostic = ExpectedDiagnosticMessage | ExpectedErrorDiagnostic;
|
||||
|
||||
export interface SolutionBuilderDiagnostic {
|
||||
interface SolutionBuilderDiagnostic {
|
||||
kind: DiagnosticKind;
|
||||
diagnostic: ts.Diagnostic;
|
||||
}
|
||||
@@ -606,6 +443,32 @@ ${indentText}${text}`;
|
||||
return text;
|
||||
}
|
||||
|
||||
function expectedDiagnosticRelatedInformationToText({ location, ...diagnosticMessage }: ExpectedDiagnosticRelatedInformation) {
|
||||
const text = expectedDiagnosticMessageChainToText(diagnosticMessage);
|
||||
if (location) {
|
||||
const { file, start, length } = location;
|
||||
return `${file}(${start}:${length}):: ${text}`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function expectedErrorDiagnosticToText({ relatedInformation, ...diagnosticRelatedInformation }: ExpectedErrorDiagnostic) {
|
||||
let text = `${DiagnosticKind.Error}!: ${expectedDiagnosticRelatedInformationToText(diagnosticRelatedInformation)}`;
|
||||
if (relatedInformation) {
|
||||
for (const kid of relatedInformation) {
|
||||
text += `
|
||||
related:: ${expectedDiagnosticRelatedInformationToText(kid)}`;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function expectedDiagnosticToText(errorOrStatus: ExpectedDiagnostic) {
|
||||
return ts.isArray(errorOrStatus) ?
|
||||
`${DiagnosticKind.Status}!: ${expectedDiagnosticMessageToText(errorOrStatus)}` :
|
||||
expectedErrorDiagnosticToText(errorOrStatus);
|
||||
}
|
||||
|
||||
function diagnosticMessageChainToText({ messageText, next}: ts.DiagnosticMessageChain, indent = 0) {
|
||||
let text = indentedText(indent, messageText);
|
||||
if (next) {
|
||||
@@ -662,39 +525,14 @@ ${indentText}${text}`;
|
||||
return sys;
|
||||
}
|
||||
|
||||
|
||||
function expectedDiagnosticRelatedInformationToText({ location, ...diagnosticMessage }: ExpectedDiagnosticRelatedInformation) {
|
||||
const text = expectedDiagnosticMessageChainToText(diagnosticMessage);
|
||||
if (location) {
|
||||
const { file, start, length } = location;
|
||||
return `${file}(${start}:${length}):: ${text}`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function expectedErrorDiagnosticToText({ relatedInformation, ...diagnosticRelatedInformation }: ExpectedErrorDiagnostic) {
|
||||
let text = `${DiagnosticKind.Error}!: ${expectedDiagnosticRelatedInformationToText(diagnosticRelatedInformation)}`;
|
||||
if (relatedInformation) {
|
||||
for (const kid of relatedInformation) {
|
||||
text += `
|
||||
related:: ${expectedDiagnosticRelatedInformationToText(kid)}`;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function expectedDiagnosticToText(errorOrStatus: ExpectedDiagnostic) {
|
||||
return ts.isArray(errorOrStatus) ?
|
||||
`${DiagnosticKind.Status}!: ${expectedDiagnosticMessageToText(errorOrStatus)}` :
|
||||
expectedErrorDiagnosticToText(errorOrStatus);
|
||||
}
|
||||
|
||||
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
|
||||
createProgram: ts.CreateProgram<ts.BuilderProgram>;
|
||||
|
||||
private constructor(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>) {
|
||||
super(sys, options, setParentNodes);
|
||||
this.createProgram = createProgram || ts.createEmitAndSemanticDiagnosticsBuilderProgram;
|
||||
}
|
||||
|
||||
static create(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>) {
|
||||
const host = new SolutionBuilderHost(sys, options, setParentNodes, createProgram);
|
||||
patchHostForBuildInfoReadWrite(host.sys);
|
||||
|
||||
@@ -1598,4 +1598,23 @@ namespace vfs {
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export function iteratePatch(patch: FileSet | undefined): IterableIterator<[string, string]> | null {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
return patch ? Harness.Compiler.iterateOutputs(iteratePatchWorker("", patch)) : null;
|
||||
}
|
||||
|
||||
function* iteratePatchWorker(dirname: string, container: FileSet): IterableIterator<documents.TextDocument> {
|
||||
for (const name of Object.keys(container)) {
|
||||
const entry = normalizeFileSetEntry(container[name]);
|
||||
const file = dirname ? vpath.combine(dirname, name) : name;
|
||||
if (entry instanceof Directory) {
|
||||
yield* ts.arrayFrom(iteratePatchWorker(file, entry.files));
|
||||
}
|
||||
else if (entry instanceof File) {
|
||||
const content = typeof entry.data === "string" ? entry.data : entry.data.toString("utf8");
|
||||
yield new documents.TextDocument(file, content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
namespace ts.TestFSWithWatch {
|
||||
export function createWatchedSystem(fileOrFolderList: readonly vfs.FileOrFolderOrSymLink[], params?: vfs.TestServerHostCreationParameters): TestServerHost {
|
||||
export function createWatchedSystem(fileOrFolderList: readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost {
|
||||
return new TestServerHost(/*withSafelist*/ false, fileOrFolderList, params);
|
||||
}
|
||||
|
||||
export function createServerHost(fileOrFolderList: readonly vfs.FileOrFolderOrSymLink[], params?: vfs.TestServerHostCreationParameters): TestServerHost {
|
||||
export function createServerHost(fileOrFolderList: readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost {
|
||||
const host = new TestServerHost(/*withSafelist*/ true, fileOrFolderList, params);
|
||||
// Just like sys, patch the host to use writeFile
|
||||
patchWriteFileEnsuringDirectory(host);
|
||||
return host;
|
||||
}
|
||||
export function verifyMapSize(caption: string, map: ESMap<string, any>, expectedKeys: readonly string[]) {
|
||||
assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}${vfs.getDiffInKeys(map, expectedKeys)}`);
|
||||
assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}${getDiffInKeys(map, expectedKeys)}`);
|
||||
}
|
||||
|
||||
export type MapValueTester<T, U> = [ESMap<string, U[]> | undefined, (value: T) => U];
|
||||
@@ -147,18 +147,18 @@ namespace ts.TestFSWithWatch {
|
||||
return host;
|
||||
}
|
||||
|
||||
export function getTsBuildProjectFile(project: string, file: string): vfs.File {
|
||||
export function getTsBuildProjectFile(project: string, file: string): File {
|
||||
return {
|
||||
path: vfs.getTsBuildProjectFilePath(project, file),
|
||||
path: getTsBuildProjectFilePath(project, file),
|
||||
content: Harness.IO.readFile(`${Harness.IO.getWorkspaceRoot()}/tests/projects/${project}/${file}`)!
|
||||
};
|
||||
}
|
||||
|
||||
export class TestServerHost extends vfs.VirtualServerHost implements server.ServerHost {
|
||||
export class TestServerHost extends VirtualServerHost implements server.ServerHost {
|
||||
constructor(
|
||||
public withSafeList: boolean,
|
||||
fileOrFolderorSymLinkList: readonly vfs.FileOrFolderOrSymLink[],
|
||||
options: vfs.TestServerHostCreationParameters = {}) {
|
||||
fileOrFolderorSymLinkList: readonly FileOrFolderOrSymLink[],
|
||||
options: TestServerHostCreationParameters = {}) {
|
||||
super(withSafeList, fileOrFolderorSymLinkList, options);
|
||||
}
|
||||
runQueuedImmediateCallbacks(checkCount?: number) {
|
||||
|
||||
@@ -758,7 +758,7 @@ namespace ts.server {
|
||||
readonly toCanonicalFileName: (f: string) => string;
|
||||
|
||||
public host: ServerHost;
|
||||
public fs: vfs.VirtualServerHost | undefined;
|
||||
public fs: ts.TestFSWithWatch.VirtualServerHost | undefined;
|
||||
public readonly logger: Logger;
|
||||
public readonly cancellationToken: HostCancellationToken;
|
||||
public readonly useSingleInferredProject: boolean;
|
||||
@@ -3693,7 +3693,7 @@ namespace ts.server {
|
||||
// 1. set some internal tsserver state for mocked FS (if it hasn't already been set, this might not be the first message)
|
||||
if (!this.fs) {
|
||||
// -nervous laugh-
|
||||
this.fs = vfs.createVirtualServerHost([])
|
||||
this.fs = ts.TestFSWithWatch.createVirtualServerHost([])
|
||||
;(this as any).host = this.fs
|
||||
;(this.session as any).host = this.fs
|
||||
}
|
||||
|
||||
@@ -40,18 +40,15 @@ interface Array<T> { length: number; [n: number]: T; }`
|
||||
return `// some copy right notice
|
||||
${'content' in file ? file.content : file.fileContent}`;
|
||||
}
|
||||
function verifyFileSystem(fs: vfs.FileSystem | undefined, files: protocol.FileSystemRequestArgs[]) {
|
||||
// TODO: This is almost certainly in harness/virtualFileSystemHost.ts, or should be.
|
||||
function verifyFileSystem(host: ts.TestFSWithWatch.VirtualServerHost | undefined, files: protocol.FileSystemRequestArgs[]) {
|
||||
// 1. make sure that everything in files is there
|
||||
assert.isDefined(fs)
|
||||
const result = fs!.scanSync('.', "descendants-or-self", {
|
||||
accept: (_, stats) => stats.isFile()
|
||||
})
|
||||
assert.equal(result.length, files.length)
|
||||
let i = 0
|
||||
assert.isDefined(host)
|
||||
const fs = (host as any).fs as ESMap<string, ts.TestFSWithWatch.FSEntry>
|
||||
assert.equal(fs.size, files.length)
|
||||
for (const { file, fileContent } of files) {
|
||||
assert.equal(result[i], file)
|
||||
assert.equal(fs!.readFileSync(file, 'utf8'), fileContent)
|
||||
i++
|
||||
assert(host?.fileExists(file))
|
||||
assert.equal(host!.readFile(file), fileContent)
|
||||
}
|
||||
// 2. make sure nothing else is
|
||||
}
|
||||
@@ -79,9 +76,9 @@ ${'content' in file ? file.content : file.fileContent}`;
|
||||
});
|
||||
const service = session.getProjectService(); // session -> service -> project
|
||||
const project = service.configuredProjects.get(config.file)!;
|
||||
const vfs = (session as any).host.vfs
|
||||
const fakehost = (session as any).host as fakes.FakeServerHost
|
||||
assert.isDefined(vfs);
|
||||
const v = (session as any).host.vfs
|
||||
const fakehost = (session as any).host as ts.TestFSWithWatch.VirtualServerHost
|
||||
assert.isDefined(v);
|
||||
assert.isDefined(project);
|
||||
assert.equal(fakehost.fsWatches.size, 0)
|
||||
assert.equal(fakehost.fsWatchesRecursive.size, 0)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* @internal */
|
||||
namespace ts.vfs {
|
||||
namespace ts.TestFSWithWatch {
|
||||
export const libFile: File = {
|
||||
path: "/a/lib/lib.d.ts",
|
||||
content: `/// <reference no-default-lib="true"/>
|
||||
@@ -85,20 +85,20 @@ interface Array<T> { length: number; [n: number]: T; }`
|
||||
modifiedTime: Date;
|
||||
}
|
||||
|
||||
interface FsFile extends FSEntryBase {
|
||||
export interface FsFile extends FSEntryBase {
|
||||
content: string;
|
||||
fileSize?: number;
|
||||
}
|
||||
|
||||
interface FsFolder extends FSEntryBase {
|
||||
export interface FsFolder extends FSEntryBase {
|
||||
entries: SortedArray<FSEntry>;
|
||||
}
|
||||
|
||||
interface FsSymLink extends FSEntryBase {
|
||||
export interface FsSymLink extends FSEntryBase {
|
||||
symLink: string;
|
||||
}
|
||||
|
||||
type FSEntry = FsFile | FsFolder | FsSymLink;
|
||||
export type FSEntry = FsFile | FsFolder | FsSymLink;
|
||||
|
||||
function isFsFolder(s: FSEntry | undefined): s is FsFolder {
|
||||
return !!s && isArray((s as FsFolder).entries);
|
||||
|
||||
Reference in New Issue
Block a user