Add test when rename event occurs when file has already reappeared

This commit is contained in:
Sheetal Nandi committed 2022-05-06 12:15:35 -07:00
1 parent c7bbb97717
commit 189bbc22da
3 files changed
+260 -32

No files matched your search

+34 -32
View File
@@ -267,13 +267,17 @@ interface Array<T> { length: number; [n: number]: T; }`
inode: number | undefined;
}
export interface ReloadWatchInvokeOptions {
export interface WatchInvokeOptions {
/** Invokes the directory watcher for the parent instead of the file changed */
invokeDirectoryWatcherInsteadOfFileChanged: boolean;
/** When new file is created, do not invoke watches for it */
ignoreWatchInvokedWithTriggerAsFileCreate: boolean;
/** Invoke the file delete, followed by create instead of file changed */
invokeFileDeleteCreateAsPartInsteadOfChange: boolean;
/** Dont invoke delete watches */
ignoreDelete: boolean;
/** Skip inode check on file or folder create*/
skipInodeCheckOnCreate: boolean;
}
export enum Tsc_WatchFile {
@@ -324,7 +328,7 @@ interface Array<T> { length: number; [n: number]: T; }`
public defaultWatchFileKind?: () => WatchFileKind | undefined;
public storeFilesChangingSignatureDuringEmit = true;
watchFile: HostWatchFile;
private readonly inodeWatching: boolean | undefined;
private inodeWatching: boolean | undefined;
private readonly inodes?: ESMap<Path, number>;
watchDirectory: HostWatchDirectory;
constructor(
@@ -376,7 +380,7 @@ interface Array<T> { length: number; [n: number]: T; }`
tscWatchDirectory,
defaultWatchFileKind: () => this.defaultWatchFileKind?.(),
inodeWatching: !!this.inodeWatching,
sysLog: this.write.bind(this)
sysLog: s => this.write(s + this.newLine),
});
this.watchFile = watchFile;
this.watchDirectory = watchDirectory;
@@ -441,7 +445,7 @@ interface Array<T> { length: number; [n: number]: T; }`
}
}
modifyFile(filePath: string, content: string, options?: Partial<ReloadWatchInvokeOptions>) {
modifyFile(filePath: string, content: string, options?: Partial<WatchInvokeOptions>) {
const path = this.toFullPath(filePath);
const currentEntry = this.fs.get(path);
if (!currentEntry || !isFsFile(currentEntry)) {
@@ -449,8 +453,8 @@ interface Array<T> { length: number; [n: number]: T; }`
}
if (options && options.invokeFileDeleteCreateAsPartInsteadOfChange) {
this.removeFileOrFolder(currentEntry, returnFalse);
this.ensureFileOrFolder({ path: filePath, content });
this.removeFileOrFolder(currentEntry, /*isRenaming*/ false, options);
this.ensureFileOrFolder({ path: filePath, content }, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ undefined, /*ignoreParentWatch*/ undefined, options);
}
else {
currentEntry.content = content;
@@ -475,7 +479,7 @@ interface Array<T> { length: number; [n: number]: T; }`
Debug.assert(!!file);
// Only remove the file
this.removeFileOrFolder(file, returnFalse, /*isRenaming*/ true);
this.removeFileOrFolder(file, /*isRenaming*/ true);
// Add updated folder with new folder name
const newFullPath = getNormalizedAbsolutePath(newFileName, this.currentDirectory);
@@ -495,7 +499,7 @@ interface Array<T> { length: number; [n: number]: T; }`
Debug.assert(!!folder);
// Only remove the folder
this.removeFileOrFolder(folder, returnFalse, /*isRenaming*/ true);
this.removeFileOrFolder(folder, /*isRenaming*/ true);
// Add updated folder with new folder name
const newFullPath = getNormalizedAbsolutePath(newFolderName, this.currentDirectory);
@@ -530,29 +534,29 @@ interface Array<T> { length: number; [n: number]: T; }`
}
}
ensureFileOrFolder(fileOrDirectoryOrSymLink: FileOrFolderOrSymLink, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean, ignoreParentWatch?: boolean) {
ensureFileOrFolder(fileOrDirectoryOrSymLink: FileOrFolderOrSymLink, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean, ignoreParentWatch?: boolean, options?: Partial<WatchInvokeOptions>) {
if (isFile(fileOrDirectoryOrSymLink)) {
const file = this.toFsFile(fileOrDirectoryOrSymLink);
// file may already exist when updating existing type declaration file
if (!this.fs.get(file.path)) {
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath), ignoreParentWatch);
this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate);
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath), ignoreParentWatch, options);
this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate, options);
}
}
else if (isSymLink(fileOrDirectoryOrSymLink)) {
const symLink = this.toFsSymLink(fileOrDirectoryOrSymLink);
Debug.assert(!this.fs.get(symLink.path));
const baseFolder = this.ensureFolder(getDirectoryPath(symLink.fullPath), ignoreParentWatch);
this.addFileOrFolderInFolder(baseFolder, symLink, ignoreWatchInvokedWithTriggerAsFileCreate);
const baseFolder = this.ensureFolder(getDirectoryPath(symLink.fullPath), ignoreParentWatch, options);
this.addFileOrFolderInFolder(baseFolder, symLink, ignoreWatchInvokedWithTriggerAsFileCreate, options);
}
else {
const fullPath = getNormalizedAbsolutePath(fileOrDirectoryOrSymLink.path, this.currentDirectory);
this.ensureFolder(getDirectoryPath(fullPath), ignoreParentWatch);
this.ensureFolder(fullPath, ignoreWatchInvokedWithTriggerAsFileCreate);
this.ensureFolder(getDirectoryPath(fullPath), ignoreParentWatch, options);
this.ensureFolder(fullPath, ignoreWatchInvokedWithTriggerAsFileCreate, options);
}
}
private ensureFolder(fullPath: string, ignoreWatch: boolean | undefined): FsFolder {
private ensureFolder(fullPath: string, ignoreWatch: boolean | undefined, options: Partial<WatchInvokeOptions> | undefined): FsFolder {
const path = this.toPath(fullPath);
let folder = this.fs.get(path) as FsFolder;
if (!folder) {
@@ -560,8 +564,8 @@ interface Array<T> { length: number; [n: number]: T; }`
const baseFullPath = getDirectoryPath(fullPath);
if (fullPath !== baseFullPath) {
// Add folder in the base folder
const baseFolder = this.ensureFolder(baseFullPath, ignoreWatch);
this.addFileOrFolderInFolder(baseFolder, folder, ignoreWatch);
const baseFolder = this.ensureFolder(baseFullPath, ignoreWatch, options);
this.addFileOrFolderInFolder(baseFolder, folder, ignoreWatch, options);
}
else {
// root folder
@@ -574,7 +578,7 @@ interface Array<T> { length: number; [n: number]: T; }`
return folder;
}
private addFileOrFolderInFolder(folder: FsFolder, fileOrDirectory: FsFile | FsFolder | FsSymLink, ignoreWatch?: boolean) {
private addFileOrFolderInFolder(folder: FsFolder, fileOrDirectory: FsFile | FsFolder | FsSymLink, ignoreWatch?: boolean, options?: Partial<WatchInvokeOptions>) {
if (!this.fs.has(fileOrDirectory.path)) {
insertSorted(folder.entries, fileOrDirectory, (a, b) => compareStringsCaseSensitive(getBaseFileName(a.path), getBaseFileName(b.path)));
}
@@ -585,11 +589,14 @@ interface Array<T> { length: number; [n: number]: T; }`
if (ignoreWatch) {
return;
}
const inodeWatching = this.inodeWatching;
if (options?.skipInodeCheckOnCreate) this.inodeWatching = false;
this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
this.invokeFileAndFsWatches(folder.fullPath, FileWatcherEventKind.Changed);
this.inodeWatching = inodeWatching;
}
private removeFileOrFolder(fileOrDirectory: FsFile | FsFolder | FsSymLink, isRemovableLeafFolder: (folder: FsFolder) => boolean, isRenaming = false) {
private removeFileOrFolder(fileOrDirectory: FsFile | FsFolder | FsSymLink, isRenaming?: boolean, options?: Partial<WatchInvokeOptions>) {
const basePath = getDirectoryPath(fileOrDirectory.path);
const baseFolder = this.fs.get(basePath) as FsFolder;
if (basePath !== fileOrDirectory.path) {
@@ -602,21 +609,16 @@ interface Array<T> { length: number; [n: number]: T; }`
if (isFsFolder(fileOrDirectory)) {
Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming);
}
this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
if (!options?.ignoreDelete) this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
this.inodes?.delete(fileOrDirectory.path);
this.invokeFileAndFsWatches(baseFolder.fullPath, FileWatcherEventKind.Changed);
if (basePath !== fileOrDirectory.path &&
baseFolder.entries.length === 0 &&
isRemovableLeafFolder(baseFolder)) {
this.removeFileOrFolder(baseFolder, isRemovableLeafFolder);
}
if (!options?.ignoreDelete) this.invokeFileAndFsWatches(baseFolder.fullPath, FileWatcherEventKind.Changed);
}
deleteFile(filePath: string) {
const path = this.toFullPath(filePath);
const currentEntry = this.fs.get(path) as FsFile;
Debug.assert(isFsFile(currentEntry));
this.removeFileOrFolder(currentEntry, returnFalse);
this.removeFileOrFolder(currentEntry);
}
deleteFolder(folderPath: string, recursive?: boolean) {
@@ -630,11 +632,11 @@ interface Array<T> { length: number; [n: number]: T; }`
this.deleteFolder(fsEntry.fullPath, recursive);
}
else {
this.removeFileOrFolder(fsEntry, returnFalse);
this.removeFileOrFolder(fsEntry);
}
});
}
this.removeFileOrFolder(currentEntry, returnFalse);
this.removeFileOrFolder(currentEntry);
}
private watchFileWorker(fileName: string, cb: FileWatcherCallback, pollingInterval: PollingInterval) {
@@ -947,11 +949,11 @@ interface Array<T> { length: number; [n: number]: T; }`
}
}
prependFile(path: string, content: string, options?: Partial<ReloadWatchInvokeOptions>): void {
prependFile(path: string, content: string, options?: Partial<WatchInvokeOptions>): void {
this.modifyFile(path, content + this.readFile(path), options);
}
appendFile(path: string, content: string, options?: Partial<ReloadWatchInvokeOptions>): void {
appendFile(path: string, content: string, options?: Partial<WatchInvokeOptions>): void {
this.modifyFile(path, this.readFile(path) + content, options);
}
@@ -612,6 +612,45 @@ namespace ts.tscWatch {
},
]
});
verifyTscWatch({
scenario,
subScenario: `fsWatch/when using file watching thats on inode when rename occurs when file is still on the disk`,
commandLineArgs: ["-w", "--extendedDiagnostics"],
sys: () => createWatchedSystem(
{
[libFile.path]: libFile.content,
[`${projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`,
[`${projectRoot}/foo.ts`]: `export declare function foo(): string;`,
[`${projectRoot}/tsconfig.json`]: JSON.stringify({
watchOptions: { watchFile: "useFsEvents" },
files: ["foo.ts", "main.ts"]
}),
},
{
currentDirectory: projectRoot,
inodeWatching: true,
}
),
changes: [
{
caption: "Introduce error such that when callback happens file is already appeared",
// vm's wq generates this kind of event
// Skip delete event so inode changes but when the create's rename occurs file is on disk
change: sys => sys.modifyFile(`${projectRoot}/foo.ts`, `export declare function foo2(): string;`, {
invokeFileDeleteCreateAsPartInsteadOfChange: true,
ignoreDelete: true,
skipInodeCheckOnCreate: true
}),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1),
},
{
caption: "Replace file with rename event that fixes error",
change: sys => sys.modifyFile(`${projectRoot}/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }),
timeouts: sys => sys.checkTimeoutQueueLengthAndRun(0),
},
]
});
});
});
}
@@ -0,0 +1,187 @@
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.ts] Inode:: 9
export declare function foo(): string;
//// [/user/username/projects/myproject/tsconfig.json] Inode:: 10
{"watchOptions":{"watchFile":"useFsEvents"},"files":["foo.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.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.ts 250 {"watchFile":4} Source file
FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/main.ts 250 {"watchFile":4} Source file
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
[12:00:28 AM] Found 0 errors. Watching for file changes.
Program root files: ["/user/username/projects/myproject/foo.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.ts
/user/username/projects/myproject/main.ts
Semantic diagnostics in builder refreshed for::
/a/lib/lib.d.ts
/user/username/projects/myproject/foo.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.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.ts:
{"directoryName":"/user/username/projects/myproject/foo.ts","inode":9}
/user/username/projects/myproject/main.ts:
{"directoryName":"/user/username/projects/myproject/main.ts","inode":8}
/a/lib/lib.d.ts:
{"directoryName":"/a/lib/lib.d.ts","inode":3}
FsWatchesRecursive::
exitCode:: ExitStatus.undefined
//// [/user/username/projects/myproject/foo.js] Inode:: 11
"use strict";
exports.__esModule = true;
//// [/user/username/projects/myproject/main.js] Inode:: 12
"use strict";
exports.__esModule = true;
var foo_1 = require("./foo");
(0, foo_1.foo)();
Change:: Introduce error such that when callback happens file is already appeared
Input::
//// [/user/username/projects/myproject/foo.ts] Inode:: 13
export declare function foo2(): string;
Output::
FileWatcher:: Triggered with /user/username/projects/myproject/foo.ts 0:: WatchInfo: /user/username/projects/myproject/foo.ts 250 {"watchFile":4} Source file
Scheduling update
Elapsed:: *ms FileWatcher:: Triggered with /user/username/projects/myproject/foo.ts 0:: WatchInfo: /user/username/projects/myproject/foo.ts 250 {"watchFile":4} Source file
Synchronizing program
[12:00:32 AM] File change detected. Starting incremental compilation...
CreatingProgramWith::
roots: ["/user/username/projects/myproject/foo.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.ts:1:25
1 export declare function foo2(): string;
   ~~~~
'foo2' is declared here.
[12:00:39 AM] Found 1 error. Watching for file changes.
Program root files: ["/user/username/projects/myproject/foo.ts","/user/username/projects/myproject/main.ts"]
Program options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"}
Program structureReused: Completely
Program files::
/a/lib/lib.d.ts
/user/username/projects/myproject/foo.ts
/user/username/projects/myproject/main.ts
Semantic diagnostics in builder refreshed for::
/user/username/projects/myproject/foo.ts
/user/username/projects/myproject/main.ts
Shape signatures in builder refreshed for::
/user/username/projects/myproject/foo.ts (computed .d.ts)
/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.ts:
{"directoryName":"/user/username/projects/myproject/foo.ts","inode":9}
/user/username/projects/myproject/main.ts:
{"directoryName":"/user/username/projects/myproject/main.ts","inode":8}
/a/lib/lib.d.ts:
{"directoryName":"/a/lib/lib.d.ts","inode":3}
FsWatchesRecursive::
exitCode:: ExitStatus.undefined
//// [/user/username/projects/myproject/foo.js] file written with same contents Inode:: 11
//// [/user/username/projects/myproject/main.js] file written with same contents Inode:: 12
Change:: Replace file with rename event that fixes error
Input::
//// [/user/username/projects/myproject/foo.ts] Inode:: 14
export declare function foo(): string;
Output::
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.ts:
{"directoryName":"/user/username/projects/myproject/foo.ts","inode":9}
/user/username/projects/myproject/main.ts:
{"directoryName":"/user/username/projects/myproject/main.ts","inode":8}
/a/lib/lib.d.ts:
{"directoryName":"/a/lib/lib.d.ts","inode":3}
FsWatchesRecursive::
exitCode:: ExitStatus.undefined