Add test when rename event occurs on mac with ~ appended to file name

This commit is contained in:
Sheetal Nandi
2022-05-25 13:54:19 -07:00
parent f2c710cd25
commit 78377e17af
3 changed files with 256 additions and 22 deletions
+26 -22
View File
@@ -297,6 +297,8 @@ interface Array<T> { length: number; [n: number]: T; }`
ignoreDelete: boolean;
/** Skip inode check on file or folder create*/
skipInodeCheckOnCreate: boolean;
/** When invoking rename event on fs watch, send event with file name suffixed with tilde */
useTildeAsSuffixInRenameEventFileName: boolean;
}
export enum Tsc_WatchFile {
@@ -490,11 +492,11 @@ interface Array<T> { length: number; [n: number]: T; }`
if (options && options.invokeDirectoryWatcherInsteadOfFileChanged) {
const directoryFullPath = getDirectoryPath(currentEntry.fullPath);
this.invokeFileWatcher(directoryFullPath, FileWatcherEventKind.Changed, /*useFileNameInCallback*/ true);
this.invokeFsWatchesCallbacks(directoryFullPath, "rename", currentEntry.fullPath);
this.invokeRecursiveFsWatches(directoryFullPath, "rename", currentEntry.fullPath);
this.invokeFsWatchesCallbacks(directoryFullPath, "rename", currentEntry.fullPath, options.useTildeAsSuffixInRenameEventFileName);
this.invokeRecursiveFsWatches(directoryFullPath, "rename", currentEntry.fullPath, options.useTildeAsSuffixInRenameEventFileName);
}
else {
this.invokeFileAndFsWatches(currentEntry.fullPath, FileWatcherEventKind.Changed);
this.invokeFileAndFsWatches(currentEntry.fullPath, FileWatcherEventKind.Changed, options?.useTildeAsSuffixInRenameEventFileName);
}
}
}
@@ -618,8 +620,8 @@ interface Array<T> { length: number; [n: number]: T; }`
}
const inodeWatching = this.inodeWatching;
if (options?.skipInodeCheckOnCreate) this.inodeWatching = false;
this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
this.invokeFileAndFsWatches(folder.fullPath, FileWatcherEventKind.Changed);
this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Created, options?.useTildeAsSuffixInRenameEventFileName);
this.invokeFileAndFsWatches(folder.fullPath, FileWatcherEventKind.Changed, options?.useTildeAsSuffixInRenameEventFileName);
this.inodeWatching = inodeWatching;
}
@@ -636,9 +638,9 @@ interface Array<T> { length: number; [n: number]: T; }`
if (isFsFolder(fileOrDirectory)) {
Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming);
}
if (!options?.ignoreDelete) this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
if (!options?.ignoreDelete) this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted, options?.useTildeAsSuffixInRenameEventFileName);
this.inodes?.delete(fileOrDirectory.path);
if (!options?.ignoreDelete) this.invokeFileAndFsWatches(baseFolder.fullPath, FileWatcherEventKind.Changed);
if (!options?.ignoreDelete) this.invokeFileAndFsWatches(baseFolder.fullPath, FileWatcherEventKind.Changed, options?.useTildeAsSuffixInRenameEventFileName);
}
deleteFile(filePath: string) {
@@ -700,45 +702,47 @@ interface Array<T> { length: number; [n: number]: T; }`
invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(fileFullPath)), ({ cb, fileName }) => cb(useFileNameInCallback ? fileName : fileFullPath, eventKind));
}
private fsWatchCallback(map: MultiMap<Path, TestFsWatcher>, fullPath: string, eventName: "rename" | "change", entryFullPath?: string) {
private fsWatchCallback(map: MultiMap<Path, TestFsWatcher>, fullPath: string, eventName: "rename" | "change", entryFullPath: string | undefined, useTildeSuffix: boolean | undefined) {
const path = this.toPath(fullPath);
const currentInode = this.inodes?.get(path);
invokeWatcherCallbacks(map.get(path), ({ cb, inode }) => {
// TODO::
if (this.inodeWatching && inode !== undefined && inode !== currentInode) return;
cb(eventName, entryFullPath ? this.getRelativePathToDirectory(fullPath, entryFullPath) : "");
let relativeFileName = (entryFullPath ? this.getRelativePathToDirectory(fullPath, entryFullPath) : "");
if (useTildeSuffix) relativeFileName = (relativeFileName ? relativeFileName : getBaseFileName(fullPath)) + "~";
cb(eventName, relativeFileName);
});
}
invokeFsWatchesCallbacks(fullPath: string, eventName: "rename" | "change", entryFullPath?: string) {
this.fsWatchCallback(this.fsWatches, fullPath, eventName, entryFullPath);
invokeFsWatchesCallbacks(fullPath: string, eventName: "rename" | "change", entryFullPath?: string, useTildeSuffix?: boolean) {
this.fsWatchCallback(this.fsWatches, fullPath, eventName, entryFullPath, useTildeSuffix);
}
invokeFsWatchesRecursiveCallbacks(fullPath: string, eventName: "rename" | "change", entryFullPath?: string) {
this.fsWatchCallback(this.fsWatchesRecursive, fullPath, eventName, entryFullPath);
invokeFsWatchesRecursiveCallbacks(fullPath: string, eventName: "rename" | "change", entryFullPath?: string, useTildeSuffix?: boolean) {
this.fsWatchCallback(this.fsWatchesRecursive, fullPath, eventName, entryFullPath, useTildeSuffix);
}
private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) {
return getRelativePathToDirectoryOrUrl(directoryFullPath, fileFullPath, this.currentDirectory, this.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
}
private invokeRecursiveFsWatches(fullPath: string, eventName: "rename" | "change", entryFullPath?: string) {
this.invokeFsWatchesRecursiveCallbacks(fullPath, eventName, entryFullPath);
private invokeRecursiveFsWatches(fullPath: string, eventName: "rename" | "change", entryFullPath?: string, useTildeSuffix?: boolean) {
this.invokeFsWatchesRecursiveCallbacks(fullPath, eventName, entryFullPath, useTildeSuffix);
const basePath = getDirectoryPath(fullPath);
if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) {
this.invokeRecursiveFsWatches(basePath, eventName, entryFullPath || fullPath);
this.invokeRecursiveFsWatches(basePath, eventName, entryFullPath || fullPath, useTildeSuffix);
}
}
private invokeFsWatches(fullPath: string, eventName: "rename" | "change") {
this.invokeFsWatchesCallbacks(fullPath, eventName);
this.invokeFsWatchesCallbacks(getDirectoryPath(fullPath), eventName, fullPath);
this.invokeRecursiveFsWatches(fullPath, eventName);
private invokeFsWatches(fullPath: string, eventName: "rename" | "change", useTildeSuffix: boolean | undefined) {
this.invokeFsWatchesCallbacks(fullPath, eventName, fullPath, useTildeSuffix);
this.invokeFsWatchesCallbacks(getDirectoryPath(fullPath), eventName, fullPath, useTildeSuffix);
this.invokeRecursiveFsWatches(fullPath, eventName, /*entryFullPath*/ undefined, useTildeSuffix);
}
private invokeFileAndFsWatches(fileOrFolderFullPath: string, eventKind: FileWatcherEventKind) {
private invokeFileAndFsWatches(fileOrFolderFullPath: string, eventKind: FileWatcherEventKind, useTildeSuffix?: boolean) {
this.invokeFileWatcher(fileOrFolderFullPath, eventKind);
this.invokeFsWatches(fileOrFolderFullPath, eventKind === FileWatcherEventKind.Changed ? "change" : "rename");
this.invokeFsWatches(fileOrFolderFullPath, eventKind === FileWatcherEventKind.Changed ? "change" : "rename", useTildeSuffix);
}
private toFsEntry(path: string): FSEntryBase {
@@ -648,6 +648,36 @@ namespace ts.tscWatch {
]
});
verifyTscWatch({
scenario,
subScenario: `fsWatch/when using file watching thats on inode when rename event ends with tilde`,
commandLineArgs: ["-w", "--extendedDiagnostics"],
sys: () => createWatchedSystem(
{
[libFile.path]: libFile.content,
[`${projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`,
[`${projectRoot}/foo.d.ts`]: `export function foo(): string;`,
[`${projectRoot}/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }),
},
{
currentDirectory: projectRoot,
inodeWatching: true
}
),
changes: [
{
caption: "Replace file with rename event that introduces error",
change: sys => sys.modifyFile(`${projectRoot}/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Replace file with rename event that fixes error",
change: sys => sys.modifyFile(`${projectRoot}/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(0),
},
]
});
verifyTscWatch({
scenario,
subScenario: `fsWatch/when using file watching thats on inode when rename occurs when file is still on the disk`,
@@ -0,0 +1,200 @@
Input::
//// [/a/lib/lib.d.ts] Inode:: 3
/// <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; }
//// [/user/username/projects/myproject/main.ts] Inode:: 8
import { foo } from "./foo"; foo();
//// [/user/username/projects/myproject/foo.d.ts] Inode:: 9
export function foo(): string;
//// [/user/username/projects/myproject/tsconfig.json] Inode:: 10
{"watchOptions":{"watchFile":"useFsEvents"},"files":["foo.d.ts","main.ts"]}
/a/lib/tsc.js -w --extendedDiagnostics
Output::
[12:00:23 AM] Starting compilation in watch mode...
Current directory: /user/username/projects/myproject CaseSensitiveFileNames: false
FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 {"watchFile":4} Config file
Synchronizing program
CreatingProgramWith::
roots: ["/user/username/projects/myproject/foo.d.ts","/user/username/projects/myproject/main.ts"]
options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"}
FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/foo.d.ts 250 {"watchFile":4} Source file
FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/main.ts 250 {"watchFile":4} Source file
DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 250 {"watchFile":4} Source file
DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 {"watchFile":4} Type roots
Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 {"watchFile":4} Type roots
DirectoryWatcher:: Triggered with /user/username/projects/myproject/main.js :: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/myproject/main.js :: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
[12:00:26 AM] Found 0 errors. Watching for file changes.
Program root files: ["/user/username/projects/myproject/foo.d.ts","/user/username/projects/myproject/main.ts"]
Program options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"}
Program structureReused: Not
Program files::
/a/lib/lib.d.ts
/user/username/projects/myproject/foo.d.ts
/user/username/projects/myproject/main.ts
Semantic diagnostics in builder refreshed for::
/a/lib/lib.d.ts
/user/username/projects/myproject/foo.d.ts
/user/username/projects/myproject/main.ts
Shape signatures in builder refreshed for::
/a/lib/lib.d.ts (used version)
/user/username/projects/myproject/foo.d.ts (used version)
/user/username/projects/myproject/main.ts (used version)
WatchedFiles::
/user/username/projects/myproject/node_modules/@types:
{"fileName":"/user/username/projects/myproject/node_modules/@types","pollingInterval":500}
FsWatches::
/user/username/projects/myproject/tsconfig.json:
{"directoryName":"/user/username/projects/myproject/tsconfig.json","inode":10}
/user/username/projects/myproject/foo.d.ts:
{"directoryName":"/user/username/projects/myproject/foo.d.ts","inode":9}
/user/username/projects/myproject/main.ts:
{"directoryName":"/user/username/projects/myproject/main.ts","inode":8}
/user/username/projects/myproject:
{"directoryName":"/user/username/projects/myproject","inode":7}
/a/lib/lib.d.ts:
{"directoryName":"/a/lib/lib.d.ts","inode":3}
FsWatchesRecursive::
exitCode:: ExitStatus.undefined
//// [/user/username/projects/myproject/main.js] Inode:: 11
"use strict";
exports.__esModule = true;
var foo_1 = require("./foo");
(0, foo_1.foo)();
Change:: Replace file with rename event that introduces error
Input::
//// [/user/username/projects/myproject/foo.d.ts] Inode:: 12
export function foo2(): string;
Output::
FileWatcher:: Triggered with /user/username/projects/myproject/foo.d.ts 2:: WatchInfo: /user/username/projects/myproject/foo.d.ts 250 {"watchFile":4} Source file
Scheduling update
Elapsed:: *ms FileWatcher:: Triggered with /user/username/projects/myproject/foo.d.ts 2:: WatchInfo: /user/username/projects/myproject/foo.d.ts 250 {"watchFile":4} Source file
DirectoryWatcher:: Triggered with /user/username/projects/myproject/foo.d.ts~ :: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/myproject/foo.d.ts~ :: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
DirectoryWatcher:: Triggered with /user/username/projects/myproject/foo.d.ts~ :: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/myproject/foo.d.ts~ :: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
Synchronizing program
[12:00:30 AM] File change detected. Starting incremental compilation...
CreatingProgramWith::
roots: ["/user/username/projects/myproject/foo.d.ts","/user/username/projects/myproject/main.ts"]
options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"}
main.ts:1:10 - error TS2724: '"./foo"' has no exported member named 'foo'. Did you mean 'foo2'?
1 import { foo } from "./foo"; foo();
   ~~~
foo.d.ts:1:17
1 export function foo2(): string;
   ~~~~
'foo2' is declared here.
[12:00:34 AM] Found 1 error. Watching for file changes.
Program root files: ["/user/username/projects/myproject/foo.d.ts","/user/username/projects/myproject/main.ts"]
Program options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"}
Program structureReused: SafeModules
Program files::
/a/lib/lib.d.ts
/user/username/projects/myproject/foo.d.ts
/user/username/projects/myproject/main.ts
Semantic diagnostics in builder refreshed for::
/user/username/projects/myproject/foo.d.ts
/user/username/projects/myproject/main.ts
Shape signatures in builder refreshed for::
/user/username/projects/myproject/foo.d.ts (used version)
/user/username/projects/myproject/main.ts (computed .d.ts)
WatchedFiles::
/user/username/projects/myproject/node_modules/@types:
{"fileName":"/user/username/projects/myproject/node_modules/@types","pollingInterval":500}
FsWatches::
/user/username/projects/myproject/tsconfig.json:
{"directoryName":"/user/username/projects/myproject/tsconfig.json","inode":10}
/user/username/projects/myproject/foo.d.ts:
{"directoryName":"/user/username/projects/myproject/foo.d.ts","inode":9}
/user/username/projects/myproject/main.ts:
{"directoryName":"/user/username/projects/myproject/main.ts","inode":8}
/user/username/projects/myproject:
{"directoryName":"/user/username/projects/myproject","inode":7}
/a/lib/lib.d.ts:
{"directoryName":"/a/lib/lib.d.ts","inode":3}
FsWatchesRecursive::
exitCode:: ExitStatus.undefined
//// [/user/username/projects/myproject/main.js] file written with same contents Inode:: 11
Change:: Replace file with rename event that fixes error
Input::
//// [/user/username/projects/myproject/foo.d.ts] Inode:: 13
export function foo(): string;
Output::
DirectoryWatcher:: Triggered with /user/username/projects/myproject/foo.d.ts~ :: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/myproject/foo.d.ts~ :: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
DirectoryWatcher:: Triggered with /user/username/projects/myproject/foo.d.ts~ :: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/myproject/foo.d.ts~ :: WatchInfo: /user/username/projects/myproject 0 {"watchFile":4} Failed Lookup Locations
WatchedFiles::
/user/username/projects/myproject/node_modules/@types:
{"fileName":"/user/username/projects/myproject/node_modules/@types","pollingInterval":500}
FsWatches::
/user/username/projects/myproject/tsconfig.json:
{"directoryName":"/user/username/projects/myproject/tsconfig.json","inode":10}
/user/username/projects/myproject/foo.d.ts:
{"directoryName":"/user/username/projects/myproject/foo.d.ts","inode":9}
/user/username/projects/myproject/main.ts:
{"directoryName":"/user/username/projects/myproject/main.ts","inode":8}
/user/username/projects/myproject:
{"directoryName":"/user/username/projects/myproject","inode":7}
/a/lib/lib.d.ts:
{"directoryName":"/a/lib/lib.d.ts","inode":3}
FsWatchesRecursive::
exitCode:: ExitStatus.undefined