Switch tests to baselines

This moves diff/snap back into VirtualServerHost to make the switch
easier.
This commit is contained in:
Nathan Shively-Sanders
2022-05-13 10:35:35 -07:00
parent 77b3e5a00e
commit b0edfb1321
5 changed files with 309 additions and 213 deletions
-91
View File
@@ -618,33 +618,6 @@ interface Array<T> { length: number; [n: number]: T; }`
this.clearOutput();
}
snap(): ESMap<Path, FSEntry> {
const result = new Map<Path, FSEntry>();
this.fs.forEach((value, key) => {
const cloneValue = clone(value);
if (isFsFolder(cloneValue)) {
cloneValue.entries = cloneValue.entries.map(clone) as SortedArray<FSEntry>;
}
result.set(key, cloneValue);
});
return result;
}
writtenFiles?: ESMap<Path, number>;
diff(baseline: string[], base: ESMap<string, FSEntry> = new Map()) {
this.fs.forEach(newFsEntry => {
diffFsEntry(baseline, base.get(newFsEntry.path), newFsEntry, this.writtenFiles);
});
base.forEach(oldFsEntry => {
const newFsEntry = this.fs.get(oldFsEntry.path);
if (!newFsEntry) {
diffFsEntry(baseline, oldFsEntry, newFsEntry, this.writtenFiles);
}
});
baseline.push("");
}
serializeWatches(baseline: string[]) {
serializeMultiMap(baseline, "WatchedFiles", this.watchedFiles, ({ fileName, pollingInterval }) => ({ fileName, pollingInterval }));
baseline.push("");
@@ -659,70 +632,6 @@ interface Array<T> { length: number; [n: number]: T; }`
}
}
function diffFsFile(baseline: string[], fsEntry: FsFile) {
baseline.push(`//// [${fsEntry.fullPath}]\r\n${fsEntry.content}`, "");
}
function diffFsSymLink(baseline: string[], fsEntry: FsSymLink) {
baseline.push(`//// [${fsEntry.fullPath}] symlink(${fsEntry.symLink})`);
}
function diffFsEntry(baseline: string[], oldFsEntry: FSEntry | undefined, newFsEntry: FSEntry | undefined, writtenFiles: ESMap<string, any> | undefined): void {
const file = newFsEntry && newFsEntry.fullPath;
if (isFsFile(oldFsEntry)) {
if (isFsFile(newFsEntry)) {
if (oldFsEntry.content !== newFsEntry.content) {
diffFsFile(baseline, newFsEntry);
}
else if (oldFsEntry.modifiedTime !== newFsEntry.modifiedTime) {
if (oldFsEntry.fullPath !== newFsEntry.fullPath) {
baseline.push(`//// [${file}] file was renamed from file ${oldFsEntry.fullPath}`);
}
else if (writtenFiles && !writtenFiles.has(newFsEntry.path)) {
baseline.push(`//// [${file}] file changed its modified time`);
}
else {
baseline.push(`//// [${file}] file written with same contents`);
}
}
}
else {
baseline.push(`//// [${oldFsEntry.fullPath}] deleted`);
if (isFsSymLink(newFsEntry)) {
diffFsSymLink(baseline, newFsEntry);
}
}
}
else if (isFsSymLink(oldFsEntry)) {
if (isFsSymLink(newFsEntry)) {
if (oldFsEntry.symLink !== newFsEntry.symLink) {
diffFsSymLink(baseline, newFsEntry);
}
else if (oldFsEntry.modifiedTime !== newFsEntry.modifiedTime) {
if (oldFsEntry.fullPath !== newFsEntry.fullPath) {
baseline.push(`//// [${file}] symlink was renamed from symlink ${oldFsEntry.fullPath}`);
}
else if (writtenFiles && !writtenFiles.has(newFsEntry.path)) {
baseline.push(`//// [${file}] symlink changed its modified time`);
}
else {
baseline.push(`//// [${file}] symlink written with same link`);
}
}
}
else {
baseline.push(`//// [${oldFsEntry.fullPath}] deleted symlink`);
if (isFsFile(newFsEntry)) {
diffFsFile(baseline, newFsEntry);
}
}
}
else if (isFsFile(newFsEntry)) {
diffFsFile(baseline, newFsEntry);
}
else if (isFsSymLink(newFsEntry)) {
diffFsSymLink(baseline, newFsEntry);
}
}
function serializeTestFsWatcher({ directoryName, fallbackPollingInterval, fallbackOptions }: VirtualFsWatcher) {
return {
directoryName,
@@ -40,131 +40,68 @@ interface Array<T> { length: number; [n: number]: T; }`
return `// some copy right notice
${file.fileContent}`;
}
// TODO: This is almost certainly in harness/virtualFileSystemHost.ts, or should be.
function verifyFileSystem(host: VirtualFS.VirtualServerHost | undefined, files: protocol.FileSystemRequestArgs[]) {
// 1. make sure that everything in files is there
assert.isDefined(host);
const fs = (host as any).fs as ESMap<string, VirtualFS.FSEntry>;
// console.log(Array.from(fs.values() as any as Iterable<ts.TestFSWithWatch.FSEntry>).filter(ts.TestFSWithWatch.isFsFile))
assert.equal(Array.from(fs.values() as any as Iterable<VirtualFS.FSEntry>).filter(VirtualFS.isFsFile).length, files.length);
for (const { file, fileContent } of files) {
assert(host?.fileExists(file));
assert.equal(host!.readFile(file), fileContent);
function baselineFileSystem(scenario: string, subScenario: string, requests: [string, Partial<protocol.Request>][], host: VirtualFS.VirtualServerHost, session: TestSession) {
const history: string[] = []
let prev = host.snap()
for (const [name, request] of requests) {
session.executeCommandSeq(request)
history.push("")
history.push("#### " + name)
host.diff(history, prev)
prev = host.snap()
}
// 2. make sure nothing else is
Harness.Baseline.runBaseline(`tsserver/${scenario}/${subScenario.split(" ").join("-")}.txt`, history.join("\r\n"));
baselineTsserverLogs(scenario, subScenario, session)
}
it("with updateFileSystem request", () => {
// TODO: probably some other watcher tests, not sure what
const host = VirtualFS.createVirtualServerHost({ executingFilePath: "/a/tsc.js" });
const session = createSession(host, { fshost: host });
const files = [app, file1, file2, file3, config, lib];
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
command: protocol.CommandTypes.UpdateFileSystem,
arguments:{
fileSystem: "memfs",
files,
deleted: [],
}
});
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: { file: app.file }
});
const service = session.getProjectService(); // session -> service -> project
const project = service.configuredProjects.get(config.file)!;
const fakehost = (session as any).host as VirtualFS.VirtualServerHost;
assert.isDefined(project);
assert.equal(fakehost.fsWatches.size, 0);
assert.equal(fakehost.fsWatchesRecursive.size, 2);
assert.equal(fakehost.watchedFiles.size, 5);
verifyProjectVersion(project, 1);
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: {
file: file3.file,
fileContent: fileContentWithComment(file3)
}
});
verifyProjectVersion(project, 2);
// Verify Texts
verifyFileSystem(host, files);
verifyText(service, file1.file, file1.fileContent);
verifyText(service, commonFile2.path, commonFile2.content);
verifyText(service, app.file, app.fileContent);
verifyText(service, file3.file, fileContentWithComment(file3));
assert.equal(fakehost.fsWatches.size, 0);
assert.equal(fakehost.fsWatchesRecursive.size, 2);
assert.equal(fakehost.watchedFiles.size, 4);
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
command: protocol.CommandTypes.UpdateFileSystem,
arguments:{
fileSystem: "memfs",
files: [],
deleted: [],
}
});
// no change when not deleting file
verifyProjectVersion(project, 2);
// Verify Texts
verifyFileSystem(host, files);
verifyText(service, file1.file, file1.fileContent);
verifyText(service, commonFile2.path, commonFile2.content);
verifyText(service, app.file, app.fileContent);
verifyText(service, file3.file, fileContentWithComment(file3));
assert.equal(fakehost.fsWatches.size, 0);
assert.equal(fakehost.fsWatchesRecursive.size, 2);
assert.equal(fakehost.watchedFiles.size, 4);
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
command: protocol.CommandTypes.UpdateFileSystem,
arguments:{
fileSystem: "memfs",
files: [],
deleted: [file1.file],
}
});
verifyProjectVersion(project, 3);
// Verify Texts
verifyFileSystem(host, [app, file2, file3, config, lib]);
verifyText(service, commonFile2.path, commonFile2.content);
verifyText(service, app.file, app.fileContent);
verifyText(service, file3.file, fileContentWithComment(file3));
assert.equal(fakehost.fsWatches.size, 0);
assert.equal(fakehost.fsWatchesRecursive.size, 2);
assert.equal(fakehost.watchedFiles.size, 3);
session.executeCommandSeq<protocol.CloseRequest>({
command: protocol.CommandTypes.Close,
arguments: { file: app.file }
});
// also no change when closing a file??? (changes session version but not project version?)
verifyProjectVersion(project, 3);
// Verify Texts
verifyFileSystem(host, [app, file2, file3, config, lib]);
verifyText(service, commonFile2.path, commonFile2.content);
verifyText(service, app.file, app.fileContent);
verifyText(service, file3.file, fileContentWithComment(file3));
assert.equal(fakehost.fsWatches.size, 0);
assert.equal(fakehost.fsWatchesRecursive.size, 2);
assert.equal(fakehost.watchedFiles.size, 4);
const session = createSession(host, { fshost: host, logger: createLoggerWithInMemoryLogs(), canUseEvents: true });
const requests: [string, Partial<protocol.Request>][] = [
["Initial updateFileSystem", {
command: protocol.CommandTypes.UpdateFileSystem,
arguments:{
fileSystem: "memfs",
files: [app, file1, file2, file3, config, lib],
deleted: [],
}
}],
["Opening app.ts", {
command: protocol.CommandTypes.Open,
arguments: { file: app.file }
}],
["Opening file3.ts", {
command: protocol.CommandTypes.Open,
arguments: {
file: file3.file,
fileContent: fileContentWithComment(file3)
}
}],
["non-delete", {
command: protocol.CommandTypes.UpdateFileSystem,
arguments:{
fileSystem: "memfs",
files: [],
deleted: [],
}
}],
["delete", {
command: protocol.CommandTypes.UpdateFileSystem,
arguments:{
fileSystem: "memfs",
files: [],
deleted: [file1.file],
}
}],
["close", {
command: protocol.CommandTypes.Close,
arguments: { file: app.file }
}],
]
const scenario = "updateFileSystem"
const subScenario = "open and close files"
baselineFileSystem(scenario, subScenario, requests, host, session);
});
function verifyText(service: server.ProjectService, file: string, expected: string) {
const info = service.getScriptInfo(file)!;
const snap = info.getSnapshot();
// Verified applied in reverse order
assert.equal(snap.getText(0, snap.getLength()), expected, `Text of changed file: ${file}`);
}
function verifyProjectVersion(project: server.Project, expected: number) {
assert.equal(Number(project.getProjectVersion()), expected);
}
});
describe("unittests:: tsserver:: applyChangesToOpenFiles", () => {
const configFile: File = {
+102 -2
View File
@@ -516,8 +516,9 @@ namespace ts.VirtualFS {
throw new Error("clearTimeout Not implemented in virtual filesystem host.");
}
setImmediate(_callback: (...args: any[]) => void, ..._args: any[]): any {
throw new Error("setImmediate Not implemented in virtual filesystem host.");
setImmediate(callback: (...args: any[]) => void, ...args: any[]): any {
// tslint:disable-next-line:no-restricted-globals
setImmediate(callback, ...args)
}
clearImmediate(_timeoutId: any): void {
@@ -601,5 +602,104 @@ namespace ts.VirtualFS {
getEnvironmentVariable(_name: string) {
return "";
}
snap(): ESMap<Path, FSEntry> {
const result = new Map<Path, FSEntry>();
this.fs.forEach((value, key) => {
const cloneValue = clone(value);
if (isFsFolder(cloneValue)) {
cloneValue.entries = cloneValue.entries.map(clone) as SortedArray<FSEntry>;
}
result.set(key, cloneValue);
});
return result;
}
writtenFiles?: ESMap<Path, number>;
diff(baseline: string[], base: ESMap<string, FSEntry> = new Map()) {
const len = baseline.length;
this.fs.forEach(newFsEntry => {
diffFsEntry(baseline, base.get(newFsEntry.path), newFsEntry, this.writtenFiles);
});
base.forEach(oldFsEntry => {
const newFsEntry = this.fs.get(oldFsEntry.path);
if (!newFsEntry) {
diffFsEntry(baseline, oldFsEntry, newFsEntry, this.writtenFiles);
}
});
if (len === baseline.length) {
baseline.push("No changes.")
}
else {
baseline.push("");
}
}
}
function diffFsFile(baseline: string[], fsEntry: FsFile) {
baseline.push(`//// [${fsEntry.fullPath}] added\r\n${fsEntry.content}`, "");
}
function diffFsSymLink(baseline: string[], fsEntry: FsSymLink) {
baseline.push(`//// [${fsEntry.fullPath}] symlink(${fsEntry.symLink})`);
}
function diffFsEntry(baseline: string[], oldFsEntry: FSEntry | undefined, newFsEntry: FSEntry | undefined, writtenFiles: ESMap<string, any> | undefined): void {
const file = newFsEntry && newFsEntry.fullPath;
if (isFsFile(oldFsEntry)) {
if (isFsFile(newFsEntry)) {
if (oldFsEntry.content !== newFsEntry.content) {
diffFsFile(baseline, newFsEntry);
}
else if (oldFsEntry.modifiedTime !== newFsEntry.modifiedTime) {
if (oldFsEntry.fullPath !== newFsEntry.fullPath) {
baseline.push(`//// [${file}] file was renamed from file ${oldFsEntry.fullPath}`);
}
else if (writtenFiles && !writtenFiles.has(newFsEntry.path)) {
baseline.push(`//// [${file}] file changed its modified time`);
}
else {
baseline.push(`//// [${file}] file written with same contents`);
}
}
}
else {
baseline.push(`//// [${oldFsEntry.fullPath}] deleted`);
if (isFsSymLink(newFsEntry)) {
diffFsSymLink(baseline, newFsEntry);
}
}
}
else if (isFsSymLink(oldFsEntry)) {
if (isFsSymLink(newFsEntry)) {
if (oldFsEntry.symLink !== newFsEntry.symLink) {
diffFsSymLink(baseline, newFsEntry);
}
else if (oldFsEntry.modifiedTime !== newFsEntry.modifiedTime) {
if (oldFsEntry.fullPath !== newFsEntry.fullPath) {
baseline.push(`//// [${file}] symlink was renamed from symlink ${oldFsEntry.fullPath}`);
}
else if (writtenFiles && !writtenFiles.has(newFsEntry.path)) {
baseline.push(`//// [${file}] symlink changed its modified time`);
}
else {
baseline.push(`//// [${file}] symlink written with same link`);
}
}
}
else {
baseline.push(`//// [${oldFsEntry.fullPath}] deleted symlink`);
if (isFsFile(newFsEntry)) {
diffFsFile(baseline, newFsEntry);
}
}
}
else if (isFsFile(newFsEntry)) {
diffFsFile(baseline, newFsEntry);
}
else if (isFsSymLink(newFsEntry)) {
diffFsSymLink(baseline, newFsEntry);
}
}
}
@@ -0,0 +1,103 @@
Provided types map file "/a/typesMap.json" doesn't exist
request:{"command":"updateFileSystem","arguments":{"fileSystem":"memfs","files":[{"file":"/a/b/app.ts","fileContent":"import { xyz } from './file3'; let x = xyz"},{"file":"/a/b/commonFile1.ts","fileContent":"let x = 1"},{"file":"/a/b/commonFile2.ts","fileContent":"let y = 1"},{"file":"/a/b/file3.ts","fileContent":"export let xyz = 1;"},{"file":"/a/b/tsconfig.json","fileContent":"{}"},{"file":"/a/lib/lib.d.ts","fileContent":"/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }"}],"deleted":[]},"seq":1,"type":"request"}
response:{"response":true,"responseRequired":true}
request:{"command":"open","arguments":{"file":"/a/b/app.ts"},"seq":2,"type":"request"}
Search path: /a/b
For info: /a/b/app.ts :: Config file name: /a/b/tsconfig.json
Creating configuration project /a/b/tsconfig.json
FileWatcher:: Added:: WatchInfo: /a/b/tsconfig.json 2000 undefined Project: /a/b/tsconfig.json WatchType: Config file
event:
{"seq":0,"type":"event","event":"projectLoadingStart","body":{"projectName":"/a/b/tsconfig.json","reason":"Creating possible configured project for /a/b/app.ts to open"}}
Config: /a/b/tsconfig.json : {
"rootNames": [
"/a/b/app.ts",
"/a/b/commonFile1.ts",
"/a/b/commonFile2.ts",
"/a/b/file3.ts"
],
"options": {
"configFilePath": "/a/b/tsconfig.json"
}
}
DirectoryWatcher:: Added:: WatchInfo: /a/b 1 undefined Config: /a/b/tsconfig.json WatchType: Wild card directory
Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /a/b 1 undefined Config: /a/b/tsconfig.json WatchType: Wild card directory
Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded
FileWatcher:: Added:: WatchInfo: /a/b/commonFile1.ts 500 undefined WatchType: Closed Script info
FileWatcher:: Added:: WatchInfo: /a/b/commonFile2.ts 500 undefined WatchType: Closed Script info
FileWatcher:: Added:: WatchInfo: /a/b/file3.ts 500 undefined WatchType: Closed Script info
Starting updateGraphWorker: Project: /a/b/tsconfig.json
FileWatcher:: Added:: WatchInfo: /a/lib.d.ts 500 undefined Project: /a/b/tsconfig.json WatchType: Missing file
DirectoryWatcher:: Added:: WatchInfo: /a/b/node_modules/@types 1 undefined Project: /a/b/tsconfig.json WatchType: Type roots
Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /a/b/node_modules/@types 1 undefined Project: /a/b/tsconfig.json WatchType: Type roots
Finishing updateGraphWorker: Project: /a/b/tsconfig.json Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms
Project '/a/b/tsconfig.json' (Configured)
Files (4)
/a/b/file3.ts
/a/b/app.ts
/a/b/commonFile1.ts
/a/b/commonFile2.ts
file3.ts
Imported via './file3' from file 'app.ts'
Matched by default include pattern '**/*'
app.ts
Matched by default include pattern '**/*'
commonFile1.ts
Matched by default include pattern '**/*'
commonFile2.ts
Matched by default include pattern '**/*'
-----------------------------------------------
event:
{"seq":0,"type":"event","event":"projectLoadingFinish","body":{"projectName":"/a/b/tsconfig.json"}}
event:
{"seq":0,"type":"event","event":"configFileDiag","body":{"triggerFile":"/a/b/app.ts","configFile":"/a/b/tsconfig.json","diagnostics":[{"text":"File '/a/lib.d.ts' not found.\n The file is in the program because:\n Default library for target 'es3'","code":6053,"category":"error"},{"text":"Cannot find global type 'Array'.","code":2318,"category":"error"},{"text":"Cannot find global type 'Boolean'.","code":2318,"category":"error"},{"text":"Cannot find global type 'Function'.","code":2318,"category":"error"},{"text":"Cannot find global type 'IArguments'.","code":2318,"category":"error"},{"text":"Cannot find global type 'Number'.","code":2318,"category":"error"},{"text":"Cannot find global type 'Object'.","code":2318,"category":"error"},{"text":"Cannot find global type 'RegExp'.","code":2318,"category":"error"},{"text":"Cannot find global type 'String'.","code":2318,"category":"error"}]}}
Project '/a/b/tsconfig.json' (Configured)
Files (4)
-----------------------------------------------
Open files:
FileName: /a/b/app.ts ProjectRootPath: undefined
Projects: /a/b/tsconfig.json
response:{"responseRequired":false}
request:{"command":"open","arguments":{"file":"/a/b/file3.ts","fileContent":"// some copy right notice\nexport let xyz = 1;"},"seq":3,"type":"request"}
FileWatcher:: Close:: WatchInfo: /a/b/file3.ts 500 undefined WatchType: Closed Script info
Search path: /a/b
For info: /a/b/file3.ts :: Config file name: /a/b/tsconfig.json
Starting updateGraphWorker: Project: /a/b/tsconfig.json
Finishing updateGraphWorker: Project: /a/b/tsconfig.json Version: 2 structureChanged: false structureIsReused:: Completely Elapsed:: *ms
Different program with same set of files
Project '/a/b/tsconfig.json' (Configured)
Files (4)
-----------------------------------------------
Open files:
FileName: /a/b/app.ts ProjectRootPath: undefined
Projects: /a/b/tsconfig.json
FileName: /a/b/file3.ts ProjectRootPath: undefined
Projects: /a/b/tsconfig.json
response:{"responseRequired":false}
request:{"command":"updateFileSystem","arguments":{"fileSystem":"memfs","files":[],"deleted":[]},"seq":4,"type":"request"}
response:{"response":true,"responseRequired":true}
request:{"command":"updateFileSystem","arguments":{"fileSystem":"memfs","files":[],"deleted":["/a/b/commonFile1.ts"]},"seq":5,"type":"request"}
FileWatcher:: Triggered with /a/b/commonFile1.ts 2:: WatchInfo: /a/b/commonFile1.ts 500 undefined WatchType: Closed Script info
FileWatcher:: Close:: WatchInfo: /a/b/commonFile1.ts 500 undefined WatchType: Closed Script info
Scheduled: /a/b/tsconfig.json
Scheduled: *ensureProjectForOpenFiles*
Elapsed:: *ms FileWatcher:: Triggered with /a/b/commonFile1.ts 2:: WatchInfo: /a/b/commonFile1.ts 500 undefined WatchType: Closed Script info
DirectoryWatcher:: Triggered with /a/b/commonFile1.ts :: WatchInfo: /a/b 1 undefined Config: /a/b/tsconfig.json WatchType: Wild card directory
Scheduled: /a/b/tsconfig.json
Scheduled: *ensureProjectForOpenFiles*
Elapsed:: *ms DirectoryWatcher:: Triggered with /a/b/commonFile1.ts :: WatchInfo: /a/b 1 undefined Config: /a/b/tsconfig.json WatchType: Wild card directory
response:{"response":true,"responseRequired":true}
request:{"command":"close","arguments":{"file":"/a/b/app.ts"},"seq":6,"type":"request"}
FileWatcher:: Added:: WatchInfo: /a/b/app.ts 500 undefined WatchType: Closed Script info
Project '/a/b/tsconfig.json' (Configured)
Files (4)
-----------------------------------------------
Open files:
FileName: /a/b/file3.ts ProjectRootPath: undefined
Projects: /a/b/tsconfig.json
response:{"responseRequired":false}
@@ -0,0 +1,47 @@
#### Initial updateFileSystem
//// [/a/b/app.ts] added
import { xyz } from './file3'; let x = xyz
//// [/a/b/commonFile1.ts] added
let x = 1
//// [/a/b/commonFile2.ts] added
let y = 1
//// [/a/b/file3.ts] added
export let xyz = 1;
//// [/a/b/tsconfig.json] added
{}
//// [/a/lib/lib.d.ts] added
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
#### Opening app.ts
No changes.
#### Opening file3.ts
No changes.
#### non-delete
No changes.
#### delete
//// [/a/b/commonFile1.ts] deleted
#### close
No changes.