Skip to content

Commit

Permalink
Handle fsWatch event with accesstime change on mac os (microsoft#56403)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheetalkamat authored Nov 15, 2023
1 parent d1d14e6 commit e40730f
Show file tree
Hide file tree
Showing 5 changed files with 398 additions and 3 deletions.
22 changes: 19 additions & 3 deletions src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,7 @@ export interface CreateSystemWatchFunctions {
useNonPollingWatchers?: boolean;
tscWatchDirectory: string | undefined;
inodeWatching: boolean;
fsWatchWithTimestamp: boolean | undefined;
sysLog: (s: string) => void;
}

Expand All @@ -934,6 +935,7 @@ export function createSystemWatchFunctions({
useNonPollingWatchers,
tscWatchDirectory,
inodeWatching,
fsWatchWithTimestamp,
sysLog,
}: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } {
const pollingWatches = new Map<string, SingleFileWatcher<FileWatcherCallback>>();
Expand Down Expand Up @@ -1189,7 +1191,7 @@ export function createSystemWatchFunctions({
return watchPresentFileSystemEntryWithFsWatchFile();
}
try {
const presentWatcher = fsWatchWorker(
const presentWatcher = (!fsWatchWithTimestamp ? fsWatchWorker : fsWatchWorkerHandlingTimestamp)(
fileOrDirectory,
recursive,
inodeWatching ?
Expand Down Expand Up @@ -1286,6 +1288,18 @@ export function createSystemWatchFunctions({
);
}
}

function fsWatchWorkerHandlingTimestamp(fileOrDirectory: string, recursive: boolean, callback: FsWatchCallback): FsWatchWorkerWatcher {
let modifiedTime = getModifiedTime(fileOrDirectory) || missingFileModifiedTime;
return fsWatchWorker(fileOrDirectory, recursive, (eventName, relativeFileName, currentModifiedTime) => {
if (eventName === "change") {
currentModifiedTime ||= getModifiedTime(fileOrDirectory) || missingFileModifiedTime;
if (currentModifiedTime.getTime() === modifiedTime.getTime()) return;
}
modifiedTime = currentModifiedTime || getModifiedTime(fileOrDirectory) || missingFileModifiedTime;
callback(eventName, relativeFileName, modifiedTime);
});
}
}

/**
Expand Down Expand Up @@ -1482,7 +1496,8 @@ export let sys: System = (() => {
from?(input: string, encoding?: string): any;
} = require("buffer").Buffer;

const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin";
const isMacOs = process.platform === "darwin";
const isLinuxOrMacOs = process.platform === "linux" || isMacOs;

const platform: string = _os.platform();
const useCaseSensitiveFileNames = isFileSystemCaseSensitive();
Expand All @@ -1495,7 +1510,7 @@ export let sys: System = (() => {
// Note that if we ever emit as files like cjs/mjs, this check will be wrong.
const executingFilePath = __filename.endsWith("sys.js") ? _path.join(_path.dirname(__dirname), "__fake__.js") : __filename;

const fsSupportsRecursiveFsWatch = process.platform === "win32" || process.platform === "darwin";
const fsSupportsRecursiveFsWatch = process.platform === "win32" || isMacOs;
const getCurrentDirectory = memoize(() => process.cwd());
const { watchFile, watchDirectory } = createSystemWatchFunctions({
pollingWatchFileWorker: fsWatchFileWorker,
Expand All @@ -1515,6 +1530,7 @@ export let sys: System = (() => {
useNonPollingWatchers: !!process.env.TSC_NONPOLLING_WATCHER,
tscWatchDirectory: process.env.TSC_WATCHDIRECTORY,
inodeWatching: isLinuxOrMacOs,
fsWatchWithTimestamp: isMacOs,
sysLog,
});
const nodeSystem: System = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export interface TestServerHostCreationParameters {
runWithoutRecursiveWatches?: boolean;
runWithFallbackPolling?: boolean;
inodeWatching?: boolean;
fsWatchWithTimestamp?: boolean;
}

export function createWatchedSystem(fileOrFolderList: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost {
Expand Down Expand Up @@ -377,6 +378,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
runWithoutRecursiveWatches,
runWithFallbackPolling,
inodeWatching,
fsWatchWithTimestamp,
}: TestServerHostCreationParameters = {},
) {
this.useCaseSensitiveFileNames = !!useCaseSensitiveFileNames;
Expand Down Expand Up @@ -414,6 +416,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
tscWatchFile,
tscWatchDirectory,
inodeWatching: !!this.inodeWatching,
fsWatchWithTimestamp,
sysLog: s => this.write(s + this.newLine),
});
this.watchFile = watchFile;
Expand Down
36 changes: 36 additions & 0 deletions src/testRunner/unittests/tscWatch/watchEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,42 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po
});
});

describe("with fsWatch with fsWatchWithTimestamp", () => {
function verify(fsWatchWithTimestamp: boolean) {
verifyTscWatch({
scenario,
subScenario: `fsWatch/fsWatchWithTimestamp ${fsWatchWithTimestamp}`,
commandLineArgs: ["-w", "--extendedDiagnostics"],
sys: () =>
createWatchedSystem(
{
[libFile.path]: libFile.content,
"/user/username/projects/myproject/main.ts": `export const x = 10;`,
"/user/username/projects/myproject/tsconfig.json": jsonToReadableText({ files: ["main.ts"] }),
},
{
currentDirectory: "/user/username/projects/myproject",
fsWatchWithTimestamp,
},
),
edits: [
{
caption: "emulate access",
edit: sys => sys.invokeFsWatches("/user/username/projects/myproject/main.ts", "change", /*modifiedTime*/ undefined, /*useTildeSuffix*/ undefined),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
{
caption: "modify file contents",
edit: sys => sys.appendFile("/user/username/projects/myproject/main.ts", "export const y = 10;"),
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
},
],
});
}
verify(/*fsWatchWithTimestamp*/ true);
verify(/*fsWatchWithTimestamp*/ false);
});

