@fluidframework/test-utils 2.0.0-dev.2.3.0.115467 → 2.0.0-dev.4.1.0.148229

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.
Files changed (68) hide show
  1. package/.eslintrc.js +8 -10
  2. package/README.md +41 -11
  3. package/api-extractor.json +2 -2
  4. package/dist/DriverWrappers.d.ts.map +1 -1
  5. package/dist/DriverWrappers.js.map +1 -1
  6. package/dist/TestConfigs.d.ts.map +1 -1
  7. package/dist/TestConfigs.js +3 -4
  8. package/dist/TestConfigs.js.map +1 -1
  9. package/dist/TestSummaryUtils.d.ts +20 -4
  10. package/dist/TestSummaryUtils.d.ts.map +1 -1
  11. package/dist/TestSummaryUtils.js +41 -33
  12. package/dist/TestSummaryUtils.js.map +1 -1
  13. package/dist/containerUtils.d.ts +29 -0
  14. package/dist/containerUtils.d.ts.map +1 -0
  15. package/dist/containerUtils.js +45 -0
  16. package/dist/containerUtils.js.map +1 -0
  17. package/dist/index.d.ts +5 -4
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +3 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/interfaces.d.ts.map +1 -1
  22. package/dist/interfaces.js.map +1 -1
  23. package/dist/loaderContainerTracker.d.ts +3 -9
  24. package/dist/loaderContainerTracker.d.ts.map +1 -1
  25. package/dist/loaderContainerTracker.js +45 -47
  26. package/dist/loaderContainerTracker.js.map +1 -1
  27. package/dist/localCodeLoader.d.ts.map +1 -1
  28. package/dist/localCodeLoader.js.map +1 -1
  29. package/dist/localLoader.d.ts.map +1 -1
  30. package/dist/localLoader.js.map +1 -1
  31. package/dist/packageVersion.d.ts +1 -1
  32. package/dist/packageVersion.js +1 -1
  33. package/dist/packageVersion.js.map +1 -1
  34. package/dist/retry.d.ts.map +1 -1
  35. package/dist/retry.js.map +1 -1
  36. package/dist/testContainerRuntimeFactory.d.ts +2 -2
  37. package/dist/testContainerRuntimeFactory.d.ts.map +1 -1
  38. package/dist/testContainerRuntimeFactory.js +3 -3
  39. package/dist/testContainerRuntimeFactory.js.map +1 -1
  40. package/dist/testFluidObject.d.ts.map +1 -1
  41. package/dist/testFluidObject.js +7 -3
  42. package/dist/testFluidObject.js.map +1 -1
  43. package/dist/testObjectProvider.d.ts +6 -1
  44. package/dist/testObjectProvider.d.ts.map +1 -1
  45. package/dist/testObjectProvider.js +49 -16
  46. package/dist/testObjectProvider.js.map +1 -1
  47. package/dist/timeoutUtils.d.ts +11 -2
  48. package/dist/timeoutUtils.d.ts.map +1 -1
  49. package/dist/timeoutUtils.js +122 -18
  50. package/dist/timeoutUtils.js.map +1 -1
  51. package/package.json +65 -58
  52. package/prettier.config.cjs +1 -1
  53. package/src/DriverWrappers.ts +40 -37
  54. package/src/TestConfigs.ts +7 -7
  55. package/src/TestSummaryUtils.ts +123 -124
  56. package/src/containerUtils.ts +48 -0
  57. package/src/index.ts +24 -23
  58. package/src/interfaces.ts +10 -7
  59. package/src/loaderContainerTracker.ts +618 -577
  60. package/src/localCodeLoader.ts +85 -77
  61. package/src/localLoader.ts +24 -24
  62. package/src/packageVersion.ts +1 -1
  63. package/src/retry.ts +31 -25
  64. package/src/testContainerRuntimeFactory.ts +59 -56
  65. package/src/testFluidObject.ts +168 -152
  66. package/src/testObjectProvider.ts +477 -384
  67. package/src/timeoutUtils.ts +191 -41
  68. package/tsconfig.json +9 -12
@@ -3,61 +3,211 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { Container } from "@fluidframework/container-loader";
6
+ import { assert, Deferred } from "@fluidframework/common-utils";
7
7
 
8
+ // @deprecated this value is no longer used
8
9
  export const defaultTimeoutDurationMs = 250;
9
10
 
