@fluidframework/test-utils 2.0.0-dev.2.3.0.115467 → 2.0.0-dev.4.1.0.148229

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 (68) hide show
  1. package/.eslintrc.js +8 -10
  2. package/README.md +41 -11
  3. package/api-extractor.json +2 -2
  4. package/dist/DriverWrappers.d.ts.map +1 -1
  5. package/dist/DriverWrappers.js.map +1 -1
  6. package/dist/TestConfigs.d.ts.map +1 -1
  7. package/dist/TestConfigs.js +3 -4
  8. package/dist/TestConfigs.js.map +1 -1
  9. package/dist/TestSummaryUtils.d.ts +20 -4
  10. package/dist/TestSummaryUtils.d.ts.map +1 -1
  11. package/dist/TestSummaryUtils.js +41 -33
  12. package/dist/TestSummaryUtils.js.map +1 -1
  13. package/dist/containerUtils.d.ts +29 -0
  14. package/dist/containerUtils.d.ts.map +1 -0
  15. package/dist/containerUtils.js +45 -0
  16. package/dist/containerUtils.js.map +1 -0
  17. package/dist/index.d.ts +5 -4
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +3 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/interfaces.d.ts.map +1 -1
  22. package/dist/interfaces.js.map +1 -1
  23. package/dist/loaderContainerTracker.d.ts +3 -9
  24. package/dist/loaderContainerTracker.d.ts.map +1 -1
  25. package/dist/loaderContainerTracker.js +45 -47
  26. package/dist/loaderContainerTracker.js.map +1 -1
  27. package/dist/localCodeLoader.d.ts.map +1 -1
  28. package/dist/localCodeLoader.js.map +1 -1
  29. package/dist/localLoader.d.ts.map +1 -1
  30. package/dist/localLoader.js.map +1 -1
  31. package/dist/packageVersion.d.ts +1 -1
  32. package/dist/packageVersion.js +1 -1
  33. package/dist/packageVersion.js.map +1 -1
  34. package/dist/retry.d.ts.map +1 -1
  35. package/dist/retry.js.map +1 -1
  36. package/dist/testContainerRuntimeFactory.d.ts +2 -2
  37. package/dist/testContainerRuntimeFactory.d.ts.map +1 -1
  38. package/dist/testContainerRuntimeFactory.js +3 -3
  39. package/dist/testContainerRuntimeFactory.js.map +1 -1
  40. package/dist/testFluidObject.d.ts.map +1 -1
  41. package/dist/testFluidObject.js +7 -3
  42. package/dist/testFluidObject.js.map +1 -1
  43. package/dist/testObjectProvider.d.ts +6 -1
  44. package/dist/testObjectProvider.d.ts.map +1 -1
  45. package/dist/testObjectProvider.js +49 -16
  46. package/dist/testObjectProvider.js.map +1 -1
  47. package/dist/timeoutUtils.d.ts +11 -2
  48. package/dist/timeoutUtils.d.ts.map +1 -1
  49. package/dist/timeoutUtils.js +122 -18
  50. package/dist/timeoutUtils.js.map +1 -1
  51. package/package.json +65 -58
  52. package/prettier.config.cjs +1 -1
  53. package/src/DriverWrappers.ts +40 -37
  54. package/src/TestConfigs.ts +7 -7
  55. package/src/TestSummaryUtils.ts +123 -124
  56. package/src/containerUtils.ts +48 -0
  57. package/src/index.ts +24 -23
  58. package/src/interfaces.ts +10 -7
  59. package/src/loaderContainerTracker.ts +618 -577
  60. package/src/localCodeLoader.ts +85 -77
  61. package/src/localLoader.ts +24 -24
  62. package/src/packageVersion.ts +1 -1
  63. package/src/retry.ts +31 -25
  64. package/src/testContainerRuntimeFactory.ts +59 -56
  65. package/src/testFluidObject.ts +168 -152
  66. package/src/testObjectProvider.ts +477 -384
  67. package/src/timeoutUtils.ts +191 -41
  68. package/tsconfig.json +9 -12
@@ -3,16 +3,29 @@
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
+ LoaderHeader,
11
+ } from "@fluidframework/container-definitions";
12
+ import {
13
+ ITelemetryGenericEvent,
14
+ ITelemetryBaseLogger,
15
+ ITelemetryBaseEvent,
16
+ } from "@fluidframework/common-definitions";
17
+ import {
18
+ ILoaderProps,
19
+ Loader,
20
+ waitContainerToCatchUp as waitContainerToCatchUp_original,
12
21
  } from "@fluidframework/container-loader";
13
22
  import { IContainerRuntimeOptions } from "@fluidframework/container-runtime";
14
23
  import { IRequestHeader } from "@fluidframework/core-interfaces";
15
- import { IDocumentServiceFactory, IResolvedUrl, IUrlResolver } from "@fluidframework/driver-definitions";
24
+ import {
25
+ IDocumentServiceFactory,
26
+ IResolvedUrl,
27
+ IUrlResolver,
28
+ } from "@fluidframework/driver-definitions";
16
29
  import { ensureFluidResolvedUrl } from "@fluidframework/driver-utils";
