diff --git a/src/testRunner/unittests/sys/symlinkWatching.ts b/src/testRunner/unittests/sys/symlinkWatching.ts index b3aeadf175850..c0bd12e3d6026 100644 --- a/src/testRunner/unittests/sys/symlinkWatching.ts +++ b/src/testRunner/unittests/sys/symlinkWatching.ts @@ -6,6 +6,7 @@ import { import * as ts from "../../_namespaces/ts"; import { defer, + Deferred, } from "../../_namespaces/Utils"; import { createWatchedSystem, @@ -31,24 +32,15 @@ describe("unittests:: sys:: symlinkWatching::", () => { it(scenario, async () => { const fileResult = watchFile(file); const linkResult = watchFile(link); - delayedOp(() => sys.writeFile(getFileName?.(file) ?? file, "export const x = 100;")); - // Should invoke on file as well as link - await fileResult.deferred[0].promise; - await linkResult.deferred[0].promise; - - delayedOp(() => sys.writeFile(getFileName?.(link) ?? link, "export const x = 100;")); - // Should invoke on file as well as link - await fileResult.deferred[1].promise; - await linkResult.deferred[1].promise; + await writeFile(file); + await writeFile(link); fileResult.watcher.close(); linkResult.watcher.close(); function watchFile(toWatch: string) { - const deferred = [defer(), defer()]; - let indexForDefer = 0; - return { + const result = { watcher: sys.watchFile!( toWatch, (fileName, eventKind, modifiedTime) => { @@ -56,13 +48,23 @@ describe("unittests:: sys:: symlinkWatching::", () => { assert.equal(eventKind, ts.FileWatcherEventKind.Changed); const actual = modifiedTimeToString(modifiedTime); assert(actual === undefined || actual === modifiedTimeToString(sys.getModifiedTime!(file))); - deferred[indexForDefer++].resolve(); + result.deferred.resolve(); }, 10, watchOptions, ), - deferred, + deferred: undefined! as Deferred, }; + return result; + } + + async function writeFile(onFile: string) { + fileResult.deferred = defer(); + linkResult.deferred = defer(); + delayedOp(() => sys.writeFile(getFileName?.(onFile) ?? onFile, "export const x = 100;")); + // Should invoke on file as well as link + await fileResult.deferred.promise; + await linkResult.deferred.promise; } }); } @@ -73,52 +75,140 @@ describe("unittests:: sys:: symlinkWatching::", () => { link: string, fsWatch: (dir: string, cb: ts.FsWatchCallback, sys: System) => ts.FileWatcher, isMacOs: boolean, + isWindows: boolean, ) { it(`watchDirectory using fsEvents`, async () => { - const expectedEvent = ["rename", "change", "rename", "change"]; - const expectedFileName = ["file1.ts", "file1.ts", "file2.ts", "file2.ts"]; - const fileResult = watchDirectory(dir); - const linkResult = watchDirectory(link); - delayedOp(() => sys.writeFile(`${dir}/file1.ts`, "export const x = 100;")); - - // Should invoke on file as well as link, rename and change - await fileResult.deferred[0].promise; - await linkResult.deferred[0].promise; - if (!isMacOs) { - // MacOs does not get change events when new file is created - await fileResult.deferred[1].promise; - await linkResult.deferred[1].promise; + interface Expected { + indexForDefer: number; + deferred: readonly Deferred[]; + expectedFileName: string; + expectedEvent: string[]; } - - delayedOp(() => sys.writeFile(`${link}/file2.ts`, "export const x = 100;")); - // // Should invoke on file as well as link, rename and change - await fileResult.deferred[2].promise; - await linkResult.deferred[2].promise; - if (!isMacOs) { - // MacOs does not get change events when new file is created - await fileResult.deferred[3].promise; - await linkResult.deferred[3].promise; + type ExpectedForOperation = Pick; + interface TableOfEvents { + fileCreate: ExpectedForOperation; + linkFileCreate: ExpectedForOperation; + fileChange: ExpectedForOperation; + fileModifiedTimeChange: ExpectedForOperation; + linkModifiedTimeChange: ExpectedForOperation; + linkFileChange: ExpectedForOperation; + fileDelete: ExpectedForOperation; + linkFileDelete: ExpectedForOperation; } - fileResult.watcher.close(); + const tableOfEvents: TableOfEvents = isMacOs ? + { + fileCreate: { expectedEvent: ["rename"], expectedFileName: "file1.ts" }, + linkFileCreate: { expectedEvent: ["rename"], expectedFileName: "file2.ts" }, + fileChange: { expectedEvent: ["rename"], expectedFileName: "file1.ts" }, + linkFileChange: { expectedEvent: ["rename"], expectedFileName: "file2.ts" }, + fileModifiedTimeChange: { expectedEvent: ["rename"], expectedFileName: "file1.ts" }, + linkModifiedTimeChange: { expectedEvent: ["rename"], expectedFileName: "file2.ts" }, + fileDelete: { expectedEvent: ["rename"], expectedFileName: "file1.ts" }, + linkFileDelete: { expectedEvent: ["rename"], expectedFileName: "file2.ts" }, + } : + isWindows ? + { + fileCreate: { expectedEvent: ["rename", "change"], expectedFileName: "file1.ts" }, + linkFileCreate: { expectedEvent: ["rename", "change"], expectedFileName: "file2.ts" }, + fileChange: { expectedEvent: ["change", "change"], expectedFileName: "file1.ts" }, + linkFileChange: { expectedEvent: ["change", "change"], expectedFileName: "file2.ts" }, + fileModifiedTimeChange: { expectedEvent: ["change"], expectedFileName: "file1.ts" }, + linkModifiedTimeChange: { expectedEvent: ["change"], expectedFileName: "file2.ts" }, + fileDelete: { expectedEvent: ["rename"], expectedFileName: "file1.ts" }, + linkFileDelete: { expectedEvent: ["rename"], expectedFileName: "file2.ts" }, + } : + { + fileCreate: { expectedEvent: ["rename", "change"], expectedFileName: "file1.ts" }, + linkFileCreate: { expectedEvent: ["rename", "change"], expectedFileName: "file2.ts" }, + fileChange: { expectedEvent: ["change"], expectedFileName: "file1.ts" }, + linkFileChange: { expectedEvent: ["change"], expectedFileName: "file2.ts" }, + fileModifiedTimeChange: { expectedEvent: ["change"], expectedFileName: "file1.ts" }, + linkModifiedTimeChange: { expectedEvent: ["change"], expectedFileName: "file2.ts" }, + fileDelete: { expectedEvent: ["rename"], expectedFileName: "file1.ts" }, + linkFileDelete: { expectedEvent: ["rename"], expectedFileName: "file2.ts" }, + }; + const dirResult = watchDirectory(dir); + const linkResult = watchDirectory(link); + + await operation("fileCreate"); + await operation("linkFileCreate"); + + await operation("fileChange"); + await operation("linkFileChange"); + + await operation("fileModifiedTimeChange"); + await operation("linkModifiedTimeChange"); + + await operation("fileDelete"); + await operation("linkFileDelete"); + + dirResult.watcher.close(); linkResult.watcher.close(); function watchDirectory(dir: string) { - const deferred = [defer(), defer(), defer(), defer()]; - let indexForDefer = 0; - return { + const result = { dir, watcher: fsWatch( dir, (event, fileName) => { - assert.equal(event, expectedEvent[indexForDefer]); - assert(!fileName || fileName === expectedFileName[indexForDefer]); - deferred[indexForDefer++].resolve(); - if (isMacOs) indexForDefer++; // MacOs does not get change events when new file is created so skip that one + console.log(dir, result.expected.indexForDefer, event, fileName); // To ensure we can get the data on all OS + assert.equal(event, result.expected.expectedEvent[result.expected.indexForDefer]); + assert.equal(fileName, result.expected.expectedFileName); + result.expected.deferred[result.expected.indexForDefer++].resolve(); }, sys, ), - deferred, + expected: undefined! as Expected, }; + return result; + } + + async function operation(opType: keyof TableOfEvents) { + const expected = tableOfEvents[opType]; + console.log(""); + console.log(opType); + dirResult.expected = { + indexForDefer: 0, + deferred: ts.arrayOf(expected.expectedEvent.length, defer), + ...expected, + }; + linkResult.expected = { + indexForDefer: 0, + deferred: ts.arrayOf(expected.expectedEvent.length, defer), + ...expected, + }; + let op; + switch (opType) { + case "fileCreate": + op = () => sys.writeFile(`${dir}/file1.ts`, "export const x = 100;"); + break; + case "linkFileCreate": + op = () => sys.writeFile(`${link}/file2.ts`, "export const x = 100;"); + break; + case "fileChange": + op = () => sys.writeFile(`${dir}/file1.ts`, "export const x2 = 100;"); + break; + case "linkFileChange": + op = () => sys.writeFile(`${link}/file2.ts`, "export const x2 = 100;"); + break; + case "fileModifiedTimeChange": + op = () => sys.setModifiedTime!(`${dir}/file1.ts`, new Date()); + break; + case "linkModifiedTimeChange": + op = () => sys.setModifiedTime!(`${link}/file2.ts`, new Date()); + break; + case "fileDelete": + op = () => sys.deleteFile!(`${dir}/file1.ts`); + break; + case "linkFileDelete": + op = () => sys.deleteFile!(`${link}/file2.ts`); + break; + default: + ts.Debug.assertNever(opType); + } + delayedOp(op); + await Promise.all(dirResult.expected.deferred.map(d => d.promise)); + await Promise.all(linkResult.expected.deferred.map(d => d.promise)); } }); } @@ -129,6 +219,8 @@ describe("unittests:: sys:: symlinkWatching::", () => { describe("with ts.sys::", () => { const root = ts.normalizePath(IO.joinPath(IO.getWorkspaceRoot(), "tests/baselines/symlinks")); + const isMacOs = process.platform === "darwin"; + const isWindows = process.platform === "win32"; before(() => { cleanup(); ts.sys.writeFile(`${root}/polling/file.ts`, "export const x = 10;"); @@ -184,7 +276,8 @@ describe("unittests:: sys:: symlinkWatching::", () => { `${root}/dirfsevents`, `${root}/linkeddirfsevents`, (dir, cb) => fs.watch(dir, { persistent: true }, cb), - process.platform === "darwin", + isMacOs, + isWindows, ); }); @@ -220,21 +313,32 @@ describe("unittests:: sys:: symlinkWatching::", () => { getFileName(), ); - verifyWatchDirectoryUsingFsEvents( - getSys(), - `${root}/folder`, - `${root}/linked`, - (dir, cb, sys) => sys.fsWatchWorker(dir, /*recursive*/ false, cb), - /*isMacOs*/ false, - ); + // TODO (sheetal) add test for each os behaviour + // verifyWatchDirectoryUsingFsEvents( + // getSys(), + // `${root}/folder`, + // `${root}/linked`, + // (dir, cb, sys) => sys.fsWatchWorker(dir, /*recursive*/ false, cb), + // /*isMacOs*/ false, + // /*isWindows*/ true, + // ); + + // verifyWatchDirectoryUsingFsEvents( + // getSys(), + // `${root}/folder`, + // `${root}/linked`, + // (dir, cb, sys) => sys.fsWatchWorker(dir, /*recursive*/ false, cb), + // /*isMacOs*/ true, + // /*isWindows*/ false, + // ); - // TODO (sheetal) add test for mac os behaviour so we have it on host to verify // verifyWatchDirectoryUsingFsEvents( // getSys(), // `${root}/folder`, // `${root}/linked`, // (dir, cb, sys) => sys.fsWatchWorker(dir, /*recursive*/ false, cb), - // /*isMacOs*/ true + // /*isMacOs*/ false, + // /*isWindows*/ false, // ); }); });