11
+ // TestTimeout class manage tracking of test timeout. It create a timer when timeout is in effect,
12
+ // and provide a promise that will be reject before the test timeout happen with a `timeBuffer` of 15 ms.
13
+ // Once rejected, a new TestTimeout object will be create for the timeout.
14
+
15
+ const timeBuffer = 15; // leave 15 ms leeway for finish processing
16
+
17
+ class TestTimeout {
18
+ private timeout: number = 0;
19
+ private timer: NodeJS.Timeout | undefined;
20
+ private readonly deferred: Deferred<void>;
21
+ private rejected = false;
22
+
23
+ private static instance: TestTimeout = new TestTimeout();
24
+ public static reset(runnable: Mocha.Runnable) {
25
+ TestTimeout.clear();
26
+ TestTimeout.instance.resetTimer(runnable);
27
+ }
28
+
29
+ public static clear() {
30
+ if (TestTimeout.instance.rejected) {
31
+ TestTimeout.instance = new TestTimeout();
32
+ } else {
33
+ TestTimeout.instance.clearTimer();
34
+ }
35
+ }
36
+
37
+ public static getInstance() {
38
+ return TestTimeout.instance;
39
+ }
40
+
41
+ public async getPromise() {
42
+ return this.deferred.promise;
43
+ }
44
+
45
+ public getTimeout() {
46
+ return this.timeout;
47
+ }
48
+
49
+ private constructor() {
50
+ this.deferred = new Deferred();
51
+ // Ignore rejection for timeout promise if no one is waiting for it.
52
+ this.deferred.promise.catch(() => {});
53
+ }
54
+
55
+ private resetTimer(runnable: Mocha.Runnable) {
56
+ assert(!this.timer, "clearTimer should have been called before reset");
57
+ assert(!this.deferred.isCompleted, "can't reset a completed TestTimeout");
58
+
59
+ // Check the test timeout setting
60
+ const timeout = runnable.timeout();
61
+ if (!(Number.isFinite(timeout) && timeout > 0)) {
62
+ return;
63
+ }
64
+
65
+ // subtract a buffer
66
+ this.timeout = Math.max(timeout - timeBuffer, 1);
67
+
68
+ // Set up timer to reject near the test timeout.
69
+ this.timer = setTimeout(() => {
70
+ this.deferred.reject(this);
71
+ this.rejected = true;
72
+ }, this.timeout);
73
+ }
74
+ private clearTimer() {
75
+ if (this.timer) {
76
+ clearTimeout(this.timer);
77
+ this.timer = undefined;
78
+ }
79
+ }
80
+ }
81
+
82
+ // only register if we are running with mocha-test-setup loaded
83
+ if (globalThis.getMochaModule !== undefined) {
84
+ // patching resetTimeout and clearTimeout on the runnable object
85
+ // so we can track when test timeout are enforced
86
+ const mochaModule = globalThis.getMochaModule() as typeof Mocha;
87
+ const runnablePrototype = mochaModule.Runnable.prototype;
88
+ // eslint-disable-next-line @typescript-eslint/unbound-method
89
+ const oldResetTimeoutFunc = runnablePrototype.resetTimeout;
90
+ runnablePrototype.resetTimeout = function (this: Mocha.Runnable) {
91
+ oldResetTimeoutFunc.call(this);
92
+ TestTimeout.reset(this);
93
+ };
94
+ // eslint-disable-next-line @typescript-eslint/unbound-method
95
+ const oldClearTimeoutFunc = runnablePrototype.clearTimeout;
96
+ runnablePrototype.clearTimeout = function (this: Mocha.Runnable) {
97
+ TestTimeout.clear();
98
+ oldClearTimeoutFunc.call(this);
99
+ };
100
+ }
101
+
10
102
  export interface TimeoutWithError {
11
- durationMs?: number;
12
- reject?: true;
13
- errorMsg?: string;
103
+ /**
104
+ * Timeout duration in milliseconds, if it is great than 0 and not Infinity
105
+ * If it is undefined, then it will use test timeout if we are in side the test function
106
+ * Otherwise, there is no timeout
107
+ */
108
+ durationMs?: number;
109
+ reject?: true;
110
+ errorMsg?: string;
14
111
  }
15
112
  export interface TimeoutWithValue<T = void> {
16
- durationMs?: number;
17
- reject: false;
18
- value: T;
113
+ /**
114
+ * Timeout duration in milliseconds, if it is great than 0 and not Infinity
115
+ * If it is undefined, then it will use test timeout if we are in side the test function
116
+ * Otherwise, there is no timeout
117
+ */
118
+ durationMs?: number;
119
+ reject: false;
120
+ value: T;
19
121
  }
20
122
 
123
+ export type PromiseExecutor<T = void> = (
124
+ resolve: (value: T | PromiseLike<T>) => void,
125
+ reject: (reason?: any) => void,
126
+ ) => void;
127
+
21
128
  export async function timeoutAwait<T = void>(
22
- promise: PromiseLike<T>,
23
- timeoutOptions: TimeoutWithError | TimeoutWithValue<T> = {},
129
+ promise: PromiseLike<T>,
130
+ timeoutOptions: TimeoutWithError | TimeoutWithValue<T> = {},
24
131
  ) {
25
- return Promise.race([promise, timeoutPromise<T>(() => { }, timeoutOptions)]);
132
+ return Promise.race([promise, timeoutPromise<T>(() => {}, timeoutOptions)]);
26
133
  }
