• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import * as ts from "../../../_namespaces/ts";
2
3describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => {
4    function verifyFiles(caption: string, actual: readonly string[], expected: readonly string[]) {
5        assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`);
6        const seen = new ts.Map<string, true>();
7        ts.forEach(actual, f => {
8            assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`);
9            seen.set(f, true);
10            assert.isTrue(ts.contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`);
11        });
12    }
13
14    function createVerifyInitialOpen(session: ts.projectSystem.TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: ts.server.ProjectsUpdatedInBackgroundEvent[]) => void) {
15        return (file: ts.projectSystem.File) => {
16            session.executeCommandSeq({
17                command: ts.server.CommandNames.Open,
18                arguments: {
19                    file: file.path
20                }
21            } as ts.projectSystem.protocol.OpenRequest);
22            verifyProjectsUpdatedInBackgroundEventHandler([]);
23        };
24    }
25
26    interface ProjectsUpdatedInBackgroundEventVerifier {
27        session: ts.projectSystem.TestSession;
28        verifyProjectsUpdatedInBackgroundEventHandler(events: ts.server.ProjectsUpdatedInBackgroundEvent[]): void;
29        verifyInitialOpen(file: ts.projectSystem.File): void;
30    }
31
32    function verifyProjectsUpdatedInBackgroundEvent(scenario: string, createSession: (host: ts.projectSystem.TestServerHost, logger?: ts.projectSystem.Logger) => ProjectsUpdatedInBackgroundEventVerifier) {
33        it("when adding new file", () => {
34            const commonFile1: ts.projectSystem.File = {
35                path: "/a/b/file1.ts",
36                content: "export var x = 10;"
37            };
38            const commonFile2: ts.projectSystem.File = {
39                path: "/a/b/file2.ts",
40                content: "export var y = 10;"
41            };
42            const commonFile3: ts.projectSystem.File = {
43                path: "/a/b/file3.ts",
44                content: "export var z = 10;"
45            };
46            const configFile: ts.projectSystem.File = {
47                path: "/a/b/tsconfig.json",
48                content: `{}`
49            };
50            const openFiles = [commonFile1.path];
51            const host = ts.projectSystem.createServerHost([commonFile1, ts.projectSystem.libFile, configFile]);
52            const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host);
53            verifyInitialOpen(commonFile1);
54
55            host.writeFile(commonFile2.path, commonFile2.content);
56            host.runQueuedTimeoutCallbacks();
57            verifyProjectsUpdatedInBackgroundEventHandler([{
58                eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
59                data: {
60                    openFiles
61                }
62            }]);
63
64            host.writeFile(commonFile3.path, commonFile3.content);
65            host.runQueuedTimeoutCallbacks();
66            verifyProjectsUpdatedInBackgroundEventHandler([{
67                eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
68                data: {
69                    openFiles
70                }
71            }]);
72        });
73
74        describe("with --out or --outFile setting", () => {
75            function verifyEventWithOutSettings(compilerOptions: ts.CompilerOptions = {}) {
76                const config: ts.projectSystem.File = {
77                    path: "/a/tsconfig.json",
78                    content: JSON.stringify({
79                        compilerOptions
80                    })
81                };
82
83                const f1: ts.projectSystem.File = {
84                    path: "/a/a.ts",
85                    content: "export let x = 1"
86                };
87                const f2: ts.projectSystem.File = {
88                    path: "/a/b.ts",
89                    content: "export let y = 1"
90                };
91
92                const openFiles = [f1.path];
93                const files = [f1, config, ts.projectSystem.libFile];
94                const host = ts.projectSystem.createServerHost(files);
95                const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host);
96                verifyInitialOpen(f1);
97
98                host.writeFile(f2.path, f2.content);
99                host.runQueuedTimeoutCallbacks();
100
101                verifyProjectsUpdatedInBackgroundEventHandler([{
102                    eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
103                    data: {
104                        openFiles
105                    }
106                }]);
107
108                host.writeFile(f2.path, "export let x = 11");
109                host.runQueuedTimeoutCallbacks();
110                verifyProjectsUpdatedInBackgroundEventHandler([{
111                    eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
112                    data: {
113                        openFiles
114                    }
115                }]);
116            }
117
118            it("when both options are not set", () => {
119                verifyEventWithOutSettings();
120            });
121
122            it("when --out is set", () => {
123                const outJs = "/a/out.js";
124                verifyEventWithOutSettings({ out: outJs });
125            });
126
127            it("when --outFile is set", () => {
128                const outJs = "/a/out.js";
129                verifyEventWithOutSettings({ outFile: outJs });
130            });
131        });
132
133        describe("with modules and configured project", () => {
134            const file1Consumer1Path = "/a/b/file1Consumer1.ts";
135            const moduleFile1Path = "/a/b/moduleFile1.ts";
136            const configFilePath = "/a/b/tsconfig.json";
137            interface InitialStateParams {
138                /** custom config file options */
139                configObj?: any;
140                /** Additional files and folders to add */
141                getAdditionalFileOrFolder?(): ts.projectSystem.File[];
142                /** initial list of files to reload in fs and first file in this list being the file to open */
143                firstReloadFileList?: string[];
144            }
145            function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) {
146                const moduleFile1: ts.projectSystem.File = {
147                    path: moduleFile1Path,
148                    content: "export function Foo() { };",
149                };
150
151                const file1Consumer1: ts.projectSystem.File = {
152                    path: file1Consumer1Path,
153                    content: `import {Foo} from "./moduleFile1"; export var y = 10;`,
154                };
155
156                const file1Consumer2: ts.projectSystem.File = {
157                    path: "/a/b/file1Consumer2.ts",
158                    content: `import {Foo} from "./moduleFile1"; let z = 10;`,
159                };
160
161                const moduleFile2: ts.projectSystem.File = {
162                    path: "/a/b/moduleFile2.ts",
163                    content: `export var Foo4 = 10;`,
164                };
165
166                const globalFile3: ts.projectSystem.File = {
167                    path: "/a/b/globalFile3.ts",
168                    content: `interface GlobalFoo { age: number }`
169                };
170
171                const additionalFiles = getAdditionalFileOrFolder ? getAdditionalFileOrFolder() : [];
172                const configFile = {
173                    path: configFilePath,
174                    content: JSON.stringify(configObj || { compilerOptions: {} })
175                };
176
177                const files: ts.projectSystem.File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, ts.projectSystem.libFile, configFile];
178
179                const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files;
180                const host = ts.projectSystem.createServerHost([filesToReload[0], configFile]);
181
182                // Initial project creation
183                const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host);
184                const openFiles = [filesToReload[0].path];
185                verifyInitialOpen(filesToReload[0]);
186
187                // Since this is first event, it will have all the files
188                filesToReload.forEach(f => host.ensureFileOrFolder(f));
189                if (!firstReloadFileList) host.runQueuedTimeoutCallbacks(); // Invalidated module resolutions to schedule project update
190                verifyProjectsUpdatedInBackgroundEvent();
191
192                return {
193                    host,
194                    moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile,
195                    updateContentOfOpenFile,
196                    verifyNoProjectsUpdatedInBackgroundEvent,
197                    verifyProjectsUpdatedInBackgroundEvent
198                };
199
200                function getFiles(filelist: string[]) {
201                    return ts.map(filelist, getFile);
202                }
203
204                function getFile(fileName: string) {
205                    return ts.find(files, file => file.path === fileName)!;
206                }
207
208                function verifyNoProjectsUpdatedInBackgroundEvent() {
209                    host.runQueuedTimeoutCallbacks();
210                    verifyProjectsUpdatedInBackgroundEventHandler([]);
211                }
212
213                function verifyProjectsUpdatedInBackgroundEvent() {
214                    host.runQueuedTimeoutCallbacks();
215                    verifyProjectsUpdatedInBackgroundEventHandler([{
216                        eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
217                        data: {
218                            openFiles
219                        }
220                    }]);
221                }
222
223                function updateContentOfOpenFile(file: ts.projectSystem.File, newContent: string) {
224                    session.executeCommandSeq<ts.projectSystem.protocol.ChangeRequest>({
225                        command: ts.server.CommandNames.Change,
226                        arguments: {
227                            file: file.path,
228                            insertString: newContent,
229                            endLine: 1,
230                            endOffset: file.content.length,
231                            line: 1,
232                            offset: 1
233                        }
234                    });
235                    file.content = newContent;
236                }
237            }
238
239            it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => {
240                const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState();
241
242                // Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
243                host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
244                verifyProjectsUpdatedInBackgroundEvent();
245
246                // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`
247                host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { console.log('hi'); };`);
248                verifyProjectsUpdatedInBackgroundEvent();
249            });
250
251            it("should be up-to-date with the reference map changes", () => {
252                const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState();
253
254                // Change file1Consumer1 content to `export let y = Foo();`
255                updateContentOfOpenFile(file1Consumer1, "export let y = Foo();");
256                verifyNoProjectsUpdatedInBackgroundEvent();
257
258                // Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
259                host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
260                verifyProjectsUpdatedInBackgroundEvent();
261
262                // Add the import statements back to file1Consumer1
263                updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`);
264                verifyNoProjectsUpdatedInBackgroundEvent();
265
266                // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`
267                host.writeFile(moduleFile1.path, `export var T: number;export var T2: string;export function Foo() { };`);
268                verifyProjectsUpdatedInBackgroundEvent();
269
270                // Multiple file edits in one go:
271
272                // Change file1Consumer1 content to `export let y = Foo();`
273                // Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
274                updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`);
275                host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
276                verifyProjectsUpdatedInBackgroundEvent();
277            });
278
279            it("should be up-to-date with deleted files", () => {
280                const { host, moduleFile1, file1Consumer2, verifyProjectsUpdatedInBackgroundEvent } = getInitialState();
281
282                // Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
283                host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
284
285                // Delete file1Consumer2
286                host.deleteFile(file1Consumer2.path);
287                verifyProjectsUpdatedInBackgroundEvent();
288            });
289
290            it("should be up-to-date with newly created files", () => {
291                const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState();
292
293                host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
294                host.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`);
295                verifyProjectsUpdatedInBackgroundEvent();
296            });
297
298            it("should detect changes in non-root files", () => {
299                const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
300                    configObj: { files: [file1Consumer1Path] },
301                });
302
303                host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
304                verifyProjectsUpdatedInBackgroundEvent();
305
306                // change file1 internal, and verify only file1 is affected
307                host.writeFile(moduleFile1.path, moduleFile1.content + "var T1: number;");
308                verifyProjectsUpdatedInBackgroundEvent();
309            });
310
311            it("should return all files if a global file changed shape", () => {
312                const { host, globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState();
313
314                host.writeFile(globalFile3.path, globalFile3.content + "var T2: string;");
315                verifyProjectsUpdatedInBackgroundEvent();
316            });
317
318            it("should always return the file itself if '--isolatedModules' is specified", () => {
319                const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
320                    configObj: { compilerOptions: { isolatedModules: true } }
321                });
322
323                host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
324                verifyProjectsUpdatedInBackgroundEvent();
325            });
326
327            it("should always return the file itself if '--out' or '--outFile' is specified", () => {
328                const outFilePath = "/a/b/out.js";
329                const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
330                    configObj: { compilerOptions: { module: "system", outFile: outFilePath } }
331                });
332
333                host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
334                verifyProjectsUpdatedInBackgroundEvent();
335            });
336
337            it("should return cascaded affected file list", () => {
338                const file1Consumer1Consumer1: ts.projectSystem.File = {
339                    path: "/a/b/file1Consumer1Consumer1.ts",
340                    content: `import {y} from "./file1Consumer1";`
341                };
342                const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
343                    getAdditionalFileOrFolder: () => [file1Consumer1Consumer1]
344                });
345
346                updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;");
347                verifyNoProjectsUpdatedInBackgroundEvent();
348
349                // Doesnt change the shape of file1Consumer1
350                host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`);
351                verifyProjectsUpdatedInBackgroundEvent();
352
353                // Change both files before the timeout
354                updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;");
355                host.writeFile(moduleFile1.path, `export var T2: number;export function Foo() { };`);
356                verifyProjectsUpdatedInBackgroundEvent();
357            });
358
359            it("should work fine for files with circular references", () => {
360                const file1: ts.projectSystem.File = {
361                    path: "/a/b/file1.ts",
362                    content: `
363                    /// <reference path="./file2.ts" />
364                    export var t1 = 10;`
365                };
366                const file2: ts.projectSystem.File = {
367                    path: "/a/b/file2.ts",
368                    content: `
369                    /// <reference path="./file1.ts" />
370                    export var t2 = 10;`
371                };
372                const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
373                    getAdditionalFileOrFolder: () => [file1, file2],
374                    firstReloadFileList: [file1.path, ts.projectSystem.libFile.path, file2.path, configFilePath]
375                });
376
377                host.writeFile(file2.path, file2.content + "export var t3 = 10;");
378                verifyProjectsUpdatedInBackgroundEvent();
379            });
380
381            it("should detect removed code file", () => {
382                const referenceFile1: ts.projectSystem.File = {
383                    path: "/a/b/referenceFile1.ts",
384                    content: `
385                    /// <reference path="./moduleFile1.ts" />
386                    export var x = Foo();`
387                };
388                const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
389                    getAdditionalFileOrFolder: () => [referenceFile1],
390                    firstReloadFileList: [referenceFile1.path, ts.projectSystem.libFile.path, moduleFile1Path, configFilePath]
391                });
392
393                host.deleteFile(moduleFile1Path);
394                verifyProjectsUpdatedInBackgroundEvent();
395            });
396
397            it("should detect non-existing code file", () => {
398                const referenceFile1: ts.projectSystem.File = {
399                    path: "/a/b/referenceFile1.ts",
400                    content: `
401                    /// <reference path="./moduleFile2.ts" />
402                    export var x = Foo();`
403                };
404                const { host, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
405                    getAdditionalFileOrFolder: () => [referenceFile1],
406                    firstReloadFileList: [referenceFile1.path, ts.projectSystem.libFile.path, configFilePath]
407                });
408
409                updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();");
410                verifyNoProjectsUpdatedInBackgroundEvent();
411
412                // Create module File2 and see both files are saved
413                host.writeFile(moduleFile2.path, moduleFile2.content);
414                verifyProjectsUpdatedInBackgroundEvent();
415            });
416        });
417
418        describe("resolution when resolution cache size", () => {
419            function verifyWithMaxCacheLimit(subScenario: string, useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) {
420                it(subScenario, () => {
421                    const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/";
422                    const file1: ts.projectSystem.File = {
423                        path: rootFolder + "a/b/project/file1.ts",
424                        content: 'import a from "file2"'
425                    };
426                    const file2: ts.projectSystem.File = {
427                        path: rootFolder + "a/b/node_modules/file2.d.ts",
428                        content: "export class a { }"
429                    };
430                    const file3: ts.projectSystem.File = {
431                        path: rootFolder + "a/b/project/file3.ts",
432                        content: "export class c { }"
433                    };
434                    const configFile: ts.projectSystem.File = {
435                        path: rootFolder + "a/b/project/tsconfig.json",
436                        content: JSON.stringify({ compilerOptions: { typeRoots: [] } })
437                    };
438
439                    const openFiles = [file1.path];
440                    const host = ts.projectSystem.createServerHost([file1, file3, ts.projectSystem.libFile, configFile]);
441                    const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host, ts.projectSystem.createLoggerWithInMemoryLogs(host));
442                    verifyInitialOpen(file1);
443
444                    file3.content += "export class d {}";
445                    host.writeFile(file3.path, file3.content);
446                    host.checkTimeoutQueueLengthAndRun(2);
447
448                    // Since this is first event
449                    verifyProjectsUpdatedInBackgroundEventHandler([{
450                        eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
451                        data: {
452                            openFiles
453                        }
454                    }]);
455
456                    host.writeFile(file2.path, file2.content);
457                    host.runQueuedTimeoutCallbacks(); // For invalidation
458                    host.runQueuedTimeoutCallbacks(); // For actual update
459
460                    verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{
461                        eventName: ts.server.ProjectsUpdatedInBackgroundEvent,
462                        data: {
463                            openFiles
464                        }
465                    }] : []);
466                    ts.projectSystem.baselineTsserverLogs("projectUpdatedInBackground", `${scenario} and ${subScenario}`, session);
467                });
468            }
469            verifyWithMaxCacheLimit("project is not at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true);
470            verifyWithMaxCacheLimit("project is at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false);
471        });
472    }
473
474    describe("when event handler is set in the session", () => {
475        verifyProjectsUpdatedInBackgroundEvent("when event handler is set in the session", createSessionWithProjectChangedEventHandler);
476
477        function createSessionWithProjectChangedEventHandler(host: ts.projectSystem.TestServerHost, logger: ts.projectSystem.Logger | undefined): ProjectsUpdatedInBackgroundEventVerifier {
478            const { session, events: projectChangedEvents } = ts.projectSystem.createSessionWithEventTracking<ts.server.ProjectsUpdatedInBackgroundEvent>(
479                host,
480                ts.server.ProjectsUpdatedInBackgroundEvent,
481                logger && { logger }
482            );
483            return {
484                session,
485                verifyProjectsUpdatedInBackgroundEventHandler,
486                verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler)
487            };
488
489            function eventToString(event: ts.server.ProjectsUpdatedInBackgroundEvent) {
490                return JSON.stringify(event && { eventName: event.eventName, data: event.data });
491            }
492
493            function eventsToString(events: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) {
494                return "[" + ts.map(events, eventToString).join(",") + "]";
495            }
496
497            function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) {
498                assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`);
499                ts.forEach(projectChangedEvents, (actualEvent, i) => {
500                    const expectedEvent = expectedEvents[i];
501                    assert.strictEqual(actualEvent.eventName, expectedEvent.eventName);
502                    verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles);
503                });
504
505                // Verified the events, reset them
506                projectChangedEvents.length = 0;
507            }
508        }
509    });
510
511    describe("when event handler is not set but session is created with canUseEvents = true", () => {
512        describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => {
513            verifyProjectsUpdatedInBackgroundEvent("without noGetErrOnBackgroundUpdate", createSessionThatUsesEvents);
514        });
515
516        describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => {
517            verifyProjectsUpdatedInBackgroundEvent("with noGetErrOnBackgroundUpdate", (host, logger) => createSessionThatUsesEvents(host, logger, /*noGetErrOnBackgroundUpdate*/ true));
518        });
519
520
521        function createSessionThatUsesEvents(host: ts.projectSystem.TestServerHost, logger: ts.projectSystem.Logger | undefined, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier {
522            const { session, getEvents, clearEvents } = ts.projectSystem.createSessionWithDefaultEventHandler<ts.projectSystem.protocol.ProjectsUpdatedInBackgroundEvent>(
523                host,
524                ts.server.ProjectsUpdatedInBackgroundEvent,
525                { noGetErrOnBackgroundUpdate, logger: logger || ts.projectSystem.createHasErrorMessageLogger() }
526            );
527
528            return {
529                session,
530                verifyProjectsUpdatedInBackgroundEventHandler,
531                verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler)
532            };
533
534            function verifyProjectsUpdatedInBackgroundEventHandler(expected: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) {
535                const expectedEvents: ts.projectSystem.protocol.ProjectsUpdatedInBackgroundEventBody[] = ts.map(expected, e => {
536                    return {
537                        openFiles: e.data.openFiles
538                    };
539                });
540                const events = getEvents();
541                assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${ts.map(events, e => e.body)} Expected: ${expectedEvents}`);
542                ts.forEach(events, (actualEvent, i) => {
543                    const expectedEvent = expectedEvents[i];
544                    verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles);
545                });
546
547                // Verified the events, reset them
548                clearEvents();
549
550                if (events.length) {
551                    host.checkTimeoutQueueLength(noGetErrOnBackgroundUpdate ? 0 : 1); // Error checking queued only if not noGetErrOnBackgroundUpdate
552                }
553            }
554        }
555    });
556});
557