@fluidframework/test-utils 1.4.0-115997 → 2.0.0-dev-rc.1.0.0.224419

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.eslintrc.js +8 -7
  2. package/.mocharc.js +12 -0
  3. package/CHANGELOG.md +177 -0
  4. package/README.md +44 -14
  5. package/api-extractor-lint.json +4 -0
  6. package/api-extractor.json +2 -2
  7. package/api-report/test-utils.api.md +395 -0
  8. package/dist/DriverWrappers.d.ts +32 -0
  9. package/dist/DriverWrappers.d.ts.map +1 -0
  10. package/dist/DriverWrappers.js +57 -0
  11. package/dist/DriverWrappers.js.map +1 -0
  12. package/dist/TestConfigs.d.ts +10 -0
  13. package/dist/TestConfigs.d.ts.map +1 -0
  14. package/dist/TestConfigs.js +17 -0
  15. package/dist/TestConfigs.js.map +1 -0
  16. package/dist/TestSummaryUtils.d.ts +61 -0
  17. package/dist/TestSummaryUtils.d.ts.map +1 -0
  18. package/dist/TestSummaryUtils.js +135 -0
  19. package/dist/TestSummaryUtils.js.map +1 -0
  20. package/dist/containerUtils.d.ts +46 -0
  21. package/dist/containerUtils.d.ts.map +1 -0
  22. package/dist/containerUtils.js +85 -0
  23. package/dist/containerUtils.js.map +1 -0
  24. package/dist/index.d.ts +14 -9
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +43 -19
  27. package/dist/index.js.map +1 -1
  28. package/dist/interfaces.d.ts +6 -0
  29. package/dist/interfaces.d.ts.map +1 -1
  30. package/dist/interfaces.js.map +1 -1
  31. package/dist/loaderContainerTracker.d.ts +39 -15
  32. package/dist/loaderContainerTracker.d.ts.map +1 -1
  33. package/dist/loaderContainerTracker.js +214 -86
  34. package/dist/loaderContainerTracker.js.map +1 -1
  35. package/dist/localCodeLoader.d.ts +9 -2
  36. package/dist/localCodeLoader.d.ts.map +1 -1
  37. package/dist/localCodeLoader.js +18 -17
  38. package/dist/localCodeLoader.js.map +1 -1
  39. package/dist/localLoader.d.ts +3 -2
  40. package/dist/localLoader.d.ts.map +1 -1
  41. package/dist/localLoader.js +2 -0
  42. package/dist/localLoader.js.map +1 -1
  43. package/dist/packageVersion.d.ts +1 -1
  44. package/dist/packageVersion.d.ts.map +1 -1
  45. package/dist/packageVersion.js +1 -1
  46. package/dist/packageVersion.js.map +1 -1
  47. package/dist/retry.d.ts +1 -0
  48. package/dist/retry.d.ts.map +1 -1
  49. package/dist/retry.js +3 -2
  50. package/dist/retry.js.map +1 -1
  51. package/dist/test-utils-alpha.d.ts +305 -0
  52. package/dist/test-utils-beta.d.ts +202 -0
  53. package/dist/test-utils-public.d.ts +202 -0
  54. package/dist/test-utils-untrimmed.d.ts +1001 -0
  55. package/dist/testContainerRuntimeFactory.d.ts +4 -2
  56. package/dist/testContainerRuntimeFactory.d.ts.map +1 -1
  57. package/dist/testContainerRuntimeFactory.js +67 -12
  58. package/dist/testContainerRuntimeFactory.js.map +1 -1
  59. package/dist/testContainerRuntimeFactoryWithDefaultDataStore.d.ts +23 -0
  60. package/dist/testContainerRuntimeFactoryWithDefaultDataStore.d.ts.map +1 -0
  61. package/dist/testContainerRuntimeFactoryWithDefaultDataStore.js +32 -0
  62. package/dist/testContainerRuntimeFactoryWithDefaultDataStore.js.map +1 -0
  63. package/dist/testFluidObject.d.ts +30 -12
  64. package/dist/testFluidObject.d.ts.map +1 -1
  65. package/dist/testFluidObject.js +74 -43
  66. package/dist/testFluidObject.js.map +1 -1
  67. package/dist/testObjectProvider.d.ts +298 -41
  68. package/dist/testObjectProvider.d.ts.map +1 -1
  69. package/dist/testObjectProvider.js +377 -61
  70. package/dist/testObjectProvider.js.map +1 -1
  71. package/dist/timeoutUtils.d.ts +42 -0
  72. package/dist/timeoutUtils.d.ts.map +1 -1
  73. package/dist/timeoutUtils.js +142 -9
  74. package/dist/timeoutUtils.js.map +1 -1
  75. package/dist/tsdoc-metadata.json +11 -0
  76. package/lib/test-utils-alpha.d.ts +305 -0
  77. package/lib/test-utils-beta.d.ts +202 -0
  78. package/lib/test-utils-public.d.ts +202 -0
  79. package/lib/test-utils-untrimmed.d.ts +1001 -0
  80. package/package.json +106 -72
  81. package/prettier.config.cjs +8 -0
  82. package/src/DriverWrappers.ts +83 -0
  83. package/src/TestConfigs.ts +17 -0
  84. package/src/TestSummaryUtils.ts +210 -0
  85. package/src/containerUtils.ts +90 -0
  86. package/src/index.ts +47 -9
  87. package/src/interfaces.ts +16 -7
  88. package/src/loaderContainerTracker.ts +754 -534
  89. package/src/localCodeLoader.ts +87 -86
  90. package/src/localLoader.ts +27 -26
  91. package/src/packageVersion.ts +1 -1
  92. package/src/retry.ts +33 -26
  93. package/src/testContainerRuntimeFactory.ts +152 -59
  94. package/src/testContainerRuntimeFactoryWithDefaultDataStore.ts +61 -0
  95. package/src/testFluidObject.ts +190 -147
  96. package/src/testObjectProvider.ts +931 -372
  97. package/src/timeoutUtils.ts +226 -37
  98. package/tsconfig.json +9 -12
@@ -3,101 +3,235 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { IContainer, IHostLoader, IFluidCodeDetails } from "@fluidframework/container-definitions";
7
- import { ITelemetryGenericEvent, ITelemetryBaseLogger, ITelemetryBaseEvent } from "@fluidframework/common-definitions";
8
6
  import {
9
- ILoaderProps,
10
- Loader,
11
- waitContainerToCatchUp as waitContainerToCatchUp_original,
7
+ IContainer,
8
+ IHostLoader,
9
+ IFluidCodeDetails,
10
+ ILoader,
11
+ } from "@fluidframework/container-definitions";
12
+ import {
13
+ ILoaderProps,
14
+ Loader,
15
+ waitContainerToCatchUp as waitContainerToCatchUp_original,
12
16
  } from "@fluidframework/container-loader";
13
17
  import { IContainerRuntimeOptions } from "@fluidframework/container-runtime";
14
- import { IRequestHeader } from "@fluidframework/core-interfaces";
15
- import { IDocumentServiceFactory, IResolvedUrl, IUrlResolver } from "@fluidframework/driver-definitions";
16
- import { ensureFluidResolvedUrl } from "@fluidframework/driver-utils";
18
+ import {
19
+ ITelemetryGenericEvent,
20
+ ITelemetryBaseLogger,
21
+ ITelemetryBaseEvent,
22
+ IRequestHeader,
23
+ } from "@fluidframework/core-interfaces";
24
+ import {
25
+ IDocumentServiceFactory,
26
+ IResolvedUrl,
27
+ IUrlResolver,
28
+ } from "@fluidframework/driver-definitions";
17
29
  import { ITestDriver, TestDriverTypes } from "@fluidframework/test-driver-definitions";