17
30
  import { ITestDriver, TestDriverTypes } from "@fluidframework/test-driver-definitions";
18
31
  import { v4 as uuid } from "uuid";
@@ -23,81 +36,90 @@ import { createAndAttachContainer } from "./localLoader";
23
36
  import { ChannelFactoryRegistry } from "./testFluidObject";
24
37
 
25
38
  const defaultCodeDetails: IFluidCodeDetails = {
26
- package: "defaultTestPackage",
27
- config: {},
39
+ package: "defaultTestPackage",
40
+ config: {},
28
41
  };
29
42
 
30
43
  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;
44
+ processIncoming(...containers: IContainer[]): Promise<void>;
45
+ processOutgoing(...containers: IContainer[]): Promise<void>;
46
+ pauseProcessing(...containers: IContainer[]): Promise<void>;
47
+ resumeProcessing(...containers: IContainer[]): void;
35
48
  }
36
49
 
37
50
  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(timeoutDuration?: number): Promise<void>;
70
- reset(): void;
71
-
72
- documentId: string;
73
- driver: ITestDriver;
51
+ createFluidEntryPoint: (testContainerConfig?: ITestContainerConfig) => fluidEntryPoint;
52
+ createLoader(
53
+ packageEntries: Iterable<[IFluidCodeDetails, fluidEntryPoint]>,
54
+ loaderProps?: Partial<ILoaderProps>,
55
+ ): IHostLoader;
56
+ createContainer(
57
+ entryPoint: fluidEntryPoint,
58
+ loaderProps?: Partial<ILoaderProps>,
59
+ ): Promise<IContainer>;
60
+ loadContainer(
61
+ entryPoint: fluidEntryPoint,
62
+ loaderProps?: Partial<ILoaderProps>,
63
+ requestHeader?: IRequestHeader,
64
+ ): Promise<IContainer>;
65
+
66
+ /**
67
+ * Used to create a test Container. The Loader/ContainerRuntime/DataRuntime might be different versioned.
68
+ * In generateLocalCompatTest(), this Container and its runtime will be arbitrarily-versioned.
69
+ */
70
+ makeTestLoader(testContainerConfig?: ITestContainerConfig): IHostLoader;
71
+ makeTestContainer(testContainerConfig?: ITestContainerConfig): Promise<IContainer>;
72
+ loadTestContainer(
73
+ testContainerConfig?: ITestContainerConfig,
74
+ requestHeader?: IRequestHeader,
75
+ ): Promise<IContainer>;
76
+ /**
77
+ *
78
+ * @param url - Resolved container URL
79
+ */
80
+ updateDocumentId(url: IResolvedUrl | undefined): void;
81
+
82
+ logger: ITelemetryBaseLogger;
83
+ documentServiceFactory: IDocumentServiceFactory;
84
+ urlResolver: IUrlResolver;
85
+ defaultCodeDetails: IFluidCodeDetails;
86
+ opProcessingController: IOpProcessingController;
87
+
88
+ ensureSynchronized(timeoutDuration?: number): Promise<void>;
89
+ reset(): void;
90
+
91
+ documentId: string;
92
+ driver: ITestDriver;
74
93
  }
75
94
 
76
95
  export enum DataObjectFactoryType {
77
- Primed, // default
78
- Test,
96
+ Primed, // default
97
+ Test,
79
98
  }
80
99
 
81
100
  export interface ITestContainerConfig {
82
- /** TestFluidDataObject instead of PrimedDataStore */
83
- fluidDataObjectType?: DataObjectFactoryType;
101
+ /** TestFluidDataObject instead of PrimedDataStore */
102
+ fluidDataObjectType?: DataObjectFactoryType;
103
+
104
+ /** An array of channel name and DDS factory pair to create on container creation time */
105
+ registry?: ChannelFactoryRegistry;
84
106
 
85
- /** An array of channel name and DDS factory pair to create on container creation time */
86
- registry?: ChannelFactoryRegistry;
107
+ /** Container runtime options for the container instance */
108
+ runtimeOptions?: IContainerRuntimeOptions;
87
109
 
88
- /** Container runtime options for the container instance */
89
- runtimeOptions?: IContainerRuntimeOptions;
110
+ /** Whether this runtime should be instantiated using a mixed-in attributor class */
111
+ enableAttribution?: boolean;
90
112
 
91
- /** Loader options for the loader used to create containers */
92
- loaderProps?: Partial<ILoaderProps>;
113
+ /** Loader options for the loader used to create containers */
114
+ loaderProps?: Partial<ILoaderProps>;
93
115
  }
94
116
 
95
117
  export const createDocumentId = (): string => uuid();
96
118
 
97
119
  interface IDocumentIdStrategy {
98
- get(): string;
99
- update(resolvedUrl?: IResolvedUrl): void;
100
- reset(): void;
120
+ get(): string;
121
+ update(resolvedUrl?: IResolvedUrl): void;
122
+ reset(): void;
101
123
  }
