@fluidframework/azure-end-to-end-tests 2.60.0 → 2.61.0-355054

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.
@@ -110,6 +110,18 @@ function createSendFunction() {
110
110
  throw new Error("process.send is not defined");
111
111
  }
112
112
  const send = createSendFunction();
113
+ function sendAttendeeConnected(attendee) {
114
+ send({
115
+ event: "attendeeConnected",
116
+ attendeeId: attendee.attendeeId,
117
+ });
118
+ }
119
+ function sendAttendeeDisconnected(attendee) {
120
+ send({
121
+ event: "attendeeDisconnected",
122
+ attendeeId: attendee.attendeeId,
123
+ });
124
+ }
113
125
  function isConnected(container) {
114
126
  return container !== undefined && container.connectionState === ConnectionState.Connected;
115
127
  }
@@ -264,17 +276,22 @@ class MessageHandler {
264
276
  this.container = container;
265
277
  this.presence = presence;
266
278
  this.containerId = containerId;
267
- presence.attendees.events.on("attendeeConnected", (attendee) => {
268
- send({ event: "attendeeConnected", attendeeId: attendee.attendeeId });
269
- });
270
- presence.attendees.events.on("attendeeDisconnected", (attendee) => {
271
- send({ event: "attendeeDisconnected", attendeeId: attendee.attendeeId });
272
- });
279
+ // Acknowledge connection before sending current attendee information
273
280
  send({
274
281
  event: "connected",
275
282
  containerId,
276
283
  attendeeId: presence.attendees.getMyself().attendeeId,
277
284
  });
285
+ // Send existing attendees excluding self to parent/orchestrator
286
+ const self = presence.attendees.getMyself();
287
+ for (const attendee of presence.attendees.getAttendees()) {
288
+ if (attendee !== self) {
289
+ sendAttendeeConnected(attendee);
290
+ }
291
+ }
292
+ // Listen for presence events to notify parent/orchestrator when a new attendee joins or leaves the session.
293
+ presence.attendees.events.on("attendeeConnected", sendAttendeeConnected);
294
+ presence.attendees.events.on("attendeeDisconnected", sendAttendeeDisconnected);
278
295
  }
279
296
  handleDisconnectSelf() {
280
297
  if (!this.container) {
@@ -434,4 +451,4 @@ function setupMessageHandler() {
434
451
  });
435
452
  }
436
453
  setupMessageHandler();
437
- //# sourceMappingURL=childClient.js.map
454
+ //# sourceMappingURL=childClient.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"childClient.tool.js","sourceRoot":"","sources":["../../../src/test/multiprocess/childClient.tool.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\";\n\nimport {\n\tAzureClient,\n\ttype AzureContainerServices,\n\ttype AzureLocalConnectionConfig,\n\ttype AzureRemoteConnectionConfig,\n\ttype ITelemetryBaseEvent,\n} from \"@fluidframework/azure-client\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { LogLevel } from \"@fluidframework/core-interfaces\";\nimport type { ScopeType } from \"@fluidframework/driver-definitions/legacy\";\nimport type { ContainerSchema, IFluidContainer } from \"@fluidframework/fluid-static\";\nimport {\n\tgetPresence,\n\ttype Attendee,\n\ttype Presence,\n\tStateFactory,\n\ttype LatestRaw,\n\ttype LatestMapRaw,\n\ttype StatesWorkspace,\n} from \"@fluidframework/presence/beta\";\nimport { InsecureTokenProvider } from \"@fluidframework/test-runtime-utils/internal\";\nimport { timeoutPromise } from \"@fluidframework/test-utils/internal\";\n\nimport { createAzureTokenProvider } from \"../AzureTokenFactory.js\";\nimport { TestDataObject } from \"../TestDataObject.js\";\n\nimport type { MessageFromChild, MessageToChild, UserIdAndName } from \"./messageTypes.js\";\n\ntype MessageFromParent = MessageToChild;\ntype MessageToParent = Required<MessageFromChild>;\nconst connectTimeoutMs = 10_000;\n// Identifier given to child process\nconst process_id = process.argv[2];\nconst verbosity = process.argv[3] ?? \"\";\n\nconst useAzure = process.env.FLUID_CLIENT === \"azure\";\nconst tenantId = useAzure\n\t? (process.env.azure__fluid__relay__service__tenantId as string)\n\t: \"frs-client-tenant\";\nconst endPoint = process.env.azure__fluid__relay__service__endpoint as string;\nif (useAzure && endPoint === undefined) {\n\tthrow new Error(\"Azure Fluid Relay service endpoint is missing\");\n}\n\nfunction selectiveVerboseLog(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {\n\tif (event.eventName.includes(\":Signal\") || event.eventName.includes(\":Join\")) {\n\t\tconsole.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {\n\t\t\teventName: event.eventName,\n\t\t\tdetails: event.details,\n\t\t\tcontainerConnectionState: event.containerConnectionState,\n\t\t});\n\t} else if (\n\t\tevent.eventName.includes(\":Container:\") ||\n\t\tevent.eventName.includes(\":Presence:\")\n\t) {\n\t\tconsole.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {\n\t\t\teventName: event.eventName,\n\t\t\tcontainerConnectionState: event.containerConnectionState,\n\t\t});\n\t}\n}\n\n/**\n * Get or create a Fluid container with Presence in initialObjects.\n */\nconst getOrCreatePresenceContainer = async (\n\tid: string | undefined,\n\tuser: UserIdAndName,\n\tscopes?: ScopeType[],\n\tcreateScopes?: ScopeType[],\n): Promise<{\n\tcontainer: IFluidContainer;\n\tpresence: Presence;\n\tservices: AzureContainerServices;\n\tclient: AzureClient;\n\tcontainerId: string;\n}> => {\n\tlet container: IFluidContainer;\n\tlet containerId: string;\n\tconst connectionProps: AzureRemoteConnectionConfig | AzureLocalConnectionConfig = useAzure\n\t\t? {\n\t\t\t\ttenantId,\n\t\t\t\ttokenProvider: createAzureTokenProvider(\n\t\t\t\t\tuser.id ?? \"foo\",\n\t\t\t\t\tuser.name ?? \"bar\",\n\t\t\t\t\tscopes,\n\t\t\t\t\tcreateScopes,\n\t\t\t\t),\n\t\t\t\tendpoint: endPoint,\n\t\t\t\ttype: \"remote\",\n\t\t\t}\n\t\t: {\n\t\t\t\ttokenProvider: new InsecureTokenProvider(\"fooBar\", user, scopes, createScopes),\n\t\t\t\tendpoint: \"http://localhost:7071\",\n\t\t\t\ttype: \"local\",\n\t\t\t};\n\tconst client = new AzureClient({\n\t\tconnection: connectionProps,\n\t\tlogger: {\n\t\t\tsend: verbosity.includes(\"telem\") ? selectiveVerboseLog : () => {},\n\t\t},\n\t});\n\tconst schema: ContainerSchema = {\n\t\tinitialObjects: {\n\t\t\t// A DataObject is added as otherwise fluid-static complains \"Container cannot be initialized without any DataTypes\"\n\t\t\t_unused: TestDataObject,\n\t\t},\n\t};\n\tlet services: AzureContainerServices;\n\tif (id === undefined) {\n\t\t({ container, services } = await client.createContainer(schema, \"2\"));\n\t\tcontainerId = await container.attach();\n\t} else {\n\t\tcontainerId = id;\n\t\t({ container, services } = await client.getContainer(containerId, schema, \"2\"));\n\t}\n\t// wait for 'ConnectionState.Connected' so we return with client connected to container\n\tif (container.connectionState !== ConnectionState.Connected) {\n\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\tdurationMs: connectTimeoutMs,\n\t\t\terrorMsg: \"container connect() timeout\",\n\t\t});\n\t}\n\tassert.strictEqual(\n\t\tcontainer.attachState,\n\t\tAttachState.Attached,\n\t\t\"Container is not attached after attach is called\",\n\t);\n\n\tconst presence = getPresence(container);\n\treturn {\n\t\tclient,\n\t\tcontainer,\n\t\tpresence,\n\t\tservices,\n\t\tcontainerId,\n\t};\n};\nfunction createSendFunction(): (msg: MessageToParent) => void {\n\tif (process.send) {\n\t\tconst sendFn = process.send.bind(process);\n\t\tif (verbosity.includes(\"msgs\")) {\n\t\t\treturn (msg: MessageToParent) => {\n\t\t\t\tconsole.log(`[${process_id}] Sending`, msg);\n\t\t\t\tsendFn(msg);\n\t\t\t};\n\t\t}\n\t\treturn sendFn;\n\t}\n\tthrow new Error(\"process.send is not defined\");\n}\n\nconst send = createSendFunction();\n\nfunction sendAttendeeConnected(attendee: Attendee): void {\n\tsend({\n\t\tevent: \"attendeeConnected\",\n\t\tattendeeId: attendee.attendeeId,\n\t});\n}\nfunction sendAttendeeDisconnected(attendee: Attendee): void {\n\tsend({\n\t\tevent: \"attendeeDisconnected\",\n\t\tattendeeId: attendee.attendeeId,\n\t});\n}\n\nfunction isConnected(container: IFluidContainer | undefined): boolean {\n\treturn container !== undefined && container.connectionState === ConnectionState.Connected;\n}\n\nfunction isStringOrNumberRecord(value: unknown): value is Record<string, string | number> {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn false;\n\t}\n\n\tconst stringKeys = Object.keys(value);\n\tconst allKeys = Reflect.ownKeys(value);\n\n\tif (stringKeys.length !== allKeys.length) {\n\t\t// If there are non-string/symbol keys, return false\n\t\treturn false;\n\t}\n\tfor (const key of stringKeys) {\n\t\tif (!(typeof value[key] === \"string\" || typeof value[key] === \"number\")) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n// NOTE:\n// - This schema intentionally uses optional keys (latest?, latestMap?) so tests can register\n// states conditionally at runtime.\n// - Optional keys are not explicitly supported in StatesWorkspace typing today, which means\n// workspace.states.<key> is typed as any. As a result, usages below require casts\n// (e.g., to LatestRaw / LatestMapRaw) to recover concrete types.\n// - Track adding proper optional-key support to Presence state workspace typing here:\n// Work item: AB#47518\n// - Fallout: Until the above is addressed, keep the casts in place and document new usages accordingly.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ntype WorkspaceSchema = {\n\tlatest?: ReturnType<typeof StateFactory.latest<{ value: string }>>;\n\tlatestMap?: ReturnType<\n\t\ttypeof StateFactory.latestMap<{ value: Record<string, string | number> }, string>\n\t>;\n};\nconst WorkspaceSchema: WorkspaceSchema = {};\n\nclass MessageHandler {\n\tpublic presence: Presence | undefined;\n\tpublic container: IFluidContainer | undefined;\n\tpublic containerId: string | undefined;\n\tprivate readonly workspaces = new Map<string, StatesWorkspace<WorkspaceSchema>>();\n\n\tprivate registerWorkspace(\n\t\tworkspaceId: string,\n\t\toptions: { latest?: boolean; latestMap?: boolean },\n\t): void {\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tconst { latest, latestMap } = options;\n\t\tconst workspace: StatesWorkspace<WorkspaceSchema> = this.presence.states.getWorkspace(\n\t\t\t`test:${workspaceId}`,\n\t\t\tWorkspaceSchema,\n\t\t);\n\n\t\tif (latest && !workspace.states.latest) {\n\t\t\tworkspace.add(\n\t\t\t\t\"latest\",\n\t\t\t\tStateFactory.latest<{ value: string }>({ local: { value: \"initial\" } }),\n\t\t\t);\n\t\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t\t// TODO: AB#47518\n\t\t\tconst latestState = workspace.states.latest as LatestRaw<{ value: string }>;\n\t\t\tlatestState.events.on(\"remoteUpdated\", (update) => {\n\t\t\t\tsend({\n\t\t\t\t\tevent: \"latestValueUpdated\",\n\t\t\t\t\tworkspaceId,\n\t\t\t\t\tattendeeId: update.attendee.attendeeId,\n\t\t\t\t\tvalue: update.value.value,\n\t\t\t\t});\n\t\t\t});\n\t\t\tfor (const remote of latestState.getRemotes()) {\n\t\t\t\tsend({\n\t\t\t\t\tevent: \"latestValueUpdated\",\n\t\t\t\t\tworkspaceId,\n\t\t\t\t\tattendeeId: remote.attendee.attendeeId,\n\t\t\t\t\tvalue: remote.value.value,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (latestMap && !workspace.states.latestMap) {\n\t\t\tworkspace.add(\n\t\t\t\t\"latestMap\",\n\t\t\t\tStateFactory.latestMap<{ value: Record<string, string | number> }, string>({\n\t\t\t\t\tlocal: {},\n\t\t\t\t}),\n\t\t\t);\n\t\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t\t// TODO: AB#47518\n\t\t\tconst latestMapState = workspace.states.latestMap as LatestMapRaw<\n\t\t\t\t{ value: Record<string, string | number> },\n\t\t\t\tstring\n\t\t\t>;\n\t\t\tlatestMapState.events.on(\"remoteUpdated\", (update) => {\n\t\t\t\tfor (const [key, valueWithMetadata] of update.items) {\n\t\t\t\t\tsend({\n\t\t\t\t\t\tevent: \"latestMapValueUpdated\",\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tattendeeId: update.attendee.attendeeId,\n\t\t\t\t\t\tkey: String(key),\n\t\t\t\t\t\tvalue: valueWithMetadata.value.value,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t\tfor (const remote of latestMapState.getRemotes()) {\n\t\t\t\tfor (const [key, valueWithMetadata] of remote.items) {\n\t\t\t\t\tsend({\n\t\t\t\t\t\tevent: \"latestMapValueUpdated\",\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tattendeeId: remote.attendee.attendeeId,\n\t\t\t\t\t\tkey: String(key),\n\t\t\t\t\t\tvalue: valueWithMetadata.value.value,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.workspaces.set(workspaceId, workspace);\n\t\tsend({\n\t\t\tevent: \"workspaceRegistered\",\n\t\t\tworkspaceId,\n\t\t\tlatest: latest ?? false,\n\t\t\tlatestMap: latestMap ?? false,\n\t\t});\n\t}\n\n\tpublic async onMessage(msg: MessageFromParent): Promise<void> {\n\t\tif (verbosity.includes(\"msgs\")) {\n\t\t\tconsole.log(`[${process_id}] Received`, msg);\n\t\t}\n\t\tswitch (msg.command) {\n\t\t\tcase \"ping\": {\n\t\t\t\tthis.handlePing();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"connect\": {\n\t\t\t\tawait this.handleConnect(msg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"disconnectSelf\": {\n\t\t\t\tthis.handleDisconnectSelf();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"setLatestValue\": {\n\t\t\t\tthis.handleSetLatestValue(msg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"setLatestMapValue\": {\n\t\t\t\tthis.handleSetLatestMapValue(msg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"getLatestValue\": {\n\t\t\t\tthis.handleGetLatestValue(msg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"getLatestMapValue\": {\n\t\t\t\tthis.handleGetLatestMapValue(msg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"registerWorkspace\": {\n\t\t\t\tthis.registerWorkspace(msg.workspaceId, {\n\t\t\t\t\tlatest: msg.latest,\n\t\t\t\t\tlatestMap: msg.latestMap,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconsole.error(`${process_id}: Unknown command`);\n\t\t\t\tsend({ event: \"error\", error: `${process_id} Unknown command` });\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handlePing(): void {\n\t\tsend({ event: \"ack\" });\n\t}\n\n\tprivate async handleConnect(\n\t\tmsg: Extract<MessageFromParent, { command: \"connect\" }>,\n\t): Promise<void> {\n\t\tif (!msg.user) {\n\t\t\tsend({ event: \"error\", error: `${process_id}: No azure user information given` });\n\t\t\treturn;\n\t\t}\n\t\tif (isConnected(this.container)) {\n\t\t\tsend({ event: \"error\", error: `${process_id}: Already connected to container` });\n\t\t\treturn;\n\t\t}\n\t\tconst { container, presence, containerId } = await getOrCreatePresenceContainer(\n\t\t\tmsg.containerId,\n\t\t\tmsg.user,\n\t\t\tmsg.scopes,\n\t\t\tmsg.createScopes,\n\t\t);\n\t\tthis.container = container;\n\t\tthis.presence = presence;\n\t\tthis.containerId = containerId;\n\n\t\t// Acknowledge connection before sending current attendee information\n\t\tsend({\n\t\t\tevent: \"connected\",\n\t\t\tcontainerId,\n\t\t\tattendeeId: presence.attendees.getMyself().attendeeId,\n\t\t});\n\n\t\t// Send existing attendees excluding self to parent/orchestrator\n\t\tconst self = presence.attendees.getMyself();\n\t\tfor (const attendee of presence.attendees.getAttendees()) {\n\t\t\tif (attendee !== self) {\n\t\t\t\tsendAttendeeConnected(attendee);\n\t\t\t}\n\t\t}\n\n\t\t// Listen for presence events to notify parent/orchestrator when a new attendee joins or leaves the session.\n\t\tpresence.attendees.events.on(\"attendeeConnected\", sendAttendeeConnected);\n\t\tpresence.attendees.events.on(\"attendeeDisconnected\", sendAttendeeDisconnected);\n\t}\n\n\tprivate handleDisconnectSelf(): void {\n\t\tif (!this.container) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to container` });\n\t\t\treturn;\n\t\t}\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tthis.container.disconnect();\n\t\tsend({\n\t\t\tevent: \"disconnectedSelf\",\n\t\t\tattendeeId: this.presence.attendees.getMyself().attendeeId,\n\t\t});\n\t}\n\n\tprivate handleSetLatestValue(\n\t\tmsg: Extract<MessageFromParent, { command: \"setLatestValue\" }>,\n\t): void {\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tconst workspace = this.workspaces.get(msg.workspaceId);\n\t\tif (!workspace) {\n\t\t\tsend({ event: \"error\", error: `${process_id} workspace ${msg.workspaceId} not found` });\n\t\t\treturn;\n\t\t}\n\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t// TODO: AB#47518\n\t\tconst latestState = workspace.states.latest as LatestRaw<{ value: string }> | undefined;\n\t\tif (!latestState) {\n\t\t\tsend({\n\t\t\t\tevent: \"error\",\n\t\t\t\terror: `${process_id} latest state not registered for workspace ${msg.workspaceId}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (typeof msg.value !== \"string\") {\n\t\t\treturn;\n\t\t}\n\t\tlatestState.local = { value: msg.value };\n\t}\n\n\tprivate handleSetLatestMapValue(\n\t\tmsg: Extract<MessageFromParent, { command: \"setLatestMapValue\" }>,\n\t): void {\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tif (typeof msg.key !== \"string\") {\n\t\t\tsend({ event: \"error\", error: `${process_id} invalid key type` });\n\t\t\treturn;\n\t\t}\n\t\tconst workspace = this.workspaces.get(msg.workspaceId);\n\t\tif (!workspace) {\n\t\t\tsend({ event: \"error\", error: `${process_id} workspace ${msg.workspaceId} not found` });\n\t\t\treturn;\n\t\t}\n\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t// TODO: AB#47518\n\t\tconst latestMapState = workspace.states.latestMap as\n\t\t\t| LatestMapRaw<{ value: Record<string, string | number> }, string>\n\t\t\t| undefined;\n\t\tif (!latestMapState) {\n\t\t\tsend({\n\t\t\t\tevent: \"error\",\n\t\t\t\terror: `${process_id} latestMap state not registered for workspace ${msg.workspaceId}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (!isStringOrNumberRecord(msg.value)) {\n\t\t\treturn;\n\t\t}\n\t\tlatestMapState.local.set(msg.key, { value: msg.value });\n\t}\n\n\tprivate handleGetLatestValue(\n\t\tmsg: Extract<MessageFromParent, { command: \"getLatestValue\" }>,\n\t): void {\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tconst workspace = this.workspaces.get(msg.workspaceId);\n\t\tif (!workspace) {\n\t\t\tsend({ event: \"error\", error: `${process_id} workspace ${msg.workspaceId} not found` });\n\t\t\treturn;\n\t\t}\n\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t// TODO: AB#47518\n\t\tconst latestState = workspace.states.latest as LatestRaw<{ value: string }> | undefined;\n\t\tif (!latestState) {\n\t\t\tsend({\n\t\t\t\tevent: \"error\",\n\t\t\t\terror: `${process_id} latest state not registered for workspace ${msg.workspaceId}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tlet value: { value: string };\n\t\tif (msg.attendeeId) {\n\t\t\tconst attendee = this.presence.attendees.getAttendee(msg.attendeeId);\n\t\t\tconst remoteData = latestState.getRemote(attendee);\n\t\t\tvalue = remoteData.value;\n\t\t} else {\n\t\t\tvalue = latestState.local;\n\t\t}\n\t\tsend({\n\t\t\tevent: \"latestValueGetResponse\",\n\t\t\tworkspaceId: msg.workspaceId,\n\t\t\tattendeeId: msg.attendeeId,\n\t\t\tvalue: value.value,\n\t\t});\n\t}\n\n\tprivate handleGetLatestMapValue(\n\t\tmsg: Extract<MessageFromParent, { command: \"getLatestMapValue\" }>,\n\t): void {\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tif (typeof msg.key !== \"string\") {\n\t\t\tsend({ event: \"error\", error: `${process_id} invalid key type` });\n\t\t\treturn;\n\t\t}\n\t\tconst workspace = this.workspaces.get(msg.workspaceId);\n\t\tif (!workspace) {\n\t\t\tsend({ event: \"error\", error: `${process_id} workspace ${msg.workspaceId} not found` });\n\t\t\treturn;\n\t\t}\n\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t// TODO: AB#47518\n\t\tconst latestMapState = workspace.states.latestMap as\n\t\t\t| LatestMapRaw<{ value: Record<string, string | number> }, string>\n\t\t\t| undefined;\n\t\tif (!latestMapState) {\n\t\t\tsend({\n\t\t\t\tevent: \"error\",\n\t\t\t\terror: `${process_id} latestMap state not registered for workspace ${msg.workspaceId}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tlet value: { value: Record<string, string | number> } | undefined;\n\t\tif (msg.attendeeId) {\n\t\t\tconst attendee = this.presence.attendees.getAttendee(msg.attendeeId);\n\t\t\tconst remoteData = latestMapState.getRemote(attendee);\n\t\t\tconst keyData = remoteData.get(msg.key);\n\t\t\tvalue = keyData?.value;\n\t\t} else {\n\t\t\tvalue = latestMapState.local.get(msg.key);\n\t\t}\n\t\tsend({\n\t\t\tevent: \"latestMapValueGetResponse\",\n\t\t\tworkspaceId: msg.workspaceId,\n\t\t\tattendeeId: msg.attendeeId,\n\t\t\tkey: msg.key,\n\t\t\tvalue: value?.value,\n\t\t});\n\t}\n}\n\nfunction setupMessageHandler(): void {\n\tconst messageHandler = new MessageHandler();\n\tprocess.on(\"message\", (msg: MessageFromParent) => {\n\t\tmessageHandler.onMessage(msg).catch((error: Error) => {\n\t\t\tconsole.error(`Error in client ${process_id}`, error);\n\t\t\tsend({ event: \"error\", error: `${process_id}: ${error.message}` });\n\t\t});\n\t});\n}\n\nsetupMessageHandler();\n"]}
@@ -40,7 +40,7 @@ export async function forkChildProcesses(numProcesses, cleanUpAccumulator) {
40
40
  const childReadyPromises = [];
41
41
  const childErrorPromises = [];
42
42
  for (let i = 0; i < numProcesses; i++) {
43
- const child = fork("./lib/test/multiprocess/childClient.js", [
43
+ const child = fork("./lib/test/multiprocess/childClient.tool.js", [
44
44
  `child ${i}` /* identifier passed to child process */,
45
45
  childLoggingVerbosity /* console logging verbosity */,
46
46
  ]);
@@ -85,7 +85,7 @@ function composeConnectMessage(id, scopes = [ScopeType.DocRead]) {
85
85
  name: `test-user-name-${id}`,
86
86
  },
87
87
  scopes,
88
- createScopes: [ScopeType.DocWrite],
88
+ createScopes: [ScopeType.DocWrite, ScopeType.DocRead],
89
89
  };
90
90
  }
91
91
  /**
@@ -116,9 +116,7 @@ export async function connectChildProcesses(childProcesses, { writeClients, read
116
116
  // Note that DocWrite is used to have this attendee be the "leader".
117
117
  // DocRead would also be valid as DocWrite is specified for attach when there
118
118
  // is no document id (container id).
119
- const connectContainerCreator = composeConnectMessage(0, [
120
- writeClients > 0 ? ScopeType.DocWrite : ScopeType.DocRead,
121
- ]);
119
+ const connectContainerCreator = composeConnectMessage(0, writeClients > 0 ? [ScopeType.DocWrite, ScopeType.DocRead] : [ScopeType.DocRead]);
122
120
  firstChild.send(connectContainerCreator);
123
121
  }
124
122
  const { containerCreatorAttendeeId, containerId } = await timeoutAwait(containerReadyPromise, {
@@ -131,9 +129,7 @@ export async function connectChildProcesses(childProcesses, { writeClients, read
131
129
  attendeeIdPromises.push(Promise.resolve(containerCreatorAttendeeId));
132
130
  continue;
133
131
  }
134
- const message = composeConnectMessage(index, [
135
- index < writeClients ? ScopeType.DocWrite : ScopeType.DocRead,
136
- ]);
132
+ const message = composeConnectMessage(index, index < writeClients ? [ScopeType.DocWrite, ScopeType.DocRead] : [ScopeType.DocRead]);
137
133
  message.containerId = containerId;
138
134
  attendeeIdPromises.push(new Promise((resolve, reject) => {
139
135
  child.once("message", (msg) => {
@@ -150,20 +146,26 @@ export async function connectChildProcesses(childProcesses, { writeClients, read
150
146
  return { containerCreatorAttendeeId, attendeeIdPromises };
151
147
  }
152
148
  /**
153
- * Connects the child processes and waits for the specified number of attendees to connect.
149
+ * Connects the child processes and creates promises for the specified number of
150
+ * attendees to connect.
154
151
  */
155
- export async function connectAndWaitForAttendees(children, { writeClients, attendeeCountRequired, childConnectTimeoutMs, attendeesJoinedTimeoutMs, }, earlyExitPromise) {
156
- const attendeeConnectedPromise = new Promise((resolve) => {
152
+ export async function connectAndListenForAttendees(children, { writeClients, attendeeCountRequired, childConnectTimeoutMs, }) {
153
+ // Setup
154
+ const attendeeCountRequiredPromises = children.map(
155
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
156
+ (child) => new Promise((resolve) => {
157
157
  let attendeesJoinedEvents = 0;
158
- children[0].on("message", (msg) => {
158
+ const listenForAttendees = (msg) => {
159
159
  if (msg.event === "attendeeConnected") {
160
160
  attendeesJoinedEvents++;
161
161
  if (attendeesJoinedEvents >= attendeeCountRequired) {
162
162
  resolve();
163
163
  }
164
164
  }
165
- });
166
- });
165
+ };
166
+ child.on("message", listenForAttendees);
167
+ }));
168
+ // Act - connect all child processes
167
169
  const connectResult = await connectChildProcesses(children, {
168
170
  writeClients,
169
171
  readyTimeoutMs: childConnectTimeoutMs,
@@ -173,11 +175,28 @@ export async function connectAndWaitForAttendees(children, { writeClients, atten
173
175
  .catch((error) => {
174
176
  testConsole.error("Error connecting children:", error);
175
177
  });
178
+ return { ...connectResult, attendeeCountRequiredPromises };
179
+ }
180
+ /**
181
+ * Connects the child processes and waits for the specified number of attendees to connect.
182
+ *
183
+ * @remarks
184
+ * This function can be used directly as a test. Comments in the functionality describe the
185
+ * breakdown of test blocks.
186
+ */
187
+ export async function connectAndWaitForAttendees(children, { writeClients, attendeeCountRequired, childConnectTimeoutMs, allAttendeesJoinedTimeoutMs, }, earlyExitPromise) {
188
+ // Setup and Act - connect all child processes
189
+ const connectAndListenResult = await connectAndListenForAttendees(children, {
190
+ writeClients,
191
+ attendeeCountRequired,
192
+ childConnectTimeoutMs,
193
+ });
194
+ const attendeeConnectedPromise = connectAndListenResult.attendeeCountRequiredPromises[0];
176
195
  await timeoutAwait(Promise.race([attendeeConnectedPromise, earlyExitPromise]), {
177
- durationMs: attendeesJoinedTimeoutMs,
196
+ durationMs: allAttendeesJoinedTimeoutMs,
178
197
  errorMsg: "child 0 did not receive all 'attendeeConnected' events",
179
198
  });
180
- return connectResult;
199
+ return connectAndListenResult;
181
200
  }
182
201
  /**
183
202
  * Registers a workspace (latest and/or latestMap) on all provided child processes and waits for acknowledgement.
@@ -1 +1 @@
1
- {"version":3,"file":"orchestratorUtils.js","sourceRoot":"","sources":["../../../src/test/multiprocess/orchestratorUtils.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 { fork, type ChildProcess } from \"node:child_process\";\n\nimport { ScopeType } from \"@fluidframework/driver-definitions/legacy\";\nimport type { AttendeeId } from \"@fluidframework/presence/beta\";\nimport { timeoutAwait, timeoutPromise } from \"@fluidframework/test-utils/internal\";\n\nimport type {\n\tConnectCommand,\n\tMessageFromChild,\n\tLatestValueUpdatedEvent,\n\tLatestMapValueUpdatedEvent,\n\tLatestValueGetResponseEvent,\n\tLatestMapValueGetResponseEvent,\n} from \"./messageTypes.js\";\n\n/**\n * Child process to console logging verbosity\n *\n * @remarks\n * Meaningful substrings:\n * - \"msgs\"\n * - \"telem\"\n *\n * @example \"msgs+telem\"\n */\nconst childLoggingVerbosity = process.env.FLUID_TEST_VERBOSE ?? \"none\";\n\n/**\n * Capture console./warn/error before test infrastructure alters it.\n */\nexport const testConsole = {\n\tlog: console.log,\n\twarn: console.warn,\n\terror: console.error,\n};\n\n/**\n * Fork child processes to simulate multiple Fluid clients.\n *\n * @remarks\n * Individual child processes may be scheduled concurrently on a multi-core CPU\n * and separate processes will never share a port when connected to a service.\n *\n * @param numProcesses - The number of child processes to fork.\n * @param cleanUpAccumulator - An array to accumulate cleanup functions for each child.\n * @returns A collection of child processes and a promise that rejects on the first child error.\n */\nexport async function forkChildProcesses(\n\tnumProcesses: number,\n\tcleanUpAccumulator: (() => void)[],\n): Promise<{\n\tchildren: ChildProcess[];\n\t/**\n\t * Will never resolve successfully, it is only used to reject on child process error.\n\t */\n\tchildErrorPromise: Promise<never>;\n}> {\n\tconst children: ChildProcess[] = [];\n\tconst childReadyPromises: Promise<void>[] = [];\n\tconst childErrorPromises: Promise<never>[] = [];\n\tfor (let i = 0; i < numProcesses; i++) {\n\t\tconst child = fork(\"./lib/test/multiprocess/childClient.js\", [\n\t\t\t`child ${i}` /* identifier passed to child process */,\n\t\t\tchildLoggingVerbosity /* console logging verbosity */,\n\t\t]);\n\t\tcleanUpAccumulator.push(() => {\n\t\t\tchild.kill();\n\t\t\tchild.removeAllListeners();\n\t\t});\n\t\tconst readyPromise = new Promise<void>((resolve, reject) => {\n\t\t\tchild.once(\"message\", (msg: MessageFromChild) => {\n\t\t\t\tif (msg.event === \"ack\") {\n\t\t\t\t\tresolve();\n\t\t\t\t} else {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(`Unexpected (non-\"ack\") message from child${i}: ${JSON.stringify(msg)}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\tchildReadyPromises.push(readyPromise);\n\t\tconst errorPromise = new Promise<never>((_resolve, reject) => {\n\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\treject(new Error(`Child${i} process errored: ${error.message}`));\n\t\t\t});\n\t\t});\n\t\tchildErrorPromises.push(errorPromise);\n\t\tchild.send({ command: \"ping\" });\n\t\tchildren.push(child);\n\t}\n\tconst childErrorPromise = Promise.race(childErrorPromises);\n\tawait Promise.race([Promise.all(childReadyPromises), childErrorPromise]);\n\treturn { children, childErrorPromise };\n}\n\n/**\n * Creates a {@link ConnectCommand} for a test user with a deterministic id and name.\n *\n * @param id - Suffix used to construct stable test user identity.\n */\nfunction composeConnectMessage(\n\tid: string | number,\n\tscopes: ScopeType[] = [ScopeType.DocRead],\n): ConnectCommand {\n\treturn {\n\t\tcommand: \"connect\",\n\t\tuser: {\n\t\t\tid: `test-user-id-${id}`,\n\t\t\tname: `test-user-name-${id}`,\n\t\t},\n\t\tscopes,\n\t\tcreateScopes: [ScopeType.DocWrite],\n\t};\n}\n\n/**\n * Sends connect commands to the provided child processes.\n *\n * The first child will create the container unless a containerId is pre-specified; subsequent\n * children are sent the discovered containerId.\n */\nexport async function connectChildProcesses(\n\tchildProcesses: ChildProcess[],\n\t{ writeClients, readyTimeoutMs }: { writeClients: number; readyTimeoutMs: number },\n): Promise<{\n\tcontainerCreatorAttendeeId: AttendeeId;\n\tattendeeIdPromises: Promise<AttendeeId>[];\n}> {\n\tif (childProcesses.length === 0) {\n\t\tthrow new Error(\"No child processes provided for connection.\");\n\t}\n\tconst firstChild = childProcesses[0];\n\tconst containerReadyPromise = new Promise<{\n\t\tcontainerCreatorAttendeeId: AttendeeId;\n\t\tcontainerId: string;\n\t}>((resolve, reject) => {\n\t\tfirstChild.once(\"message\", (msg: MessageFromChild) => {\n\t\t\tif (msg.event === \"connected\" && msg.containerId) {\n\t\t\t\tresolve({\n\t\t\t\t\tcontainerCreatorAttendeeId: msg.attendeeId,\n\t\t\t\t\tcontainerId: msg.containerId,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\treject(new Error(`Non-connected message from child0: ${JSON.stringify(msg)}`));\n\t\t\t}\n\t\t});\n\t});\n\t{\n\t\t// Note that DocWrite is used to have this attendee be the \"leader\".\n\t\t// DocRead would also be valid as DocWrite is specified for attach when there\n\t\t// is no document id (container id).\n\t\tconst connectContainerCreator = composeConnectMessage(0, [\n\t\t\twriteClients > 0 ? ScopeType.DocWrite : ScopeType.DocRead,\n\t\t]);\n\t\tfirstChild.send(connectContainerCreator);\n\t}\n\tconst { containerCreatorAttendeeId, containerId } = await timeoutAwait(\n\t\tcontainerReadyPromise,\n\t\t{\n\t\t\tdurationMs: readyTimeoutMs,\n\t\t\terrorMsg: \"did not receive 'connected' from child process\",\n\t\t},\n\t);\n\n\tconst attendeeIdPromises: Promise<AttendeeId>[] = [];\n\tfor (const [index, child] of childProcesses.entries()) {\n\t\tif (index === 0) {\n\t\t\tattendeeIdPromises.push(Promise.resolve(containerCreatorAttendeeId));\n\t\t\tcontinue;\n\t\t}\n\t\tconst message = composeConnectMessage(index, [\n\t\t\tindex < writeClients ? ScopeType.DocWrite : ScopeType.DocRead,\n\t\t]);\n\t\tmessage.containerId = containerId;\n\t\tattendeeIdPromises.push(\n\t\t\tnew Promise<AttendeeId>((resolve, reject) => {\n\t\t\t\tchild.once(\"message\", (msg: MessageFromChild) => {\n\t\t\t\t\tif (msg.event === \"connected\") {\n\t\t\t\t\t\tresolve(msg.attendeeId);\n\t\t\t\t\t} else if (msg.event === \"error\") {\n\t\t\t\t\t\treject(new Error(`Child process error: ${msg.error}`));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}),\n\t\t);\n\t\tchild.send(message);\n\t}\n\treturn { containerCreatorAttendeeId, attendeeIdPromises };\n}\n\n/**\n * Connects the child processes and waits for the specified number of attendees to connect.\n */\nexport async function connectAndWaitForAttendees(\n\tchildren: ChildProcess[],\n\t{\n\t\twriteClients,\n\t\tattendeeCountRequired,\n\t\tchildConnectTimeoutMs,\n\t\tattendeesJoinedTimeoutMs,\n\t}: {\n\t\twriteClients: number;\n\t\tattendeeCountRequired: number;\n\t\tchildConnectTimeoutMs: number;\n\t\tattendeesJoinedTimeoutMs: number;\n\t},\n\tearlyExitPromise: Promise<never>,\n): Promise<{ containerCreatorAttendeeId: AttendeeId }> {\n\tconst attendeeConnectedPromise = new Promise<void>((resolve) => {\n\t\tlet attendeesJoinedEvents = 0;\n\t\tchildren[0].on(\"message\", (msg: MessageFromChild) => {\n\t\t\tif (msg.event === \"attendeeConnected\") {\n\t\t\t\tattendeesJoinedEvents++;\n\t\t\t\tif (attendeesJoinedEvents >= attendeeCountRequired) {\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n\tconst connectResult = await connectChildProcesses(children, {\n\t\twriteClients,\n\t\treadyTimeoutMs: childConnectTimeoutMs,\n\t});\n\tPromise.all(connectResult.attendeeIdPromises)\n\t\t.then(() => console.log(\"All attendees connected.\"))\n\t\t.catch((error) => {\n\t\t\ttestConsole.error(\"Error connecting children:\", error);\n\t\t});\n\tawait timeoutAwait(Promise.race([attendeeConnectedPromise, earlyExitPromise]), {\n\t\tdurationMs: attendeesJoinedTimeoutMs,\n\t\terrorMsg: \"child 0 did not receive all 'attendeeConnected' events\",\n\t});\n\treturn connectResult;\n}\n\n/**\n * Registers a workspace (latest and/or latestMap) on all provided child processes and waits for acknowledgement.\n *\n * @remarks\n * The listener for the acknowledgement event is attached before sending the command to avoid a race where the\n * child responds faster than the parent attaches the handler.\n *\n * @param children - Child processes representing Fluid clients.\n * @param workspaceId - Logical (unprefixed) workspace id used in tests.\n * @param options - Which state types to register plus optional timeout.\n */\nexport async function registerWorkspaceOnChildren(\n\tchildren: ChildProcess[],\n\tworkspaceId: string,\n\toptions: { latest?: boolean; latestMap?: boolean; timeoutMs: number },\n): Promise<void> {\n\tconst { latest, latestMap, timeoutMs } = options;\n\tconst promises = children.map(async (child, index) => {\n\t\tconst ackPromise = waitForEvent(\n\t\t\tchild,\n\t\t\t\"workspaceRegistered\",\n\t\t\t{\n\t\t\t\ttimeoutMs,\n\t\t\t\terrorMsg: `Child ${index} did not acknowledge workspace registration ${workspaceId}`,\n\t\t\t},\n\t\t\t(msg: MessageFromChild) =>\n\t\t\t\tmsg.event === \"workspaceRegistered\" && msg.workspaceId === workspaceId,\n\t\t);\n\t\tchild.send({\n\t\t\tcommand: \"registerWorkspace\",\n\t\t\tworkspaceId,\n\t\t\tlatest,\n\t\t\tlatestMap,\n\t\t});\n\t\tawait ackPromise;\n\t});\n\tawait Promise.all(promises);\n}\n\n// Basic command type guards\nfunction isLatestValueGetResponse(msg: MessageFromChild): msg is LatestValueGetResponseEvent {\n\treturn msg.event === \"latestValueGetResponse\";\n}\nfunction isLatestMapValueGetResponse(\n\tmsg: MessageFromChild,\n): msg is LatestMapValueGetResponseEvent {\n\treturn msg.event === \"latestMapValueGetResponse\";\n}\nfunction isLatestValueUpdated(msg: MessageFromChild): msg is LatestValueUpdatedEvent {\n\treturn msg.event === \"latestValueUpdated\";\n}\nfunction isLatestMapValueUpdated(msg: MessageFromChild): msg is LatestMapValueUpdatedEvent {\n\treturn msg.event === \"latestMapValueUpdated\";\n}\n\n/**\n * Waits for a single message of the specified event type from a child process.\n */\nexport async function waitForEvent(\n\tchild: ChildProcess,\n\teventType: MessageFromChild[\"event\"],\n\toptions: { timeoutMs: number; errorMsg?: string },\n\tpredicate?: (msg: MessageFromChild) => boolean,\n): Promise<MessageFromChild> {\n\tconst { timeoutMs, errorMsg = `did not receive '${eventType}' event` } = options;\n\n\tlet handler: ((msg: MessageFromChild) => void) | undefined;\n\n\tconst cleanup = (): void => {\n\t\tif (handler) {\n\t\t\tchild.off(\"message\", handler);\n\t\t\thandler = undefined;\n\t\t}\n\t};\n\n\treturn timeoutPromise<MessageFromChild>(\n\t\t(resolve) => {\n\t\t\thandler = (msg: MessageFromChild): void => {\n\t\t\t\tif (msg.event === eventType && (!predicate || predicate(msg))) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\tresolve(msg);\n\t\t\t\t}\n\t\t\t};\n\t\t\tchild.on(\"message\", handler);\n\t\t},\n\t\t{ durationMs: timeoutMs, errorMsg },\n\t).finally(cleanup);\n}\n\n/**\n * Waits for latest value updates for the provided workspace from all clients.\n *\n * @param clients - Child processes to wait for updates from\n * @param workspaceId - Workspace ID to filter updates\n * @param earlyExitPromise - Promise that rejects early on error\n * @param timeoutMs - Timeout in milliseconds\n * @param options - Optional filtering criteria with fromAttendeeId and expectedValue properties\n */\nexport async function waitForLatestValueUpdates(\n\tclients: ChildProcess[],\n\tworkspaceId: string,\n\tearlyExitPromise: Promise<never>,\n\ttimeoutMs: number,\n\toptions: { fromAttendeeId?: AttendeeId; expectedValue?: unknown } = {},\n): Promise<LatestValueUpdatedEvent[]> {\n\tconst { fromAttendeeId, expectedValue } = options;\n\n\tconst filterMsg = (msg: MessageFromChild): boolean => {\n\t\tif (!isLatestValueUpdated(msg) || msg.workspaceId !== workspaceId) {\n\t\t\treturn false;\n\t\t}\n\t\tif (fromAttendeeId !== undefined && msg.attendeeId !== fromAttendeeId) {\n\t\t\treturn false;\n\t\t}\n\t\tif (expectedValue !== undefined) {\n\t\t\treturn JSON.stringify(msg.value) === JSON.stringify(expectedValue);\n\t\t}\n\t\treturn true;\n\t};\n\tlet filterDescription = \"update\";\n\tif (fromAttendeeId) filterDescription += ` from attendee ${fromAttendeeId}`;\n\tif (expectedValue !== undefined)\n\t\tfilterDescription += ` with specific value ${JSON.stringify(expectedValue)}`;\n\tif (filterDescription === \"update\") filterDescription = \"any update\";\n\n\tconst updatePromises = clients.map(async (child, index) =>\n\t\twaitForEvent(\n\t\t\tchild,\n\t\t\t\"latestValueUpdated\",\n\t\t\t{\n\t\t\t\ttimeoutMs,\n\t\t\t\terrorMsg: `Client ${index} did not receive latest value ${filterDescription}`,\n\t\t\t},\n\t\t\tfilterMsg,\n\t\t),\n\t);\n\tconst responses = await Promise.race([Promise.all(updatePromises), earlyExitPromise]);\n\tconst latestValueUpdatedEvents: LatestValueUpdatedEvent[] = [];\n\tfor (const response of responses) {\n\t\tif (isLatestValueUpdated(response)) {\n\t\t\tlatestValueUpdatedEvents.push(response);\n\t\t} else {\n\t\t\tthrow new TypeError(`Expected LatestValueUpdated but got ${response.event}`);\n\t\t}\n\t}\n\treturn latestValueUpdatedEvents;\n}\n\n/**\n * Waits for latest map value updates (specific key) from all clients.\n *\n * @param clients - Child processes to wait for updates from\n * @param workspaceId - Workspace ID to filter updates\n * @param key - Map key to filter updates\n * @param earlyExitPromise - Promise that rejects early on error\n * @param timeoutMs - Timeout in milliseconds\n * @param options - Optional filtering criteria with fromAttendeeId and expectedValue properties\n */\nexport async function waitForLatestMapValueUpdates(\n\tclients: ChildProcess[],\n\tworkspaceId: string,\n\tkey: string,\n\tearlyExitPromise: Promise<never>,\n\ttimeoutMs: number,\n\toptions: { fromAttendeeId?: AttendeeId; expectedValue?: unknown } = {},\n): Promise<LatestMapValueUpdatedEvent[]> {\n\tconst { fromAttendeeId, expectedValue } = options;\n\n\tconst filterMsg = (msg: MessageFromChild): boolean => {\n\t\tif (!isLatestMapValueUpdated(msg) || msg.workspaceId !== workspaceId || msg.key !== key) {\n\t\t\treturn false;\n\t\t}\n\t\tif (fromAttendeeId !== undefined && msg.attendeeId !== fromAttendeeId) {\n\t\t\treturn false;\n\t\t}\n\t\tif (expectedValue !== undefined) {\n\t\t\treturn JSON.stringify(msg.value) === JSON.stringify(expectedValue);\n\t\t}\n\t\treturn true;\n\t};\n\tlet filterDescription = `update for key \"${key}\"`;\n\tif (fromAttendeeId) filterDescription += ` from attendee ${fromAttendeeId}`;\n\tif (expectedValue !== undefined)\n\t\tfilterDescription += ` with specific value ${JSON.stringify(expectedValue)}`;\n\n\tconst updatePromises = clients.map(async (child, index) =>\n\t\twaitForEvent(\n\t\t\tchild,\n\t\t\t\"latestMapValueUpdated\",\n\t\t\t{\n\t\t\t\ttimeoutMs,\n\t\t\t\terrorMsg: `Client ${index} did not receive latest map value ${filterDescription}`,\n\t\t\t},\n\t\t\tfilterMsg,\n\t\t),\n\t);\n\tconst responses = await Promise.race([Promise.all(updatePromises), earlyExitPromise]);\n\tconst latestMapValueUpdatedEvents: LatestMapValueUpdatedEvent[] = [];\n\tfor (const response of responses) {\n\t\tif (isLatestMapValueUpdated(response)) {\n\t\t\tlatestMapValueUpdatedEvents.push(response);\n\t\t} else {\n\t\t\tthrow new TypeError(`Expected LatestMapValueUpdated but got ${response.event}`);\n\t\t}\n\t}\n\treturn latestMapValueUpdatedEvents;\n}\n\n/**\n * Collects latest value get response events from all clients.\n */\nexport async function getLatestValueResponses(\n\tclients: ChildProcess[],\n\tworkspaceId: string,\n\tearlyExitPromise: Promise<never>,\n\ttimeoutMs: number,\n): Promise<LatestValueGetResponseEvent[]> {\n\tconst responsePromises = clients.map(async (child, index) =>\n\t\twaitForEvent(\n\t\t\tchild,\n\t\t\t\"latestValueGetResponse\",\n\t\t\t{ timeoutMs, errorMsg: `Client ${index} did not respond with latest value` },\n\t\t\t(msg: MessageFromChild) =>\n\t\t\t\tisLatestValueGetResponse(msg) && msg.workspaceId === workspaceId,\n\t\t),\n\t);\n\tconst responses = await Promise.race([Promise.all(responsePromises), earlyExitPromise]);\n\treturn responses.map((response) => {\n\t\tif (!isLatestValueGetResponse(response)) {\n\t\t\tthrow new TypeError(`Expected LatestValueGetResponse but got ${response.event}`);\n\t\t}\n\t\treturn response;\n\t});\n}\n\n/**\n * Collects latest map value get response events from all clients.\n */\nexport async function getLatestMapValueResponses(\n\tclients: ChildProcess[],\n\tworkspaceId: string,\n\tkey: string,\n\tearlyExitPromise: Promise<never>,\n\ttimeoutMs: number,\n): Promise<LatestMapValueGetResponseEvent[]> {\n\tconst responsePromises = clients.map(async (child, index) =>\n\t\twaitForEvent(\n\t\t\tchild,\n\t\t\t\"latestMapValueGetResponse\",\n\t\t\t{ timeoutMs, errorMsg: `Client ${index} did not respond with latest map value` },\n\t\t\t(msg: MessageFromChild) =>\n\t\t\t\tisLatestMapValueGetResponse(msg) && msg.workspaceId === workspaceId && msg.key === key,\n\t\t),\n\t);\n\tconst responses = await Promise.race([Promise.all(responsePromises), earlyExitPromise]);\n\treturn responses.map((response) => {\n\t\tif (!isLatestMapValueGetResponse(response)) {\n\t\t\tthrow new TypeError(`Expected LatestMapValueGetResponse but got ${response.event}`);\n\t\t}\n\t\treturn response;\n\t});\n}\n"]}
1
+ {"version":3,"file":"orchestratorUtils.js","sourceRoot":"","sources":["../../../src/test/multiprocess/orchestratorUtils.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 { fork, type ChildProcess } from \"node:child_process\";\n\nimport { ScopeType } from \"@fluidframework/driver-definitions/legacy\";\nimport type { AttendeeId } from \"@fluidframework/presence/beta\";\nimport { timeoutAwait, timeoutPromise } from \"@fluidframework/test-utils/internal\";\n\nimport type {\n\tConnectCommand,\n\tMessageFromChild,\n\tLatestValueUpdatedEvent,\n\tLatestMapValueUpdatedEvent,\n\tLatestValueGetResponseEvent,\n\tLatestMapValueGetResponseEvent,\n} from \"./messageTypes.js\";\n\n/**\n * Child process to console logging verbosity\n *\n * @remarks\n * Meaningful substrings:\n * - \"msgs\"\n * - \"telem\"\n *\n * @example \"msgs+telem\"\n */\nconst childLoggingVerbosity = process.env.FLUID_TEST_VERBOSE ?? \"none\";\n\n/**\n * Capture console./warn/error before test infrastructure alters it.\n */\nexport const testConsole = {\n\tlog: console.log,\n\twarn: console.warn,\n\terror: console.error,\n};\n\n/**\n * Fork child processes to simulate multiple Fluid clients.\n *\n * @remarks\n * Individual child processes may be scheduled concurrently on a multi-core CPU\n * and separate processes will never share a port when connected to a service.\n *\n * @param numProcesses - The number of child processes to fork.\n * @param cleanUpAccumulator - An array to accumulate cleanup functions for each child.\n * @returns A collection of child processes and a promise that rejects on the first child error.\n */\nexport async function forkChildProcesses(\n\tnumProcesses: number,\n\tcleanUpAccumulator: (() => void)[],\n): Promise<{\n\tchildren: ChildProcess[];\n\t/**\n\t * Will never resolve successfully, it is only used to reject on child process error.\n\t */\n\tchildErrorPromise: Promise<never>;\n}> {\n\tconst children: ChildProcess[] = [];\n\tconst childReadyPromises: Promise<void>[] = [];\n\tconst childErrorPromises: Promise<never>[] = [];\n\tfor (let i = 0; i < numProcesses; i++) {\n\t\tconst child = fork(\"./lib/test/multiprocess/childClient.tool.js\", [\n\t\t\t`child ${i}` /* identifier passed to child process */,\n\t\t\tchildLoggingVerbosity /* console logging verbosity */,\n\t\t]);\n\t\tcleanUpAccumulator.push(() => {\n\t\t\tchild.kill();\n\t\t\tchild.removeAllListeners();\n\t\t});\n\t\tconst readyPromise = new Promise<void>((resolve, reject) => {\n\t\t\tchild.once(\"message\", (msg: MessageFromChild) => {\n\t\t\t\tif (msg.event === \"ack\") {\n\t\t\t\t\tresolve();\n\t\t\t\t} else {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(`Unexpected (non-\"ack\") message from child${i}: ${JSON.stringify(msg)}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\tchildReadyPromises.push(readyPromise);\n\t\tconst errorPromise = new Promise<never>((_resolve, reject) => {\n\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\treject(new Error(`Child${i} process errored: ${error.message}`));\n\t\t\t});\n\t\t});\n\t\tchildErrorPromises.push(errorPromise);\n\t\tchild.send({ command: \"ping\" });\n\t\tchildren.push(child);\n\t}\n\tconst childErrorPromise = Promise.race(childErrorPromises);\n\tawait Promise.race([Promise.all(childReadyPromises), childErrorPromise]);\n\treturn { children, childErrorPromise };\n}\n\n/**\n * Creates a {@link ConnectCommand} for a test user with a deterministic id and name.\n *\n * @param id - Suffix used to construct stable test user identity.\n */\nfunction composeConnectMessage(\n\tid: string | number,\n\tscopes: ScopeType[] = [ScopeType.DocRead],\n): ConnectCommand {\n\treturn {\n\t\tcommand: \"connect\",\n\t\tuser: {\n\t\t\tid: `test-user-id-${id}`,\n\t\t\tname: `test-user-name-${id}`,\n\t\t},\n\t\tscopes,\n\t\tcreateScopes: [ScopeType.DocWrite, ScopeType.DocRead],\n\t};\n}\n\ninterface CreatorAttendeeIdAndAttendeePromises {\n\tcontainerCreatorAttendeeId: AttendeeId;\n\tattendeeIdPromises: Promise<AttendeeId>[];\n}\n\n/**\n * Sends connect commands to the provided child processes.\n *\n * The first child will create the container unless a containerId is pre-specified; subsequent\n * children are sent the discovered containerId.\n */\nexport async function connectChildProcesses(\n\tchildProcesses: ChildProcess[],\n\t{ writeClients, readyTimeoutMs }: { writeClients: number; readyTimeoutMs: number },\n): Promise<CreatorAttendeeIdAndAttendeePromises> {\n\tif (childProcesses.length === 0) {\n\t\tthrow new Error(\"No child processes provided for connection.\");\n\t}\n\tconst firstChild = childProcesses[0];\n\tconst containerReadyPromise = new Promise<{\n\t\tcontainerCreatorAttendeeId: AttendeeId;\n\t\tcontainerId: string;\n\t}>((resolve, reject) => {\n\t\tfirstChild.once(\"message\", (msg: MessageFromChild) => {\n\t\t\tif (msg.event === \"connected\" && msg.containerId) {\n\t\t\t\tresolve({\n\t\t\t\t\tcontainerCreatorAttendeeId: msg.attendeeId,\n\t\t\t\t\tcontainerId: msg.containerId,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\treject(new Error(`Non-connected message from child0: ${JSON.stringify(msg)}`));\n\t\t\t}\n\t\t});\n\t});\n\t{\n\t\t// Note that DocWrite is used to have this attendee be the \"leader\".\n\t\t// DocRead would also be valid as DocWrite is specified for attach when there\n\t\t// is no document id (container id).\n\t\tconst connectContainerCreator = composeConnectMessage(\n\t\t\t0,\n\t\t\twriteClients > 0 ? [ScopeType.DocWrite, ScopeType.DocRead] : [ScopeType.DocRead],\n\t\t);\n\t\tfirstChild.send(connectContainerCreator);\n\t}\n\tconst { containerCreatorAttendeeId, containerId } = await timeoutAwait(\n\t\tcontainerReadyPromise,\n\t\t{\n\t\t\tdurationMs: readyTimeoutMs,\n\t\t\terrorMsg: \"did not receive 'connected' from child process\",\n\t\t},\n\t);\n\n\tconst attendeeIdPromises: Promise<AttendeeId>[] = [];\n\tfor (const [index, child] of childProcesses.entries()) {\n\t\tif (index === 0) {\n\t\t\tattendeeIdPromises.push(Promise.resolve(containerCreatorAttendeeId));\n\t\t\tcontinue;\n\t\t}\n\t\tconst message = composeConnectMessage(\n\t\t\tindex,\n\t\t\tindex < writeClients ? [ScopeType.DocWrite, ScopeType.DocRead] : [ScopeType.DocRead],\n\t\t);\n\t\tmessage.containerId = containerId;\n\t\tattendeeIdPromises.push(\n\t\t\tnew Promise<AttendeeId>((resolve, reject) => {\n\t\t\t\tchild.once(\"message\", (msg: MessageFromChild) => {\n\t\t\t\t\tif (msg.event === \"connected\") {\n\t\t\t\t\t\tresolve(msg.attendeeId);\n\t\t\t\t\t} else if (msg.event === \"error\") {\n\t\t\t\t\t\treject(new Error(`Child process error: ${msg.error}`));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}),\n\t\t);\n\t\tchild.send(message);\n\t}\n\treturn { containerCreatorAttendeeId, attendeeIdPromises };\n}\n\ninterface ConnectAndListenForAttendees extends CreatorAttendeeIdAndAttendeePromises {\n\tattendeeCountRequiredPromises: Promise<void>[];\n}\n\n/**\n * Connects the child processes and creates promises for the specified number of\n * attendees to connect.\n */\nexport async function connectAndListenForAttendees(\n\tchildren: ChildProcess[],\n\t{\n\t\twriteClients,\n\t\tattendeeCountRequired,\n\t\tchildConnectTimeoutMs,\n\t}: {\n\t\t/**\n\t\t * The number of clients that should have write access.\n\t\t */\n\t\twriteClients: number;\n\t\t/**\n\t\t * The number of attendees that must connect.\n\t\t */\n\t\tattendeeCountRequired: number;\n\t\t/**\n\t\t * Timeout duration for child process connections.\n\t\t */\n\t\tchildConnectTimeoutMs: number;\n\t},\n): Promise<ConnectAndListenForAttendees> {\n\t// Setup\n\tconst attendeeCountRequiredPromises = children.map(\n\t\t// eslint-disable-next-line @typescript-eslint/promise-function-async\n\t\t(child) =>\n\t\t\tnew Promise<void>((resolve) => {\n\t\t\t\tlet attendeesJoinedEvents = 0;\n\t\t\t\tconst listenForAttendees = (msg: MessageFromChild): void => {\n\t\t\t\t\tif (msg.event === \"attendeeConnected\") {\n\t\t\t\t\t\tattendeesJoinedEvents++;\n\t\t\t\t\t\tif (attendeesJoinedEvents >= attendeeCountRequired) {\n\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tchild.on(\"message\", listenForAttendees);\n\t\t\t}),\n\t);\n\n\t// Act - connect all child processes\n\tconst connectResult = await connectChildProcesses(children, {\n\t\twriteClients,\n\t\treadyTimeoutMs: childConnectTimeoutMs,\n\t});\n\n\tPromise.all(connectResult.attendeeIdPromises)\n\t\t.then(() => console.log(\"All attendees connected.\"))\n\t\t.catch((error) => {\n\t\t\ttestConsole.error(\"Error connecting children:\", error);\n\t\t});\n\n\treturn { ...connectResult, attendeeCountRequiredPromises };\n}\n\n/**\n * Connects the child processes and waits for the specified number of attendees to connect.\n *\n * @remarks\n * This function can be used directly as a test. Comments in the functionality describe the\n * breakdown of test blocks.\n */\nexport async function connectAndWaitForAttendees(\n\tchildren: ChildProcess[],\n\t{\n\t\twriteClients,\n\t\tattendeeCountRequired,\n\t\tchildConnectTimeoutMs,\n\t\tallAttendeesJoinedTimeoutMs,\n\t}: {\n\t\t/**\n\t\t * The number of clients that should have write access.\n\t\t */\n\t\twriteClients: number;\n\t\t/**\n\t\t * The number of attendees that must connect.\n\t\t */\n\t\tattendeeCountRequired: number;\n\t\t/**\n\t\t * Timeout duration for child process connections.\n\t\t */\n\t\tchildConnectTimeoutMs: number;\n\t\t/**\n\t\t * Timeout duration for all required attendees to join.\n\t\t */\n\t\tallAttendeesJoinedTimeoutMs: number;\n\t},\n\tearlyExitPromise: Promise<never>,\n): Promise<{ containerCreatorAttendeeId: AttendeeId }> {\n\t// Setup and Act - connect all child processes\n\tconst connectAndListenResult = await connectAndListenForAttendees(children, {\n\t\twriteClients,\n\t\tattendeeCountRequired,\n\t\tchildConnectTimeoutMs,\n\t});\n\n\tconst attendeeConnectedPromise = connectAndListenResult.attendeeCountRequiredPromises[0];\n\n\tawait timeoutAwait(Promise.race([attendeeConnectedPromise, earlyExitPromise]), {\n\t\tdurationMs: allAttendeesJoinedTimeoutMs,\n\t\terrorMsg: \"child 0 did not receive all 'attendeeConnected' events\",\n\t});\n\treturn connectAndListenResult;\n}\n\n/**\n * Registers a workspace (latest and/or latestMap) on all provided child processes and waits for acknowledgement.\n *\n * @remarks\n * The listener for the acknowledgement event is attached before sending the command to avoid a race where the\n * child responds faster than the parent attaches the handler.\n *\n * @param children - Child processes representing Fluid clients.\n * @param workspaceId - Logical (unprefixed) workspace id used in tests.\n * @param options - Which state types to register plus optional timeout.\n */\nexport async function registerWorkspaceOnChildren(\n\tchildren: ChildProcess[],\n\tworkspaceId: string,\n\toptions: { latest?: boolean; latestMap?: boolean; timeoutMs: number },\n): Promise<void> {\n\tconst { latest, latestMap, timeoutMs } = options;\n\tconst promises = children.map(async (child, index) => {\n\t\tconst ackPromise = waitForEvent(\n\t\t\tchild,\n\t\t\t\"workspaceRegistered\",\n\t\t\t{\n\t\t\t\ttimeoutMs,\n\t\t\t\terrorMsg: `Child ${index} did not acknowledge workspace registration ${workspaceId}`,\n\t\t\t},\n\t\t\t(msg: MessageFromChild) =>\n\t\t\t\tmsg.event === \"workspaceRegistered\" && msg.workspaceId === workspaceId,\n\t\t);\n\t\tchild.send({\n\t\t\tcommand: \"registerWorkspace\",\n\t\t\tworkspaceId,\n\t\t\tlatest,\n\t\t\tlatestMap,\n\t\t});\n\t\tawait ackPromise;\n\t});\n\tawait Promise.all(promises);\n}\n\n// Basic command type guards\nfunction isLatestValueGetResponse(msg: MessageFromChild): msg is LatestValueGetResponseEvent {\n\treturn msg.event === \"latestValueGetResponse\";\n}\nfunction isLatestMapValueGetResponse(\n\tmsg: MessageFromChild,\n): msg is LatestMapValueGetResponseEvent {\n\treturn msg.event === \"latestMapValueGetResponse\";\n}\nfunction isLatestValueUpdated(msg: MessageFromChild): msg is LatestValueUpdatedEvent {\n\treturn msg.event === \"latestValueUpdated\";\n}\nfunction isLatestMapValueUpdated(msg: MessageFromChild): msg is LatestMapValueUpdatedEvent {\n\treturn msg.event === \"latestMapValueUpdated\";\n}\n\n/**\n * Waits for a single message of the specified event type from a child process.\n */\nexport async function waitForEvent(\n\tchild: ChildProcess,\n\teventType: MessageFromChild[\"event\"],\n\toptions: { timeoutMs: number; errorMsg?: string },\n\tpredicate?: (msg: MessageFromChild) => boolean,\n): Promise<MessageFromChild> {\n\tconst { timeoutMs, errorMsg = `did not receive '${eventType}' event` } = options;\n\n\tlet handler: ((msg: MessageFromChild) => void) | undefined;\n\n\tconst cleanup = (): void => {\n\t\tif (handler) {\n\t\t\tchild.off(\"message\", handler);\n\t\t\thandler = undefined;\n\t\t}\n\t};\n\n\treturn timeoutPromise<MessageFromChild>(\n\t\t(resolve) => {\n\t\t\thandler = (msg: MessageFromChild): void => {\n\t\t\t\tif (msg.event === eventType && (!predicate || predicate(msg))) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\tresolve(msg);\n\t\t\t\t}\n\t\t\t};\n\t\t\tchild.on(\"message\", handler);\n\t\t},\n\t\t{ durationMs: timeoutMs, errorMsg },\n\t).finally(cleanup);\n}\n\n/**\n * Waits for latest value updates for the provided workspace from all clients.\n *\n * @param clients - Child processes to wait for updates from\n * @param workspaceId - Workspace ID to filter updates\n * @param earlyExitPromise - Promise that rejects early on error\n * @param timeoutMs - Timeout in milliseconds\n * @param options - Optional filtering criteria with fromAttendeeId and expectedValue properties\n */\nexport async function waitForLatestValueUpdates(\n\tclients: ChildProcess[],\n\tworkspaceId: string,\n\tearlyExitPromise: Promise<never>,\n\ttimeoutMs: number,\n\toptions: { fromAttendeeId?: AttendeeId; expectedValue?: unknown } = {},\n): Promise<LatestValueUpdatedEvent[]> {\n\tconst { fromAttendeeId, expectedValue } = options;\n\n\tconst filterMsg = (msg: MessageFromChild): boolean => {\n\t\tif (!isLatestValueUpdated(msg) || msg.workspaceId !== workspaceId) {\n\t\t\treturn false;\n\t\t}\n\t\tif (fromAttendeeId !== undefined && msg.attendeeId !== fromAttendeeId) {\n\t\t\treturn false;\n\t\t}\n\t\tif (expectedValue !== undefined) {\n\t\t\treturn JSON.stringify(msg.value) === JSON.stringify(expectedValue);\n\t\t}\n\t\treturn true;\n\t};\n\tlet filterDescription = \"update\";\n\tif (fromAttendeeId) filterDescription += ` from attendee ${fromAttendeeId}`;\n\tif (expectedValue !== undefined)\n\t\tfilterDescription += ` with specific value ${JSON.stringify(expectedValue)}`;\n\tif (filterDescription === \"update\") filterDescription = \"any update\";\n\n\tconst updatePromises = clients.map(async (child, index) =>\n\t\twaitForEvent(\n\t\t\tchild,\n\t\t\t\"latestValueUpdated\",\n\t\t\t{\n\t\t\t\ttimeoutMs,\n\t\t\t\terrorMsg: `Client ${index} did not receive latest value ${filterDescription}`,\n\t\t\t},\n\t\t\tfilterMsg,\n\t\t),\n\t);\n\tconst responses = await Promise.race([Promise.all(updatePromises), earlyExitPromise]);\n\tconst latestValueUpdatedEvents: LatestValueUpdatedEvent[] = [];\n\tfor (const response of responses) {\n\t\tif (isLatestValueUpdated(response)) {\n\t\t\tlatestValueUpdatedEvents.push(response);\n\t\t} else {\n\t\t\tthrow new TypeError(`Expected LatestValueUpdated but got ${response.event}`);\n\t\t}\n\t}\n\treturn latestValueUpdatedEvents;\n}\n\n/**\n * Waits for latest map value updates (specific key) from all clients.\n *\n * @param clients - Child processes to wait for updates from\n * @param workspaceId - Workspace ID to filter updates\n * @param key - Map key to filter updates\n * @param earlyExitPromise - Promise that rejects early on error\n * @param timeoutMs - Timeout in milliseconds\n * @param options - Optional filtering criteria with fromAttendeeId and expectedValue properties\n */\nexport async function waitForLatestMapValueUpdates(\n\tclients: ChildProcess[],\n\tworkspaceId: string,\n\tkey: string,\n\tearlyExitPromise: Promise<never>,\n\ttimeoutMs: number,\n\toptions: { fromAttendeeId?: AttendeeId; expectedValue?: unknown } = {},\n): Promise<LatestMapValueUpdatedEvent[]> {\n\tconst { fromAttendeeId, expectedValue } = options;\n\n\tconst filterMsg = (msg: MessageFromChild): boolean => {\n\t\tif (!isLatestMapValueUpdated(msg) || msg.workspaceId !== workspaceId || msg.key !== key) {\n\t\t\treturn false;\n\t\t}\n\t\tif (fromAttendeeId !== undefined && msg.attendeeId !== fromAttendeeId) {\n\t\t\treturn false;\n\t\t}\n\t\tif (expectedValue !== undefined) {\n\t\t\treturn JSON.stringify(msg.value) === JSON.stringify(expectedValue);\n\t\t}\n\t\treturn true;\n\t};\n\tlet filterDescription = `update for key \"${key}\"`;\n\tif (fromAttendeeId) filterDescription += ` from attendee ${fromAttendeeId}`;\n\tif (expectedValue !== undefined)\n\t\tfilterDescription += ` with specific value ${JSON.stringify(expectedValue)}`;\n\n\tconst updatePromises = clients.map(async (child, index) =>\n\t\twaitForEvent(\n\t\t\tchild,\n\t\t\t\"latestMapValueUpdated\",\n\t\t\t{\n\t\t\t\ttimeoutMs,\n\t\t\t\terrorMsg: `Client ${index} did not receive latest map value ${filterDescription}`,\n\t\t\t},\n\t\t\tfilterMsg,\n\t\t),\n\t);\n\tconst responses = await Promise.race([Promise.all(updatePromises), earlyExitPromise]);\n\tconst latestMapValueUpdatedEvents: LatestMapValueUpdatedEvent[] = [];\n\tfor (const response of responses) {\n\t\tif (isLatestMapValueUpdated(response)) {\n\t\t\tlatestMapValueUpdatedEvents.push(response);\n\t\t} else {\n\t\t\tthrow new TypeError(`Expected LatestMapValueUpdated but got ${response.event}`);\n\t\t}\n\t}\n\treturn latestMapValueUpdatedEvents;\n}\n\n/**\n * Collects latest value get response events from all clients.\n */\nexport async function getLatestValueResponses(\n\tclients: ChildProcess[],\n\tworkspaceId: string,\n\tearlyExitPromise: Promise<never>,\n\ttimeoutMs: number,\n): Promise<LatestValueGetResponseEvent[]> {\n\tconst responsePromises = clients.map(async (child, index) =>\n\t\twaitForEvent(\n\t\t\tchild,\n\t\t\t\"latestValueGetResponse\",\n\t\t\t{ timeoutMs, errorMsg: `Client ${index} did not respond with latest value` },\n\t\t\t(msg: MessageFromChild) =>\n\t\t\t\tisLatestValueGetResponse(msg) && msg.workspaceId === workspaceId,\n\t\t),\n\t);\n\tconst responses = await Promise.race([Promise.all(responsePromises), earlyExitPromise]);\n\treturn responses.map((response) => {\n\t\tif (!isLatestValueGetResponse(response)) {\n\t\t\tthrow new TypeError(`Expected LatestValueGetResponse but got ${response.event}`);\n\t\t}\n\t\treturn response;\n\t});\n}\n\n/**\n * Collects latest map value get response events from all clients.\n */\nexport async function getLatestMapValueResponses(\n\tclients: ChildProcess[],\n\tworkspaceId: string,\n\tkey: string,\n\tearlyExitPromise: Promise<never>,\n\ttimeoutMs: number,\n): Promise<LatestMapValueGetResponseEvent[]> {\n\tconst responsePromises = clients.map(async (child, index) =>\n\t\twaitForEvent(\n\t\t\tchild,\n\t\t\t\"latestMapValueGetResponse\",\n\t\t\t{ timeoutMs, errorMsg: `Client ${index} did not respond with latest map value` },\n\t\t\t(msg: MessageFromChild) =>\n\t\t\t\tisLatestMapValueGetResponse(msg) && msg.workspaceId === workspaceId && msg.key === key,\n\t\t),\n\t);\n\tconst responses = await Promise.race([Promise.all(responsePromises), earlyExitPromise]);\n\treturn responses.map((response) => {\n\t\tif (!isLatestMapValueGetResponse(response)) {\n\t\t\tthrow new TypeError(`Expected LatestMapValueGetResponse but got ${response.event}`);\n\t\t}\n\t\treturn response;\n\t});\n}\n"]}