@fluidframework/azure-end-to-end-tests 2.70.0 → 2.72.0

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.
@@ -21,7 +21,7 @@ const debuggerAttached = inspector.url() !== undefined;
21
21
  /**
22
22
  * Set this to a high number when debugging to avoid timeouts from debugging time.
23
23
  */
24
- const timeoutMultiplier = debuggerAttached ? 1000 : useAzure ? 3 : 1;
24
+ const timeoutMultiplier = debuggerAttached ? 1000 : useAzure ? 5 : 1;
25
25
  /**
26
26
  * Sets the timeout for the given test context.
27
27
  *
@@ -97,10 +97,10 @@ describe(`Presence with AzureClient`, () => {
97
97
  */
98
98
  const allAttendeesFullyJoinedTimeoutMs = (2000 + 300 * numClients) * timeoutMultiplier;
99
99
  for (const writeClients of [numClients, 1]) {
100
- it(`announces 'attendeeConnected' when remote client joins session [${numClients} clients, ${writeClients} writers]`, async function () {
100
+ it(`announces 'attendeeConnected' when remote client joins session [${numClients} clients, ${writeClients} writers]`, async function testAnnouncesAttendeeConnected() {
101
101
  setTestTimeout(this, childConnectTimeoutMs + allAttendeesJoinedTimeoutMs + 1000);
102
102
  // Setup
103
- const { children, childErrorPromise } = await forkChildProcesses(numClients, afterCleanUp);
103
+ const { children, childErrorPromise } = await forkChildProcesses(this.test?.title ?? "", numClients, afterCleanUp);
104
104
  // Further Setup with Act and Verify
105
105
  await connectAndWaitForAttendees(children, {
106
106
  writeClients,
@@ -109,7 +109,7 @@ describe(`Presence with AzureClient`, () => {
109
109
  allAttendeesJoinedTimeoutMs,
110
110
  }, childErrorPromise);
111
111
  });
112
- it(`announces 'attendeeDisconnected' when remote client disconnects [${numClients} clients, ${writeClients} writers]`, async function () {
112
+ it(`announces 'attendeeDisconnected' when remote client disconnects [${numClients} clients, ${writeClients} writers]`, async function testAnnouncesAttendeeDisconnected() {
113
113
  if (useAzure && numClients > 50) {
114
114
  // Even with increased timeouts, more than 50 clients can be too large for AFR.
115
115
  // This may be due to slow responses/inactivity from the clients that are
@@ -122,7 +122,7 @@ describe(`Presence with AzureClient`, () => {
122
122
  childDisconnectTimeoutMs +
123
123
  1000);
124
124
  // Setup
125
- const { children, childErrorPromise } = await forkChildProcesses(numClients, afterCleanUp);
125
+ const { children, childErrorPromise } = await forkChildProcesses(this.test?.title ?? "", numClients, afterCleanUp);
126
126
  const startConnectAndFullJoin = performance.now();
127
127
  const connectResult = await connectAndListenForAttendees(children, {
128
128
  writeClients,
@@ -243,53 +243,65 @@ describe(`Presence with AzureClient`, () => {
243
243
  /**
244
244
  * Timeout for child processes to connect to container ({@link ConnectedEvent})
245
245
  */
246
- const childConnectTimeoutMs = 1000 * numClients * timeoutMultiplier;
247
- let children;
248
- let childErrorPromise;
249
- let containerCreatorAttendeeId;
250
- let attendeeIdPromises;
251
- let remoteClients;
252
- const testValue = "testValue";
253
- const workspaceId = "presenceTestWorkspace";
254
- beforeEach(async () => {
255
- ({ children, childErrorPromise } = await forkChildProcesses(numClients, afterCleanUp));
256
- ({ containerCreatorAttendeeId, attendeeIdPromises } = await connectChildProcesses(children, { writeClients: numClients, readyTimeoutMs: childConnectTimeoutMs }));
257
- await Promise.all(attendeeIdPromises);
258
- remoteClients = children.filter((_, index) => index !== 0);
259
- // NOTE: For testing purposes child clients will expect a Latest value of type string (StateFactory.latest<{ value: string }>).
260
- await registerWorkspaceOnChildren(children, workspaceId, {
261
- latest: true,
262
- timeoutMs: workspaceRegisterTimeoutMs,
263
- });
264
- });
265
- it(`allows clients to read Latest state from other clients [${numClients} clients]`, async function () {
266
- // Setup
267
- const updateEventsPromise = waitForLatestValueUpdates(remoteClients, workspaceId, childErrorPromise, stateUpdateTimeoutMs, { fromAttendeeId: containerCreatorAttendeeId, expectedValue: testValue });
268
- // Act - Trigger the update
269
- children[0].send({
270
- command: "setLatestValue",
271
- workspaceId,
272
- value: testValue,
246
+ const childConnectTimeoutMs = (4000 + 1000 * numClients) * timeoutMultiplier;
247
+ const testCaseTimeoutMs = 1000;
248
+ const testSetupAndActTimeoutMs = childConnectTimeoutMs + testCaseTimeoutMs;
249
+ // These tests use beforeEach to setup complex state that takes a lot of time
250
+ // and is dependent on number of clients. Keeping the work in beforeEach
251
+ // allows time reporting to report the tested scenario apart from the setup time.
252
+ // So this describe block isolates those beforeEach setups from each distinct
253
+ // client count. Test cases descriptions also have the client count for clarity.
254
+ describe(`with ${numClients} clients`, () => {
255
+ let children;
256
+ let childErrorPromise;
257
+ let containerCreatorAttendeeId;
258
+ let attendeeIdPromises;
259
+ let remoteClients;
260
+ const testValue = "testValue";
261
+ const workspaceId = "presenceTestWorkspace";
262
+ beforeEach(async function usingLatestStateObject_beforeEach() {
263
+ const startTime = performance.now();
264
+ setTestTimeout(this, testSetupAndActTimeoutMs);
265
+ ({ children, childErrorPromise } = await forkChildProcesses(this.currentTest?.title ?? "", numClients, afterCleanUp));
266
+ ({ containerCreatorAttendeeId, attendeeIdPromises } = await connectChildProcesses(children, { writeClients: numClients, readyTimeoutMs: childConnectTimeoutMs }));
267
+ await Promise.all(attendeeIdPromises);
268
+ remoteClients = children.filter((_, index) => index !== 0);
269
+ // NOTE: For testing purposes child clients will expect a Latest value of type string (StateFactory.latest<{ value: string }>).
270
+ await registerWorkspaceOnChildren(children, workspaceId, {
271
+ latest: true,
272
+ timeoutMs: workspaceRegisterTimeoutMs,
273
+ });
274
+ testConsole.log(` Setup for "${this.currentTest?.title}" completed in ${performance.now() - startTime}ms`);
273
275
  });
274
- const updateEvents = await updateEventsPromise;
275
- // Verify all events are from the expected attendee
276
- for (const updateEvent of updateEvents) {
277
- assert.strictEqual(updateEvent.attendeeId, containerCreatorAttendeeId);
278
- assert.deepStrictEqual(updateEvent.value, testValue);
279
- }
280
- // Act - Request each remote client to read latest state from container creator
281
- for (const child of remoteClients) {
282
- child.send({
283
- command: "getLatestValue",
276
+ it(`allows clients to read Latest state from other clients [${numClients} clients]`, async function () {
277
+ // Setup
278
+ const updateEventsPromise = waitForLatestValueUpdates(remoteClients, workspaceId, childErrorPromise, stateUpdateTimeoutMs, { fromAttendeeId: containerCreatorAttendeeId, expectedValue: testValue });
279
+ // Act - Trigger the update
280
+ children[0].send({
281
+ command: "setLatestValue",
284
282
  workspaceId,
285
- attendeeId: containerCreatorAttendeeId,
283
+ value: testValue,
286
284
  });
287
- }
288
- const getResponses = await getLatestValueResponses(remoteClients, workspaceId, childErrorPromise, getStateTimeoutMs);
289
- // Verify - all responses should contain the expected value
290
- for (const getResponse of getResponses) {
291
- assert.deepStrictEqual(getResponse.value, testValue);
292
- }
285
+ const updateEvents = await updateEventsPromise;
286
+ // Verify all events are from the expected attendee
287
+ for (const updateEvent of updateEvents) {
288
+ assert.strictEqual(updateEvent.attendeeId, containerCreatorAttendeeId);
289
+ assert.deepStrictEqual(updateEvent.value, testValue);
290
+ }
291
+ // Act - Request each remote client to read latest state from container creator
292
+ for (const child of remoteClients) {
293
+ child.send({
294
+ command: "getLatestValue",
295
+ workspaceId,
296
+ attendeeId: containerCreatorAttendeeId,
297
+ });
298
+ }
299
+ const getResponses = await getLatestValueResponses(remoteClients, workspaceId, childErrorPromise, getStateTimeoutMs);
300
+ // Verify - all responses should contain the expected value
301
+ for (const getResponse of getResponses) {
302
+ assert.deepStrictEqual(getResponse.value, testValue);
303
+ }
304
+ });
293
305
  });
294
306
  }
295
307
  });
@@ -301,125 +313,137 @@ describe(`Presence with AzureClient`, () => {
301
313
  /**
302
314
  * Timeout for child processes to connect to container ({@link ConnectedEvent})
303
315
  */
304
- const childConnectTimeoutMs = 1000 * numClients * timeoutMultiplier;
305
- let children;
306
- let childErrorPromise;
307
- let containerCreatorAttendeeId;
308
- let attendeeIdPromises;
309
- let remoteClients;
310
- const workspaceId = "presenceTestWorkspace";
311
- const key1 = "player1";
312
- const key2 = "player2";
313
- const value1 = { name: "Alice", score: 100 };
314
- const value2 = { name: "Bob", score: 200 };
315
- beforeEach(async () => {
316
- ({ children, childErrorPromise } = await forkChildProcesses(numClients, afterCleanUp));
317
- ({ containerCreatorAttendeeId, attendeeIdPromises } = await connectChildProcesses(children, { writeClients: numClients, readyTimeoutMs: childConnectTimeoutMs }));
318
- await Promise.all(attendeeIdPromises);
319
- remoteClients = children.filter((_, index) => index !== 0);
320
- // NOTE: For testing purposes child clients will expect a LatestMap value of type Record<string, string | number> (StateFactory.latestMap<{ value: Record<string, string | number> }, string>).
321
- await registerWorkspaceOnChildren(children, workspaceId, {
322
- latestMap: true,
323
- timeoutMs: workspaceRegisterTimeoutMs,
324
- });
325
- });
326
- it(`allows clients to read LatestMap values from other clients [${numClients} clients]`, async () => {
327
- // Setup
328
- const testKey = "cursor";
329
- const testValue = { x: 150, y: 300 };
330
- const updateEventsPromise = waitForLatestMapValueUpdates(remoteClients, workspaceId, testKey, childErrorPromise, stateUpdateTimeoutMs, { fromAttendeeId: containerCreatorAttendeeId, expectedValue: testValue });
331
- // Act
332
- children[0].send({
333
- command: "setLatestMapValue",
334
- workspaceId,
335
- key: testKey,
336
- value: testValue,
316
+ const childConnectTimeoutMs = (4000 + 1000 * numClients) * timeoutMultiplier;
317
+ const testCaseTimeoutMs = 1000;
318
+ const testSetupAndActTimeoutMs = childConnectTimeoutMs + testCaseTimeoutMs;
319
+ // These tests use beforeEach to setup complex state that takes a lot of time
320
+ // and is dependent on number of clients. Keeping the work in beforeEach
321
+ // allows time reporting to report the tested scenario apart from the setup time.
322
+ // So this describe block isolates those beforeEach setups from each distinct
323
+ // client count. Test cases descriptions also have the client count for clarity.
324
+ describe(`with ${numClients} clients`, () => {
325
+ let children;
326
+ let childErrorPromise;
327
+ let containerCreatorAttendeeId;
328
+ let attendeeIdPromises;
329
+ let remoteClients;
330
+ const workspaceId = "presenceTestWorkspace";
331
+ const key1 = "player1";
332
+ const key2 = "player2";
333
+ const value1 = { name: "Alice", score: 100 };
334
+ const value2 = { name: "Bob", score: 200 };
335
+ beforeEach(async function usingLatestMapStateObject_beforeEach() {
336
+ const startTime = performance.now();
337
+ setTestTimeout(this, testSetupAndActTimeoutMs);
338
+ ({ children, childErrorPromise } = await forkChildProcesses(this.currentTest?.title ?? "", numClients, afterCleanUp));
339
+ ({ containerCreatorAttendeeId, attendeeIdPromises } = await connectChildProcesses(children, { writeClients: numClients, readyTimeoutMs: childConnectTimeoutMs }));
340
+ await Promise.all(attendeeIdPromises);
341
+ remoteClients = children.filter((_, index) => index !== 0);
342
+ // NOTE: For testing purposes child clients will expect a LatestMap value of type Record<string, string | number> (StateFactory.latestMap<{ value: Record<string, string | number> }, string>).
343
+ await registerWorkspaceOnChildren(children, workspaceId, {
344
+ latestMap: true,
345
+ timeoutMs: workspaceRegisterTimeoutMs,
346
+ });
347
+ testConsole.log(` Setup for "${this.currentTest?.title}" completed in ${performance.now() - startTime}ms`);
337
348
  });
338
- const updateEvents = await updateEventsPromise;
339
- // Check all events are from the expected attendee
340
- for (const updateEvent of updateEvents) {
341
- assert.strictEqual(updateEvent.attendeeId, containerCreatorAttendeeId);
342
- assert.strictEqual(updateEvent.key, testKey);
343
- assert.deepStrictEqual(updateEvent.value, testValue);
344
- }
345
- for (const child of remoteClients) {
346
- child.send({
347
- command: "getLatestMapValue",
349
+ it(`allows clients to read LatestMap values from other clients [${numClients} clients]`, async () => {
350
+ // Setup
351
+ const testKey = "cursor";
352
+ const testValue = { x: 150, y: 300 };
353
+ const updateEventsPromise = waitForLatestMapValueUpdates(remoteClients, workspaceId, testKey, childErrorPromise, stateUpdateTimeoutMs, { fromAttendeeId: containerCreatorAttendeeId, expectedValue: testValue });
354
+ // Act
355
+ children[0].send({
356
+ command: "setLatestMapValue",
348
357
  workspaceId,
349
358
  key: testKey,
350
- attendeeId: containerCreatorAttendeeId,
359
+ value: testValue,
351
360
  });
352
- }
353
- const getResponses = await getLatestMapValueResponses(remoteClients, workspaceId, testKey, childErrorPromise, getStateTimeoutMs);
354
- // Verify
355
- for (const getResponse of getResponses) {
356
- assert.deepStrictEqual(getResponse.value, testValue);
357
- }
358
- });
359
- it(`returns per-key values on read [${numClients} clients]`, async function () {
360
- // Setup
361
- const allAttendeeIds = await Promise.all(attendeeIdPromises);
362
- const attendee0Id = containerCreatorAttendeeId;
363
- const attendee1Id = allAttendeeIds[1];
364
- const key1Recipients = children.filter((_, index) => index !== 0);
365
- const key2Recipients = children.filter((_, index) => index !== 1);
366
- const key1UpdateEventsPromise = waitForLatestMapValueUpdates(key1Recipients, workspaceId, key1, childErrorPromise, stateUpdateTimeoutMs, { fromAttendeeId: attendee0Id, expectedValue: value1 });
367
- const key2UpdateEventsPromise = waitForLatestMapValueUpdates(key2Recipients, workspaceId, key2, childErrorPromise, stateUpdateTimeoutMs, { fromAttendeeId: attendee1Id, expectedValue: value2 });
368
- // Act
369
- children[0].send({
370
- command: "setLatestMapValue",
371
- workspaceId,
372
- key: key1,
373
- value: value1,
374
- });
375
- const key1UpdateEvents = await key1UpdateEventsPromise;
376
- children[1].send({
377
- command: "setLatestMapValue",
378
- workspaceId,
379
- key: key2,
380
- value: value2,
361
+ const updateEvents = await updateEventsPromise;
362
+ // Check all events are from the expected attendee
363
+ for (const updateEvent of updateEvents) {
364
+ assert.strictEqual(updateEvent.attendeeId, containerCreatorAttendeeId);
365
+ assert.strictEqual(updateEvent.key, testKey);
366
+ assert.deepStrictEqual(updateEvent.value, testValue);
367
+ }
368
+ for (const child of remoteClients) {
369
+ child.send({
370
+ command: "getLatestMapValue",
371
+ workspaceId,
372
+ key: testKey,
373
+ attendeeId: containerCreatorAttendeeId,
374
+ });
375
+ }
376
+ const getResponses = await getLatestMapValueResponses(remoteClients, workspaceId, testKey, childErrorPromise, getStateTimeoutMs);
377
+ // Verify
378
+ for (const getResponse of getResponses) {
379
+ assert.deepStrictEqual(getResponse.value, testValue);
380
+ }
381
381
  });
382
- const key2UpdateEvents = await key2UpdateEventsPromise;
383
- // Verify all events are from the expected attendees
384
- for (const updateEvent of key1UpdateEvents) {
385
- assert.strictEqual(updateEvent.attendeeId, attendee0Id);
386
- assert.strictEqual(updateEvent.key, key1);
387
- assert.deepStrictEqual(updateEvent.value, value1);
388
- }
389
- for (const updateEvent of key2UpdateEvents) {
390
- assert.strictEqual(updateEvent.attendeeId, attendee1Id);
391
- assert.strictEqual(updateEvent.key, key2);
392
- assert.deepStrictEqual(updateEvent.value, value2);
393
- }
394
- // Read key1 of attendee0 from all children
395
- for (const child of children) {
396
- child.send({
397
- command: "getLatestMapValue",
382
+ it(`returns per-key values on read [${numClients} clients]`, async function () {
383
+ // Setup
384
+ const allAttendeeIds = await Promise.all(attendeeIdPromises);
385
+ const attendee0Id = containerCreatorAttendeeId;
386
+ const attendee1Id = allAttendeeIds[1];
387
+ const key1Recipients = children.filter((_, index) => index !== 0);
388
+ const key2Recipients = children.filter((_, index) => index !== 1);
389
+ const key1UpdateEventsPromise = waitForLatestMapValueUpdates(key1Recipients, workspaceId, key1, childErrorPromise, stateUpdateTimeoutMs, { fromAttendeeId: attendee0Id, expectedValue: value1 });
390
+ const key2UpdateEventsPromise = waitForLatestMapValueUpdates(key2Recipients, workspaceId, key2, childErrorPromise, stateUpdateTimeoutMs, { fromAttendeeId: attendee1Id, expectedValue: value2 });
391
+ // Act
392
+ children[0].send({
393
+ command: "setLatestMapValue",
398
394
  workspaceId,
399
395
  key: key1,
400
- attendeeId: attendee0Id,
396
+ value: value1,
401
397
  });
402
- }
403
- const key1Responses = await getLatestMapValueResponses(children, workspaceId, key1, childErrorPromise, getStateTimeoutMs);
404
- // Read key2 of attendee1 from all children
405
- for (const child of children) {
406
- child.send({
407
- command: "getLatestMapValue",
398
+ const key1UpdateEvents = await key1UpdateEventsPromise;
399
+ children[1].send({
400
+ command: "setLatestMapValue",
408
401
  workspaceId,
409
402
  key: key2,
410
- attendeeId: attendee1Id,
403
+ value: value2,
411
404
  });
412
- }
413
- const key2Responses = await getLatestMapValueResponses(children, workspaceId, key2, childErrorPromise, getStateTimeoutMs);
414
- // Verify
415
- assert.strictEqual(key1Responses.length, numClients, "Expected responses from all clients for key1");
416
- assert.strictEqual(key2Responses.length, numClients, "Expected responses from all clients for key2");
417
- for (const response of key1Responses) {
418
- assert.deepStrictEqual(response.value, value1, "Key1 value should match");
419
- }
420
- for (const response of key2Responses) {
421
- assert.deepStrictEqual(response.value, value2, "Key2 value should match");
422
- }
405
+ const key2UpdateEvents = await key2UpdateEventsPromise;
406
+ // Verify all events are from the expected attendees
407
+ for (const updateEvent of key1UpdateEvents) {
408
+ assert.strictEqual(updateEvent.attendeeId, attendee0Id);
409
+ assert.strictEqual(updateEvent.key, key1);
410
+ assert.deepStrictEqual(updateEvent.value, value1);
411
+ }
412
+ for (const updateEvent of key2UpdateEvents) {
413
+ assert.strictEqual(updateEvent.attendeeId, attendee1Id);
414
+ assert.strictEqual(updateEvent.key, key2);
415
+ assert.deepStrictEqual(updateEvent.value, value2);
416
+ }
417
+ // Read key1 of attendee0 from all children
418
+ for (const child of children) {
419
+ child.send({
420
+ command: "getLatestMapValue",
421
+ workspaceId,
422
+ key: key1,
423
+ attendeeId: attendee0Id,
424
+ });
425
+ }
426
+ const key1Responses = await getLatestMapValueResponses(children, workspaceId, key1, childErrorPromise, getStateTimeoutMs);
427
+ // Read key2 of attendee1 from all children
428
+ for (const child of children) {
429
+ child.send({
430
+ command: "getLatestMapValue",
431
+ workspaceId,
432
+ key: key2,
433
+ attendeeId: attendee1Id,
434
+ });
435
+ }
436
+ const key2Responses = await getLatestMapValueResponses(children, workspaceId, key2, childErrorPromise, getStateTimeoutMs);
437
+ // Verify
438
+ assert.strictEqual(key1Responses.length, numClients, "Expected responses from all clients for key1");
439
+ assert.strictEqual(key2Responses.length, numClients, "Expected responses from all clients for key2");
440
+ for (const response of key1Responses) {
441
+ assert.deepStrictEqual(response.value, value1, "Key1 value should match");
442
+ }
443
+ for (const response of key2Responses) {
444
+ assert.deepStrictEqual(response.value, value2, "Key2 value should match");
445
+ }
446
+ });
423
447
  });
424
448
  }
425
449
  });
@@ -1 +1 @@
1
- {"version":3,"file":"presenceTest.spec.js","sourceRoot":"","sources":["../../../src/test/multiprocess/presenceTest.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { strict as assert } from \"node:assert\";\nimport type { ChildProcess } from \"node:child_process\";\nimport inspector from \"node:inspector\";\n\nimport type { AttendeeId } from \"@fluidframework/presence/beta\";\nimport { timeoutAwait, timeoutPromise } from \"@fluidframework/test-utils/internal\";\n\nimport type { MessageFromChild } from \"./messageTypes.js\";\nimport {\n\tconnectAndListenForAttendees,\n\tconnectAndWaitForAttendees,\n\tconnectChildProcesses,\n\texecuteDebugReports,\n\tforkChildProcesses,\n\tgetLatestMapValueResponses,\n\tgetLatestValueResponses,\n\tregisterWorkspaceOnChildren,\n\ttestConsole,\n\twaitForLatestMapValueUpdates,\n\twaitForLatestValueUpdates,\n} from \"./orchestratorUtils.js\";\n\n/**\n * When true, slower (long running time) tests will be run.\n * Otherwise, those test will not appear. Console output is used to show that\n * they exist. (They could be skipped, though skipped test are often an\n * indication of a problem.)\n */\nconst shouldRunScaleTests = process.env.FLUID_TEST_SCALE !== undefined;\n\nconst useAzure = process.env.FLUID_CLIENT === \"azure\";\n\n/**\n * Detects if the debugger is attached (when code loaded).\n */\nconst debuggerAttached = inspector.url() !== undefined;\n\n/**\n * Set this to a high number when debugging to avoid timeouts from debugging time.\n */\nconst timeoutMultiplier = debuggerAttached ? 1000 : useAzure ? 3 : 1;\n\n/**\n * Sets the timeout for the given test context.\n *\n * @remarks\n * If a debugger is attached, the timeout is set to 0 to prevent timeouts during debugging.\n * Otherwise, it sets the timeout to the maximum of the current timeout and the specified duration.\n *\n * @param context - The Mocha test context.\n * @param duration - The duration in milliseconds to set the timeout to. Zero disables the timeout.\n */\nfunction setTestTimeout(context: Mocha.Context, duration: number): void {\n\tconst currentTimeout = context.timeout();\n\tconst newTimeout =\n\t\tdebuggerAttached || currentTimeout === 0 || duration === 0\n\t\t\t? 0\n\t\t\t: Math.max(currentTimeout, duration);\n\tif (newTimeout !== currentTimeout) {\n\t\ttestConsole.log(\n\t\t\t`${context.test?.title}: setting timeout to ${newTimeout}ms (was ${currentTimeout}ms)`,\n\t\t);\n\t\tcontext.timeout(newTimeout);\n\t}\n}\n\n/**\n * This test suite is a prototype for a multi-process end to end test for Fluid using the new Presence API on AzureClient.\n * In the future we hope to expand and generalize this pattern to broadly test more Fluid features.\n * Other E2E tests are limited to running multiple clients on a single process which does not effectively\n * simulate real-world production scenarios where clients are usually running on different machines. Since\n * the Fluid Framework client is designed to carry most of the work burden, multi-process testing from a\n * single machine is also not representative but does at least work past some limitations of a single\n * Node.js process handling multiple clients.\n *\n * The pattern demonstrated in this test suite is as follows:\n *\n * This main test file acts as the 'Orchestrator'. The orchestrator's job includes:\n * - Fork child processes to simulate multiple Fluid clients\n * - Send command messages to child clients to perform specific Fluid actions.\n * - Receive response messages from child clients to verify expected behavior.\n * - Clean up child processes after each test.\n *\n * The child processes are located in the `childClient.tool.ts` file. Each child process simulates a Fluid client.\n *\n * The child client's job includes:\n * - Create/Get + connect to Fluid container.\n * - Listen for command messages from the orchestrator.\n * - Perform the requested action.\n * - Send response messages including any relevant data back to the orchestrator to verify expected behavior.\n */\n\ndescribe(`Presence with AzureClient`, () => {\n\tconst afterCleanUp: (() => void)[] = [];\n\n\t// After each test, call any cleanup functions that were registered (kill each child process)\n\tafterEach(async () => {\n\t\tfor (const cleanUp of afterCleanUp) {\n\t\t\tcleanUp();\n\t\t}\n\t\tafterCleanUp.length = 0;\n\t});\n\n\tdescribe(\"`attendees` support\", () => {\n\t\tconst numClientsForAttendeeTests = [5, 40, 100, 250];\n\t\tfor (const numClients of numClientsForAttendeeTests) {\n\t\t\tif (numClients > 50 && !shouldRunScaleTests) {\n\t\t\t\ttestConsole.log(\n\t\t\t\t\t`skipping Presence attendee scale tests with ${numClients} clients (set FLUID_TEST_SCALE=true to run)`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tassert(numClients > 1, \"Must have at least two clients\");\n\t\t\t/**\n\t\t\t * Timeout for child processes to connect to container ({@link ConnectedEvent})\n\t\t\t */\n\t\t\tconst childConnectTimeoutMs = 1000 * numClients * timeoutMultiplier;\n\t\t\t/**\n\t\t\t * Timeout for presence attendees to join per first child perspective {@link AttendeeConnectedEvent}\n\t\t\t */\n\t\t\tconst allAttendeesJoinedTimeoutMs = (1000 + 200 * numClients) * timeoutMultiplier;\n\t\t\t/**\n\t\t\t * Timeout for presence attendees to fully join (everyone knows about everyone) {@link AttendeeConnectedEvent}\n\t\t\t */\n\t\t\tconst allAttendeesFullyJoinedTimeoutMs = (2000 + 300 * numClients) * timeoutMultiplier;\n\n\t\t\tfor (const writeClients of [numClients, 1]) {\n\t\t\t\tit(`announces 'attendeeConnected' when remote client joins session [${numClients} clients, ${writeClients} writers]`, async function () {\n\t\t\t\t\tsetTestTimeout(this, childConnectTimeoutMs + allAttendeesJoinedTimeoutMs + 1000);\n\n\t\t\t\t\t// Setup\n\t\t\t\t\tconst { children, childErrorPromise } = await forkChildProcesses(\n\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\tafterCleanUp,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Further Setup with Act and Verify\n\t\t\t\t\tawait connectAndWaitForAttendees(\n\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\twriteClients,\n\t\t\t\t\t\t\tattendeeCountRequired: numClients - 1,\n\t\t\t\t\t\t\tchildConnectTimeoutMs,\n\t\t\t\t\t\t\tallAttendeesJoinedTimeoutMs,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t);\n\t\t\t\t});\n\n\t\t\t\tit(`announces 'attendeeDisconnected' when remote client disconnects [${numClients} clients, ${writeClients} writers]`, async function () {\n\t\t\t\t\tif (useAzure && numClients > 50) {\n\t\t\t\t\t\t// Even with increased timeouts, more than 50 clients can be too large for AFR.\n\t\t\t\t\t\t// This may be due to slow responses/inactivity from the clients that are\n\t\t\t\t\t\t// creating pressure on ADO agent.\n\t\t\t\t\t\tthis.skip();\n\t\t\t\t\t}\n\n\t\t\t\t\tconst childDisconnectTimeoutMs = 10_000 * timeoutMultiplier;\n\n\t\t\t\t\tsetTestTimeout(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\tchildConnectTimeoutMs +\n\t\t\t\t\t\t\tallAttendeesFullyJoinedTimeoutMs +\n\t\t\t\t\t\t\tchildDisconnectTimeoutMs +\n\t\t\t\t\t\t\t1000,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Setup\n\t\t\t\t\tconst { children, childErrorPromise } = await forkChildProcesses(\n\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\tafterCleanUp,\n\t\t\t\t\t);\n\n\t\t\t\t\tconst startConnectAndFullJoin = performance.now();\n\t\t\t\t\tconst connectResult = await connectAndListenForAttendees(children, {\n\t\t\t\t\t\twriteClients,\n\t\t\t\t\t\tattendeeCountRequired: numClients - 1,\n\t\t\t\t\t\tchildConnectTimeoutMs,\n\t\t\t\t\t});\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-floating-promises\n\t\t\t\t\tconnectResult.attendeeCountRequiredPromises[0].then(() =>\n\t\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t\t`[${new Date().toISOString()}] All attendees joined per child 0 after ${performance.now() - startConnectAndFullJoin}ms`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\n\t\t\t\t\t// Wait for all attendees to be fully joined\n\t\t\t\t\t// Keep a tally for debuggability\n\t\t\t\t\tlet childrenFullyJoined = 0;\n\t\t\t\t\tconst setNotFullyJoined = new Set<number>();\n\t\t\t\t\tfor (let i = 0; i < children.length; i++) {\n\t\t\t\t\t\tsetNotFullyJoined.add(i);\n\t\t\t\t\t}\n\t\t\t\t\tconst allAttendeesFullyJoined = Promise.all(\n\t\t\t\t\t\tconnectResult.attendeeCountRequiredPromises.map(\n\t\t\t\t\t\t\tasync (attendeeFullyJoinedPromise, index) => {\n\t\t\t\t\t\t\t\tawait attendeeFullyJoinedPromise;\n\t\t\t\t\t\t\t\tchildrenFullyJoined++;\n\t\t\t\t\t\t\t\tsetNotFullyJoined.delete(index);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tlet timedout = true;\n\t\t\t\t\tconst allFullyJoinedOrChildError = Promise.race([\n\t\t\t\t\t\tallAttendeesFullyJoined,\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t]).finally(() => (timedout = false));\n\t\t\t\t\tawait timeoutAwait(allFullyJoinedOrChildError, {\n\t\t\t\t\t\tdurationMs: allAttendeesFullyJoinedTimeoutMs,\n\t\t\t\t\t\terrorMsg: \"Not all attendees fully joined\",\n\t\t\t\t\t}).catch(async (error) => {\n\t\t\t\t\t\t// Ideally this information would just be in the timeout error message, but that\n\t\t\t\t\t\t// must be a resolved string (not dynamic). So, just log it separately.\n\t\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t\t`[${new Date().toISOString()}] ${childrenFullyJoined} attendees fully joined before error...`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (timedout) {\n\t\t\t\t\t\t\t// Gather additional timing data if timed out to understand what increased\n\t\t\t\t\t\t\t// timeout could work. Test will still fail if this secondary wait succeeds.\n\t\t\t\t\t\t\tconst startAdditionalWait = performance.now();\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait timeoutAwait(allFullyJoinedOrChildError, {\n\t\t\t\t\t\t\t\t\tdurationMs: allAttendeesFullyJoinedTimeoutMs,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t\t\t\t`[${new Date().toISOString()}] All attendees fully joined after additional wait (${performance.now() - startAdditionalWait}ms)`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} catch (secondaryError) {\n\t\t\t\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t\t\t\t`[${new Date().toISOString()}] Secondary await resulted in`,\n\t\t\t\t\t\t\t\t\tsecondaryError,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Gather and report debug info from children\n\t\t\t\t\t\t// If there are less than 10 children, get all reports.\n\t\t\t\t\t\t// Otherwise, just child 0 and those not fully joined.\n\t\t\t\t\t\tsetTestTimeout(this, 0); // Disable test timeout. Will throw within 20s below.\n\t\t\t\t\t\tconst childrenRequestedToReport =\n\t\t\t\t\t\t\tchildren.length <= 10\n\t\t\t\t\t\t\t\t? children\n\t\t\t\t\t\t\t\t: // Just those not fully joined\n\t\t\t\t\t\t\t\t\tchildren.filter((_, index) => index === 0 || setNotFullyJoined.has(index));\n\t\t\t\t\t\tawait timeoutAwait(\n\t\t\t\t\t\t\tPromise.race([\n\t\t\t\t\t\t\t\texecuteDebugReports(childrenRequestedToReport),\n\t\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t{ durationMs: 20_000, errorMsg: \"Debug report timeout\" },\n\t\t\t\t\t\t).catch((debugAwaitError) => {\n\t\t\t\t\t\t\ttestConsole.error(\"Debug report await resulted in\", debugAwaitError);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t});\n\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t`[${new Date().toISOString()}] All attendees fully joined after ${performance.now() - startConnectAndFullJoin}ms`,\n\t\t\t\t\t);\n\n\t\t\t\t\tlet child0ReportRequested = false;\n\t\t\t\t\tconst waitForDisconnected = children.map(async (child, index) =>\n\t\t\t\t\t\tindex === 0\n\t\t\t\t\t\t\t? Promise.resolve()\n\t\t\t\t\t\t\t: timeoutPromise(\n\t\t\t\t\t\t\t\t\t(resolve) => {\n\t\t\t\t\t\t\t\t\t\tchild.on(\"message\", (msg: MessageFromChild) => {\n\t\t\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t\t\tmsg.event === \"attendeeDisconnected\" &&\n\t\t\t\t\t\t\t\t\t\t\t\tmsg.attendeeId === connectResult.containerCreatorAttendeeId\n\t\t\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\t\t\tconsole.log(`Child[${index}] saw creator disconnect`);\n\t\t\t\t\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tdurationMs: childDisconnectTimeoutMs,\n\t\t\t\t\t\t\t\t\t\terrorMsg: `Attendee[${index}] Disconnected Timeout`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t).catch(async (error) => {\n\t\t\t\t\t\t\t\t\tconst childrenRequestedToReport = [child];\n\t\t\t\t\t\t\t\t\tif (!child0ReportRequested) {\n\t\t\t\t\t\t\t\t\t\tchildrenRequestedToReport.unshift(children[0]);\n\t\t\t\t\t\t\t\t\t\tchild0ReportRequested = true;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tawait timeoutAwait(\n\t\t\t\t\t\t\t\t\t\tPromise.race([\n\t\t\t\t\t\t\t\t\t\t\texecuteDebugReports(childrenRequestedToReport),\n\t\t\t\t\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t\t{ durationMs: 20_000, errorMsg: \"Debug report timeout\" },\n\t\t\t\t\t\t\t\t\t).catch((debugAwaitError) => {\n\t\t\t\t\t\t\t\t\t\ttestConsole.error(\"Debug report await resulted in\", debugAwaitError);\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\t// Act - disconnect first child process\n\t\t\t\t\tchildren[0].send({ command: \"disconnectSelf\" });\n\n\t\t\t\t\t// Verify - wait for all 'attendeeDisconnected' events\n\t\t\t\t\tawait Promise.race([Promise.all(waitForDisconnected), childErrorPromise]);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t});\n\n\t{\n\t\t/**\n\t\t * Timeout for workspace registration {@link WorkspaceRegisteredEvent}\n\t\t */\n\t\tconst workspaceRegisterTimeoutMs = 5000;\n\t\t/**\n\t\t * Timeout for presence update events {@link LatestMapValueUpdatedEvent} and {@link LatestValueUpdatedEvent}\n\t\t */\n\t\tconst stateUpdateTimeoutMs = 5000;\n\t\t/**\n\t\t * Timeout for {@link LatestMapValueGetResponseEvent} and {@link LatestValueGetResponseEvent}\n\t\t */\n\t\tconst getStateTimeoutMs = 5000;\n\n\t\t// This test suite focuses on the synchronization of Latest state between clients.\n\t\t// NOTE: For testing purposes child clients will expect a Latest value of type string.\n\t\tdescribe(`using Latest state object`, () => {\n\t\t\tfor (const numClients of [5, 20]) {\n\t\t\t\tassert(numClients > 1, \"Must have at least two clients\");\n\t\t\t\t/**\n\t\t\t\t * Timeout for child processes to connect to container ({@link ConnectedEvent})\n\t\t\t\t */\n\t\t\t\tconst childConnectTimeoutMs = 1000 * numClients * timeoutMultiplier;\n\n\t\t\t\tlet children: ChildProcess[];\n\t\t\t\tlet childErrorPromise: Promise<never>;\n\t\t\t\tlet containerCreatorAttendeeId: AttendeeId;\n\t\t\t\tlet attendeeIdPromises: Promise<AttendeeId>[];\n\t\t\t\tlet remoteClients: ChildProcess[];\n\t\t\t\tconst testValue = \"testValue\";\n\t\t\t\tconst workspaceId = \"presenceTestWorkspace\";\n\n\t\t\t\tbeforeEach(async () => {\n\t\t\t\t\t({ children, childErrorPromise } = await forkChildProcesses(\n\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\tafterCleanUp,\n\t\t\t\t\t));\n\t\t\t\t\t({ containerCreatorAttendeeId, attendeeIdPromises } = await connectChildProcesses(\n\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t{ writeClients: numClients, readyTimeoutMs: childConnectTimeoutMs },\n\t\t\t\t\t));\n\t\t\t\t\tawait Promise.all(attendeeIdPromises);\n\t\t\t\t\tremoteClients = children.filter((_, index) => index !== 0);\n\t\t\t\t\t// NOTE: For testing purposes child clients will expect a Latest value of type string (StateFactory.latest<{ value: string }>).\n\t\t\t\t\tawait registerWorkspaceOnChildren(children, workspaceId, {\n\t\t\t\t\t\tlatest: true,\n\t\t\t\t\t\ttimeoutMs: workspaceRegisterTimeoutMs,\n\t\t\t\t\t});\n\t\t\t\t});\n\n\t\t\t\tit(`allows clients to read Latest state from other clients [${numClients} clients]`, async function () {\n\t\t\t\t\t// Setup\n\t\t\t\t\tconst updateEventsPromise = waitForLatestValueUpdates(\n\t\t\t\t\t\tremoteClients,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\tstateUpdateTimeoutMs,\n\t\t\t\t\t\t{ fromAttendeeId: containerCreatorAttendeeId, expectedValue: testValue },\n\t\t\t\t\t);\n\n\t\t\t\t\t// Act - Trigger the update\n\t\t\t\t\tchildren[0].send({\n\t\t\t\t\t\tcommand: \"setLatestValue\",\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tvalue: testValue,\n\t\t\t\t\t});\n\t\t\t\t\tconst updateEvents = await updateEventsPromise;\n\n\t\t\t\t\t// Verify all events are from the expected attendee\n\t\t\t\t\tfor (const updateEvent of updateEvents) {\n\t\t\t\t\t\tassert.strictEqual(updateEvent.attendeeId, containerCreatorAttendeeId);\n\t\t\t\t\t\tassert.deepStrictEqual(updateEvent.value, testValue);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Act - Request each remote client to read latest state from container creator\n\t\t\t\t\tfor (const child of remoteClients) {\n\t\t\t\t\t\tchild.send({\n\t\t\t\t\t\t\tcommand: \"getLatestValue\",\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tattendeeId: containerCreatorAttendeeId,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tconst getResponses = await getLatestValueResponses(\n\t\t\t\t\t\tremoteClients,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\tgetStateTimeoutMs,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Verify - all responses should contain the expected value\n\t\t\t\t\tfor (const getResponse of getResponses) {\n\t\t\t\t\t\tassert.deepStrictEqual(getResponse.value, testValue);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t// This test suite focuses on the synchronization of LatestMap state between clients.\n\t\t// NOTE: For testing purposes child clients will expect a LatestMap value of type Record<string, string | number>.\n\t\tdescribe(`using LatestMap state object`, () => {\n\t\t\tfor (const numClients of [5, 20]) {\n\t\t\t\tassert(numClients > 1, \"Must have at least two clients\");\n\t\t\t\t/**\n\t\t\t\t * Timeout for child processes to connect to container ({@link ConnectedEvent})\n\t\t\t\t */\n\t\t\t\tconst childConnectTimeoutMs = 1000 * numClients * timeoutMultiplier;\n\n\t\t\t\tlet children: ChildProcess[];\n\t\t\t\tlet childErrorPromise: Promise<never>;\n\t\t\t\tlet containerCreatorAttendeeId: AttendeeId;\n\t\t\t\tlet attendeeIdPromises: Promise<AttendeeId>[];\n\t\t\t\tlet remoteClients: ChildProcess[];\n\t\t\t\tconst workspaceId = \"presenceTestWorkspace\";\n\t\t\t\tconst key1 = \"player1\";\n\t\t\t\tconst key2 = \"player2\";\n\t\t\t\tconst value1 = { name: \"Alice\", score: 100 };\n\t\t\t\tconst value2 = { name: \"Bob\", score: 200 };\n\n\t\t\t\tbeforeEach(async () => {\n\t\t\t\t\t({ children, childErrorPromise } = await forkChildProcesses(\n\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\tafterCleanUp,\n\t\t\t\t\t));\n\t\t\t\t\t({ containerCreatorAttendeeId, attendeeIdPromises } = await connectChildProcesses(\n\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t{ writeClients: numClients, readyTimeoutMs: childConnectTimeoutMs },\n\t\t\t\t\t));\n\t\t\t\t\tawait Promise.all(attendeeIdPromises);\n\t\t\t\t\tremoteClients = children.filter((_, index) => index !== 0);\n\t\t\t\t\t// NOTE: For testing purposes child clients will expect a LatestMap value of type Record<string, string | number> (StateFactory.latestMap<{ value: Record<string, string | number> }, string>).\n\t\t\t\t\tawait registerWorkspaceOnChildren(children, workspaceId, {\n\t\t\t\t\t\tlatestMap: true,\n\t\t\t\t\t\ttimeoutMs: workspaceRegisterTimeoutMs,\n\t\t\t\t\t});\n\t\t\t\t});\n\n\t\t\t\tit(`allows clients to read LatestMap values from other clients [${numClients} clients]`, async () => {\n\t\t\t\t\t// Setup\n\t\t\t\t\tconst testKey = \"cursor\";\n\t\t\t\t\tconst testValue = { x: 150, y: 300 };\n\t\t\t\t\tconst updateEventsPromise = waitForLatestMapValueUpdates(\n\t\t\t\t\t\tremoteClients,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\ttestKey,\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\tstateUpdateTimeoutMs,\n\t\t\t\t\t\t{ fromAttendeeId: containerCreatorAttendeeId, expectedValue: testValue },\n\t\t\t\t\t);\n\n\t\t\t\t\t// Act\n\t\t\t\t\tchildren[0].send({\n\t\t\t\t\t\tcommand: \"setLatestMapValue\",\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tkey: testKey,\n\t\t\t\t\t\tvalue: testValue,\n\t\t\t\t\t});\n\t\t\t\t\tconst updateEvents = await updateEventsPromise;\n\n\t\t\t\t\t// Check all events are from the expected attendee\n\t\t\t\t\tfor (const updateEvent of updateEvents) {\n\t\t\t\t\t\tassert.strictEqual(updateEvent.attendeeId, containerCreatorAttendeeId);\n\t\t\t\t\t\tassert.strictEqual(updateEvent.key, testKey);\n\t\t\t\t\t\tassert.deepStrictEqual(updateEvent.value, testValue);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const child of remoteClients) {\n\t\t\t\t\t\tchild.send({\n\t\t\t\t\t\t\tcommand: \"getLatestMapValue\",\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tkey: testKey,\n\t\t\t\t\t\t\tattendeeId: containerCreatorAttendeeId,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst getResponses = await getLatestMapValueResponses(\n\t\t\t\t\t\tremoteClients,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\ttestKey,\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\tgetStateTimeoutMs,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Verify\n\t\t\t\t\tfor (const getResponse of getResponses) {\n\t\t\t\t\t\tassert.deepStrictEqual(getResponse.value, testValue);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tit(`returns per-key values on read [${numClients} clients]`, async function () {\n\t\t\t\t\t// Setup\n\t\t\t\t\tconst allAttendeeIds = await Promise.all(attendeeIdPromises);\n\t\t\t\t\tconst attendee0Id = containerCreatorAttendeeId;\n\t\t\t\t\tconst attendee1Id = allAttendeeIds[1];\n\n\t\t\t\t\tconst key1Recipients = children.filter((_, index) => index !== 0);\n\t\t\t\t\tconst key2Recipients = children.filter((_, index) => index !== 1);\n\t\t\t\t\tconst key1UpdateEventsPromise = waitForLatestMapValueUpdates(\n\t\t\t\t\t\tkey1Recipients,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tkey1,\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\tstateUpdateTimeoutMs,\n\t\t\t\t\t\t{ fromAttendeeId: attendee0Id, expectedValue: value1 },\n\t\t\t\t\t);\n\t\t\t\t\tconst key2UpdateEventsPromise = waitForLatestMapValueUpdates(\n\t\t\t\t\t\tkey2Recipients,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tkey2,\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\tstateUpdateTimeoutMs,\n\t\t\t\t\t\t{ fromAttendeeId: attendee1Id, expectedValue: value2 },\n\t\t\t\t\t);\n\n\t\t\t\t\t// Act\n\t\t\t\t\tchildren[0].send({\n\t\t\t\t\t\tcommand: \"setLatestMapValue\",\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tkey: key1,\n\t\t\t\t\t\tvalue: value1,\n\t\t\t\t\t});\n\t\t\t\t\tconst key1UpdateEvents = await key1UpdateEventsPromise;\n\t\t\t\t\tchildren[1].send({\n\t\t\t\t\t\tcommand: \"setLatestMapValue\",\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tkey: key2,\n\t\t\t\t\t\tvalue: value2,\n\t\t\t\t\t});\n\t\t\t\t\tconst key2UpdateEvents = await key2UpdateEventsPromise;\n\n\t\t\t\t\t// Verify all events are from the expected attendees\n\t\t\t\t\tfor (const updateEvent of key1UpdateEvents) {\n\t\t\t\t\t\tassert.strictEqual(updateEvent.attendeeId, attendee0Id);\n\t\t\t\t\t\tassert.strictEqual(updateEvent.key, key1);\n\t\t\t\t\t\tassert.deepStrictEqual(updateEvent.value, value1);\n\t\t\t\t\t}\n\t\t\t\t\tfor (const updateEvent of key2UpdateEvents) {\n\t\t\t\t\t\tassert.strictEqual(updateEvent.attendeeId, attendee1Id);\n\t\t\t\t\t\tassert.strictEqual(updateEvent.key, key2);\n\t\t\t\t\t\tassert.deepStrictEqual(updateEvent.value, value2);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Read key1 of attendee0 from all children\n\t\t\t\t\tfor (const child of children) {\n\t\t\t\t\t\tchild.send({\n\t\t\t\t\t\t\tcommand: \"getLatestMapValue\",\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tkey: key1,\n\t\t\t\t\t\t\tattendeeId: attendee0Id,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst key1Responses = await getLatestMapValueResponses(\n\t\t\t\t\t\tchildren,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tkey1,\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\tgetStateTimeoutMs,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Read key2 of attendee1 from all children\n\t\t\t\t\tfor (const child of children) {\n\t\t\t\t\t\tchild.send({\n\t\t\t\t\t\t\tcommand: \"getLatestMapValue\",\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tkey: key2,\n\t\t\t\t\t\t\tattendeeId: attendee1Id,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst key2Responses = await getLatestMapValueResponses(\n\t\t\t\t\t\tchildren,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tkey2,\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\tgetStateTimeoutMs,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Verify\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tkey1Responses.length,\n\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\t\"Expected responses from all clients for key1\",\n\t\t\t\t\t);\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tkey2Responses.length,\n\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\t\"Expected responses from all clients for key2\",\n\t\t\t\t\t);\n\n\t\t\t\t\tfor (const response of key1Responses) {\n\t\t\t\t\t\tassert.deepStrictEqual(response.value, value1, \"Key1 value should match\");\n\t\t\t\t\t}\n\t\t\t\t\tfor (const response of key2Responses) {\n\t\t\t\t\t\tassert.deepStrictEqual(response.value, value2, \"Key2 value should match\");\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n});\n"]}
1
+ {"version":3,"file":"presenceTest.spec.js","sourceRoot":"","sources":["../../../src/test/multiprocess/presenceTest.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { strict as assert } from \"node:assert\";\nimport type { ChildProcess } from \"node:child_process\";\nimport inspector from \"node:inspector\";\n\nimport type { AttendeeId } from \"@fluidframework/presence/beta\";\nimport { timeoutAwait, timeoutPromise } from \"@fluidframework/test-utils/internal\";\n\nimport type { MessageFromChild } from \"./messageTypes.js\";\nimport {\n\tconnectAndListenForAttendees,\n\tconnectAndWaitForAttendees,\n\tconnectChildProcesses,\n\texecuteDebugReports,\n\tforkChildProcesses,\n\tgetLatestMapValueResponses,\n\tgetLatestValueResponses,\n\tregisterWorkspaceOnChildren,\n\ttestConsole,\n\twaitForLatestMapValueUpdates,\n\twaitForLatestValueUpdates,\n} from \"./orchestratorUtils.js\";\n\n/**\n * When true, slower (long running time) tests will be run.\n * Otherwise, those test will not appear. Console output is used to show that\n * they exist. (They could be skipped, though skipped test are often an\n * indication of a problem.)\n */\nconst shouldRunScaleTests = process.env.FLUID_TEST_SCALE !== undefined;\n\nconst useAzure = process.env.FLUID_CLIENT === \"azure\";\n\n/**\n * Detects if the debugger is attached (when code loaded).\n */\nconst debuggerAttached = inspector.url() !== undefined;\n\n/**\n * Set this to a high number when debugging to avoid timeouts from debugging time.\n */\nconst timeoutMultiplier = debuggerAttached ? 1000 : useAzure ? 5 : 1;\n\n/**\n * Sets the timeout for the given test context.\n *\n * @remarks\n * If a debugger is attached, the timeout is set to 0 to prevent timeouts during debugging.\n * Otherwise, it sets the timeout to the maximum of the current timeout and the specified duration.\n *\n * @param context - The Mocha test context.\n * @param duration - The duration in milliseconds to set the timeout to. Zero disables the timeout.\n */\nfunction setTestTimeout(context: Mocha.Context, duration: number): void {\n\tconst currentTimeout = context.timeout();\n\tconst newTimeout =\n\t\tdebuggerAttached || currentTimeout === 0 || duration === 0\n\t\t\t? 0\n\t\t\t: Math.max(currentTimeout, duration);\n\tif (newTimeout !== currentTimeout) {\n\t\ttestConsole.log(\n\t\t\t`${context.test?.title}: setting timeout to ${newTimeout}ms (was ${currentTimeout}ms)`,\n\t\t);\n\t\tcontext.timeout(newTimeout);\n\t}\n}\n\n/**\n * This test suite is a prototype for a multi-process end to end test for Fluid using the new Presence API on AzureClient.\n * In the future we hope to expand and generalize this pattern to broadly test more Fluid features.\n * Other E2E tests are limited to running multiple clients on a single process which does not effectively\n * simulate real-world production scenarios where clients are usually running on different machines. Since\n * the Fluid Framework client is designed to carry most of the work burden, multi-process testing from a\n * single machine is also not representative but does at least work past some limitations of a single\n * Node.js process handling multiple clients.\n *\n * The pattern demonstrated in this test suite is as follows:\n *\n * This main test file acts as the 'Orchestrator'. The orchestrator's job includes:\n * - Fork child processes to simulate multiple Fluid clients\n * - Send command messages to child clients to perform specific Fluid actions.\n * - Receive response messages from child clients to verify expected behavior.\n * - Clean up child processes after each test.\n *\n * The child processes are located in the `childClient.tool.ts` file. Each child process simulates a Fluid client.\n *\n * The child client's job includes:\n * - Create/Get + connect to Fluid container.\n * - Listen for command messages from the orchestrator.\n * - Perform the requested action.\n * - Send response messages including any relevant data back to the orchestrator to verify expected behavior.\n */\n\ndescribe(`Presence with AzureClient`, () => {\n\tconst afterCleanUp: (() => void)[] = [];\n\n\t// After each test, call any cleanup functions that were registered (kill each child process)\n\tafterEach(async () => {\n\t\tfor (const cleanUp of afterCleanUp) {\n\t\t\tcleanUp();\n\t\t}\n\t\tafterCleanUp.length = 0;\n\t});\n\n\tdescribe(\"`attendees` support\", () => {\n\t\tconst numClientsForAttendeeTests = [5, 40, 100, 250];\n\t\tfor (const numClients of numClientsForAttendeeTests) {\n\t\t\tif (numClients > 50 && !shouldRunScaleTests) {\n\t\t\t\ttestConsole.log(\n\t\t\t\t\t`skipping Presence attendee scale tests with ${numClients} clients (set FLUID_TEST_SCALE=true to run)`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tassert(numClients > 1, \"Must have at least two clients\");\n\t\t\t/**\n\t\t\t * Timeout for child processes to connect to container ({@link ConnectedEvent})\n\t\t\t */\n\t\t\tconst childConnectTimeoutMs = 1000 * numClients * timeoutMultiplier;\n\t\t\t/**\n\t\t\t * Timeout for presence attendees to join per first child perspective {@link AttendeeConnectedEvent}\n\t\t\t */\n\t\t\tconst allAttendeesJoinedTimeoutMs = (1000 + 200 * numClients) * timeoutMultiplier;\n\t\t\t/**\n\t\t\t * Timeout for presence attendees to fully join (everyone knows about everyone) {@link AttendeeConnectedEvent}\n\t\t\t */\n\t\t\tconst allAttendeesFullyJoinedTimeoutMs = (2000 + 300 * numClients) * timeoutMultiplier;\n\n\t\t\tfor (const writeClients of [numClients, 1]) {\n\t\t\t\tit(`announces 'attendeeConnected' when remote client joins session [${numClients} clients, ${writeClients} writers]`, async function testAnnouncesAttendeeConnected() {\n\t\t\t\t\tsetTestTimeout(this, childConnectTimeoutMs + allAttendeesJoinedTimeoutMs + 1000);\n\n\t\t\t\t\t// Setup\n\t\t\t\t\tconst { children, childErrorPromise } = await forkChildProcesses(\n\t\t\t\t\t\tthis.test?.title ?? \"\",\n\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\tafterCleanUp,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Further Setup with Act and Verify\n\t\t\t\t\tawait connectAndWaitForAttendees(\n\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\twriteClients,\n\t\t\t\t\t\t\tattendeeCountRequired: numClients - 1,\n\t\t\t\t\t\t\tchildConnectTimeoutMs,\n\t\t\t\t\t\t\tallAttendeesJoinedTimeoutMs,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t);\n\t\t\t\t});\n\n\t\t\t\tit(`announces 'attendeeDisconnected' when remote client disconnects [${numClients} clients, ${writeClients} writers]`, async function testAnnouncesAttendeeDisconnected() {\n\t\t\t\t\tif (useAzure && numClients > 50) {\n\t\t\t\t\t\t// Even with increased timeouts, more than 50 clients can be too large for AFR.\n\t\t\t\t\t\t// This may be due to slow responses/inactivity from the clients that are\n\t\t\t\t\t\t// creating pressure on ADO agent.\n\t\t\t\t\t\tthis.skip();\n\t\t\t\t\t}\n\n\t\t\t\t\tconst childDisconnectTimeoutMs = 10_000 * timeoutMultiplier;\n\n\t\t\t\t\tsetTestTimeout(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\tchildConnectTimeoutMs +\n\t\t\t\t\t\t\tallAttendeesFullyJoinedTimeoutMs +\n\t\t\t\t\t\t\tchildDisconnectTimeoutMs +\n\t\t\t\t\t\t\t1000,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Setup\n\t\t\t\t\tconst { children, childErrorPromise } = await forkChildProcesses(\n\t\t\t\t\t\tthis.test?.title ?? \"\",\n\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\tafterCleanUp,\n\t\t\t\t\t);\n\n\t\t\t\t\tconst startConnectAndFullJoin = performance.now();\n\t\t\t\t\tconst connectResult = await connectAndListenForAttendees(children, {\n\t\t\t\t\t\twriteClients,\n\t\t\t\t\t\tattendeeCountRequired: numClients - 1,\n\t\t\t\t\t\tchildConnectTimeoutMs,\n\t\t\t\t\t});\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-floating-promises\n\t\t\t\t\tconnectResult.attendeeCountRequiredPromises[0].then(() =>\n\t\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t\t`[${new Date().toISOString()}] All attendees joined per child 0 after ${performance.now() - startConnectAndFullJoin}ms`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\n\t\t\t\t\t// Wait for all attendees to be fully joined\n\t\t\t\t\t// Keep a tally for debuggability\n\t\t\t\t\tlet childrenFullyJoined = 0;\n\t\t\t\t\tconst setNotFullyJoined = new Set<number>();\n\t\t\t\t\tfor (let i = 0; i < children.length; i++) {\n\t\t\t\t\t\tsetNotFullyJoined.add(i);\n\t\t\t\t\t}\n\t\t\t\t\tconst allAttendeesFullyJoined = Promise.all(\n\t\t\t\t\t\tconnectResult.attendeeCountRequiredPromises.map(\n\t\t\t\t\t\t\tasync (attendeeFullyJoinedPromise, index) => {\n\t\t\t\t\t\t\t\tawait attendeeFullyJoinedPromise;\n\t\t\t\t\t\t\t\tchildrenFullyJoined++;\n\t\t\t\t\t\t\t\tsetNotFullyJoined.delete(index);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tlet timedout = true;\n\t\t\t\t\tconst allFullyJoinedOrChildError = Promise.race([\n\t\t\t\t\t\tallAttendeesFullyJoined,\n\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t]).finally(() => (timedout = false));\n\t\t\t\t\tawait timeoutAwait(allFullyJoinedOrChildError, {\n\t\t\t\t\t\tdurationMs: allAttendeesFullyJoinedTimeoutMs,\n\t\t\t\t\t\terrorMsg: \"Not all attendees fully joined\",\n\t\t\t\t\t}).catch(async (error) => {\n\t\t\t\t\t\t// Ideally this information would just be in the timeout error message, but that\n\t\t\t\t\t\t// must be a resolved string (not dynamic). So, just log it separately.\n\t\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t\t`[${new Date().toISOString()}] ${childrenFullyJoined} attendees fully joined before error...`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (timedout) {\n\t\t\t\t\t\t\t// Gather additional timing data if timed out to understand what increased\n\t\t\t\t\t\t\t// timeout could work. Test will still fail if this secondary wait succeeds.\n\t\t\t\t\t\t\tconst startAdditionalWait = performance.now();\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait timeoutAwait(allFullyJoinedOrChildError, {\n\t\t\t\t\t\t\t\t\tdurationMs: allAttendeesFullyJoinedTimeoutMs,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t\t\t\t`[${new Date().toISOString()}] All attendees fully joined after additional wait (${performance.now() - startAdditionalWait}ms)`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} catch (secondaryError) {\n\t\t\t\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t\t\t\t`[${new Date().toISOString()}] Secondary await resulted in`,\n\t\t\t\t\t\t\t\t\tsecondaryError,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Gather and report debug info from children\n\t\t\t\t\t\t// If there are less than 10 children, get all reports.\n\t\t\t\t\t\t// Otherwise, just child 0 and those not fully joined.\n\t\t\t\t\t\tsetTestTimeout(this, 0); // Disable test timeout. Will throw within 20s below.\n\t\t\t\t\t\tconst childrenRequestedToReport =\n\t\t\t\t\t\t\tchildren.length <= 10\n\t\t\t\t\t\t\t\t? children\n\t\t\t\t\t\t\t\t: // Just those not fully joined\n\t\t\t\t\t\t\t\t\tchildren.filter((_, index) => index === 0 || setNotFullyJoined.has(index));\n\t\t\t\t\t\tawait timeoutAwait(\n\t\t\t\t\t\t\tPromise.race([\n\t\t\t\t\t\t\t\texecuteDebugReports(childrenRequestedToReport),\n\t\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t{ durationMs: 20_000, errorMsg: \"Debug report timeout\" },\n\t\t\t\t\t\t).catch((debugAwaitError) => {\n\t\t\t\t\t\t\ttestConsole.error(\"Debug report await resulted in\", debugAwaitError);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t});\n\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t`[${new Date().toISOString()}] All attendees fully joined after ${performance.now() - startConnectAndFullJoin}ms`,\n\t\t\t\t\t);\n\n\t\t\t\t\tlet child0ReportRequested = false;\n\t\t\t\t\tconst waitForDisconnected = children.map(async (child, index) =>\n\t\t\t\t\t\tindex === 0\n\t\t\t\t\t\t\t? Promise.resolve()\n\t\t\t\t\t\t\t: timeoutPromise(\n\t\t\t\t\t\t\t\t\t(resolve) => {\n\t\t\t\t\t\t\t\t\t\tchild.on(\"message\", (msg: MessageFromChild) => {\n\t\t\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t\t\tmsg.event === \"attendeeDisconnected\" &&\n\t\t\t\t\t\t\t\t\t\t\t\tmsg.attendeeId === connectResult.containerCreatorAttendeeId\n\t\t\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\t\t\tconsole.log(`Child[${index}] saw creator disconnect`);\n\t\t\t\t\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tdurationMs: childDisconnectTimeoutMs,\n\t\t\t\t\t\t\t\t\t\terrorMsg: `Attendee[${index}] Disconnected Timeout`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t).catch(async (error) => {\n\t\t\t\t\t\t\t\t\tconst childrenRequestedToReport = [child];\n\t\t\t\t\t\t\t\t\tif (!child0ReportRequested) {\n\t\t\t\t\t\t\t\t\t\tchildrenRequestedToReport.unshift(children[0]);\n\t\t\t\t\t\t\t\t\t\tchild0ReportRequested = true;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tawait timeoutAwait(\n\t\t\t\t\t\t\t\t\t\tPromise.race([\n\t\t\t\t\t\t\t\t\t\t\texecuteDebugReports(childrenRequestedToReport),\n\t\t\t\t\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t\t{ durationMs: 20_000, errorMsg: \"Debug report timeout\" },\n\t\t\t\t\t\t\t\t\t).catch((debugAwaitError) => {\n\t\t\t\t\t\t\t\t\t\ttestConsole.error(\"Debug report await resulted in\", debugAwaitError);\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\t// Act - disconnect first child process\n\t\t\t\t\tchildren[0].send({ command: \"disconnectSelf\" });\n\n\t\t\t\t\t// Verify - wait for all 'attendeeDisconnected' events\n\t\t\t\t\tawait Promise.race([Promise.all(waitForDisconnected), childErrorPromise]);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t});\n\n\t{\n\t\t/**\n\t\t * Timeout for workspace registration {@link WorkspaceRegisteredEvent}\n\t\t */\n\t\tconst workspaceRegisterTimeoutMs = 5000;\n\t\t/**\n\t\t * Timeout for presence update events {@link LatestMapValueUpdatedEvent} and {@link LatestValueUpdatedEvent}\n\t\t */\n\t\tconst stateUpdateTimeoutMs = 5000;\n\t\t/**\n\t\t * Timeout for {@link LatestMapValueGetResponseEvent} and {@link LatestValueGetResponseEvent}\n\t\t */\n\t\tconst getStateTimeoutMs = 5000;\n\n\t\t// This test suite focuses on the synchronization of Latest state between clients.\n\t\t// NOTE: For testing purposes child clients will expect a Latest value of type string.\n\t\tdescribe(`using Latest state object`, () => {\n\t\t\tfor (const numClients of [5, 20]) {\n\t\t\t\tassert(numClients > 1, \"Must have at least two clients\");\n\t\t\t\t/**\n\t\t\t\t * Timeout for child processes to connect to container ({@link ConnectedEvent})\n\t\t\t\t */\n\t\t\t\tconst childConnectTimeoutMs = (4000 + 1000 * numClients) * timeoutMultiplier;\n\t\t\t\tconst testCaseTimeoutMs = 1000;\n\t\t\t\tconst testSetupAndActTimeoutMs = childConnectTimeoutMs + testCaseTimeoutMs;\n\n\t\t\t\t// These tests use beforeEach to setup complex state that takes a lot of time\n\t\t\t\t// and is dependent on number of clients. Keeping the work in beforeEach\n\t\t\t\t// allows time reporting to report the tested scenario apart from the setup time.\n\t\t\t\t// So this describe block isolates those beforeEach setups from each distinct\n\t\t\t\t// client count. Test cases descriptions also have the client count for clarity.\n\t\t\t\tdescribe(`with ${numClients} clients`, () => {\n\t\t\t\t\tlet children: ChildProcess[];\n\t\t\t\t\tlet childErrorPromise: Promise<never>;\n\t\t\t\t\tlet containerCreatorAttendeeId: AttendeeId;\n\t\t\t\t\tlet attendeeIdPromises: Promise<AttendeeId>[];\n\t\t\t\t\tlet remoteClients: ChildProcess[];\n\t\t\t\t\tconst testValue = \"testValue\";\n\t\t\t\t\tconst workspaceId = \"presenceTestWorkspace\";\n\n\t\t\t\t\tbeforeEach(async function usingLatestStateObject_beforeEach(): Promise<void> {\n\t\t\t\t\t\tconst startTime = performance.now();\n\t\t\t\t\t\tsetTestTimeout(this, testSetupAndActTimeoutMs);\n\n\t\t\t\t\t\t({ children, childErrorPromise } = await forkChildProcesses(\n\t\t\t\t\t\t\tthis.currentTest?.title ?? \"\",\n\t\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\t\tafterCleanUp,\n\t\t\t\t\t\t));\n\t\t\t\t\t\t({ containerCreatorAttendeeId, attendeeIdPromises } = await connectChildProcesses(\n\t\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t\t{ writeClients: numClients, readyTimeoutMs: childConnectTimeoutMs },\n\t\t\t\t\t\t));\n\t\t\t\t\t\tawait Promise.all(attendeeIdPromises);\n\t\t\t\t\t\tremoteClients = children.filter((_, index) => index !== 0);\n\t\t\t\t\t\t// NOTE: For testing purposes child clients will expect a Latest value of type string (StateFactory.latest<{ value: string }>).\n\t\t\t\t\t\tawait registerWorkspaceOnChildren(children, workspaceId, {\n\t\t\t\t\t\t\tlatest: true,\n\t\t\t\t\t\t\ttimeoutMs: workspaceRegisterTimeoutMs,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t\t` Setup for \"${this.currentTest?.title}\" completed in ${performance.now() - startTime}ms`,\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\n\t\t\t\t\tit(`allows clients to read Latest state from other clients [${numClients} clients]`, async function () {\n\t\t\t\t\t\t// Setup\n\t\t\t\t\t\tconst updateEventsPromise = waitForLatestValueUpdates(\n\t\t\t\t\t\t\tremoteClients,\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\tstateUpdateTimeoutMs,\n\t\t\t\t\t\t\t{ fromAttendeeId: containerCreatorAttendeeId, expectedValue: testValue },\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// Act - Trigger the update\n\t\t\t\t\t\tchildren[0].send({\n\t\t\t\t\t\t\tcommand: \"setLatestValue\",\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tvalue: testValue,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst updateEvents = await updateEventsPromise;\n\n\t\t\t\t\t\t// Verify all events are from the expected attendee\n\t\t\t\t\t\tfor (const updateEvent of updateEvents) {\n\t\t\t\t\t\t\tassert.strictEqual(updateEvent.attendeeId, containerCreatorAttendeeId);\n\t\t\t\t\t\t\tassert.deepStrictEqual(updateEvent.value, testValue);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Act - Request each remote client to read latest state from container creator\n\t\t\t\t\t\tfor (const child of remoteClients) {\n\t\t\t\t\t\t\tchild.send({\n\t\t\t\t\t\t\t\tcommand: \"getLatestValue\",\n\t\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\t\tattendeeId: containerCreatorAttendeeId,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst getResponses = await getLatestValueResponses(\n\t\t\t\t\t\t\tremoteClients,\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\tgetStateTimeoutMs,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// Verify - all responses should contain the expected value\n\t\t\t\t\t\tfor (const getResponse of getResponses) {\n\t\t\t\t\t\t\tassert.deepStrictEqual(getResponse.value, testValue);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t// This test suite focuses on the synchronization of LatestMap state between clients.\n\t\t// NOTE: For testing purposes child clients will expect a LatestMap value of type Record<string, string | number>.\n\t\tdescribe(`using LatestMap state object`, () => {\n\t\t\tfor (const numClients of [5, 20]) {\n\t\t\t\tassert(numClients > 1, \"Must have at least two clients\");\n\t\t\t\t/**\n\t\t\t\t * Timeout for child processes to connect to container ({@link ConnectedEvent})\n\t\t\t\t */\n\t\t\t\tconst childConnectTimeoutMs = (4000 + 1000 * numClients) * timeoutMultiplier;\n\t\t\t\tconst testCaseTimeoutMs = 1000;\n\t\t\t\tconst testSetupAndActTimeoutMs = childConnectTimeoutMs + testCaseTimeoutMs;\n\n\t\t\t\t// These tests use beforeEach to setup complex state that takes a lot of time\n\t\t\t\t// and is dependent on number of clients. Keeping the work in beforeEach\n\t\t\t\t// allows time reporting to report the tested scenario apart from the setup time.\n\t\t\t\t// So this describe block isolates those beforeEach setups from each distinct\n\t\t\t\t// client count. Test cases descriptions also have the client count for clarity.\n\t\t\t\tdescribe(`with ${numClients} clients`, () => {\n\t\t\t\t\tlet children: ChildProcess[];\n\t\t\t\t\tlet childErrorPromise: Promise<never>;\n\t\t\t\t\tlet containerCreatorAttendeeId: AttendeeId;\n\t\t\t\t\tlet attendeeIdPromises: Promise<AttendeeId>[];\n\t\t\t\t\tlet remoteClients: ChildProcess[];\n\t\t\t\t\tconst workspaceId = \"presenceTestWorkspace\";\n\t\t\t\t\tconst key1 = \"player1\";\n\t\t\t\t\tconst key2 = \"player2\";\n\t\t\t\t\tconst value1 = { name: \"Alice\", score: 100 };\n\t\t\t\t\tconst value2 = { name: \"Bob\", score: 200 };\n\n\t\t\t\t\tbeforeEach(async function usingLatestMapStateObject_beforeEach(): Promise<void> {\n\t\t\t\t\t\tconst startTime = performance.now();\n\n\t\t\t\t\t\tsetTestTimeout(this, testSetupAndActTimeoutMs);\n\n\t\t\t\t\t\t({ children, childErrorPromise } = await forkChildProcesses(\n\t\t\t\t\t\t\tthis.currentTest?.title ?? \"\",\n\t\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\t\tafterCleanUp,\n\t\t\t\t\t\t));\n\t\t\t\t\t\t({ containerCreatorAttendeeId, attendeeIdPromises } = await connectChildProcesses(\n\t\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t\t{ writeClients: numClients, readyTimeoutMs: childConnectTimeoutMs },\n\t\t\t\t\t\t));\n\t\t\t\t\t\tawait Promise.all(attendeeIdPromises);\n\t\t\t\t\t\tremoteClients = children.filter((_, index) => index !== 0);\n\t\t\t\t\t\t// NOTE: For testing purposes child clients will expect a LatestMap value of type Record<string, string | number> (StateFactory.latestMap<{ value: Record<string, string | number> }, string>).\n\t\t\t\t\t\tawait registerWorkspaceOnChildren(children, workspaceId, {\n\t\t\t\t\t\t\tlatestMap: true,\n\t\t\t\t\t\t\ttimeoutMs: workspaceRegisterTimeoutMs,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\ttestConsole.log(\n\t\t\t\t\t\t\t` Setup for \"${this.currentTest?.title}\" completed in ${performance.now() - startTime}ms`,\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\n\t\t\t\t\tit(`allows clients to read LatestMap values from other clients [${numClients} clients]`, async () => {\n\t\t\t\t\t\t// Setup\n\t\t\t\t\t\tconst testKey = \"cursor\";\n\t\t\t\t\t\tconst testValue = { x: 150, y: 300 };\n\t\t\t\t\t\tconst updateEventsPromise = waitForLatestMapValueUpdates(\n\t\t\t\t\t\t\tremoteClients,\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\ttestKey,\n\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\tstateUpdateTimeoutMs,\n\t\t\t\t\t\t\t{ fromAttendeeId: containerCreatorAttendeeId, expectedValue: testValue },\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// Act\n\t\t\t\t\t\tchildren[0].send({\n\t\t\t\t\t\t\tcommand: \"setLatestMapValue\",\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tkey: testKey,\n\t\t\t\t\t\t\tvalue: testValue,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst updateEvents = await updateEventsPromise;\n\n\t\t\t\t\t\t// Check all events are from the expected attendee\n\t\t\t\t\t\tfor (const updateEvent of updateEvents) {\n\t\t\t\t\t\t\tassert.strictEqual(updateEvent.attendeeId, containerCreatorAttendeeId);\n\t\t\t\t\t\t\tassert.strictEqual(updateEvent.key, testKey);\n\t\t\t\t\t\t\tassert.deepStrictEqual(updateEvent.value, testValue);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (const child of remoteClients) {\n\t\t\t\t\t\t\tchild.send({\n\t\t\t\t\t\t\t\tcommand: \"getLatestMapValue\",\n\t\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\t\tkey: testKey,\n\t\t\t\t\t\t\t\tattendeeId: containerCreatorAttendeeId,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst getResponses = await getLatestMapValueResponses(\n\t\t\t\t\t\t\tremoteClients,\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\ttestKey,\n\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\tgetStateTimeoutMs,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// Verify\n\t\t\t\t\t\tfor (const getResponse of getResponses) {\n\t\t\t\t\t\t\tassert.deepStrictEqual(getResponse.value, testValue);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tit(`returns per-key values on read [${numClients} clients]`, async function () {\n\t\t\t\t\t\t// Setup\n\t\t\t\t\t\tconst allAttendeeIds = await Promise.all(attendeeIdPromises);\n\t\t\t\t\t\tconst attendee0Id = containerCreatorAttendeeId;\n\t\t\t\t\t\tconst attendee1Id = allAttendeeIds[1];\n\n\t\t\t\t\t\tconst key1Recipients = children.filter((_, index) => index !== 0);\n\t\t\t\t\t\tconst key2Recipients = children.filter((_, index) => index !== 1);\n\t\t\t\t\t\tconst key1UpdateEventsPromise = waitForLatestMapValueUpdates(\n\t\t\t\t\t\t\tkey1Recipients,\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tkey1,\n\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\tstateUpdateTimeoutMs,\n\t\t\t\t\t\t\t{ fromAttendeeId: attendee0Id, expectedValue: value1 },\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst key2UpdateEventsPromise = waitForLatestMapValueUpdates(\n\t\t\t\t\t\t\tkey2Recipients,\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tkey2,\n\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\tstateUpdateTimeoutMs,\n\t\t\t\t\t\t\t{ fromAttendeeId: attendee1Id, expectedValue: value2 },\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// Act\n\t\t\t\t\t\tchildren[0].send({\n\t\t\t\t\t\t\tcommand: \"setLatestMapValue\",\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tkey: key1,\n\t\t\t\t\t\t\tvalue: value1,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst key1UpdateEvents = await key1UpdateEventsPromise;\n\t\t\t\t\t\tchildren[1].send({\n\t\t\t\t\t\t\tcommand: \"setLatestMapValue\",\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tkey: key2,\n\t\t\t\t\t\t\tvalue: value2,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst key2UpdateEvents = await key2UpdateEventsPromise;\n\n\t\t\t\t\t\t// Verify all events are from the expected attendees\n\t\t\t\t\t\tfor (const updateEvent of key1UpdateEvents) {\n\t\t\t\t\t\t\tassert.strictEqual(updateEvent.attendeeId, attendee0Id);\n\t\t\t\t\t\t\tassert.strictEqual(updateEvent.key, key1);\n\t\t\t\t\t\t\tassert.deepStrictEqual(updateEvent.value, value1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const updateEvent of key2UpdateEvents) {\n\t\t\t\t\t\t\tassert.strictEqual(updateEvent.attendeeId, attendee1Id);\n\t\t\t\t\t\t\tassert.strictEqual(updateEvent.key, key2);\n\t\t\t\t\t\t\tassert.deepStrictEqual(updateEvent.value, value2);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Read key1 of attendee0 from all children\n\t\t\t\t\t\tfor (const child of children) {\n\t\t\t\t\t\t\tchild.send({\n\t\t\t\t\t\t\t\tcommand: \"getLatestMapValue\",\n\t\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\t\tkey: key1,\n\t\t\t\t\t\t\t\tattendeeId: attendee0Id,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst key1Responses = await getLatestMapValueResponses(\n\t\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tkey1,\n\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\tgetStateTimeoutMs,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// Read key2 of attendee1 from all children\n\t\t\t\t\t\tfor (const child of children) {\n\t\t\t\t\t\t\tchild.send({\n\t\t\t\t\t\t\t\tcommand: \"getLatestMapValue\",\n\t\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\t\tkey: key2,\n\t\t\t\t\t\t\t\tattendeeId: attendee1Id,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst key2Responses = await getLatestMapValueResponses(\n\t\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\t\tkey2,\n\t\t\t\t\t\t\tchildErrorPromise,\n\t\t\t\t\t\t\tgetStateTimeoutMs,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// Verify\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tkey1Responses.length,\n\t\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\t\t\"Expected responses from all clients for key1\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tkey2Responses.length,\n\t\t\t\t\t\t\tnumClients,\n\t\t\t\t\t\t\t\"Expected responses from all clients for key2\",\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tfor (const response of key1Responses) {\n\t\t\t\t\t\t\tassert.deepStrictEqual(response.value, value1, \"Key1 value should match\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const response of key2Responses) {\n\t\t\t\t\t\t\tassert.deepStrictEqual(response.value, value2, \"Key2 value should match\");\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n});\n"]}