Skip to content

Commit c33b78f

Browse files
authored
test(client): debuggability via console or debugger (#25351)
Console logging updates: - Log extra error from test process to console without mocha redirection - Use FLUID_TEST_VERBOSE to control child logging to optionally output messaging and select telemetry Under debugger updates: - For longer test cases, modify that case's timeout without impacting general timeouts. - Detect when debugger is attached and alter timeouts to increase by x1000
1 parent 81df72b commit c33b78f

File tree

4 files changed

+117
-16
lines changed

4 files changed

+117
-16
lines changed

packages/service-clients/end-to-end-tests/azure-client/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"test:realsvc:tinylicious": "start-server-and-test start:tinylicious:test 7071 test:realsvc:tinylicious:run",
3636
"test:realsvc:tinylicious:report": "npm run test:realsvc:tinylicious",
3737
"test:realsvc:tinylicious:run": "npm run test:realsvc:azure:run -- --driver=t9s",
38-
"test:realsvc:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:realsvc"
38+
"test:realsvc:verbose": "cross-env FLUID_TEST_VERBOSE=msgs npm run test:realsvc",
39+
"test:realsvc:veryverbose": "cross-env FLUID_TEST_VERBOSE=msgs+telem npm run test:realsvc"
3940
},
4041
"c8": {
4142
"all": true,

packages/service-clients/end-to-end-tests/azure-client/src/test/multiprocess/childClient.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import {
1010
type AzureContainerServices,
1111
type AzureLocalConnectionConfig,
1212
type AzureRemoteConnectionConfig,
13+
type ITelemetryBaseEvent,
1314
} from "@fluidframework/azure-client";
1415
import { AttachState } from "@fluidframework/container-definitions";
1516
import { ConnectionState } from "@fluidframework/container-loader";
17+
import { LogLevel } from "@fluidframework/core-interfaces";
1618
import type { ScopeType } from "@fluidframework/driver-definitions/legacy";
1719
import type { ContainerSchema, IFluidContainer } from "@fluidframework/fluid-static";
1820
import {
@@ -37,6 +39,7 @@ type MessageToParent = Required<MessageFromChild>;
3739
const connectTimeoutMs = 10_000;
3840
// Identifier given to child process
3941
const process_id = process.argv[2];
42+
const verbosity = process.argv[3] ?? "";
4043

4144
const useAzure = process.env.FLUID_CLIENT === "azure";
4245
const tenantId = useAzure
@@ -47,6 +50,24 @@ if (useAzure && endPoint === undefined) {
4750
throw new Error("Azure Fluid Relay service endpoint is missing");
4851
}
4952

53+
function selectiveVerboseLog(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {
54+
if (event.eventName.includes(":Signal") || event.eventName.includes(":Join")) {
55+
console.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {
56+
eventName: event.eventName,
57+
details: event.details,
58+
containerConnectionState: event.containerConnectionState,
59+
});
60+
} else if (
61+
event.eventName.includes(":Container:") ||
62+
event.eventName.includes(":Presence:")
63+
) {
64+
console.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {
65+
eventName: event.eventName,
66+
containerConnectionState: event.containerConnectionState,
67+
});
68+
}
69+
}
70+
5071
/**
5172
* Get or create a Fluid container with Presence in initialObjects.
5273
*/
@@ -81,7 +102,12 @@ const getOrCreatePresenceContainer = async (
81102
endpoint: "http://localhost:7071",
82103
type: "local",
83104
};
84-
const client = new AzureClient({ connection: connectionProps });
105+
const client = new AzureClient({
106+
connection: connectionProps,
107+
logger: {
108+
send: verbosity.includes("telem") ? selectiveVerboseLog : () => {},
109+
},
110+
});
85111
const schema: ContainerSchema = {
86112
initialObjects: {
87113
// A DataObject is added as otherwise fluid-static complains "Container cannot be initialized without any DataTypes"
@@ -120,7 +146,14 @@ const getOrCreatePresenceContainer = async (
120146
};
121147
function createSendFunction(): (msg: MessageToParent) => void {
122148
if (process.send) {
123-
return process.send.bind(process);
149+
const sendFn = process.send.bind(process);
150+
if (verbosity.includes("msgs")) {
151+
return (msg: MessageToParent) => {
152+
console.log(`[${process_id}] Sending`, msg);
153+
sendFn(msg);
154+
};
155+
}
156+
return sendFn;
124157
}
125158
throw new Error("process.send is not defined");
126159
}
@@ -262,6 +295,9 @@ class MessageHandler {
262295
}
263296

264297
public async onMessage(msg: MessageFromParent): Promise<void> {
298+
if (verbosity.includes("msgs")) {
299+
console.log(`[${process_id}] Received`, msg);
300+
}
265301
switch (msg.command) {
266302
case "ping": {
267303
this.handlePing();

packages/service-clients/end-to-end-tests/azure-client/src/test/multiprocess/orchestratorUtils.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@ import type {
1818
LatestMapValueGetResponseEvent,
1919
} from "./messageTypes.js";
2020

21+
/**
22+
* Child process to console logging verbosity
23+
*
24+
* @remarks
25+
* Meaningful substrings:
26+
* - "msgs"
27+
* - "telem"
28+
*
29+
* @example "msgs+telem"
30+
*/
31+
const childLoggingVerbosity = process.env.FLUID_TEST_VERBOSE ?? "none";
32+
33+
/**
34+
* Capture console./warn/error before test infrastructure alters it.
35+
*/
36+
export const testConsole = {
37+
log: console.log,
38+
warn: console.warn,
39+
error: console.error,
40+
};
41+
2142
/**
2243
* Fork child processes to simulate multiple Fluid clients.
2344
*
@@ -44,7 +65,8 @@ export async function forkChildProcesses(
4465
const childErrorPromises: Promise<never>[] = [];
4566
for (let i = 0; i < numProcesses; i++) {
4667
const child = fork("./lib/test/multiprocess/childClient.js", [
47-
`child${i}` /* identifier passed to child process */,
68+
`child ${i}` /* identifier passed to child process */,
69+
childLoggingVerbosity /* console logging verbosity */,
4870
]);
4971
cleanUpAccumulator.push(() => {
5072
child.kill();
@@ -204,12 +226,14 @@ export async function connectAndWaitForAttendees(
204226
writeClients,
205227
readyTimeoutMs: childConnectTimeoutMs,
206228
});
207-
Promise.all(connectResult.attendeeIdPromises).catch((error) => {
208-
console.error("Error connecting children:", error);
209-
});
229+
Promise.all(connectResult.attendeeIdPromises)
230+
.then(() => console.log("All attendees connected."))
231+
.catch((error) => {
232+
testConsole.error("Error connecting children:", error);
233+
});
210234
await timeoutAwait(Promise.race([attendeeConnectedPromise, earlyExitPromise]), {
211235
durationMs: attendeesJoinedTimeoutMs,
212-
errorMsg: "did not receive all 'attendeeConnected' events",
236+
errorMsg: "child 0 did not receive all 'attendeeConnected' events",
213237
});
214238
return connectResult;
215239
}

packages/service-clients/end-to-end-tests/azure-client/src/test/multiprocess/presenceTest.spec.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { strict as assert } from "node:assert";
77
import type { ChildProcess } from "node:child_process";
8+
import inspector from "node:inspector";
89

910
import type { AttendeeId } from "@fluidframework/presence/beta";
1011
import { timeoutPromise } from "@fluidframework/test-utils/internal";
@@ -14,13 +15,45 @@ import {
1415
forkChildProcesses,
1516
connectChildProcesses,
1617
connectAndWaitForAttendees,
18+
getLatestMapValueResponses,
19+
getLatestValueResponses,
1720
registerWorkspaceOnChildren,
18-
waitForLatestValueUpdates,
21+
testConsole,
1922
waitForLatestMapValueUpdates,
20-
getLatestValueResponses,
21-
getLatestMapValueResponses,
23+
waitForLatestValueUpdates,
2224
} from "./orchestratorUtils.js";
2325

26+
const debuggerAttached = inspector.url() !== undefined;
27+
28+
/**
29+
* Set this to a high number when debugging to avoid timeouts from debugging time.
30+
*/
31+
const timeoutMultiplier = debuggerAttached ? 1000 : 1;
32+
33+
/**
34+
* Sets the timeout for the given test context.
35+
*
36+
* @remarks
37+
* If a debugger is attached, the timeout is set to 0 to prevent timeouts during debugging.
38+
* Otherwise, it sets the timeout to the maximum of the current timeout and the specified duration.
39+
*
40+
* @param context - The Mocha test context.
41+
* @param duration - The duration in milliseconds to set the timeout to. Zero disables the timeout.
42+
*/
43+
function setTimeout(context: Mocha.Context, duration: number): void {
44+
const currentTimeout = context.timeout();
45+
const newTimeout =
46+
debuggerAttached || currentTimeout === 0 || duration === 0
47+
? 0
48+
: Math.max(currentTimeout, duration);
49+
if (newTimeout !== currentTimeout) {
50+
testConsole.log(
51+
`${context.test?.title}: setting timeout to ${newTimeout}ms (was ${currentTimeout}ms)`,
52+
);
53+
context.timeout(newTimeout);
54+
}
55+
}
56+
2457
/**
2558
* This test suite is a prototype for a multi-process end to end test for Fluid using the new Presence API on AzureClient.
2659
* In the future we hope to expand and generalize this pattern to broadly test more Fluid features.
@@ -71,11 +104,11 @@ describe(`Presence with AzureClient`, () => {
71104
/**
72105
* Timeout for child processes to connect to container ({@link ConnectedEvent})
73106
*/
74-
const childConnectTimeoutMs = 1000 * numClients;
107+
const childConnectTimeoutMs = 1000 * numClients * timeoutMultiplier;
75108
/**
76109
* Timeout for presence attendees to connect {@link AttendeeConnectedEvent}
77110
*/
78-
const attendeesJoinedTimeoutMs = 2000;
111+
const attendeesJoinedTimeoutMs = (1000 + 200 * numClients) * timeoutMultiplier;
79112
/**
80113
* Timeout for workspace registration {@link WorkspaceRegisteredEvent}
81114
*/
@@ -90,7 +123,9 @@ describe(`Presence with AzureClient`, () => {
90123
const getStateTimeoutMs = 5000;
91124

92125
for (const writeClients of [numClients, 1]) {
93-
it(`announces 'attendeeConnected' when remote client joins session [${numClients} clients, ${writeClients} writers]`, async () => {
126+
it(`announces 'attendeeConnected' when remote client joins session [${numClients} clients, ${writeClients} writers]`, async function () {
127+
setTimeout(this, childConnectTimeoutMs + attendeesJoinedTimeoutMs + 1000);
128+
94129
// Setup
95130
const { children, childErrorPromise } = await forkChildProcesses(
96131
numClients,
@@ -118,6 +153,13 @@ describe(`Presence with AzureClient`, () => {
118153
this.skip();
119154
}
120155

156+
const childDisconnectTimeoutMs = 10_000 * timeoutMultiplier;
157+
158+
setTimeout(
159+
this,
160+
childConnectTimeoutMs + attendeesJoinedTimeoutMs + childDisconnectTimeoutMs + 1000,
161+
);
162+
121163
// Setup
122164
const { children, childErrorPromise } = await forkChildProcesses(
123165
numClients,
@@ -135,8 +177,6 @@ describe(`Presence with AzureClient`, () => {
135177
childErrorPromise,
136178
);
137179

138-
const childDisconnectTimeoutMs = 10_000;
139-
140180
const waitForDisconnected = children.map(async (child, index) =>
141181
index === 0
142182
? Promise.resolve()

0 commit comments

Comments
 (0)