18
30
  import { v4 as uuid } from "uuid";
19
- import { ChildLogger, MultiSinkLogger, TelemetryLogger } from "@fluidframework/telemetry-utils";
31
+ import { createChildLogger, createMultiSinkLogger } from "@fluidframework/telemetry-utils";
20
32
  import { LoaderContainerTracker } from "./loaderContainerTracker";
21
33
  import { fluidEntryPoint, LocalCodeLoader } from "./localCodeLoader";
22
34
  import { createAndAttachContainer } from "./localLoader";
23
35
  import { ChannelFactoryRegistry } from "./testFluidObject";
24
36
 
25
37
  const defaultCodeDetails: IFluidCodeDetails = {
26
- package: "defaultTestPackage",
27
- config: {},
38
+ package: "defaultTestPackage",
39
+ config: {},
28
40
  };
29
41
 
42
+ /**
43
+ * @alpha
44
+ */
30
45
  export interface IOpProcessingController {
31
- processIncoming(...containers: IContainer[]): Promise<void>;
32
- processOutgoing(...containers: IContainer[]): Promise<void>;
33
- pauseProcessing(...containers: IContainer[]): Promise<void>;
34
- resumeProcessing(...containers: IContainer[]): void;
46
+ processIncoming(...containers: IContainer[]): Promise<void>;
47
+ processOutgoing(...containers: IContainer[]): Promise<void>;
48
+ pauseProcessing(...containers: IContainer[]): Promise<void>;
49
+ resumeProcessing(...containers: IContainer[]): void;
35
50
  }
36
51
 
52
+ /**
53
+ * @internal
54
+ */
37
55
  export interface ITestObjectProvider {
38
- createFluidEntryPoint: (testContainerConfig?: ITestContainerConfig) => fluidEntryPoint;
39
- createLoader(
40
- packageEntries: Iterable<[IFluidCodeDetails, fluidEntryPoint]>,
41
- loaderProps?: Partial<ILoaderProps>,
42
- ): IHostLoader;
43
- createContainer(entryPoint: fluidEntryPoint, loaderProps?: Partial<ILoaderProps>): Promise<IContainer>;
44
- loadContainer(
45
- entryPoint: fluidEntryPoint,
46
- loaderProps?: Partial<ILoaderProps>,
47
- requestHeader?: IRequestHeader,
48
- ): Promise<IContainer>;
49
-
50
- /**
51
- * Used to create a test Container. The Loader/ContainerRuntime/DataRuntime might be different versioned.
52
- * In generateLocalCompatTest(), this Container and its runtime will be arbitrarily-versioned.
53
- */
54
- makeTestLoader(testContainerConfig?: ITestContainerConfig): IHostLoader;
55
- makeTestContainer(testContainerConfig?: ITestContainerConfig): Promise<IContainer>;
56
- loadTestContainer(testContainerConfig?: ITestContainerConfig, requestHeader?: IRequestHeader): Promise<IContainer>;
57
- /**
58
- *
59
- * @param url - Resolved container URL
60
- */
61
- updateDocumentId(url: IResolvedUrl | undefined): void;
62
-
63
- logger: ITelemetryBaseLogger;
64
- documentServiceFactory: IDocumentServiceFactory;
65
- urlResolver: IUrlResolver;
66
- defaultCodeDetails: IFluidCodeDetails;
67
- opProcessingController: IOpProcessingController;
68
-
69
- ensureSynchronized(): Promise<void>;
70
- reset(): void;
71
-
72
- documentId: string;
73
- driver: ITestDriver;
56
+ /**
57
+ * Indicates which type of test object provider is being used.
58
+ */
59
+ type: "TestObjectProvider" | "TestObjectProviderWithVersionedLoad";
60
+
61
+ /**
62
+ * The document id to retrieve or create containers
63
+ */
64
+ documentId: string;
65
+
66
+ /**
67
+ * Creates the document service after extracting different endpoints URLs from a resolved URL.
68
+ */
69
+ documentServiceFactory: IDocumentServiceFactory;
70
+
71
+ /**
72
+ * Test driver used to create the IDocumentServiceFactory. Varies depending on the test type.
73
+ */
74
+ driver: ITestDriver;
75
+
76
+ /**
77
+ * Logger used to track expected and unexpected events.
78
+ */
79
+ logger: EventAndErrorTrackingLogger | undefined;
80
+
81
+ /**
82
+ * Used to create a url for the created container with any data store path given in the relative url.
83
+ */
84
+ urlResolver: IUrlResolver;
85
+
86
+ /**
87
+ * Default IFluidCodeDetails used to create containers.
88
+ */
89
+ defaultCodeDetails: IFluidCodeDetails;
90
+
91
+ /**
92
+ * Contains functions to pause/resume op processing.
93
+ */
94
+ opProcessingController: IOpProcessingController;
95
+
96
+ /**
97
+ * Represents the entry point for a Fluid container.
98
+ */
99
+ createFluidEntryPoint: (testContainerConfig?: ITestContainerConfig) => fluidEntryPoint;
100
+
101
+ /**
102
+ * Create a loader. Containers created/loaded through this loader will be added to the OpProcessingController.
103
+ *
104
+ * Only the version of the loader will vary based on compat config. The version of
105
+ * containerRuntime/dataRuntime used in fluidEntryPoint will be used as is from what is passed in.
106
+ *
107
+ * @param packageEntries - list of code details and fluidEntryPoint pairs.
108
+ */
109
+ createLoader(
110
+ packageEntries: Iterable<[IFluidCodeDetails, fluidEntryPoint]>,
111
+ loaderProps?: Partial<ILoaderProps>,
112
+ ): IHostLoader;
113
+
114
+ /**
115
+ * Create a container using a default document id and code details.
116
+ * Container created is automatically added to the OpProcessingController to manage op flow
117
+ *
118
+ * Only the version of the loader will vary based on compat config. The version of
119
+ * containerRuntime/dataRuntime used in fluidEntryPoint will be used as is from what is passed in.
120
+ *
121
+ * @param packageEntries - list of code details and fluidEntryPoint pairs.
122
+ */
123
+
124
+ createContainer(
125
+ entryPoint: fluidEntryPoint,
126
+ loaderProps?: Partial<ILoaderProps>,
127
+ ): Promise<IContainer>;
128
+
129
+ /**
130
+ * Loads a container using the default document id
131
+ */
132
+ loadContainer(
133
+ entryPoint: fluidEntryPoint,
134
+ loaderProps?: Partial<ILoaderProps>,
135
+ requestHeader?: IRequestHeader,
136
+ ): Promise<IContainer>;
137
+
138
+ /**
139
+ * Make a test loader. Containers created/loaded through this loader will be added to the OpProcessingController.
140
+ * The version of the loader/containerRuntime/dataRuntime may vary based on compat config of the current run
141
+ * @param testContainerConfig - optional configuring the test Container
142
+ */
143
+ makeTestLoader(testContainerConfig?: ITestContainerConfig): IHostLoader;
144
+
145
+ /**
146
+ * Make a container using a default document id and code details
147
+ * Container loaded is automatically added to the OpProcessingController to manage op flow
148
+ * @param testContainerConfig - optional configuring the test Container
149
+ */
150
+ makeTestContainer(testContainerConfig?: ITestContainerConfig): Promise<IContainer>;
151
+
152
+ /**
153
+ * Load a container using a default document id and code details.
154
+ * IContainer loaded is automatically added to the OpProcessingController to manage op flow
155
+ * @param testContainerConfig - optional configuring the test Container
156
+ * @param requestHeader - optional headers to be supplied to the loader
157
+ */
158
+ loadTestContainer(
159
+ testContainerConfig?: ITestContainerConfig,
160
+ requestHeader?: IRequestHeader,
161
+ ): Promise<IContainer>;
162
+
163
+ /**
164
+ * Update the document ID from the resolved container's URL and reset the ID property
165
+ */
166
+ updateDocumentId(url: IResolvedUrl | undefined): void;
167
+
168
+ /**
169
+ * Make sure all the tracked containers are synchronized.
170
+ */
171
+ ensureSynchronized(timeoutDuration?: number): Promise<void>;
172
+
173
+ /**
174
+ * Reset the tracker, closing all containers and stop tracking them.
175
+ */
176
+ resetLoaderContainerTracker(syncSummarizerClients?: boolean);
177
+
178
+ /**
179
+ * Resets and closes all tracked containers and class states.
180
+ */
181
+ reset(): void;
74
182
  }