102
124
 
103
125
  /**
@@ -105,25 +127,29 @@ interface IDocumentIdStrategy {
105
127
  * a new container ID and accessing the container in multi-instance test cases.
106
128
  */
107
129
  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
- }
130
+ let documentId = createDocumentId();
131
+ switch (type) {
132
+ case "odsp":
133
+ return {
134
+ get: () => documentId,
135
+ update: () => {}, // do not update the document ID in odsp test cases
136
+ reset: () => {
137
+ documentId = createDocumentId();
138
+ },
139
+ };
140
+ default:
141
+ return {
142
+ get: () => documentId,
143
+ update: (resolvedUrl?: IResolvedUrl) => {
144
+ // Extract the document ID from the resolved container's URL and reset the ID property
145
+ ensureFluidResolvedUrl(resolvedUrl);
146
+ documentId = resolvedUrl.id ?? documentId;
147
+ },
148
+ reset: () => {
149
+ documentId = createDocumentId();
150
+ },
151
+ };
152
+ }
127
153
  }
128
154
 
129
155
  /**
@@ -133,316 +159,383 @@ function getDocumentIdStrategy(type?: TestDriverTypes): IDocumentIdStrategy {
133
159
  * any expected events that have not occurred.
134
160
  */
135
161
  export class EventAndErrorTrackingLogger extends TelemetryLogger {
136
- /** Even if these error events are logged, tests should still be allowed to pass */
137
- private readonly allowedErrors: string[] = [
138
- // This log was removed in current version as unnecessary, but it's still present in previous versions
139
- "fluid:telemetry:Container:NoRealStorageInDetachedContainer",
140
- ];
141
-
142
- constructor(private readonly baseLogger: ITelemetryBaseLogger) {
143
- super();
144
- }
145
-
146
- private readonly expectedEvents: ({ index: number; event: ITelemetryGenericEvent | undefined; } | undefined)[] = [];
147
- private readonly unexpectedErrors: ITelemetryBaseEvent[] = [];
148
-
149
- public registerExpectedEvent(... orderedExpectedEvents: ITelemetryGenericEvent[]) {
150
- if (this.expectedEvents.length !== 0) {
151
- // we don't have to error here. just no reason not to. given the events must be
152
- // ordered it could be tricky to figure out problems around multiple registrations.
153
- throw new Error(
154
- "Expected events already registered.\n"
155
- + "Call reportAndClearTrackedEvents to clear them before registering more");
156
- }
157
- this.expectedEvents.push(... orderedExpectedEvents.map((event, index) => ({ index, event })));
158
- }
159
-
160
- send(event: ITelemetryBaseEvent): void {
161
- const ee = this.expectedEvents[0]?.event;
162
- if (ee?.eventName === event.eventName) {
163
- let matches = true;
164
- for (const key of Object.keys(ee)) {
165
- if (ee[key] !== event[key]) {
166
- matches = false;
167
- break;
168
- }
169
- }
170
- if (matches) {
171
- // we found an expected event
172
- // so remove it from the list of expected events
173
- // and if it is an error, change it to generic
174
- // this helps keep our telemetry clear of
175
- // expected errors.
176
- this.expectedEvents.shift();
177
- if (event.category === "error") {
178
- event.category = "generic";
179
- }
180
- }
181
- }
182
- if (event.category === "error") {
183
- if (this.allowedErrors.includes(event.eventName)) {
184
- event.category = "generic";
185
- } else {
186
- this.unexpectedErrors.push(event);
187
- }
188
- }
189
-
190
- this.baseLogger.send(event);
191
- }
192
-
193
- public reportAndClearTrackedEvents() {
194
- const expectedNotFound = this.expectedEvents.splice(0, this.expectedEvents.length);
195
- const unexpectedErrors = this.unexpectedErrors.splice(0, this.unexpectedErrors.length);
196
- return {
197
- expectedNotFound,
198
- unexpectedErrors,
199
- };
200
- }
162
+ /**
163
+ * Even if these error events are logged, tests should still be allowed to pass
164
+ * Additionally, if downgrade is true, then log as generic (e.g. to avoid polluting the e2e test logs)
165
+ */
166
+ private readonly allowedErrors: { eventName: string; downgrade?: true }[] = [
167
+ // This log was removed in current version as unnecessary, but it's still present in previous versions
168
+ {
169
+ eventName: "fluid:telemetry:Container:NoRealStorageInDetachedContainer",
170
+ downgrade: true,
171
+ },
172
+ // 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.
173
+ { eventName: "fluid:telemetry:OpPerf:OpRoundtripTime" },
174
+ ];
175
+
176
+ constructor(private readonly baseLogger: ITelemetryBaseLogger) {
177
+ super();
178
+ }
179
+
180
+ private readonly expectedEvents: (
181
+ | { index: number; event: ITelemetryGenericEvent | undefined }
182
+ | undefined
183
+ )[] = [];
184
+ private readonly unexpectedErrors: ITelemetryBaseEvent[] = [];
185
+
186
+ public registerExpectedEvent(...orderedExpectedEvents: ITelemetryGenericEvent[]) {
187
+ if (this.expectedEvents.length !== 0) {
188
+ // we don't have to error here. just no reason not to. given the events must be
189
+ // ordered it could be tricky to figure out problems around multiple registrations.
190
+ throw new Error(
191
+ "Expected events already registered.\n" +
192
+ "Call reportAndClearTrackedEvents to clear them before registering more",
193
+ );
194
+ }
195
+ this.expectedEvents.push(
196
+ ...orderedExpectedEvents.map((event, index) => ({ index, event })),
197
+ );
198
+ }
199
+
200
+ send(event: ITelemetryBaseEvent): void {
201
+ const ee = this.expectedEvents[0]?.event;
202
+ if (ee?.eventName === event.eventName) {
203
+ let matches = true;
204
+ for (const key of Object.keys(ee)) {
205
+ if (ee[key] !== event[key]) {
206
+ matches = false;
207
+ break;
208
+ }
209
+ }
210
+ if (matches) {
211
+ // we found an expected event
212
+ // so remove it from the list of expected events
213
+ // and if it is an error, change it to generic
214
+ // this helps keep our telemetry clear of
215
+ // expected errors.
216
+ this.expectedEvents.shift();
217
+ if (event.category === "error") {
218
+ event.category = "generic";
219
+ }
220
+ }
221
+ }
222
+ if (event.category === "error") {
223
+ // Check to see if this error is allowed and if its category should be downgraded
224
+ const allowedError = this.allowedErrors.find(
225
+ ({ eventName }) => eventName === event.eventName,
226
+ );
227
+
228
+ if (allowedError === undefined) {
229
+ this.unexpectedErrors.push(event);
230
+ } else if (allowedError.downgrade) {
231
+ event.category = "generic";
232
+ }
233
+ }
234
+
235
+ this.baseLogger.send(event);
236
+ }
237
+
238
+ public reportAndClearTrackedEvents() {
239
+ const expectedNotFound = this.expectedEvents.splice(0, this.expectedEvents.length);
240
+ const unexpectedErrors = this.unexpectedErrors.splice(0, this.unexpectedErrors.length);
241
+ return {
242
+ expectedNotFound,
243
+ unexpectedErrors,
244
+ };
245
+ }
201
246
  }
