Tests build and run

But editorServices needs some fixes. At least, createDirectory needs a
recursive version.
This commit is contained in:
Nathan Shively-Sanders
2022-03-25 15:39:17 -07:00
parent 050e058723
commit 978c0bc50f
7 changed files with 81 additions and 233 deletions
+2 -8
View File
@@ -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
View File
@@ -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);
+19
View File
@@ -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);
}
}
}
}
+8 -8
View File
@@ -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) {
+2 -2
View File
@@ -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)
+5 -5
View File
@@ -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);