75
183
 
184
+ /**
185
+ * @internal
186
+ */
76
187
  export enum DataObjectFactoryType {
77
- Primed, // default
78
- Test,
188
+ Primed, // default
189
+ Test,
79
190
  }
80
191
 
192
+ /**
193
+ * @internal
194
+ */
81
195
  export interface ITestContainerConfig {
82
- /** TestFluidDataObject instead of PrimedDataStore */
83
- fluidDataObjectType?: DataObjectFactoryType;
196
+ /** TestFluidDataObject instead of PrimedDataStore */
197
+ fluidDataObjectType?: DataObjectFactoryType;
84
198
 
85
- /** An array of channel name and DDS factory pair to create on container creation time */
86
- registry?: ChannelFactoryRegistry;
199
+ /** An array of channel name and DDS factory pair to create on container creation time */
200
+ registry?: ChannelFactoryRegistry;
87
201
 
88
- /** Container runtime options for the container instance */
89
- runtimeOptions?: IContainerRuntimeOptions;
202
+ /** Container runtime options for the container instance */
203
+ runtimeOptions?: IContainerRuntimeOptions;
90
204
 
91
- /** Loader options for the loader used to create containers */
92
- loaderProps?: Partial<ILoaderProps>;
205
+ /** Whether this runtime should be instantiated using a mixed-in attributor class */
206
+ enableAttribution?: boolean;
207
+
208
+ /** Loader options for the loader used to create containers */
209
+ loaderProps?: Partial<ILoaderProps>;
93
210
  }
94
211
 
212
+ /**
213
+ * @internal
214
+ */
95
215
  export const createDocumentId = (): string => uuid();
96
216
 