202
247
 
203
248
  /**
204
249
  * Shared base class for test object provider. Contain code for loader and container creation and loading
205
250
  */
206
251
  export class TestObjectProvider implements ITestObjectProvider {
207
- private _loaderContainerTracker = new LoaderContainerTracker();
208
- private _documentServiceFactory: IDocumentServiceFactory | undefined;
209
- private _urlResolver: IUrlResolver | undefined;
210
- private _logger: EventAndErrorTrackingLogger | undefined;
211
- private readonly _documentIdStrategy: IDocumentIdStrategy;
212
- // Since documentId doesn't change we can only create/make one container. Call the load functions instead.
213
- private _documentCreated = false;
214
-
215
- /**
216
- * Manage objects for loading and creating container, including the driver, loader, and OpProcessingController
217
- * @param createFluidEntryPoint - callback to create a fluidEntryPoint, with an optional set of channel name
218
- * and factory for TestFluidObject
219
- */
220
- constructor(
221
- public readonly LoaderConstructor: typeof Loader,
222
- public readonly driver: ITestDriver,
223
- public readonly createFluidEntryPoint: (testContainerConfig?: ITestContainerConfig) => fluidEntryPoint,
224
- ) {
225
- this._documentIdStrategy = getDocumentIdStrategy(driver.type);
226
- }
227
-
228
- get logger(): EventAndErrorTrackingLogger {
229
- if (this._logger === undefined) {
230
- this._logger = new EventAndErrorTrackingLogger(
231
- ChildLogger.create(getTestLogger?.(), undefined,
232
- {
233
- all: {
234
- driverType: this.driver.type,
235
- driverEndpointName: this.driver.endpointName,
236
- driverTenantName: this.driver.tenantName,
237
- driverUserIndex: this.driver.userIndex,
238
- },
239
- }));
240
- }
241
- return this._logger;
242
- }
243
-
244
- set logger(logger: EventAndErrorTrackingLogger) {
245
- this._logger = logger;
246
- }
247
-
248
- get documentServiceFactory() {
249
- if (!this._documentServiceFactory) {
250
- this._documentServiceFactory = this.driver.createDocumentServiceFactory();
251
- }
252
- return this._documentServiceFactory;
253
- }
254
-
255
- get urlResolver() {
256
- if (!this._urlResolver) {
257
- this._urlResolver = this.driver.createUrlResolver();
258
- }
259
- return this._urlResolver;
260
- }
261
-
262
- get documentId() {
263
- return this._documentIdStrategy.get();
264
- }
265
-
266
- get defaultCodeDetails() {
267
- return defaultCodeDetails;
268
- }
269
-
270
- get opProcessingController(): IOpProcessingController {
271
- return this._loaderContainerTracker;
272
- }
273
-
274
- /**
275
- * Create a loader. Containers created/loaded through this loader will be added to the OpProcessingController.
276
- *
277
- * Only the version of the loader will vary based on compat config. The version of
278
- * containerRuntime/dataRuntime used in fluidEntryPoint will be used as is from what is passed in.
279
- *
280
- * @param packageEntries - list of code details and fluidEntryPoint pairs.
281
- */
282
- public createLoader(
283
- packageEntries: Iterable<[IFluidCodeDetails, fluidEntryPoint]>,
284
- loaderProps?: Partial<ILoaderProps>,
285
- ) {
286
- const multiSinkLogger = new MultiSinkLogger();
287
- multiSinkLogger.addLogger(this.logger);
288
- if (loaderProps?.logger !== undefined) {
289
- multiSinkLogger.addLogger(loaderProps.logger);
290
- }
291
-
292
- const loader = new this.LoaderConstructor({
293
- ... loaderProps,
294
- logger: multiSinkLogger,
295
- codeLoader: loaderProps?.codeLoader ?? new LocalCodeLoader(packageEntries),
296
- urlResolver: loaderProps?.urlResolver ?? this.urlResolver,
297
- documentServiceFactory: loaderProps?.documentServiceFactory ?? this.documentServiceFactory,
298
- });
299
- this._loaderContainerTracker.add(loader);
300
- return loader;
301
- }
302
-
303
- /**
304
- * Create a container using a default document id and code details.
305
- * Container created is automatically added to the OpProcessingController to manage op flow
306
- *
307
- * Only the version of the loader will vary based on compat config. The version of
308
- * containerRuntime/dataRuntime used in fluidEntryPoint will be used as is from what is passed in.
309
- *
310
- * @param packageEntries - list of code details and fluidEntryPoint pairs.
311
- */
312
- public async createContainer(entryPoint: fluidEntryPoint, loaderProps?: Partial<ILoaderProps>) {
313
- if (this._documentCreated) {
314
- throw new Error(
315
- "Only one container/document can be created. To load the container/document use loadContainer");
316
- }
317
- const loader = this.createLoader([[defaultCodeDetails, entryPoint]], loaderProps);
318
- const container = await createAndAttachContainer(
319
- defaultCodeDetails,
320
- loader,
321
- this.driver.createCreateNewRequest(this.documentId),
322
- );
323
- this._documentCreated = true;
324
- // r11s driver will generate a new ID for the new container.
325
- // update the document ID with the actual ID of the attached container.
326
- this._documentIdStrategy.update(container.resolvedUrl);
327
- return container;
328
- }
329
-
330
- public async loadContainer(
331
- entryPoint: fluidEntryPoint,
332
- loaderProps?: Partial<ILoaderProps>,
333
- requestHeader?: IRequestHeader): Promise<IContainer> {
334
- const loader = this.createLoader([[defaultCodeDetails, entryPoint]], loaderProps);
335
- return loader.resolve({ url: await this.driver.createContainerUrl(this.documentId), headers: requestHeader });
336
- }
337
-
338
- /**
339
- * Make a test loader. Containers created/loaded through this loader will be added to the OpProcessingController.
340
- * The version of the loader/containerRuntime/dataRuntime may vary based on compat config of the current run
341
- * @param testContainerConfig - optional configuring the test Container
342
- */
343
- public makeTestLoader(testContainerConfig?: ITestContainerConfig) {
344
- return this.createLoader(
345
- [[defaultCodeDetails, this.createFluidEntryPoint(testContainerConfig)]],
346
- testContainerConfig?.loaderProps,
347
- );
348
- }
349
-
350
- /**
351
- * Make a container using a default document id and code details
352
- * Container loaded is automatically added to the OpProcessingController to manage op flow
353
- * @param testContainerConfig - optional configuring the test Container
354
- */
355
- public async makeTestContainer(testContainerConfig?: ITestContainerConfig): Promise<IContainer> {
356
- if (this._documentCreated) {
357
- throw new Error(
358
- "Only one container/document can be created. To load the container/document use loadTestContainer");
359
- }
360
- const loader = this.makeTestLoader(testContainerConfig);
361
- const container =
362
- await createAndAttachContainer(
363
- defaultCodeDetails,
364
- loader,
365
- this.driver.createCreateNewRequest(this.documentId));
366
- this._documentCreated = true;
367
- // r11s driver will generate a new ID for the new container.
368
- // update the document ID with the actual ID of the attached container.
369
- this._documentIdStrategy.update(container.resolvedUrl);
370
- return container;
371
- }
372
-
373
- /**
374
- * Load a container using a default document id and code details.
375
- * IContainer loaded is automatically added to the OpProcessingController to manage op flow
376
- * @param testContainerConfig - optional configuring the test Container
377
- * @param requestHeader - optional headers to be supplied to the loader
378
- */
379
- public async loadTestContainer(
380
- testContainerConfig?: ITestContainerConfig,
381
- requestHeader?: IRequestHeader,
382
- ): Promise<IContainer> {
383
- const loader = this.makeTestLoader(testContainerConfig);
384
- const container = await loader.resolve({
385
- url: await this.driver.createContainerUrl(this.documentId),
386
- headers: requestHeader,
387
- });
388
- await this.waitContainerToCatchUp(container);
389
-
390
- return container;
391
- }
392
-
393
- public reset() {
394
- this._loaderContainerTracker.reset();
395
- this._documentServiceFactory = undefined;
396
- this._urlResolver = undefined;
397
- this._documentIdStrategy.reset();
398
- const logError = getUnexpectedLogErrorException(this._logger);
399
- if (logError) {
400
- throw logError;
401
- }
402
- this._logger = undefined;
403
- this._documentCreated = false;
404
- }
405
-
406
- public async ensureSynchronized(timeoutDuration?: number): Promise<void> {
407
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
408
- return !timeoutDuration
409
- ? this._loaderContainerTracker.ensureSynchronized()
410
- : this._loaderContainerTracker.ensureSynchronizedWithTimeout?.(timeoutDuration);
411
- }
412
-
413
- public async waitContainerToCatchUp(container: IContainer) {
414
- // The original waitContainerToCatchUp() from container loader uses either Container.resume()
415
- // or Container.connect() as part of its implementation. However, resume() was deprecated
416
- // and eventually replaced with connect(). To avoid issues during LTS compatibility testing
417
- // with older container versions issues, we use resume() when connect() is unavailable.
418
- if ((container as any).connect === undefined) {
419
- (container as any).connect = (container as any).resume;
420
- }
421
-
422
- return waitContainerToCatchUp_original(container);
423
- }
424
-
425
- updateDocumentId(resolvedUrl: IResolvedUrl | undefined) {
426
- this._documentIdStrategy.update(resolvedUrl);
427
- }
428
-
429
- public resetLoaderContainerTracker(syncSummarizerClients: boolean = false) {
430
- this._loaderContainerTracker.reset();
431
- this._loaderContainerTracker = new LoaderContainerTracker(syncSummarizerClients);
432
- }
252
+ private _loaderContainerTracker = new LoaderContainerTracker();
253
+ private _documentServiceFactory: IDocumentServiceFactory | undefined;
254
+ private _urlResolver: IUrlResolver | undefined;
255
+ private _logger: EventAndErrorTrackingLogger | undefined;
256
+ private readonly _documentIdStrategy: IDocumentIdStrategy;
257
+ // Since documentId doesn't change we can only create/make one container. Call the load functions instead.
258
+ private _documentCreated = false;
259
+
260
+ /**
261
+ * Manage objects for loading and creating container, including the driver, loader, and OpProcessingController
262
+ * @param createFluidEntryPoint - callback to create a fluidEntryPoint, with an optional set of channel name
263
+ * and factory for TestFluidObject
264
+ */
265
+ constructor(
266
+ public readonly LoaderConstructor: typeof Loader,
267
+ public readonly driver: ITestDriver,
268
+ public readonly createFluidEntryPoint: (
269
+ testContainerConfig?: ITestContainerConfig,
270
+ ) => fluidEntryPoint,
271
+ ) {
272
+ this._documentIdStrategy = getDocumentIdStrategy(driver.type);
273
+ }
274
+
275
+ get logger(): EventAndErrorTrackingLogger {
276
+ if (this._logger === undefined) {
277
+ this._logger = new EventAndErrorTrackingLogger(
278
+ ChildLogger.create(getTestLogger?.(), undefined, {
279
+ all: {
280
+ driverType: this.driver.type,
281
+ driverEndpointName: this.driver.endpointName,
282
+ driverTenantName: this.driver.tenantName,
283
+ driverUserIndex: this.driver.userIndex,
284
+ },
285
+ }),
286
+ );
287
+ }
288
+ return this._logger;
289
+ }
290
+
291
+ set logger(logger: EventAndErrorTrackingLogger) {
292
+ this._logger = logger;
293
+ }
294
+
295
+ get documentServiceFactory() {
296
+ if (!this._documentServiceFactory) {
297
+ this._documentServiceFactory = this.driver.createDocumentServiceFactory();
298
+ }
299
+ return this._documentServiceFactory;
300
+ }
301
+
302
+ get urlResolver() {
303
+ if (!this._urlResolver) {
304
+ this._urlResolver = this.driver.createUrlResolver();
305
+ }
306
+ return this._urlResolver;
307
+ }
308
+
309
+ get documentId() {
310
+ return this._documentIdStrategy.get();
311
+ }
312
+
313
+ get defaultCodeDetails() {
314
+ return defaultCodeDetails;
315
+ }
316
+
317
+ get opProcessingController(): IOpProcessingController {
318
+ return this._loaderContainerTracker;
319
+ }
320
+
321
+ /**
322
+ * Create a loader. Containers created/loaded through this loader will be added to the OpProcessingController.
323
+ *
324
+ * Only the version of the loader will vary based on compat config. The version of
325
+ * containerRuntime/dataRuntime used in fluidEntryPoint will be used as is from what is passed in.
326
+ *
327
+ * @param packageEntries - list of code details and fluidEntryPoint pairs.
328
+ */
329
+ public createLoader(
330
+ packageEntries: Iterable<[IFluidCodeDetails, fluidEntryPoint]>,
331
+ loaderProps?: Partial<ILoaderProps>,
332
+ ) {
333
+ const multiSinkLogger = new MultiSinkLogger();
334
+ multiSinkLogger.addLogger(this.logger);
335
+ if (loaderProps?.logger !== undefined) {
336
+ multiSinkLogger.addLogger(loaderProps.logger);
337
+ }
338
+
339
+ const loader = new this.LoaderConstructor({
340
+ ...loaderProps,
341
+ logger: multiSinkLogger,
342
+ codeLoader: loaderProps?.codeLoader ?? new LocalCodeLoader(packageEntries),
343
+ urlResolver: loaderProps?.urlResolver ?? this.urlResolver,
344
+ documentServiceFactory:
345
+ loaderProps?.documentServiceFactory ?? this.documentServiceFactory,
346
+ });
347
+ this._loaderContainerTracker.add(loader);
348
+ return loader;
349
+ }
350
+
351
+ /**
352
+ * Create a container using a default document id and code details.
353
+ * Container created is automatically added to the OpProcessingController to manage op flow
354
+ *
355
+ * Only the version of the loader will vary based on compat config. The version of
356
+ * containerRuntime/dataRuntime used in fluidEntryPoint will be used as is from what is passed in.
357
+ *
358
+ * @param packageEntries - list of code details and fluidEntryPoint pairs.
359
+ */
360
+ public async createContainer(entryPoint: fluidEntryPoint, loaderProps?: Partial<ILoaderProps>) {
361
+ if (this._documentCreated) {
362
+ throw new Error(
363
+ "Only one container/document can be created. To load the container/document use loadContainer",
364
+ );
365
+ }
366
+ const loader = this.createLoader([[defaultCodeDetails, entryPoint]], loaderProps);
367
+ const container = await createAndAttachContainer(
368
+ defaultCodeDetails,
369
+ loader,
370
+ this.driver.createCreateNewRequest(this.documentId),
371
+ );
372
+ this._documentCreated = true;
373
+ // r11s driver will generate a new ID for the new container.
374
+ // update the document ID with the actual ID of the attached container.
375
+ this._documentIdStrategy.update(container.resolvedUrl);
376
+ return container;
377
+ }
378
+
379
+ public async loadContainer(
380
+ entryPoint: fluidEntryPoint,
381
+ loaderProps?: Partial<ILoaderProps>,
382
+ requestHeader?: IRequestHeader,
383
+ ): Promise<IContainer> {
384
+ const loader = this.createLoader([[defaultCodeDetails, entryPoint]], loaderProps);
385
+
386
+ // Once ADO#3889 is done to switch default connection mode to "read" on load, we don't need
387
+ // to load "delayed" across the board. Remove the following code.
388
+ const delayConnection =
389
+ requestHeader === undefined || requestHeader[LoaderHeader.reconnect] !== false;
390
+ const headers: IRequestHeader = delayConnection
391
+ ? {
392
+ [LoaderHeader.loadMode]: { deltaConnection: "delayed" },
393
+ ...requestHeader,
394
+ }
395
+ : requestHeader;
396
+
397
+ const container = await loader.resolve({
398
+ url: await this.driver.createContainerUrl(this.documentId),
399
+ headers,
400
+ });
401
+
402
+ // Once ADO#3889 is done to switch default connection mode to "read" on load, we don't need
403
+ // to load "delayed" across the board. Remove the following code.
404
+ if (delayConnection) {
405
+ // Older version may not have connect, use resume instead.
406
+ const maybeContainer = container as Partial<IContainer>;
407
+ if (maybeContainer.connect !== undefined) {
408
+ container.connect();
409
+ } else {
410
+ // back compat
411
+ (container as any).resume();
412
+ }
413
+ }
414
+
415
+ return container;
416
+ }
417
+
418
+ /**
419
+ * Make a test loader. Containers created/loaded through this loader will be added to the OpProcessingController.
420
+ * The version of the loader/containerRuntime/dataRuntime may vary based on compat config of the current run
421
+ * @param testContainerConfig - optional configuring the test Container
422
+ */
423
+ public makeTestLoader(testContainerConfig?: ITestContainerConfig) {
424
+ return this.createLoader(
425
+ [[defaultCodeDetails, this.createFluidEntryPoint(testContainerConfig)]],
426
+ testContainerConfig?.loaderProps,
427
+ );
428
+ }
429
+
430
+ /**
431
+ * Make a container using a default document id and code details
432
+ * Container loaded is automatically added to the OpProcessingController to manage op flow
433
+ * @param testContainerConfig - optional configuring the test Container
434
+ */
435
+ public async makeTestContainer(
436
+ testContainerConfig?: ITestContainerConfig,
437
+ ): Promise<IContainer> {
438
+ if (this._documentCreated) {
439
+ throw new Error(
440
+ "Only one container/document can be created. To load the container/document use loadTestContainer",
441
+ );
442
+ }
443
+ const loader = this.makeTestLoader(testContainerConfig);
444
+ const container = await createAndAttachContainer(
445
+ defaultCodeDetails,
446
+ loader,
447
+ this.driver.createCreateNewRequest(this.documentId),
448
+ );
449
+ this._documentCreated = true;
450
+ // r11s driver will generate a new ID for the new container.
451
+ // update the document ID with the actual ID of the attached container.
452
+ this._documentIdStrategy.update(container.resolvedUrl);
453
+ return container;
454
+ }
455
+
456
+ /**
457
+ * Load a container using a default document id and code details.
458
+ * IContainer loaded is automatically added to the OpProcessingController to manage op flow
459
+ * @param testContainerConfig - optional configuring the test Container
460
+ * @param requestHeader - optional headers to be supplied to the loader
461
+ */
462
+ public async loadTestContainer(
463
+ testContainerConfig?: ITestContainerConfig,
464
+ requestHeader?: IRequestHeader,
465
+ ): Promise<IContainer> {
466
+ const loader = this.makeTestLoader(testContainerConfig);
467
+ const container = await loader.resolve({
468
+ url: await this.driver.createContainerUrl(this.documentId),
469
+ headers: requestHeader,
470
+ });
471
+ await this.waitContainerToCatchUp(container);
472
+
473
+ return container;
474
+ }
475
+
476
+ public reset() {
477
+ this._loaderContainerTracker.reset();
478
+ this._documentServiceFactory = undefined;
479
+ this._urlResolver = undefined;
480
+ this._documentIdStrategy.reset();
481
+ const logError = getUnexpectedLogErrorException(this._logger);
482
+ if (logError) {
483
+ throw logError;
484
+ }
485
+ this._logger = undefined;
486
+ this._documentCreated = false;
487
+ }
488
+
489
+ public async ensureSynchronized(timeoutDuration?: number): Promise<void> {
490
+ return this._loaderContainerTracker.ensureSynchronized();
491
+ }
492
+
493
+ public async waitContainerToCatchUp(container: IContainer) {
494
+ // The original waitContainerToCatchUp() from container loader uses either Container.resume()
495
+ // or Container.connect() as part of its implementation. However, resume() was deprecated
496
+ // and eventually replaced with connect(). To avoid issues during LTS compatibility testing
497
+ // with older container versions issues, we use resume() when connect() is unavailable.
498
+ if ((container as any).connect === undefined) {
499
+ (container as any).connect = (container as any).resume;
500
+ }
501
+
502
+ return waitContainerToCatchUp_original(container);
503
+ }
504
+
505
+ updateDocumentId(resolvedUrl: IResolvedUrl | undefined) {
506
+ this._documentIdStrategy.update(resolvedUrl);
507
+ }
508
+
509
+ public resetLoaderContainerTracker(syncSummarizerClients: boolean = false) {
510
+ this._loaderContainerTracker.reset();
511
+ this._loaderContainerTracker = new LoaderContainerTracker(syncSummarizerClients);
512
+ }
433
513
  }