verifyTscWatch({
scenario,
subScenario: "fsEvent for change is repeated",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
currentDirectory:: /user/username/projects/myproject useCaseSensitiveFileNames: false
Input::
//// [/a/lib/lib.d.ts]
/// <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]
export const x = 10;

//// [/user/username/projects/myproject/tsconfig.json]
{
"files": [
"main.ts"
]
}


/a/lib/tsc.js -w --extendedDiagnostics
Output::
[12:00:21 AM] Starting compilation in watch mode...

Current directory: /user/username/projects/myproject CaseSensitiveFileNames: false
FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 undefined Config file
Synchronizing program
CreatingProgramWith::
roots: ["/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/main.ts 250 undefined Source file
FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 250 undefined Source file
DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 undefined Type roots
Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 undefined Type roots
DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/node_modules/@types 1 undefined Type roots
Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/node_modules/@types 1 undefined Type roots
[12:00:24 AM] Found 0 errors. Watching for file changes.



//// [/user/username/projects/myproject/main.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.x = void 0;
exports.x = 10;



PolledWatches::
/user/username/projects/myproject/node_modules/@types: *new*
{"pollingInterval":500}
/user/username/projects/node_modules/@types: *new*
{"pollingInterval":500}

FsWatches::
/a/lib/lib.d.ts: *new*
{}
/user/username/projects/myproject/main.ts: *new*
{}
/user/username/projects/myproject/tsconfig.json: *new*
{}

Program root files: [
"/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/main.ts

Semantic diagnostics in builder refreshed for::
/a/lib/lib.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/main.ts (used version)

exitCode:: ExitStatus.undefined

Change:: emulate access

Input::

Output::
FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file
Scheduling update
Elapsed:: *ms FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file


Timeout callback:: count: 1
1: timerToUpdateProgram *new*

Before running Timeout callback:: count: 1
1: timerToUpdateProgram

After running Timeout callback:: count: 0
Output::
Synchronizing program




exitCode:: ExitStatus.undefined

Change:: modify file contents

Input::
//// [/user/username/projects/myproject/main.ts]
export const x = 10;export const y = 10;


Output::
FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file
Scheduling update
Elapsed:: *ms FileWatcher:: Triggered with /user/username/projects/myproject/main.ts 1:: WatchInfo: /user/username/projects/myproject/main.ts 250 undefined Source file


Timeout callback:: count: 1
2: timerToUpdateProgram *new*

Before running Timeout callback:: count: 1
2: timerToUpdateProgram

After running Timeout callback:: count: 0
Output::
Synchronizing program
[12:00:27 AM] File change detected. Starting incremental compilation...

CreatingProgramWith::
roots: ["/user/username/projects/myproject/main.ts"]
options: {"watch":true,"extendedDiagnostics":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"}
[12:00:31 AM] Found 0 errors. Watching for file changes.



//// [/user/username/projects/myproject/main.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.y = exports.x = void 0;
exports.x = 10;
exports.y = 10;




Program root files: [
"/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/main.ts

Semantic diagnostics in builder refreshed for::
/user/username/projects/myproject/main.ts

Shape signatures in builder refreshed for::
/user/username/projects/myproject/main.ts (computed .d.ts)

exitCode:: ExitStatus.undefined
Loading

0 comments on commit e40730f

Please sign in to comment.