27
134
 
28
- export async function ensureContainerConnected(container: Container): Promise<void> {
29
- if (!container.connected) {
30
- return timeoutPromise((resolve) => container.once("connected", () => resolve()));
31
- }
135
+ // Create a promise based on the timeout options
136
+ async function getTimeoutPromise<T = void>(
137
+ executor: (
138
+ resolve: (value: T | PromiseLike<T>) => void,
139
+ reject: (reason?: any) => void,
140
+ ) => void,
141
+ timeoutOptions: TimeoutWithError | TimeoutWithValue<T>,
142
+ err: Error | undefined,
143
+ ) {
144
+ const timeout = timeoutOptions.durationMs ?? 0;
145
+ if (timeout <= 0 || !Number.isFinite(timeout)) {
146
+ return new Promise(executor);
147
+ }
148
+
149
+ return new Promise<T>((resolve, reject) => {
150
+ const timeoutRejections = () => {
151
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
152
+ const errorObject = err!;
153
+ errorObject.message = `${errorObject.message} (${timeout}ms)`;
154
+ reject(err);
155
+ };
156
+ const timer = setTimeout(
157
+ () =>
158
+ timeoutOptions.reject === false
159
+ ? resolve(timeoutOptions.value)
160
+ : timeoutRejections(),
161
+ timeout,
162
+ );
163
+
164
+ executor(
165
+ (value) => {
166
+ clearTimeout(timer);
167
+ resolve(value);
168
+ },
169
+ (reason) => {
170
+ clearTimeout(timer);
171
+ reject(reason);
172
+ },
173
+ );
174
+ });
32
175
  }
33
176
 
177
+ // Create a promise based on test timeout and the timeout options
34
178
  export async function timeoutPromise<T = void>(
35
- executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void,
36
- timeoutOptions: TimeoutWithError | TimeoutWithValue<T> = {},
179
+ executor: (
180
+ resolve: (value: T | PromiseLike<T>) => void,
181
+ reject: (reason?: any) => void,
182
+ ) => void,
183
+ timeoutOptions: TimeoutWithError | TimeoutWithValue<T> = {},
37
184
  ): Promise<T> {
38
- const timeout =
39
- timeoutOptions.durationMs !== undefined
40
- && Number.isFinite(timeoutOptions.durationMs)
41
- && timeoutOptions.durationMs > 0
42
- ? timeoutOptions.durationMs : defaultTimeoutDurationMs;
43
- // create the timeout error outside the async task, so its callstack includes
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)`);
48
- return new Promise<T>((resolve, reject) => {
49
- const timer = setTimeout(
50
- () => timeoutOptions.reject === false ? resolve(timeoutOptions.value) : reject(err),
51
- timeout);
52
-
53
- executor(
54
- (value) => {
55
- clearTimeout(timer);
56
- resolve(value);
57
- },
58
- (reason) => {
59
- clearTimeout(timer);
60
- reject(reason);
61
- });
62
- });
185
+ // create the timeout error outside the async task, so its callstack includes
186
+ // the original call site, this makes it easier to debug
187
+ const err =
188
+ timeoutOptions.reject === false
189
+ ? undefined
190
+ : new Error(timeoutOptions.errorMsg ?? "Timed out");
191
+ const executorPromise = getTimeoutPromise(executor, timeoutOptions, err);
192
+
193
+ const currentTestTimeout = TestTimeout.getInstance();
194
+ if (currentTestTimeout === undefined) {
195
+ return executorPromise;
196
+ }
197
+
198
+ return Promise.race([executorPromise, currentTestTimeout.getPromise()]).catch((e) => {
199
+ if (e === currentTestTimeout) {
200
+ if (timeoutOptions.reject !== false) {
201
+ // If the rejection is because of the timeout then
202
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
203
+ const errorObject = err!;
204
+ errorObject.message = `${
205
+ timeoutOptions.errorMsg ?? "Test timed out"
206
+ } (${currentTestTimeout.getTimeout()}ms)`;
207
+ throw errorObject;
208
+ }
209
+ return timeoutOptions.value;
210
+ }
211
+ throw e;
212
+ }) as Promise<T>;
63
213
  }
package/tsconfig.json CHANGED
@@ -1,14 +1,11 @@
1
1
  {
2
- "extends": "@fluidframework/build-common/ts-common-config.json",
3
- "exclude": [
4
- "src/test/**/*"
5
- ],
6
- "compilerOptions": {
7
- "rootDir": "./src",
8
- "outDir": "./dist",
9
- "composite": true
10
- },
11
- "include": [
12
- "src/**/*"
13
- ]
2
+ "extends": "@fluidframework/build-common/ts-common-config.json",
3
+ "exclude": ["src/test/**/*"],
4
+ "compilerOptions": {
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+ "composite": true,
8
+ "types": ["mocha", "node"],
9
+ },
10
+ "include": ["src/**/*"],
14
11
  }