Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle fsWatch event with accesstime change on mac os #56403

Merged
merged 2 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading