@fluidframework/test-utils 2.0.0-internal.2.4.0 → 2.0.0-internal.3.0.1
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.js +4 -4
- package/dist/TestSummaryUtils.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/testObjectProvider.d.ts.map +1 -1
- package/dist/testObjectProvider.js +3 -5
- package/dist/testObjectProvider.js.map +1 -1
- package/dist/timeoutUtils.d.ts +12 -0
- package/dist/timeoutUtils.d.ts.map +1 -1
- package/dist/timeoutUtils.js +127 -12
- package/dist/timeoutUtils.js.map +1 -1
- package/package.json +28 -23
- package/src/TestSummaryUtils.ts +4 -4
- package/src/loaderContainerTracker.ts +0 -11
- package/src/packageVersion.ts +1 -1
- package/src/testObjectProvider.ts +14 -15
- package/src/timeoutUtils.ts +157 -14
- package/tsconfig.json +5 -2
package/src/timeoutUtils.ts
CHANGED
|
@@ -3,14 +3,117 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { Container } from "@fluidframework/container-loader";
|
|
7
|
+
import { assert, Deferred } from "@fluidframework/common-utils";
|
|
8
|
+
|
|
9
|
+
// @deprecated this value is no longer used
|
|
6
10
|
export const defaultTimeoutDurationMs = 250;
|
|
7
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
|
+
|
|
8
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
|
+
*/
|
|
9
107
|
durationMs?: number;
|
|
10
108
|
reject?: true;
|
|
11
109
|
errorMsg?: string;
|
|
12
110
|
}
|
|
13
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
|
+
*/
|
|
14
117
|
durationMs?: number;
|
|
15
118
|
reject: false;
|
|
16
119
|
value: T;
|
|
@@ -25,23 +128,32 @@ export async function timeoutAwait<T = void>(
|
|
|
25
128
|
return Promise.race([promise, timeoutPromise<T>(() => { }, timeoutOptions)]);
|
|
26
129
|
}
|
|
27
130
|
|
|
28
|
-
export async function
|
|
131
|
+
export async function ensureContainerConnected(container: Container): Promise<void> {
|
|
132
|
+
if (!container.connected) {
|
|
133
|
+
return timeoutPromise((resolve) => container.once("connected", () => resolve()));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Create a promise based on the timeout options
|
|
138
|
+
async function getTimeoutPromise<T = void>(
|
|
29
139
|
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void,
|
|
30
|
-
timeoutOptions: TimeoutWithError | TimeoutWithValue<T
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// the original call site, this makes it easier to debug
|
|
39
|
-
const err = timeoutOptions.reject === false
|
|
40
|
-
? undefined
|
|
41
|
-
: 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
|
+
|
|
42
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
|
+
};
|
|
43
155
|
const timer = setTimeout(
|
|
44
|
-
() => timeoutOptions.reject === false ? resolve(timeoutOptions.value) :
|
|
156
|
+
() => timeoutOptions.reject === false ? resolve(timeoutOptions.value) : timeoutRejections(),
|
|
45
157
|
timeout);
|
|
46
158
|
|
|
47
159
|
executor(
|
|
@@ -55,3 +167,34 @@ export async function timeoutPromise<T = void>(
|
|
|
55
167
|
});
|
|
56
168
|
});
|
|
57
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
|
+
}
|