@fluidframework/test-utils 2.0.0-internal.2.3.1 → 2.0.0-internal.3.0.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.
- package/dist/TestSummaryUtils.d.ts +0 -1
- package/dist/TestSummaryUtils.d.ts.map +1 -1
- package/dist/TestSummaryUtils.js +7 -13
- package/dist/TestSummaryUtils.js.map +1 -1
- package/dist/containerUtils.d.ts +38 -0
- package/dist/containerUtils.d.ts.map +1 -0
- package/dist/containerUtils.js +56 -0
- package/dist/containerUtils.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/loaderContainerTracker.d.ts +0 -4
- package/dist/loaderContainerTracker.d.ts.map +1 -1
- package/dist/loaderContainerTracker.js +2 -11
- package/dist/loaderContainerTracker.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/testContainerRuntimeFactory.d.ts +2 -2
- package/dist/testContainerRuntimeFactory.js +1 -2
- package/dist/testContainerRuntimeFactory.js.map +1 -1
- package/dist/testObjectProvider.d.ts +2 -0
- package/dist/testObjectProvider.d.ts.map +1 -1
- package/dist/testObjectProvider.js +3 -5
- package/dist/testObjectProvider.js.map +1 -1
- package/dist/timeoutUtils.d.ts +11 -0
- package/dist/timeoutUtils.d.ts.map +1 -1
- package/dist/timeoutUtils.js +120 -11
- package/dist/timeoutUtils.js.map +1 -1
- package/package.json +33 -26
- package/src/TestSummaryUtils.ts +5 -11
- package/src/containerUtils.ts +59 -0
- package/src/index.ts +4 -2
- package/src/loaderContainerTracker.ts +0 -11
- package/src/packageVersion.ts +1 -1
- package/src/testContainerRuntimeFactory.ts +1 -1
- package/src/testObjectProvider.ts +17 -15
- package/src/timeoutUtils.ts +151 -14
- package/tsconfig.json +5 -2
package/src/TestSummaryUtils.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { ContainerRuntimeFactoryWithDefaultDataStore } from "@fluidframework/aqueduct";
|
|
7
7
|
import { assert } from "@fluidframework/common-utils";
|
|
8
8
|
import { IContainer, IHostLoader, LoaderHeader } from "@fluidframework/container-definitions";
|
|
9
|
-
import { ConnectionState } from "@fluidframework/container-loader";
|
|
10
9
|
import {
|
|
11
10
|
IGCRuntimeOptions,
|
|
12
11
|
ISummarizer,
|
|
@@ -24,6 +23,8 @@ import { IConfigProviderBase } from "@fluidframework/telemetry-utils";
|
|
|
24
23
|
import { ITelemetryBaseLogger } from "@fluidframework/common-definitions";
|
|
25
24
|
import { ITestContainerConfig, ITestObjectProvider } from "./testObjectProvider";
|
|
26
25
|
import { mockConfigProvider } from "./TestConfigs";
|
|
26
|
+
import { waitForContainerConnection } from "./containerUtils";
|
|
27
|
+
import { timeoutAwait } from "./timeoutUtils";
|
|
27
28
|
|
|
28
29
|
const summarizerClientType = "summarizer";
|
|
29
30
|
|
|
@@ -65,7 +66,6 @@ const defaultSummaryOptions: ISummaryRuntimeOptions = {
|
|
|
65
66
|
maxAckWaitTime: 10000,
|
|
66
67
|
maxOpsSinceLastSummary: 7000,
|
|
67
68
|
initialSummarizerDelayMs: 0,
|
|
68
|
-
summarizerClientElection: false,
|
|
69
69
|
},
|
|
70
70
|
};
|
|
71
71
|
|
|
@@ -142,16 +142,16 @@ export async function createSummarizerWithContainer(
|
|
|
142
142
|
export async function summarizeNow(summarizer: ISummarizer, reason: string = "end-to-end test") {
|
|
143
143
|
const result = summarizer.summarizeOnDemand({ reason });
|
|
144
144
|
|
|
145
|
-
const submitResult = await result.summarySubmitted;
|
|
145
|
+
const submitResult = await timeoutAwait(result.summarySubmitted);
|
|
146
146
|
assert(submitResult.success, "on-demand summary should submit");
|
|
147
147
|
assert(submitResult.data.stage === "submit",
|
|
148
148
|
"on-demand summary submitted data stage should be submit");
|
|
149
149
|
assert(submitResult.data.summaryTree !== undefined, "summary tree should exist");
|
|
150
150
|
|
|
151
|
-
const broadcastResult = await result.summaryOpBroadcasted;
|
|
151
|
+
const broadcastResult = await timeoutAwait(result.summaryOpBroadcasted);
|
|
152
152
|
assert(broadcastResult.success, "summary op should be broadcast");
|
|
153
153
|
|
|
154
|
-
const ackNackResult = await result.receivedSummaryAckOrNack;
|
|
154
|
+
const ackNackResult = await timeoutAwait(result.receivedSummaryAckOrNack);
|
|
155
155
|
assert(ackNackResult.success, "summary op should be acked");
|
|
156
156
|
|
|
157
157
|
await new Promise((resolve) => process.nextTick(resolve));
|
|
@@ -162,9 +162,3 @@ export async function summarizeNow(summarizer: ISummarizer, reason: string = "en
|
|
|
162
162
|
summaryRefSeq: submitResult.data.referenceSequenceNumber,
|
|
163
163
|
};
|
|
164
164
|
}
|
|
165
|
-
|
|
166
|
-
export async function waitForContainerConnection(container: IContainer): Promise<void> {
|
|
167
|
-
if (container.connectionState !== ConnectionState.Connected) {
|
|
168
|
-
return new Promise((resolve) => container.once("connected", () => resolve()));
|
|
169
|
-
}
|
|
170
|
-
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IContainer } from "@fluidframework/container-definitions";
|
|
7
|
+
import { ConnectionState, Container } from "@fluidframework/container-loader";
|
|
8
|
+
import { PromiseExecutor, timeoutPromise, TimeoutWithError } from "./timeoutUtils";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Waits for the specified container to emit a 'connected' event.
|
|
12
|
+
*
|
|
13
|
+
* @deprecated Use waitForContainerConnection instead.
|
|
14
|
+
* Note that an upcoming release will change the default parameters on that function to:
|
|
15
|
+
* - failOnContainerClose = true
|
|
16
|
+
* - timeoutOptions.durationMs = 1s
|
|
17
|
+
*/
|
|
18
|
+
export async function ensureContainerConnected(container: Container): Promise<void> {
|
|
19
|
+
if (!container.connected) {
|
|
20
|
+
return timeoutPromise((resolve) => container.once("connected", () => resolve()));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Utility function to wait for the specified Container to be in Connected state.
|
|
26
|
+
* If the Container is already connected, the Promise returns immediately; otherwise it resolves when the Container emits
|
|
27
|
+
* its 'connected' event.
|
|
28
|
+
* If failOnContainerClose === true, the returned Promise will be rejected if the container emits a 'closed' event
|
|
29
|
+
* before a 'connected' event.
|
|
30
|
+
* @param container - The container to wait for.
|
|
31
|
+
* @param failOnContainerClose - If true, the returned Promise will be rejected if the container emits a 'closed' event
|
|
32
|
+
* before a 'connected' event.
|
|
33
|
+
* Defaults to false (but this will change in an upcoming version).
|
|
34
|
+
* @param timeoutOptions - Options related to the behavior of the timeout.
|
|
35
|
+
* If not provided, no timeout will be applied and the promise will wait indefinitely for the Container to emit its
|
|
36
|
+
* 'connected' (or 'closed, if failOnContainerClose === true) event.
|
|
37
|
+
* @returns A Promise that resolves when the specified container emits a 'connected' event (or immediately if the
|
|
38
|
+
* Container is already connected).
|
|
39
|
+
* If failOnContainerClose === true and the container emits a 'closed' event before a 'connected' event, the Promise
|
|
40
|
+
* is rejected with the error from the 'closed' event, if any.
|
|
41
|
+
* If timeoutOptions is provided, the Promise will reject if the container hasn't emmited a relevant event before
|
|
42
|
+
* timeoutOptions.durationMs (which defaults to 250ms if left undefined).
|
|
43
|
+
*/
|
|
44
|
+
export async function waitForContainerConnection(
|
|
45
|
+
container: IContainer,
|
|
46
|
+
failOnContainerClose: boolean = false,
|
|
47
|
+
timeoutOptions?: TimeoutWithError): Promise<void> {
|
|
48
|
+
if (container.connectionState !== ConnectionState.Connected) {
|
|
49
|
+
|
|
50
|
+
const executor: PromiseExecutor = (resolve, reject) => {
|
|
51
|
+
container.once("connected", () => resolve())
|
|
52
|
+
if (failOnContainerClose) {
|
|
53
|
+
container.once("closed", (error) => reject(error));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return timeoutOptions === undefined ? new Promise(executor) : timeoutPromise(executor, timeoutOptions);
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -27,13 +27,15 @@ export {
|
|
|
27
27
|
createSummarizerFromFactory,
|
|
28
28
|
createSummarizerWithContainer,
|
|
29
29
|
summarizeNow,
|
|
30
|
-
waitForContainerConnection,
|
|
31
30
|
} from "./TestSummaryUtils";
|
|
32
31
|
export {
|
|
33
32
|
defaultTimeoutDurationMs,
|
|
34
|
-
ensureContainerConnected,
|
|
35
33
|
timeoutAwait,
|
|
36
34
|
timeoutPromise,
|
|
37
35
|
TimeoutWithError,
|
|
38
36
|
TimeoutWithValue,
|
|
39
37
|
} from "./timeoutUtils";
|
|
38
|
+
export {
|
|
39
|
+
ensureContainerConnected,
|
|
40
|
+
waitForContainerConnection,
|
|
41
|
+
} from "./containerUtils";
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
|
6
|
-
|
|
7
5
|
import { assert } from "@fluidframework/common-utils";
|
|
8
6
|
import { IContainer, IDeltaQueue, IHostLoader } from "@fluidframework/container-definitions";
|
|
9
7
|
import { Container } from "@fluidframework/container-loader";
|
|
@@ -16,9 +14,6 @@ import { timeoutAwait, timeoutPromise } from "./timeoutUtils";
|
|
|
16
14
|
const debugOp = debug.extend("ops");
|
|
17
15
|
const debugWait = debug.extend("wait");
|
|
18
16
|
|
|
19
|
-
// set the maximum timeout value as 5 mins
|
|
20
|
-
const defaultMaxTimeout = 5 * 6000;
|
|
21
|
-
|
|
22
17
|
interface ContainerRecord {
|
|
23
18
|
// A short number for debug output
|
|
24
19
|
index: number;
|
|
@@ -187,7 +182,6 @@ export class LoaderContainerTracker implements IOpProcessingController {
|
|
|
187
182
|
* - Trailing NoOp is tracked and don't count as pending ops.
|
|
188
183
|
*/
|
|
189
184
|
private async processSynchronized(timeoutDuration: number | undefined, ...containers: IContainer[]) {
|
|
190
|
-
const start = Date.now();
|
|
191
185
|
const resumed = this.resumeProcessing(...containers);
|
|
192
186
|
|
|
193
187
|
let waitingSequenceNumberSynchronized = false;
|
|
@@ -214,14 +208,12 @@ export class LoaderContainerTracker implements IOpProcessingController {
|
|
|
214
208
|
waitingSequenceNumberSynchronized = true;
|
|
215
209
|
debugWait("Waiting for sequence number synchronized");
|
|
216
210
|
await timeoutAwait(this.waitForAnyInboundOps(containersToApply), {
|
|
217
|
-
durationMs: timeoutDuration ? timeoutDuration - (Date.now() - start) : defaultMaxTimeout,
|
|
218
211
|
errorMsg: "Timeout on waiting for sequence number synchronized",
|
|
219
212
|
});
|
|
220
213
|
}
|
|
221
214
|
} else {
|
|
222
215
|
waitingSequenceNumberSynchronized = false;
|
|
223
216
|
await timeoutAwait(this.waitForPendingClients(pendingClients), {
|
|
224
|
-
durationMs: timeoutDuration ? timeoutDuration - (Date.now() - start) : defaultMaxTimeout,
|
|
225
217
|
errorMsg: "Timeout on waiting for pending join or leave op",
|
|
226
218
|
});
|
|
227
219
|
}
|
|
@@ -230,12 +222,10 @@ export class LoaderContainerTracker implements IOpProcessingController {
|
|
|
230
222
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
231
223
|
debugWait(`Waiting container to be saved ${dirtyContainers.map((c) => this.containers.get(c)!.index)}`);
|
|
232
224
|
waitingSequenceNumberSynchronized = false;
|
|
233
|
-
const remainedDuration = timeoutDuration ? timeoutDuration - (Date.now() - start) : defaultMaxTimeout;
|
|
234
225
|
await Promise.all(dirtyContainers.map(async (c) => Promise.race(
|
|
235
226
|
[timeoutPromise(
|
|
236
227
|
(resolve) => c.once("saved", () => resolve()),
|
|
237
228
|
{
|
|
238
|
-
durationMs: remainedDuration,
|
|
239
229
|
errorMsg: "Timeout on waiting a container to be saved",
|
|
240
230
|
},
|
|
241
231
|
),
|
|
@@ -252,7 +242,6 @@ export class LoaderContainerTracker implements IOpProcessingController {
|
|
|
252
242
|
// don't call pause if resumed is empty and pause everything, which is not what we want
|
|
253
243
|
if (resumed.length !== 0) {
|
|
254
244
|
await timeoutAwait(this.pauseProcessing(...resumed), {
|
|
255
|
-
durationMs: timeoutDuration ? timeoutDuration - (Date.now() - start) : defaultMaxTimeout,
|
|
256
245
|
errorMsg: "Timeout on waiting for pausing all resumed containers",
|
|
257
246
|
});
|
|
258
247
|
}
|
package/src/packageVersion.ts
CHANGED
|
@@ -88,6 +88,9 @@ export interface ITestContainerConfig {
|
|
|
88
88
|
/** Container runtime options for the container instance */
|
|
89
89
|
runtimeOptions?: IContainerRuntimeOptions;
|
|
90
90
|
|
|
91
|
+
/** Whether this runtime should be instantiated using a mixed-in attributor class */
|
|
92
|
+
enableAttribution?: boolean;
|
|
93
|
+
|
|
91
94
|
/** Loader options for the loader used to create containers */
|
|
92
95
|
loaderProps?: Partial<ILoaderProps>;
|
|
93
96
|
}
|
|
@@ -146,7 +149,7 @@ export class EventAndErrorTrackingLogger extends TelemetryLogger {
|
|
|
146
149
|
private readonly expectedEvents: ({ index: number; event: ITelemetryGenericEvent | undefined; } | undefined)[] = [];
|
|
147
150
|
private readonly unexpectedErrors: ITelemetryBaseEvent[] = [];
|
|
148
151
|
|
|
149
|
-
public registerExpectedEvent(...
|
|
152
|
+
public registerExpectedEvent(...orderedExpectedEvents: ITelemetryGenericEvent[]) {
|
|
150
153
|
if (this.expectedEvents.length !== 0) {
|
|
151
154
|
// we don't have to error here. just no reason not to. given the events must be
|
|
152
155
|
// ordered it could be tricky to figure out problems around multiple registrations.
|
|
@@ -154,7 +157,7 @@ export class EventAndErrorTrackingLogger extends TelemetryLogger {
|
|
|
154
157
|
"Expected events already registered.\n"
|
|
155
158
|
+ "Call reportAndClearTrackedEvents to clear them before registering more");
|
|
156
159
|
}
|
|
157
|
-
this.expectedEvents.push(...
|
|
160
|
+
this.expectedEvents.push(...orderedExpectedEvents.map((event, index) => ({ index, event })));
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
send(event: ITelemetryBaseEvent): void {
|
|
@@ -229,14 +232,14 @@ export class TestObjectProvider implements ITestObjectProvider {
|
|
|
229
232
|
if (this._logger === undefined) {
|
|
230
233
|
this._logger = new EventAndErrorTrackingLogger(
|
|
231
234
|
ChildLogger.create(getTestLogger?.(), undefined,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
{
|
|
236
|
+
all: {
|
|
237
|
+
driverType: this.driver.type,
|
|
238
|
+
driverEndpointName: this.driver.endpointName,
|
|
239
|
+
driverTenantName: this.driver.tenantName,
|
|
240
|
+
driverUserIndex: this.driver.userIndex,
|
|
241
|
+
},
|
|
242
|
+
}));
|
|
240
243
|
}
|
|
241
244
|
return this._logger;
|
|
242
245
|
}
|
|
@@ -290,7 +293,7 @@ export class TestObjectProvider implements ITestObjectProvider {
|
|
|
290
293
|
}
|
|
291
294
|
|
|
292
295
|
const loader = new this.LoaderConstructor({
|
|
293
|
-
...
|
|
296
|
+
...loaderProps,
|
|
294
297
|
logger: multiSinkLogger,
|
|
295
298
|
codeLoader: loaderProps?.codeLoader ?? new LocalCodeLoader(packageEntries),
|
|
296
299
|
urlResolver: loaderProps?.urlResolver ?? this.urlResolver,
|
|
@@ -404,10 +407,9 @@ export class TestObjectProvider implements ITestObjectProvider {
|
|
|
404
407
|
}
|
|
405
408
|
|
|
406
409
|
public async ensureSynchronized(timeoutDuration?: number): Promise<void> {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
: this._loaderContainerTracker.ensureSynchronizedWithTimeout?.(timeoutDuration);
|
|
410
|
+
return this._loaderContainerTracker.ensureSynchronizedWithTimeout
|
|
411
|
+
? this._loaderContainerTracker.ensureSynchronizedWithTimeout(timeoutDuration)
|
|
412
|
+
: this._loaderContainerTracker.ensureSynchronized();
|
|
411
413
|
}
|
|
412
414
|
|
|
413
415
|
public async waitContainerToCatchUp(container: IContainer) {
|
package/src/timeoutUtils.ts
CHANGED
|
@@ -4,20 +4,123 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Container } from "@fluidframework/container-loader";
|
|
7
|
+
import { assert, Deferred } from "@fluidframework/common-utils";
|
|
7
8
|
|
|
9
|
+
// @deprecated this value is no longer used
|
|
8
10
|
export const defaultTimeoutDurationMs = 250;
|
|
9
11
|
|
|
12
|
+
// TestTimeout class manage tracking of test timeout. It create a timer when timeout is in effect,
|
|
13
|
+
// and provide a promise that will be reject before the test timeout happen with a `timeBuffer` of 15 ms.
|
|
14
|
+
// Once rejected, a new TestTimeout object will be create for the timeout.
|
|
15
|
+
|
|
16
|
+
const timeBuffer = 15; // leave 15 ms leeway for finish processing
|
|
17
|
+
|
|
18
|
+
class TestTimeout {
|
|
19
|
+
private timeout: number = 0;
|
|
20
|
+
private timer: NodeJS.Timeout | undefined;
|
|
21
|
+
private readonly deferred: Deferred<void>;
|
|
22
|
+
private rejected = false;
|
|
23
|
+
|
|
24
|
+
private static instance: TestTimeout = new TestTimeout();
|
|
25
|
+
public static reset(runnable: Mocha.Runnable) {
|
|
26
|
+
TestTimeout.clear();
|
|
27
|
+
TestTimeout.instance.resetTimer(runnable);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public static clear() {
|
|
31
|
+
if (TestTimeout.instance.rejected) {
|
|
32
|
+
TestTimeout.instance = new TestTimeout();
|
|
33
|
+
} else {
|
|
34
|
+
TestTimeout.instance.clearTimer();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public static getInstance() {
|
|
39
|
+
return TestTimeout.instance;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async getPromise() {
|
|
43
|
+
return this.deferred.promise;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public getTimeout() {
|
|
47
|
+
return this.timeout;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private constructor() {
|
|
51
|
+
this.deferred = new Deferred();
|
|
52
|
+
// Ignore rejection for timeout promise if no one is waiting for it.
|
|
53
|
+
this.deferred.promise.catch(() => { });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private resetTimer(runnable: Mocha.Runnable) {
|
|
57
|
+
assert(!this.timer, "clearTimer should have been called before reset");
|
|
58
|
+
assert(!this.deferred.isCompleted, "can't reset a completed TestTimeout");
|
|
59
|
+
|
|
60
|
+
// Check the test timeout setting
|
|
61
|
+
const timeout = runnable.timeout();
|
|
62
|
+
if (!(Number.isFinite(timeout) && timeout > 0)) { return; }
|
|
63
|
+
|
|
64
|
+
// subtract a buffer
|
|
65
|
+
this.timeout = Math.max(timeout - timeBuffer, 1);
|
|
66
|
+
|
|
67
|
+
// Set up timer to reject near the test timeout.
|
|
68
|
+
this.timer = setTimeout(() => {
|
|
69
|
+
this.deferred.reject(this);
|
|
70
|
+
this.rejected = true;
|
|
71
|
+
}, this.timeout);
|
|
72
|
+
}
|
|
73
|
+
private clearTimer() {
|
|
74
|
+
if (this.timer) {
|
|
75
|
+
clearTimeout(this.timer);
|
|
76
|
+
this.timer = undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// only register if we are running with mocha-test-setup loaded
|
|
82
|
+
if (globalThis.getMochaModule !== undefined) {
|
|
83
|
+
// patching resetTimeout and clearTimeout on the runnable object
|
|
84
|
+
// so we can track when test timeout are enforced
|
|
85
|
+
const mochaModule = globalThis.getMochaModule() as typeof Mocha;
|
|
86
|
+
const runnablePrototype = mochaModule.Runnable.prototype;
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
88
|
+
const oldResetTimeoutFunc = runnablePrototype.resetTimeout;
|
|
89
|
+
runnablePrototype.resetTimeout = function(this: Mocha.Runnable) {
|
|
90
|
+
oldResetTimeoutFunc.call(this);
|
|
91
|
+
TestTimeout.reset(this);
|
|
92
|
+
};
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
94
|
+
const oldClearTimeoutFunc = runnablePrototype.clearTimeout;
|
|
95
|
+
runnablePrototype.clearTimeout = function(this: Mocha.Runnable) {
|
|
96
|
+
TestTimeout.clear();
|
|
97
|
+
oldClearTimeoutFunc.call(this);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
10
101
|
export interface TimeoutWithError {
|
|
102
|
+
/**
|
|
103
|
+
* Timeout duration in milliseconds, if it is great than 0 and not Infinity
|
|
104
|
+
* If it is undefined, then it will use test timeout if we are in side the test function
|
|
105
|
+
* Otherwise, there is no timeout
|
|
106
|
+
*/
|
|
11
107
|
durationMs?: number;
|
|
12
108
|
reject?: true;
|
|
13
109
|
errorMsg?: string;
|
|
14
110
|
}
|
|
15
111
|
export interface TimeoutWithValue<T = void> {
|
|
112
|
+
/**
|
|
113
|
+
* Timeout duration in milliseconds, if it is great than 0 and not Infinity
|
|
114
|
+
* If it is undefined, then it will use test timeout if we are in side the test function
|
|
115
|
+
* Otherwise, there is no timeout
|
|
116
|
+
*/
|
|
16
117
|
durationMs?: number;
|
|
17
118
|
reject: false;
|
|
18
119
|
value: T;
|
|
19
120
|
}
|
|
20
121
|
|
|
122
|
+
export type PromiseExecutor<T = void> = (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void;
|
|
123
|
+
|
|
21
124
|
export async function timeoutAwait<T = void>(
|
|
22
125
|
promise: PromiseLike<T>,
|
|
23
126
|
timeoutOptions: TimeoutWithError | TimeoutWithValue<T> = {},
|
|
@@ -31,23 +134,26 @@ export async function ensureContainerConnected(container: Container): Promise<vo
|
|
|
31
134
|
}
|
|
32
135
|
}
|
|
33
136
|
|
|
34
|
-
|
|
137
|
+
// Create a promise based on the timeout options
|
|
138
|
+
async function getTimeoutPromise<T = void>(
|
|
35
139
|
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void,
|
|
36
|
-
timeoutOptions: TimeoutWithError | TimeoutWithValue<T
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// the original call site, this makes it easier to debug
|
|
45
|
-
const err = timeoutOptions.reject === false
|
|
46
|
-
? undefined
|
|
47
|
-
: new Error(`${timeoutOptions.errorMsg ?? "Timed out"}(${timeout}ms)`);
|
|
140
|
+
timeoutOptions: TimeoutWithError | TimeoutWithValue<T>,
|
|
141
|
+
err: Error | undefined,
|
|
142
|
+
) {
|
|
143
|
+
const timeout = timeoutOptions.durationMs ?? 0;
|
|
144
|
+
if (timeout <= 0 || !Number.isFinite(timeout)) {
|
|
145
|
+
return new Promise(executor);
|
|
146
|
+
}
|
|
147
|
+
|
|
48
148
|
return new Promise<T>((resolve, reject) => {
|
|
149
|
+
const timeoutRejections = () => {
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
151
|
+
const errorObject = err!;
|
|
152
|
+
errorObject.message = `${errorObject.message} (${timeout}ms)`;
|
|
153
|
+
reject(err);
|
|
154
|
+
};
|
|
49
155
|
const timer = setTimeout(
|
|
50
|
-
() => timeoutOptions.reject === false ? resolve(timeoutOptions.value) :
|
|
156
|
+
() => timeoutOptions.reject === false ? resolve(timeoutOptions.value) : timeoutRejections(),
|
|
51
157
|
timeout);
|
|
52
158
|
|
|
53
159
|
executor(
|
|
@@ -61,3 +167,34 @@ export async function timeoutPromise<T = void>(
|
|
|
61
167
|
});
|
|
62
168
|
});
|
|
63
169
|
}
|
|
170
|
+
|
|
171
|
+
// Create a promise based on test timeout and the timeout options
|
|
172
|
+
export async function timeoutPromise<T = void>(
|
|
173
|
+
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void,
|
|
174
|
+
timeoutOptions: TimeoutWithError | TimeoutWithValue<T> = {},
|
|
175
|
+
): Promise<T> {
|
|
176
|
+
// create the timeout error outside the async task, so its callstack includes
|
|
177
|
+
// the original call site, this makes it easier to debug
|
|
178
|
+
const err = timeoutOptions.reject === false
|
|
179
|
+
? undefined
|
|
180
|
+
: new Error(timeoutOptions.errorMsg ?? "Timed out");
|
|
181
|
+
const executorPromise = getTimeoutPromise(executor, timeoutOptions, err);
|
|
182
|
+
|
|
183
|
+
const currentTestTimeout = TestTimeout.getInstance();
|
|
184
|
+
if (currentTestTimeout === undefined) { return executorPromise; }
|
|
185
|
+
|
|
186
|
+
return Promise.race([executorPromise, currentTestTimeout.getPromise()]).catch((e) => {
|
|
187
|
+
if (e === currentTestTimeout) {
|
|
188
|
+
if (timeoutOptions.reject !== false) {
|
|
189
|
+
// If the rejection is because of the timeout then
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
191
|
+
const errorObject = err!;
|
|
192
|
+
errorObject.message =
|
|
193
|
+
`${timeoutOptions.errorMsg ?? "Test timed out"} (${currentTestTimeout.getTimeout()}ms)`;
|
|
194
|
+
throw errorObject;
|
|
195
|
+
}
|
|
196
|
+
return timeoutOptions.value;
|
|
197
|
+
}
|
|
198
|
+
throw e;
|
|
199
|
+
}) as Promise<T>;
|
|
200
|
+
}
|