97
- interface IDocumentIdStrategy {
98
- get(): string;
99
- update(resolvedUrl?: IResolvedUrl): void;
100
- reset(): void;
217
+ /**
218
+ * Used to retrieve, update, and reset document id based on the type of driver being used.
219
+ *
220
+ * @internal
221
+ */
222
+ export interface IDocumentIdStrategy {
223
+ /**
224
+ * Get document id
225
+ */
226
+ get(): string;
227
+ /**
228
+ * Update the document ID from the resolved container's URL and reset the ID property
229
+ */
230
+ update(resolvedUrl?: IResolvedUrl): void;
231
+ /**
232
+ * Reset document id to a new document id
233
+ */
234
+ reset(): void;
101
235
  }
102
236
 
103
237
  /**
@@ -105,25 +239,28 @@ interface IDocumentIdStrategy {
105
239
  * a new container ID and accessing the container in multi-instance test cases.
106
240
  */
107
241
  function getDocumentIdStrategy(type?: TestDriverTypes): IDocumentIdStrategy {
108
- let documentId = createDocumentId();
109
- switch (type) {
110
- case "odsp":
111
- return {
112
- get: () => documentId,
113
- update: () => { }, // do not update the document ID in odsp test cases
114
- reset: () => { documentId = createDocumentId(); },
115
- };
116
- default:
117
- return {
118
- get: () => documentId,
119
- update: (resolvedUrl?: IResolvedUrl) => {
120
- // Extract the document ID from the resolved container's URL and reset the ID property
121
- ensureFluidResolvedUrl(resolvedUrl);
122
- documentId = resolvedUrl.id ?? documentId;
123
- },
124
- reset: () => { documentId = createDocumentId(); },
125
- };
126
- }
242
+ let documentId = createDocumentId();
243
+ switch (type) {
244
+ case "odsp":
245
+ return {
246
+ get: () => documentId,
247
+ update: () => {}, // do not update the document ID in odsp test cases
248
+ reset: () => {
249
+ documentId = createDocumentId();
250
+ },
251
+ };
252
+ default:
253
+ return {
254
+ get: () => documentId,
255
+ update: (resolvedUrl?: IResolvedUrl) => {
256
+ // Extract the document ID from the resolved container's URL and reset the ID property
257
+ documentId = resolvedUrl?.id ?? documentId;
258
+ },
259
+ reset: () => {
260
+ documentId = createDocumentId();
261
+ },
262
+ };
263
+ }
127
264
  }
128
265
 
129
266
  /**
@@ -131,301 +268,723 @@ function getDocumentIdStrategy(type?: TestDriverTypes): IDocumentIdStrategy {
131
268
  * It also tracks all unexpected errors.
132
269
  * At any point you call reportAndClearTrackedEvents which will provide all unexpected errors, and
133
270
  * any expected events that have not occurred.
271
+ * @internal
134
272
  */
135
- export class EventAndErrorTrackingLogger extends TelemetryLogger {
136
- constructor(private readonly baseLogger: ITelemetryBaseLogger) {
137
- super();
138
- }
139
-
140
- private readonly expectedEvents: ({ index: number; event: ITelemetryGenericEvent | undefined; } | undefined)[] = [];
141
- private readonly unexpectedErrors: ITelemetryBaseEvent[] = [];
142
-
143
- public registerExpectedEvent(... orderedExpectedEvents: ITelemetryGenericEvent[]) {
144
- if (this.expectedEvents.length !== 0) {
145
- // we don't have to error here. just no reason not to. given the events must be
146
- // ordered it could be tricky to figure out problems around multiple registrations.
147
- throw new Error(
148
- "Expected events already registered.\n"
149
- + "Call reportAndClearTrackedEvents to clear them before registering more");
150
- }
151
- this.expectedEvents.push(... orderedExpectedEvents.map((event, index) => ({ index, event })));
152
- }
153
-
154
- send(event: ITelemetryBaseEvent): void {
155
- const ee = this.expectedEvents[0]?.event;
156
- if (ee?.eventName === event.eventName) {
157
- let matches = true;
158
- for (const key of Object.keys(ee)) {
159
- if (ee[key] !== event[key]) {
160
- matches = false;
161
- break;
162
- }
163
- }
164
- if (matches) {
165
- // we found an expected event
166
- // so remove it from the list of expected events
167
- // and if it is an error, change it to generic
168
- // this helps keep our telemetry clear of
169
- // expected errors.
170
- this.expectedEvents.shift();
171
- if (event.category === "error") {
172
- event.category = "generic";
173
- }
174
- }
175
- }
176
- if (event.category === "error") {
177
- this.unexpectedErrors.push(event);
178
- }
179
-
180
- this.baseLogger.send(event);
181
- }
182
-
183
- public reportAndClearTrackedEvents() {
184
- const expectedNotFound = this.expectedEvents.splice(0, this.expectedEvents.length);
185
- const unexpectedErrors = this.unexpectedErrors.splice(0, this.unexpectedErrors.length);
186
- return {
187
- expectedNotFound,
188
- unexpectedErrors,
189
- };
190
- }
273
+ export class EventAndErrorTrackingLogger implements ITelemetryBaseLogger {
274
+ /**
275
+ * Even if these error events are logged, tests should still be allowed to pass
276
+ * Additionally, if downgrade is true, then log as generic (e.g. to avoid polluting the e2e test logs)
277
+ */
278
+ private readonly allowedErrors: { eventName: string; downgrade?: true }[] = [
279
+ // This log was removed in current version as unnecessary, but it's still present in previous versions
280
+ {
281
+ eventName: "fluid:telemetry:Container:NoRealStorageInDetachedContainer",
282
+ downgrade: true,
283
+ },
284
+ // This log's category changes depending on the op latency. test results shouldn't be affected but if we see lots we'd like an alert from the logs.
285
+ { eventName: "fluid:telemetry:OpPerf:OpRoundtripTime" },
286
+ ];
287
+
288
+ constructor(private readonly baseLogger: ITelemetryBaseLogger) {}
289
+
290
+ private readonly expectedEvents: (
291
+ | { index: number; event: ITelemetryGenericEvent | undefined }
292
+ | undefined
293
+ )[] = [];
294
+ private readonly unexpectedErrors: ITelemetryBaseEvent[] = [];
295
+
296
+ public registerExpectedEvent(...orderedExpectedEvents: ITelemetryGenericEvent[]) {
297
+ if (this.expectedEvents.length !== 0) {
298
+ // we don't have to error here. just no reason not to. given the events must be
299
+ // ordered it could be tricky to figure out problems around multiple registrations.
300
+ throw new Error(
301
+ "Expected events already registered.\n" +
302
+ "Call reportAndClearTrackedEvents to clear them before registering more",
303
+ );
304
+ }
305
+ this.expectedEvents.push(
306
+ ...orderedExpectedEvents.map((event, index) => ({ index, event })),
307
+ );
308
+ }
309
+
310
+ send(event: ITelemetryBaseEvent): void {
311
+ const ee = this.expectedEvents[0]?.event;
312
+ if (ee?.eventName === event.eventName) {
313
+ let matches = true;
314
+ for (const key of Object.keys(ee)) {
315
+ if (ee[key] !== event[key]) {
316
+ matches = false;
317
+ break;
318
+ }
319
+ }
320
+ if (matches) {
321
+ // we found an expected event
322
+ // so remove it from the list of expected events
323
+ // and if it is an error, change it to generic
324
+ // this helps keep our telemetry clear of
325
+ // expected errors.
326
+ this.expectedEvents.shift();
327
+ if (event.category === "error") {
328
+ event.category = "generic";
329
+ }
330
+ }
331
+ }
332
+ if (event.category === "error") {
333
+ // Check to see if this error is allowed and if its category should be downgraded
334
+ const allowedError = this.allowedErrors.find(
335
+ ({ eventName }) => eventName === event.eventName,
336
+ );
337
+
338
+ if (allowedError === undefined) {
339
+ this.unexpectedErrors.push(event);
340
+ } else if (allowedError.downgrade) {
341
+ event.category = "generic";
342
+ }
343
+ }
344
+
345
+ this.baseLogger.send(event);
346
+ }
347
+
348
+ public reportAndClearTrackedEvents() {
349
+ const expectedNotFound = this.expectedEvents.splice(0, this.expectedEvents.length);
350
+ const unexpectedErrors = this.unexpectedErrors.splice(0, this.unexpectedErrors.length);
351
+ return {
352
+ expectedNotFound,
353
+ unexpectedErrors,
354
+ };
355
+ }
191
356
  }
192
357
 
193
358
  /**
194
359
  * Shared base class for test object provider. Contain code for loader and container creation and loading
360
+ * @internal
195
361
  */
196
362
  export class TestObjectProvider implements ITestObjectProvider {
197
- private _loaderContainerTracker = new LoaderContainerTracker();
198
- private _documentServiceFactory: IDocumentServiceFactory | undefined;
199
- private _urlResolver: IUrlResolver | undefined;
200
- private _logger: EventAndErrorTrackingLogger | undefined;
201
- private readonly _documentIdStrategy: IDocumentIdStrategy;
202
- // Since documentId doesn't change we can only create/make one container. Call the load functions instead.
203
- private _documentCreated = false;
204
-
205
- /**
206
- * Manage objects for loading and creating container, including the driver, loader, and OpProcessingController
207
- * @param createFluidEntryPoint - callback to create a fluidEntryPoint, with an optional set of channel name
208
- * and factory for TestFluidObject
209
- */
210
- constructor(
211
- public readonly LoaderConstructor: typeof Loader,
212
- public readonly driver: ITestDriver,
213
- public readonly createFluidEntryPoint: (testContainerConfig?: ITestContainerConfig) => fluidEntryPoint,
214
- ) {
215
- this._documentIdStrategy = getDocumentIdStrategy(driver.type);
216
- }
217
-
218
- get logger(): EventAndErrorTrackingLogger {
219
- if (this._logger === undefined) {
220
- this._logger = new EventAndErrorTrackingLogger(
221
- ChildLogger.create(getTestLogger?.(), undefined,
222
- {
223
- all: {
224
- driverType: this.driver.type,
225
- driverEndpointName: this.driver.endpointName,
226
- driverTenantName: this.driver.tenantName,
227
- driverUserIndex: this.driver.userIndex,
228
- },
229
- }));
230
- }
231
- return this._logger;
232
- }
233
-
234
- get documentServiceFactory() {
235
- if (!this._documentServiceFactory) {
236
- this._documentServiceFactory = this.driver.createDocumentServiceFactory();
237
- }
238
- return this._documentServiceFactory;
239
- }
240
-
241
- get urlResolver() {
242
- if (!this._urlResolver) {
243
- this._urlResolver = this.driver.createUrlResolver();
244
- }
245
- return this._urlResolver;
246
- }
247
-
248
- get documentId() {
249
- return this._documentIdStrategy.get();
250
- }
251
-
252
- get defaultCodeDetails() {
253
- return defaultCodeDetails;
254
- }
255
-
256
- get opProcessingController(): IOpProcessingController {
257
- return this._loaderContainerTracker;
258
- }
259
-
260
- /**
261
- * Create a loader. Containers created/loaded through this loader will be added to the OpProcessingController.
262
- *
263
- * Only the version of the loader will vary based on compat config. The version of
264
- * containerRuntime/dataRuntime used in fluidEntryPoint will be used as is from what is passed in.
265
- *
266
- * @param packageEntries - list of code details and fluidEntryPoint pairs.
267
- */
268
- public createLoader(
269
- packageEntries: Iterable<[IFluidCodeDetails, fluidEntryPoint]>,
270
- loaderProps?: Partial<ILoaderProps>,
271
- ) {
272
- const multiSinkLogger = new MultiSinkLogger();
273
- multiSinkLogger.addLogger(this.logger);
274
- if (loaderProps?.logger !== undefined) {
275
- multiSinkLogger.addLogger(loaderProps.logger);
276
- }
277
-
278
- const loader = new this.LoaderConstructor({
279
- ... loaderProps,
280
- logger: multiSinkLogger,
281
- codeLoader: loaderProps?.codeLoader ?? new LocalCodeLoader(packageEntries),
282
- urlResolver: loaderProps?.urlResolver ?? this.urlResolver,
283
- documentServiceFactory: loaderProps?.documentServiceFactory ?? this.documentServiceFactory,
284
- });
285
- this._loaderContainerTracker.add(loader);
286
- return loader;
287
- }
288
-
289
- /**
290
- * Create a container using a default document id and code details.
291
- * Container created is automatically added to the OpProcessingController to manage op flow
292
- *
293
- * Only the version of the loader will vary based on compat config. The version of
294
- * containerRuntime/dataRuntime used in fluidEntryPoint will be used as is from what is passed in.
295
- *
296
- * @param packageEntries - list of code details and fluidEntryPoint pairs.
297
- */
298
- public async createContainer(entryPoint: fluidEntryPoint, loaderProps?: Partial<ILoaderProps>) {
299
- if (this._documentCreated) {
300
- throw new Error(
301
- "Only one container/document can be created. To load the container/document use loadContainer");
302
- }
303
- const loader = this.createLoader([[defaultCodeDetails, entryPoint]], loaderProps);
304
- const container = await createAndAttachContainer(
305
- defaultCodeDetails,
306
- loader,
307
- this.driver.createCreateNewRequest(this.documentId),
308
- );
309
- this._documentCreated = true;
310
- // r11s driver will generate a new ID for the new container.
311
- // update the document ID with the actual ID of the attached container.
312
- this._documentIdStrategy.update(container.resolvedUrl);
313
- return container;
314
- }
315
-
316
- public async loadContainer(
317
- entryPoint: fluidEntryPoint,
318
- loaderProps?: Partial<ILoaderProps>,
319
- requestHeader?: IRequestHeader): Promise<IContainer> {
320
- const loader = this.createLoader([[defaultCodeDetails, entryPoint]], loaderProps);
321
- return loader.resolve({ url: await this.driver.createContainerUrl(this.documentId), headers: requestHeader });
322
- }
323
-
324
- /**
325
- * Make a test loader. Containers created/loaded through this loader will be added to the OpProcessingController.
326
- * The version of the loader/containerRuntime/dataRuntime may vary based on compat config of the current run
327
- * @param testContainerConfig - optional configuring the test Container
328
- */
329
- public makeTestLoader(testContainerConfig?: ITestContainerConfig) {
330
- return this.createLoader(
331
- [[defaultCodeDetails, this.createFluidEntryPoint(testContainerConfig)]],
332
- testContainerConfig?.loaderProps,
333
- );
334
- }
335
-
336
- /**
337
- * Make a container using a default document id and code details
338
- * Container loaded is automatically added to the OpProcessingController to manage op flow
339
- * @param testContainerConfig - optional configuring the test Container
340
- */
341
- public async makeTestContainer(testContainerConfig?: ITestContainerConfig): Promise<IContainer> {
342
- if (this._documentCreated) {
343
- throw new Error(
344
- "Only one container/document can be created. To load the container/document use loadTestContainer");
345
- }
346
- const loader = this.makeTestLoader(testContainerConfig);
347
- const container =
348
- await createAndAttachContainer(
349
- defaultCodeDetails,
350
- loader,
351
- this.driver.createCreateNewRequest(this.documentId));
352
- this._documentCreated = true;
353
- // r11s driver will generate a new ID for the new container.
354
- // update the document ID with the actual ID of the attached container.
355
- this._documentIdStrategy.update(container.resolvedUrl);
356
- return container;
357
- }
358
-
359
- /**
360
- * Load a container using a default document id and code details.
361
- * IContainer loaded is automatically added to the OpProcessingController to manage op flow
362
- * @param testContainerConfig - optional configuring the test Container
363
- * @param requestHeader - optional headers to be supplied to the loader
364
- */
365
- public async loadTestContainer(
366
- testContainerConfig?: ITestContainerConfig,
367
- requestHeader?: IRequestHeader,
368
- ): Promise<IContainer> {
369
- const loader = this.makeTestLoader(testContainerConfig);
370
- const container = await loader.resolve({
371
- url: await this.driver.createContainerUrl(this.documentId),
372
- headers: requestHeader,
373
- });
374
- await this.waitContainerToCatchUp(container);
375
-
376
- return container;
377
- }
378
-
379
- public reset() {
380
- this._loaderContainerTracker.reset();
381
- this._documentServiceFactory = undefined;
382
- this._urlResolver = undefined;
383
- this._documentIdStrategy.reset();
384
- const logError = getUnexpectedLogErrorException(this._logger);
385
- if (logError) {
386
- throw logError;
387
- }
388
- this._logger = undefined;
389
- this._documentCreated = false;
390
- }
391
-
392
- public async ensureSynchronized() {
393
- return this._loaderContainerTracker.ensureSynchronized();
394
- }
395
-
396
- public async waitContainerToCatchUp(container: IContainer) {
397
- // The original waitContainerToCatchUp() from container loader uses either Container.resume()
398
- // or Container.connect() as part of its implementation. However, resume() was deprecated
399
- // and eventually replaced with connect(). To avoid issues during LTS compatibility testing
400
- // with older container versions issues, we use resume() when connect() is unavailable.
401
- if ((container as any).connect === undefined) {
402
- (container as any).connect = (container as any).resume;
403
- }
404
-
405
- return waitContainerToCatchUp_original(container);
406
- }
407
-
408
- updateDocumentId(resolvedUrl: IResolvedUrl | undefined) {
409
- this._documentIdStrategy.update(resolvedUrl);
410
- }
411
-
412
- public resetLoaderContainerTracker(syncSummarizerClients: boolean = false) {
413
- this._loaderContainerTracker.reset();
414
- this._loaderContainerTracker = new LoaderContainerTracker(syncSummarizerClients);
415
- }
363
+ /**
364
+ * {@inheritDoc ITestObjectProvider."type"}
365
+ */
366
+ public readonly type = "TestObjectProvider";
367
+ private _loaderContainerTracker = new LoaderContainerTracker();
368
+ private _documentServiceFactory: IDocumentServiceFactory | undefined;
369
+ private _urlResolver: IUrlResolver | undefined;
370
+ private _logger: EventAndErrorTrackingLogger | undefined;
371
+ private readonly _documentIdStrategy: IDocumentIdStrategy;
372
+ // Since documentId doesn't change we can only create/make one container. Call the load functions instead.
373
+ private _documentCreated = false;
374
+
375
+ /**
376
+ * Manage objects for loading and creating container, including the driver, loader, and OpProcessingController
377
+ * @param createFluidEntryPoint - callback to create a fluidEntryPoint, with an optional set of channel name
378
+ * and factory for TestFluidObject
379
+ */
380
+ constructor(
381
+ private readonly LoaderConstructor: typeof Loader,
382
+ /**
383
+ * {@inheritDoc ITestObjectProvider.driver}
384
+ */
385
+ public readonly driver: ITestDriver,
386
+ /**
387
+ * {@inheritDoc ITestObjectProvider.createFluidEntryPoint}
388
+ */
389
+ public readonly createFluidEntryPoint: (
390
+ testContainerConfig?: ITestContainerConfig,
391
+ ) => fluidEntryPoint,
392
+ ) {
393
+ this._documentIdStrategy = getDocumentIdStrategy(driver.type);
394
+ }
395
+
396
+ /**
397
+ * {@inheritDoc ITestObjectProvider.logger}
398
+ */
399
+ public get logger(): EventAndErrorTrackingLogger {
400
+ if (this._logger === undefined) {
401
+ this._logger = new EventAndErrorTrackingLogger(
402
+ createChildLogger({
403
+ logger: getTestLogger?.(),
404
+ properties: {
405
+ all: {
406
+ driverType: this.driver.type,
407
+ driverEndpointName: this.driver.endpointName,
408
+ driverTenantName: this.driver.tenantName,
409
+ driverUserIndex: this.driver.userIndex,
410
+ },
411
+ },
412
+ }),
413
+ );
414
+ }
415
+ return this._logger;
416
+ }
417
+
418
+ private set logger(logger: EventAndErrorTrackingLogger) {
419
+ this._logger = logger;
420
+ }
421
+
422
+ /**
423
+ * {@inheritDoc ITestObjectProvider.documentServiceFactory}
424
+ */
425
+ public get documentServiceFactory() {
426
+ if (!this._documentServiceFactory) {
427
+ this._documentServiceFactory = this.driver.createDocumentServiceFactory();
428
+ }
429
+ return this._documentServiceFactory;
430
+ }
431
+
432
+ /**
433
+ * {@inheritDoc ITestObjectProvider.urlResolver}
434
+ */
435
+ public get urlResolver() {
436
+ if (!this._urlResolver) {
437
+ this._urlResolver = this.driver.createUrlResolver();
438
+ }
439
+ return this._urlResolver;
440
+ }
441
+
442
+ /**
443
+ * {@inheritDoc ITestObjectProvider.documentId}
444
+ */
445
+ public get documentId() {
446
+ return this._documentIdStrategy.get();
447
+ }
448
+
449
+ /**
450
+ * {@inheritDoc ITestObjectProvider.defaultCodeDetails}
451
+ */
452
+ public get defaultCodeDetails() {
453
+ return defaultCodeDetails;
454
+ }
455
+
456
+ /**
457
+ * {@inheritDoc ITestObjectProvider.opProcessingController}
458
+ */
459
+ public get opProcessingController(): IOpProcessingController {
460
+ return this._loaderContainerTracker;
461
+ }
462
+
463
+ /**
464
+ * {@inheritDoc ITestObjectProvider.createLoader}
465
+ */
466
+ public createLoader(
467
+ packageEntries: Iterable<[IFluidCodeDetails, fluidEntryPoint]>,
468
+ loaderProps?: Partial<ILoaderProps>,
469
+ ) {
470
+ const logger = createMultiSinkLogger({
471
+ loggers: [this.logger, loaderProps?.logger],
472
+ });
473
+
474
+ const loader = new this.LoaderConstructor({
475
+ ...loaderProps,
476
+ logger,
477
+ codeLoader: loaderProps?.codeLoader ?? new LocalCodeLoader(packageEntries),
478
+ urlResolver: loaderProps?.urlResolver ?? this.urlResolver,
479
+ documentServiceFactory:
480
+ loaderProps?.documentServiceFactory ?? this.documentServiceFactory,
481
+ });
482
+ this._loaderContainerTracker.add(loader);
483
+ return loader;
484
+ }
485
+
486
+ /**
487
+ * {@inheritDoc ITestObjectProvider.createContainer}
488
+ */
489
+ public async createContainer(entryPoint: fluidEntryPoint, loaderProps?: Partial<ILoaderProps>) {
490
+ if (this._documentCreated) {
491
+ throw new Error(
492
+ "Only one container/document can be created. To load the container/document use loadContainer",
493
+ );
494
+ }
495
+ const loader = this.createLoader([[defaultCodeDetails, entryPoint]], loaderProps);
496
+ const container = await createAndAttachContainer(
497
+ defaultCodeDetails,
498
+ loader,
499
+ this.driver.createCreateNewRequest(this.documentId),
500
+ );
501
+ this._documentCreated = true;
502
+ // r11s driver will generate a new ID for the new container.
503
+ // update the document ID with the actual ID of the attached container.
504
+ this._documentIdStrategy.update(container.resolvedUrl);
505
+ return container;
506
+ }
507
+
508
+ /**
509
+ * {@inheritDoc ITestObjectProvider.loadContainer}
510
+ */
511
+ public async loadContainer(
512
+ entryPoint: fluidEntryPoint,
513
+ loaderProps?: Partial<ILoaderProps>,
514
+ requestHeader?: IRequestHeader,
515
+ ): Promise<IContainer> {
516
+ const loader = this.createLoader([[defaultCodeDetails, entryPoint]], loaderProps);
517
+ return this.resolveContainer(loader, requestHeader);
518
+ }
519
+
520
+ private async resolveContainer(loader: ILoader, headers?: IRequestHeader) {
521
+ return loader.resolve({
522
+ url: await this.driver.createContainerUrl(this.documentId),
523
+ headers,
524
+ });
525
+ }
526
+
527
+ /**
528
+ * {@inheritDoc ITestObjectProvider.makeTestLoader}
529
+ */
530
+ public makeTestLoader(testContainerConfig?: ITestContainerConfig) {
531
+ return this.createLoader(
532
+ [[defaultCodeDetails, this.createFluidEntryPoint(testContainerConfig)]],
533
+ testContainerConfig?.loaderProps,
534
+ );
535
+ }
536
+
537
+ /**
538
+ * {@inheritDoc ITestObjectProvider.makeTestContainer}
539
+ */
540
+ public async makeTestContainer(
541
+ testContainerConfig?: ITestContainerConfig,
542
+ ): Promise<IContainer> {
543
+ if (this._documentCreated) {
544
+ throw new Error(
545
+ "Only one container/document can be created. To load the container/document use loadTestContainer",
546
+ );
547
+ }
548
+ const loader = this.makeTestLoader(testContainerConfig);
549
+ const container = await createAndAttachContainer(
550
+ defaultCodeDetails,
551
+ loader,
552
+ this.driver.createCreateNewRequest(this.documentId),
553
+ );
554
+ this._documentCreated = true;
555
+ // r11s driver will generate a new ID for the new container.
556
+ // update the document ID with the actual ID of the attached container.
557
+ this._documentIdStrategy.update(container.resolvedUrl);
558
+ return container;
559
+ }
560
+
561
+ /**
562
+ * {@inheritDoc ITestObjectProvider.loadTestContainer}
563
+ */
564
+ public async loadTestContainer(
565
+ testContainerConfig?: ITestContainerConfig,
566
+ requestHeader?: IRequestHeader,
567
+ ): Promise<IContainer> {
568
+ const loader = this.makeTestLoader(testContainerConfig);
569
+
570
+ const container = await this.resolveContainer(loader, requestHeader);
571
+ await this.waitContainerToCatchUp(container);
572
+
573
+ return container;
574
+ }
575
+
576
+ /**
577
+ * {@inheritDoc ITestObjectProvider.reset}
578
+ */
579
+ public reset() {
580
+ this._loaderContainerTracker.reset();
581
+ this._documentServiceFactory = undefined;
582
+ this._urlResolver = undefined;
583
+ this._documentIdStrategy.reset();
584
+ const logError = getUnexpectedLogErrorException(this._logger);
585
+ if (logError) {
586
+ throw logError;
587
+ }
588
+ this._logger = undefined;
589
+ this._documentCreated = false;
590
+ }
591
+
592
+ /**
593
+ * {@inheritDoc ITestObjectProvider.ensureSynchronized}
594
+ */
595
+ public async ensureSynchronized(): Promise<void> {
596
+ return this._loaderContainerTracker.ensureSynchronized();
597
+ }
598
+
599
+ private async waitContainerToCatchUp(container: IContainer) {
600
+ // The original waitContainerToCatchUp() from container loader uses either Container.resume()
601
+ // or Container.connect() as part of its implementation. However, resume() was deprecated
602
+ // and eventually replaced with connect(). To avoid issues during LTS compatibility testing
603
+ // with older container versions issues, we use resume() when connect() is unavailable.
604
+ if ((container as any).connect === undefined) {
605
+ (container as any).connect = (container as any).resume;
606
+ }
607
+
608
+ return waitContainerToCatchUp_original(container);
609
+ }
610
+
611
+ /**
612
+ * {@inheritDoc ITestObjectProvider.updateDocumentId}
613
+ */
614
+ public updateDocumentId(resolvedUrl: IResolvedUrl | undefined) {
615
+ this._documentIdStrategy.update(resolvedUrl);
616
+ }
617
+
618
+ /**
619
+ * {@inheritDoc ITestObjectProvider.resetLoaderContainerTracker}
620
+ */
621
+ public resetLoaderContainerTracker(syncSummarizerClients: boolean = false) {
622
+ this._loaderContainerTracker.reset();
623
+ this._loaderContainerTracker = new LoaderContainerTracker(syncSummarizerClients);
624
+ }
416
625
  }
417
626
 
418
- export function getUnexpectedLogErrorException(logger: EventAndErrorTrackingLogger | undefined, prefix?: string) {
419
- if (logger === undefined) {
420
- return;
421
- }
422
- const results = logger.reportAndClearTrackedEvents();
423
- if (results.unexpectedErrors.length > 0) {
424
- return new Error(
425
- `${prefix ?? ""}Unexpected Errors in Logs:\n${JSON.stringify(results.unexpectedErrors, undefined, 2)}`);
426
- }
427
- if (results.expectedNotFound.length > 0) {
428
- return new Error(
429
- `${prefix ?? ""}Expected Events not found:\n${JSON.stringify(results.expectedNotFound, undefined, 2)}`);
430
- }
627
+ /**
628
+ * Implements {@link ITestObjectProvider}, but uses different versions to create and load containers.
629
+ *
630
+ * @internal
631
+ */
632
+ export class TestObjectProviderWithVersionedLoad implements ITestObjectProvider {
633
+ /**
634
+ * {@inheritDoc ITestObjectProvider."type"}
635
+ */
636
+ public readonly type = "TestObjectProviderWithVersionedLoad";
637
+ private _loaderContainerTracker = new LoaderContainerTracker();
638
+ private _logger: EventAndErrorTrackingLogger | undefined;
639
+ private readonly _documentIdStrategy: IDocumentIdStrategy;
640
+ private _documentServiceFactory: IDocumentServiceFactory | undefined;
641
+ private _urlResolver: IUrlResolver | undefined;
642
+ // Since documentId doesn't change we can only create/make one container. Call the load functions instead.
643
+ private _documentCreated = false;
644
+
645
+ /**
646
+ * `_loadCount` is used to alternate which version we load the next container with.
647
+ * loadCount is even then we will load with the "create" version, and if odd we load with the "load" version.
648
+ * After each test we will reset loadCount to 0 to ensure we always create the first container with the create version.
649
+ *
650
+ * Note: This will only affect tests that load a container more than two times.
651
+ */
652
+
653
+ private _loadCount: number = 0;
654
+
655
+ constructor(
656
+ private readonly LoaderConstructorForCreating: typeof Loader,
657
+ private readonly LoaderConstructorForLoading: typeof Loader,
658
+ private readonly driverForCreating: ITestDriver,
659
+ private readonly driverForLoading: ITestDriver,
660
+ private readonly createFluidEntryPointForCreating: (
661
+ testContainerConfig?: ITestContainerConfig,
662
+ ) => fluidEntryPoint,
663
+ private readonly createFluidEntryPointForLoading: (
664
+ testContainerConfig?: ITestContainerConfig,
665
+ ) => fluidEntryPoint,
666
+ ) {
667
+ this._documentIdStrategy = getDocumentIdStrategy(driverForCreating.type);
668
+ }
669
+
670
+ /**
671
+ * {@inheritDoc ITestObjectProvider.logger}
672
+ */
673
+ public get logger(): EventAndErrorTrackingLogger {
674
+ if (this._logger === undefined) {
675
+ this._logger = new EventAndErrorTrackingLogger(
676
+ createChildLogger({
677
+ logger: getTestLogger?.(),
678
+ }),
679
+ );
680
+ }
681
+ return this._logger;
682
+ }
683
+
684
+ /**
685
+ * {@inheritDoc ITestObjectProvider.documentServiceFactory}
686
+ */
687
+ public get documentServiceFactory() {
688
+ if (!this._documentServiceFactory) {
689
+ this._documentServiceFactory = this.driverForCreating.createDocumentServiceFactory();
690
+ }
691
+ return this._documentServiceFactory;
692
+ }
693
+
694
+ /**
695
+ * {@inheritDoc ITestObjectProvider.urlResolver}
696
+ */
697
+ public get urlResolver() {
698
+ if (!this._urlResolver) {
699
+ this._urlResolver = this.driverForCreating.createUrlResolver();
700
+ }
701
+ return this._urlResolver;
702
+ }
703
+
704
+ /**
705
+ * {@inheritDoc ITestObjectProvider.documentId}
706
+ */
707
+ public get documentId() {
708
+ return this._documentIdStrategy.get();
709
+ }
710
+
711
+ /**
712
+ * {@inheritDoc ITestObjectProvider.defaultCodeDetails}
713
+ */
714
+ public get defaultCodeDetails() {
715
+ return defaultCodeDetails;
716
+ }
717
+
718
+ /**
719
+ * {@inheritDoc ITestObjectProvider.opProcessingController}
720
+ */
721
+ public get opProcessingController(): IOpProcessingController {
722
+ return this._loaderContainerTracker;
723
+ }
724
+
725
+ /**
726
+ * {@inheritDoc ITestObjectProvider.driver}
727
+ */
728
+ public get driver(): ITestDriver {
729
+ return this.nextLoaderShouldCreate() ? this.driverForCreating : this.driverForLoading;
730
+ }
731
+
732
+ /**
733
+ * {@inheritDoc ITestObjectProvider.createFluidEntryPoint}
734
+ */
735
+ public get createFluidEntryPoint(): (
736
+ testContainerConfig?: ITestContainerConfig,
737
+ ) => fluidEntryPoint {
738
+ return this.nextLoaderShouldCreate()
739
+ ? this.createFluidEntryPointForCreating
740
+ : this.createFluidEntryPointForLoading;
741
+ }
742
+
743
+ private createLoaderForCreating(
744
+ packageEntries: Iterable<[IFluidCodeDetails, fluidEntryPoint]>,
745
+ loaderProps?: Partial<ILoaderProps>,
746
+ ) {
747
+ const logger = createMultiSinkLogger({
748
+ loggers: [this.logger, loaderProps?.logger],
749
+ });
750
+
751
+ const loader = new this.LoaderConstructorForCreating({
752
+ ...loaderProps,
753
+ logger,
754
+ codeLoader: loaderProps?.codeLoader ?? new LocalCodeLoader(packageEntries),
755
+ urlResolver: loaderProps?.urlResolver ?? this.urlResolver,
756
+ documentServiceFactory:
757
+ loaderProps?.documentServiceFactory ?? this.documentServiceFactory,
758
+ });
759
+
760
+ this._loaderContainerTracker.add(loader);
761
+ return loader;
762
+ }
763
+
764
+ private createLoaderForLoading(
765
+ packageEntries: Iterable<[IFluidCodeDetails, fluidEntryPoint]>,
766
+ loaderProps?: Partial<ILoaderProps>,
767
+ ) {
768
+ const logger = createMultiSinkLogger({
769
+ loggers: [this.logger, loaderProps?.logger],
770
+ });
771
+
772
+ const loader = new this.LoaderConstructorForLoading({
773
+ ...loaderProps,
774
+ logger,
775
+ codeLoader: loaderProps?.codeLoader ?? new LocalCodeLoader(packageEntries),
776
+ urlResolver: loaderProps?.urlResolver ?? this.urlResolver,
777
+ documentServiceFactory:
778
+ loaderProps?.documentServiceFactory ?? this.documentServiceFactory,
779
+ });
780
+
781
+ this._loaderContainerTracker.add(loader);
782
+ return loader;
783
+ }
784
+
785
+ /**
786
+ * {@inheritDoc ITestObjectProvider.createLoader}
787
+ */
788
+ public createLoader(
789
+ packageEntries: Iterable<[IFluidCodeDetails, fluidEntryPoint]>,
790
+ loaderProps?: Partial<ILoaderProps>,
791
+ ) {
792
+ return this.nextLoaderShouldCreate(/** increment */ true)
793
+ ? this.createLoaderForCreating(packageEntries, loaderProps)
794
+ : this.createLoaderForLoading(packageEntries, loaderProps);
795
+ }
796
+
797
+ /**
798
+ * {@inheritDoc ITestObjectProvider.createContainer}
799
+ */
800
+ public async createContainer(entryPoint: fluidEntryPoint, loaderProps?: Partial<ILoaderProps>) {
801
+ if (this._documentCreated) {
802
+ throw new Error(
803
+ "Only one container/document can be created. To load the container/document use loadContainer",
804
+ );
805
+ }
806
+ const loader = this.createLoader([[defaultCodeDetails, entryPoint]], loaderProps);
807
+ const container = await createAndAttachContainer(
808
+ defaultCodeDetails,
809
+ loader,
810
+ this.driverForCreating.createCreateNewRequest(this.documentId),
811
+ );
812
+ this._documentCreated = true;
813
+ // r11s driver will generate a new ID for the new container.
814
+ // update the document ID with the actual ID of the attached container.
815
+ this._documentIdStrategy.update(container.resolvedUrl);
816
+ return container;
817
+ }
818
+
819
+ /**
820
+ * {@inheritDoc ITestObjectProvider.loadContainer}
821
+ */
822
+ public async loadContainer(
823
+ entryPoint: fluidEntryPoint,
824
+ loaderProps?: Partial<ILoaderProps>,
825
+ requestHeader?: IRequestHeader,
826
+ ): Promise<IContainer> {
827
+ const driver = this.nextLoaderShouldCreate()
828
+ ? this.driverForCreating
829
+ : this.driverForLoading;
830
+ const loader = this.createLoader([[defaultCodeDetails, entryPoint]], loaderProps);
831
+ return this.resolveContainer(loader, requestHeader, driver);
832
+ }
833
+
834
+ private async resolveContainer(
835
+ loader: ILoader,
836
+ headers?: IRequestHeader,
837
+ driver?: ITestDriver,
838
+ ) {
839
+ return loader.resolve({
840
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
841
+ url: await driver!.createContainerUrl(this.documentId),
842
+ headers,
843
+ });
844
+ }
845
+
846
+ /**
847
+ * {@inheritDoc ITestObjectProvider.makeTestLoader}
848
+ */
849
+ public makeTestLoader(testContainerConfig?: ITestContainerConfig) {
850
+ return this.createLoader(
851
+ [[defaultCodeDetails, this.createFluidEntryPoint(testContainerConfig)]],
852
+ testContainerConfig?.loaderProps,
853
+ );
854
+ }
855
+
856
+ /**
857
+ * {@inheritDoc ITestObjectProvider.makeTestContainer}
858
+ */
859
+ public async makeTestContainer(
860
+ testContainerConfig?: ITestContainerConfig,
861
+ ): Promise<IContainer> {
862
+ if (this._documentCreated) {
863
+ throw new Error(
864
+ "Only one container/document can be created. To load the container/document use loadTestContainer",
865
+ );
866
+ }
867
+ const loader = this.createLoader(
868
+ [[defaultCodeDetails, this.createFluidEntryPoint(testContainerConfig)]],
869
+ testContainerConfig?.loaderProps,
870
+ );
871
+ const container = await createAndAttachContainer(
872
+ defaultCodeDetails,
873
+ loader,
874
+ this.driverForCreating.createCreateNewRequest(this.documentId),
875
+ );
876
+ this._documentCreated = true;
877
+ // r11s driver will generate a new ID for the new container.
878
+ // update the document ID with the actual ID of the attached container.
879
+ this._documentIdStrategy.update(container.resolvedUrl);
880
+ return container;
881
+ }
882
+
883
+ /**
884
+ * {@inheritDoc ITestObjectProvider.loadTestContainer}
885
+ */
886
+ public async loadTestContainer(
887
+ testContainerConfig?: ITestContainerConfig,
888
+ requestHeader?: IRequestHeader,
889
+ ): Promise<IContainer> {
890
+ // Keep track of which Loader we are about to use so we can pass the correct driver through
891
+ const driver = this.nextLoaderShouldCreate()
892
+ ? this.driverForCreating
893
+ : this.driverForLoading;
894
+ const loader = this.makeTestLoader(testContainerConfig);
895
+ const container = await this.resolveContainer(loader, requestHeader, driver);
896
+ await this.waitContainerToCatchUp(container);
897
+
898
+ return container;
899
+ }
900
+
901
+ /**
902
+ * {@inheritDoc ITestObjectProvider.reset}
903
+ */
904
+ public reset() {
905
+ this._loadCount = 0;
906
+ this._loaderContainerTracker.reset();
907
+ this._logger = undefined;
908
+ this._documentServiceFactory = undefined;
909
+ this._urlResolver = undefined;
910
+ this._documentIdStrategy.reset();
911
+ const logError = getUnexpectedLogErrorException(this._logger);
912
+ if (logError) {
913
+ throw logError;
914
+ }
915
+ this._documentCreated = false;
916
+ }
917
+
918
+ /**
919
+ * {@inheritDoc ITestObjectProvider.ensureSynchronized}
920
+ */
921
+ public async ensureSynchronized(): Promise<void> {
922
+ return this._loaderContainerTracker.ensureSynchronized();
923
+ }
924
+
925
+ private async waitContainerToCatchUp(container: IContainer) {
926
+ // The original waitContainerToCatchUp() from container loader uses either Container.resume()
927
+ // or Container.connect() as part of its implementation. However, resume() was deprecated
928
+ // and eventually replaced with connect(). To avoid issues during LTS compatibility testing
929
+ // with older container versions issues, we use resume() when connect() is unavailable.
930
+ if ((container as any).connect === undefined) {
931
+ (container as any).connect = (container as any).resume;
932
+ }
933
+
934
+ return waitContainerToCatchUp_original(container);
935
+ }
936
+
937
+ /**
938
+ * {@inheritDoc ITestObjectProvider.updateDocumentId}
939
+ */
940
+ public updateDocumentId(resolvedUrl: IResolvedUrl | undefined) {
941
+ this._documentIdStrategy.update(resolvedUrl);
942
+ }
943
+
944
+ /**
945
+ * {@inheritDoc ITestObjectProvider.resetLoaderContainerTracker}
946
+ */
947
+ public resetLoaderContainerTracker(syncSummarizerClients: boolean = false) {
948
+ this._loaderContainerTracker.reset();
949
+ this._loaderContainerTracker = new LoaderContainerTracker(syncSummarizerClients);
950
+ }
951
+
952
+ private nextLoaderShouldCreate(increment: boolean = false): boolean {
953
+ const shouldCreate = this._loadCount % 2 === 0;
954
+ if (increment) {
955
+ this._loadCount++;
956
+ }
957
+ return shouldCreate;
958
+ }
959
+ }
960
+
961
+ /**
962
+ * @internal
963
+ */
964
+ export function getUnexpectedLogErrorException(
965
+ logger: EventAndErrorTrackingLogger | undefined,
966
+ prefix?: string,
967
+ ) {
968
+ if (logger === undefined) {
969
+ return;
970
+ }
971
+ const results = logger.reportAndClearTrackedEvents();
972
+ if (results.unexpectedErrors.length > 0) {
973
+ return new Error(
974
+ `${prefix ?? ""}Unexpected Errors in Logs:\n${JSON.stringify(
975
+ results.unexpectedErrors,
976
+ undefined,
977
+ 2,
978
+ )}`,
979
+ );
980
+ }
981
+ if (results.expectedNotFound.length > 0) {
982
+ return new Error(
983
+ `${prefix ?? ""}Expected Events not found:\n${JSON.stringify(
984
+ results.expectedNotFound,
985
+ undefined,
986
+ 2,
987
+ )}`,
988
+ );
989
+ }
431
990
  }