434
514
 
435
- export function getUnexpectedLogErrorException(logger: EventAndErrorTrackingLogger | undefined, prefix?: string) {
436
- if (logger === undefined) {
437
- return;
438
- }
439
- const results = logger.reportAndClearTrackedEvents();
440
- if (results.unexpectedErrors.length > 0) {
441
- return new Error(
442
- `${prefix ?? ""}Unexpected Errors in Logs:\n${JSON.stringify(results.unexpectedErrors, undefined, 2)}`);
443
- }
444
- if (results.expectedNotFound.length > 0) {
445
- return new Error(
446
- `${prefix ?? ""}Expected Events not found:\n${JSON.stringify(results.expectedNotFound, undefined, 2)}`);
447
- }
515
+ export function getUnexpectedLogErrorException(
516
+ logger: EventAndErrorTrackingLogger | undefined,
517
+ prefix?: string,
518
+ ) {
519
+ if (logger === undefined) {
520
+ return;
521
+ }
522
+ const results = logger.reportAndClearTrackedEvents();
523
+ if (results.unexpectedErrors.length > 0) {
524
+ return new Error(
525
+ `${prefix ?? ""}Unexpected Errors in Logs:\n${JSON.stringify(
526
+ results.unexpectedErrors,
527
+ undefined,
528
+ 2,
529
+ )}`,
530
+ );
531
+ }
532
+ if (results.expectedNotFound.length > 0) {
533
+ return new Error(
534
+ `${prefix ?? ""}Expected Events not found:\n${JSON.stringify(
535
+ results.expectedNotFound,
536
+ undefined,
537
+ 2,
538
+ )}`,
539
+ );
540
+ }
448
541
  }