diff --git a/src/testRunner/unittests/sys/symlinkWatching.ts b/src/testRunner/unittests/sys/symlinkWatching.ts index c0bd12e3d6026..b716d84e723b9 100644 --- a/src/testRunner/unittests/sys/symlinkWatching.ts +++ b/src/testRunner/unittests/sys/symlinkWatching.ts @@ -69,91 +69,273 @@ describe("unittests:: sys:: symlinkWatching::", () => { }); } - function verifyWatchDirectoryUsingFsEvents( + // function verifyWatchDirectoryUsingFsEvents( + // sys: System, + // dir: string, + // link: string, + // fsWatch: (dir: string, cb: ts.FsWatchCallback, sys: System) => ts.FileWatcher, + // isMacOs: boolean, + // isWindows: boolean, + // ) { + // it(`watchDirectory using fsEvents`, async () => { + // interface Expected { + // indexForDefer: number; + // deferred: readonly Deferred[]; + // expectedFileName: string; + // expectedEvent: string[]; + // } + // type ExpectedForOperation = Pick; + // interface TableOfEvents { + // fileCreate: ExpectedForOperation; + // linkFileCreate: ExpectedForOperation; + // fileChange: ExpectedForOperation; + // fileModifiedTimeChange: ExpectedForOperation; + // linkModifiedTimeChange: ExpectedForOperation; + // linkFileChange: ExpectedForOperation; + // fileDelete: ExpectedForOperation; + // linkFileDelete: ExpectedForOperation; + // } + // 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 result = { + // dir, + // watcher: fsWatch( + // dir, + // (event, fileName) => { + // 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, + // ), + // 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)); + // } + // }); + // } + + function verifyRecursiveWatchDirectoryUsingFsEvents( sys: System, dir: string, link: string, fsWatch: (dir: string, cb: ts.FsWatchCallback, sys: System) => ts.FileWatcher, isMacOs: boolean, - isWindows: boolean, ) { it(`watchDirectory using fsEvents`, async () => { + interface ExpectedForOperation { + event: string; + fileName: string; + } interface Expected { indexForDefer: number; deferred: readonly Deferred[]; - expectedFileName: string; - expectedEvent: string[]; + expected: readonly ExpectedForOperation[]; } - type ExpectedForOperation = Pick; + // Need a table of expected events for each OS and operation instead interface TableOfEvents { - fileCreate: ExpectedForOperation; - linkFileCreate: ExpectedForOperation; - fileChange: ExpectedForOperation; - fileModifiedTimeChange: ExpectedForOperation; - linkModifiedTimeChange: ExpectedForOperation; - linkFileChange: ExpectedForOperation; - fileDelete: ExpectedForOperation; - linkFileDelete: ExpectedForOperation; + fileCreate: readonly ExpectedForOperation[]; + // linkFileCreate: readonly ExpectedForOperation[]; + // fileChange: readonly ExpectedForOperation[]; + // fileModifiedTimeChange: readonly ExpectedForOperation[]; + // linkModifiedTimeChange: readonly ExpectedForOperation[]; + // linkFileChange: readonly ExpectedForOperation[]; + // fileDelete: readonly ExpectedForOperation[]; + // linkFileDelete: readonly ExpectedForOperation[]; } - const tableOfEvents: TableOfEvents = isMacOs ? + const eventsForDir: 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" }, + fileCreate: [ + { event: "rename", fileName: "sub/folder/file1.ts" }, + { event: "change", fileName: "sub/folder/file1.ts" }, + { event: "change", fileName: "sub/folder" }, + ], } : - 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: [ + { event: "rename", fileName: "sub/folder/file1.ts" }, + { event: "change", fileName: "sub/folder/file1.ts" }, + { event: "change", fileName: "sub/folder" }, + ], + }; + + const eventsForLink: TableOfEvents = isMacOs ? + { + fileCreate: [ + { event: "rename", fileName: "sub/folder/file1.ts" }, + { event: "change", fileName: "sub/folder/file1.ts" }, + { event: "change", fileName: "sub/folder" }, + ], + } : + { + fileCreate: [ + { event: "rename", fileName: "sub/folder/file1.ts" }, + { event: "change", fileName: "sub/folder/file1.ts" }, + { event: "change", fileName: "sub/folder" }, + ], + }; + + const eventsForSubDir: TableOfEvents = isMacOs ? + { + fileCreate: [ + { event: "rename", fileName: "folder/file1.ts" }, + { event: "change", fileName: "folder/file1.ts" }, + { event: "change", fileName: "folder" }, + ], } : { - 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" }, + fileCreate: [ + { event: "rename", fileName: "folder/file1.ts" }, + { event: "change", fileName: "folder/file1.ts" }, + { event: "change", fileName: "folder" }, + ], }; - const dirResult = watchDirectory(dir); - const linkResult = watchDirectory(link); + + const eventsForLinkSubDir: TableOfEvents = isMacOs ? + { + fileCreate: [ + { event: "rename", fileName: "folder/file1.ts" }, + { event: "change", fileName: "folder/file1.ts" }, + { event: "change", fileName: "folder" }, + ], + } : + { + fileCreate: [ + { event: "rename", fileName: "folder/file1.ts" }, + { event: "change", fileName: "folder/file1.ts" }, + { event: "change", fileName: "folder" }, + ], + }; + + const dirResult = watchDirectory(dir, "dir"); + const linkResult = watchDirectory(link, "link"); + const subDirResult = watchDirectory(`${dir}/sub`, "subDir"); + const linkedSubDirResult = watchDirectory(`${dir}/linkedsub`, "linkSubDir"); await operation("fileCreate"); - await operation("linkFileCreate"); + // await operation("linkFileCreate"); - await operation("fileChange"); - await operation("linkFileChange"); + // await operation("fileChange"); + // await operation("linkFileChange"); - await operation("fileModifiedTimeChange"); - await operation("linkModifiedTimeChange"); + // await operation("fileModifiedTimeChange"); + // await operation("linkModifiedTimeChange"); - await operation("fileDelete"); - await operation("linkFileDelete"); + // await operation("fileDelete"); + // await operation("linkFileDelete"); dirResult.watcher.close(); linkResult.watcher.close(); + subDirResult.watcher.close(); + linkedSubDirResult.watcher.close(); - function watchDirectory(dir: string) { + function watchDirectory(dir: string, forDir: string) { const result = { dir, watcher: fsWatch( dir, (event, fileName) => { - 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); + console.log(forDir, result.expected.indexForDefer, event, fileName ? ts.normalizeSlashes(fileName) : fileName); // To ensure we can get the data on all OS + // assert.equal(event, result.expected.expected[result.expected.indexForDefer].event); + // assert.equal(ts.normalizeSlashes(fileName!), result.expected.expected[result.expected.indexForDefer].fileName); result.expected.deferred[result.expected.indexForDefer++].resolve(); }, sys, @@ -164,51 +346,54 @@ describe("unittests:: sys:: symlinkWatching::", () => { } 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, - }; + initForOp(dirResult, eventsForDir, opType); + initForOp(linkResult, eventsForLink, opType); + initForOp(subDirResult, eventsForSubDir, opType); + initForOp(linkedSubDirResult, eventsForLinkSubDir, opType); 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`); + op = () => sys.writeFile(`${dir}/sub/folder/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)); + await Promise.all(subDirResult.expected.deferred.map(d => d.promise)); + await Promise.all(linkedSubDirResult.expected.deferred.map(d => d.promise)); + } + + function initForOp(result: ReturnType, table: TableOfEvents, opType: keyof TableOfEvents) { + result.expected = { + indexForDefer: 0, + deferred: ts.arrayOf(table[opType].length, defer), + expected: table[opType], + }; } }); } @@ -231,6 +416,9 @@ describe("unittests:: sys:: symlinkWatching::", () => { withSwallowException(() => fs.symlinkSync(`${root}/dirpolling`, `${root}/linkeddirpolling`, "junction")); ts.sys.writeFile(`${root}/dirfsevents/file.ts`, "export const x = 10;"); withSwallowException(() => fs.symlinkSync(`${root}/dirfsevents`, `${root}/linkeddirfsevents`, "junction")); + ts.sys.writeFile(`${root}/recursivefsevents/sub/folder/file.ts`, "export const x = 10;"); + withSwallowException(() => fs.symlinkSync(`${root}/recursivefsevents`, `${root}/recursivelinkedfsevents`, "junction")); + withSwallowException(() => fs.symlinkSync(`${root}/recursivefsevents/sub`, `${root}/recursivefsevents/linkedsub`, "junction")); }); after(() => { cleanup(); @@ -247,38 +435,48 @@ describe("unittests:: sys:: symlinkWatching::", () => { catch { /* swallow */ } } - verifyWatchFile( - "watchFile using polling", - ts.sys, - `${root}/polling/file.ts`, - `${root}/linkedpolling/file.ts`, - { watchFile: ts.WatchFileKind.PriorityPollingInterval }, - ); - verifyWatchFile( - "watchFile using fsEvents", - ts.sys, - `${root}/fsevents/file.ts`, - `${root}/linkedfsevents/file.ts`, - { watchFile: ts.WatchFileKind.UseFsEvents }, - ); + // verifyWatchFile( + // "watchFile using polling", + // ts.sys, + // `${root}/polling/file.ts`, + // `${root}/linkedpolling/file.ts`, + // { watchFile: ts.WatchFileKind.PriorityPollingInterval }, + // ); + // verifyWatchFile( + // "watchFile using fsEvents", + // ts.sys, + // `${root}/fsevents/file.ts`, + // `${root}/linkedfsevents/file.ts`, + // { watchFile: ts.WatchFileKind.UseFsEvents }, + // ); - verifyWatchFile( - "watchDirectory using polling", - ts.sys, - `${root}/dirpolling`, - `${root}/linkeddirpolling`, - { watchFile: ts.WatchFileKind.PriorityPollingInterval }, - getFileName(), - ); + // verifyWatchFile( + // "watchDirectory using polling", + // ts.sys, + // `${root}/dirpolling`, + // `${root}/linkeddirpolling`, + // { watchFile: ts.WatchFileKind.PriorityPollingInterval }, + // getFileName(), + // ); - verifyWatchDirectoryUsingFsEvents( - ts.sys, - `${root}/dirfsevents`, - `${root}/linkeddirfsevents`, - (dir, cb) => fs.watch(dir, { persistent: true }, cb), - isMacOs, - isWindows, - ); + // verifyWatchDirectoryUsingFsEvents( + // ts.sys, + // `${root}/dirfsevents`, + // `${root}/linkeddirfsevents`, + // (dir, cb) => fs.watch(dir, { persistent: true }, cb), + // isMacOs, + // isWindows, + // ); + + if (isMacOs || isWindows) { + verifyRecursiveWatchDirectoryUsingFsEvents( + ts.sys, + `${root}/recursivefsevents`, + `${root}/recursivelinkedfsevents`, + (dir, cb) => fs.watch(dir, { persistent: true, recursive: true }, cb), + isMacOs, + ); + } }); describe("with virtualFileSystem::", () => { @@ -340,5 +538,21 @@ describe("unittests:: sys:: symlinkWatching::", () => { // /*isMacOs*/ false, // /*isWindows*/ false, // ); + + // verifyRecursiveWatchDirectoryUsingFsEvents( + // ts.sys, + // `${root}/recursivefsevents`, + // `${root}/recursivelinkedfsevents`, + // (dir, cb) => fs.watch(dir, { persistent: true, recursive: true }, cb), + // /*isMacOs*/ false, + // ); + + // verifyRecursiveWatchDirectoryUsingFsEvents( + // ts.sys, + // `${root}/recursivefsevents`, + // `${root}/recursivelinkedfsevents`, + // (dir, cb) => fs.watch(dir, { persistent: true, recursive: true }, cb), + // /*isMacOs*/ true, + // ); }); });