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

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