Skip to content

Commit f2f91cc

Browse files
authored
Make canUseWatchEvents test framework more generic so we can add more tests easily (#58962)
1 parent b384054 commit f2f91cc

File tree

6 files changed

+1435
-1077
lines changed

6 files changed

+1435
-1077
lines changed

src/testRunner/unittests/helpers/tsserver.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from "path";
12
import { incrementalVerifier } from "../../../harness/incrementalUtils.js";
23
import { patchServiceForStateBaseline } from "../../../harness/projectServiceStateLogger.js";
34
import {
@@ -67,6 +68,130 @@ export function patchHostTimeouts(
6768
}
6869
}
6970

71+
function patchSessionToHandleWatchEvents(session: TestSession) {
72+
const event = session.event;
73+
const idToClose = new Map<number, () => void>();
74+
session.event = (data, eventName) => {
75+
event.call(session, data, eventName);
76+
switch (eventName) {
77+
case ts.server.CreateFileWatcherEvent:
78+
watchFile(data as ts.server.protocol.CreateFileWatcherEventBody);
79+
break;
80+
case ts.server.CreateDirectoryWatcherEvent:
81+
watchDirectory(data as ts.server.protocol.CreateDirectoryWatcherEventBody);
82+
break;
83+
case ts.server.CloseFileWatcherEvent:
84+
closeWatcher(data as ts.server.protocol.CloseFileWatcherEventBody);
85+
break;
86+
default:
87+
break;
88+
}
89+
};
90+
91+
function watchFile(event: ts.server.protocol.CreateFileWatcherEventBody) {
92+
createWatcher(
93+
"watchFile",
94+
event,
95+
recordChange =>
96+
session.host.watchUtils.pollingWatch(
97+
session.host.toNormalizedAbsolutePath(event.path),
98+
{
99+
cb: (fileName, eventKind) =>
100+
recordChange(
101+
event.id,
102+
session.host.windowsStyleRoot ?
103+
path.win32.resolve(fileName) :
104+
path.posix.resolve(fileName),
105+
eventKind === ts.FileWatcherEventKind.Created ?
106+
"created" :
107+
eventKind === ts.FileWatcherEventKind.Deleted ? "deleted" : "updated",
108+
/*ignoreUpdate*/ false,
109+
),
110+
pollingInterval: undefined!,
111+
event,
112+
},
113+
),
114+
);
115+
}
116+
117+
function watchDirectory(event: ts.server.protocol.CreateDirectoryWatcherEventBody) {
118+
createWatcher(
119+
"watchDirectory",
120+
event,
121+
recordChange =>
122+
session.host.watchUtils.fsWatch(
123+
session.host.toNormalizedAbsolutePath(event.path),
124+
event.recursive,
125+
{
126+
cb: (eventName, relativeFileName) => {
127+
if (!relativeFileName) return;
128+
const fileName = session.host.windowsStyleRoot ?
129+
path.win32.join(event.path, relativeFileName) :
130+
path.posix.join(event.path, relativeFileName);
131+
if (eventName === "change") {
132+
recordChange(
133+
event.id,
134+
fileName,
135+
"updated",
136+
!!event.ignoreUpdate,
137+
);
138+
}
139+
else {
140+
recordChange(
141+
event.id,
142+
fileName,
143+
session.host.fileExists(fileName) || session.host.directoryExists(fileName) ?
144+
"created" :
145+
"deleted",
146+
!!event.ignoreUpdate,
147+
);
148+
}
149+
},
150+
inode: undefined,
151+
event,
152+
},
153+
),
154+
);
155+
}
156+
157+
function createWatcher(
158+
watchType: string,
159+
event: ts.server.protocol.CreateFileWatcherEventBody | ts.server.protocol.CreateDirectoryWatcherEventBody,
160+
create: (
161+
recordChange: (
162+
id: number,
163+
file: string,
164+
eventType: "created" | "deleted" | "updated",
165+
ignoreUpdate: boolean,
166+
) => void,
167+
) => ts.FileWatcher,
168+
) {
169+
session.logger.log(`Custom ${watchType}:: Added:: ${JSON.stringify(event)}`);
170+
ts.Debug.assert(!idToClose.has(event.id));
171+
const result = create((id, file, eventType, ignoreUpdate) => {
172+
const ignored = eventType === "updated" && ignoreUpdate;
173+
session.logger.log(`Custom ${watchType}:: Triggered${ignoreUpdate ? " Ignored" : ""}:: ${JSON.stringify(event)}:: ${file} ${eventType}`);
174+
if (!ignored) {
175+
let watchChange = session.watchChanges.get(id);
176+
if (!watchChange) session.watchChanges.set(id, watchChange = { id });
177+
(watchChange[eventType] ??= []).push(file);
178+
}
179+
});
180+
idToClose.set(event.id, () => {
181+
session.logger.log(`Custom ${watchType}:: Close:: ${JSON.stringify(event)}`);
182+
result.close();
183+
});
184+
}
185+
186+
function closeWatcher(data: ts.server.protocol.CloseFileWatcherEventBody) {
187+
const close = idToClose.get(data.id);
188+
if (close) {
189+
idToClose.delete(data.id);
190+
close();
191+
}
192+
}
193+
}
194+
70195
export interface TestSessionOptions extends ts.server.SessionOptions, TestTypingsInstallerOptions {
71196
host: TestServerHost;
72197
logger: LoggerWithInMemoryLogs;
@@ -90,6 +215,7 @@ export class TestSession extends ts.server.Session {
90215
public override logger!: LoggerWithInMemoryLogs;
91216
public override readonly typingsInstaller!: TestTypingsInstallerAdapter;
92217
public serverCancellationToken: TestServerCancellationToken;
218+
public watchChanges = new Map<number, ts.server.protocol.WatchChangeRequestArgs>();
93219

94220
constructor(optsOrHost: TestSessionConstructorOptions) {
95221
const opts = getTestSessionPartialOptionsAndHost(optsOrHost);
@@ -125,6 +251,7 @@ export class TestSession extends ts.server.Session {
125251
if (opts.regionDiagLineCountThreshold !== undefined) {
126252
this.regionDiagLineCountThreshold = opts.regionDiagLineCountThreshold;
127253
}
254+
if (opts.canUseWatchEvents) patchSessionToHandleWatchEvents(this);
128255
}
129256

130257
getProjectService() {
@@ -159,6 +286,15 @@ export class TestSession extends ts.server.Session {
159286
request.type = "request";
160287
return this.executeCommand(request);
161288
}
289+
290+
public invokeWatchChanges() {
291+
const changes = ts.singleOrMany(ts.arrayFrom(this.watchChanges.values()));
292+
this.watchChanges.clear();
293+
this.executeCommandSeq<ts.server.protocol.WatchChangeRequest>({
294+
command: ts.server.protocol.CommandTypes.WatchChange,
295+
arguments: changes,
296+
});
297+
}
162298
}
163299

164300
export function createSessionWithCustomEventHandler(

src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import {
33
Watches,
44
WatchUtils,
55
} from "../../../harness/watchUtils.js";
6+
import {
7+
CreateDirectoryWatcherEventBody,
8+
CreateFileWatcherEventBody,
9+
} from "../../../server/protocol.js";
610
import {
711
append,
812
arrayFrom,
@@ -276,11 +280,13 @@ type TimeOutCallback = (...args: any[]) => void;
276280
export interface TestFileWatcher {
277281
cb: FileWatcherCallback;
278282
pollingInterval: PollingInterval;
283+
event?: CreateFileWatcherEventBody;
279284
}
280285

281286
export interface TestFsWatcher {
282287
cb: FsWatchCallback;
283288
inode: number | undefined;
289+
event?: CreateDirectoryWatcherEventBody;
284290
}
285291

286292
export interface WatchInvokeOptions {

0 commit comments

Comments
 (0)