@fluidframework/test-utils 1.4.0-115997 → 2.0.0-dev-rc.1.0.0.224419

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 (98) hide show
  1. package/.eslintrc.js +8 -7
  2. package/.mocharc.js +12 -0
  3. package/CHANGELOG.md +177 -0
  4. package/README.md +44 -14
  5. package/api-extractor-lint.json +4 -0
  6. package/api-extractor.json +2 -2
  7. package/api-report/test-utils.api.md +395 -0
  8. package/dist/DriverWrappers.d.ts +32 -0
  9. package/dist/DriverWrappers.d.ts.map +1 -0
  10. package/dist/DriverWrappers.js +57 -0
  11. package/dist/DriverWrappers.js.map +1 -0
  12. package/dist/TestConfigs.d.ts +10 -0
  13. package/dist/TestConfigs.d.ts.map +1 -0
  14. package/dist/TestConfigs.js +17 -0
  15. package/dist/TestConfigs.js.map +1 -0
  16. package/dist/TestSummaryUtils.d.ts +61 -0
  17. package/dist/TestSummaryUtils.d.ts.map +1 -0
  18. package/dist/TestSummaryUtils.js +135 -0
  19. package/dist/TestSummaryUtils.js.map +1 -0
  20. package/dist/containerUtils.d.ts +46 -0
  21. package/dist/containerUtils.d.ts.map +1 -0
  22. package/dist/containerUtils.js +85 -0
  23. package/dist/containerUtils.js.map +1 -0
  24. package/dist/index.d.ts +14 -9
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +43 -19
  27. package/dist/index.js.map +1 -1
  28. package/dist/interfaces.d.ts +6 -0
  29. package/dist/interfaces.d.ts.map +1 -1
  30. package/dist/interfaces.js.map +1 -1
  31. package/dist/loaderContainerTracker.d.ts +39 -15
  32. package/dist/loaderContainerTracker.d.ts.map +1 -1
  33. package/dist/loaderContainerTracker.js +214 -86
  34. package/dist/loaderContainerTracker.js.map +1 -1
  35. package/dist/localCodeLoader.d.ts +9 -2
  36. package/dist/localCodeLoader.d.ts.map +1 -1
  37. package/dist/localCodeLoader.js +18 -17
  38. package/dist/localCodeLoader.js.map +1 -1
  39. package/dist/localLoader.d.ts +3 -2
  40. package/dist/localLoader.d.ts.map +1 -1
  41. package/dist/localLoader.js +2 -0
  42. package/dist/localLoader.js.map +1 -1
  43. package/dist/packageVersion.d.ts +1 -1
  44. package/dist/packageVersion.d.ts.map +1 -1
  45. package/dist/packageVersion.js +1 -1
  46. package/dist/packageVersion.js.map +1 -1
  47. package/dist/retry.d.ts +1 -0
  48. package/dist/retry.d.ts.map +1 -1
  49. package/dist/retry.js +3 -2
  50. package/dist/retry.js.map +1 -1
  51. package/dist/test-utils-alpha.d.ts +305 -0
  52. package/dist/test-utils-beta.d.ts +202 -0
  53. package/dist/test-utils-public.d.ts +202 -0
  54. package/dist/test-utils-untrimmed.d.ts +1001 -0
  55. package/dist/testContainerRuntimeFactory.d.ts +4 -2
  56. package/dist/testContainerRuntimeFactory.d.ts.map +1 -1
  57. package/dist/testContainerRuntimeFactory.js +67 -12
  58. package/dist/testContainerRuntimeFactory.js.map +1 -1
  59. package/dist/testContainerRuntimeFactoryWithDefaultDataStore.d.ts +23 -0
  60. package/dist/testContainerRuntimeFactoryWithDefaultDataStore.d.ts.map +1 -0
  61. package/dist/testContainerRuntimeFactoryWithDefaultDataStore.js +32 -0
  62. package/dist/testContainerRuntimeFactoryWithDefaultDataStore.js.map +1 -0
  63. package/dist/testFluidObject.d.ts +30 -12
  64. package/dist/testFluidObject.d.ts.map +1 -1
  65. package/dist/testFluidObject.js +74 -43
  66. package/dist/testFluidObject.js.map +1 -1
  67. package/dist/testObjectProvider.d.ts +298 -41
  68. package/dist/testObjectProvider.d.ts.map +1 -1
  69. package/dist/testObjectProvider.js +377 -61
  70. package/dist/testObjectProvider.js.map +1 -1
  71. package/dist/timeoutUtils.d.ts +42 -0
  72. package/dist/timeoutUtils.d.ts.map +1 -1
  73. package/dist/timeoutUtils.js +142 -9
  74. package/dist/timeoutUtils.js.map +1 -1
  75. package/dist/tsdoc-metadata.json +11 -0
  76. package/lib/test-utils-alpha.d.ts +305 -0
  77. package/lib/test-utils-beta.d.ts +202 -0
  78. package/lib/test-utils-public.d.ts +202 -0
  79. package/lib/test-utils-untrimmed.d.ts +1001 -0
  80. package/package.json +106 -72
  81. package/prettier.config.cjs +8 -0
  82. package/src/DriverWrappers.ts +83 -0
  83. package/src/TestConfigs.ts +17 -0
  84. package/src/TestSummaryUtils.ts +210 -0
  85. package/src/containerUtils.ts +90 -0
  86. package/src/index.ts +47 -9
  87. package/src/interfaces.ts +16 -7
  88. package/src/loaderContainerTracker.ts +754 -534
  89. package/src/localCodeLoader.ts +87 -86
  90. package/src/localLoader.ts +27 -26
  91. package/src/packageVersion.ts +1 -1
  92. package/src/retry.ts +33 -26
  93. package/src/testContainerRuntimeFactory.ts +152 -59
  94. package/src/testContainerRuntimeFactoryWithDefaultDataStore.ts +61 -0
  95. package/src/testFluidObject.ts +190 -147
  96. package/src/testObjectProvider.ts +931 -372
  97. package/src/timeoutUtils.ts +226 -37
  98. package/tsconfig.json +9 -12
@@ -3,53 +3,242 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { assert, Deferred } from "@fluidframework/core-utils";
7
+
8
+ // @deprecated this value is no longer used
9
+ /**
10
+ * @internal
11
+ */
6
12
  export const defaultTimeoutDurationMs = 250;
7
13
 
14
+ const timeBuffer = 15; // leave 15 ms leeway for finish processing
15
+
16
+ // TestTimeout class that manages tracking of test timeout. It creates a timer when timeout is in effect,
17
+ // and provides a promise that will be rejected some time (as defined by `timeBuffer`) before the test timeout happens.
18
+ // This will ensure that async awaits in tests do not end up timing out the tests but resolve / reject
19
+ // before that happens.
20
+ // Once rejected, a new TestTimeout object will be create for the timeout.
21
+ class TestTimeout {
22
+ private timeout: number = 0;
23
+ private timer: NodeJS.Timeout | undefined;
24
+ private readonly deferred: Deferred<void>;
25
+ private rejected = false;
26
+
27
+ private static instance: TestTimeout = new TestTimeout();
28
+ public static reset(runnable: Mocha.Runnable) {
29
+ TestTimeout.clear();
30
+ TestTimeout.instance.resetTimer(runnable);
31
+ }
32
+
33
+ public static clear() {
34
+ if (TestTimeout.instance.rejected) {
35
+ TestTimeout.instance = new TestTimeout();
36
+ } else {
37
+ TestTimeout.instance.clearTimer();
38
+ }
39
+ }
40
+
41
+ public static getInstance() {
42
+ return TestTimeout.instance;
43
+ }
44
+
45
+ public async getPromise() {
46
+ return this.deferred.promise;
47
+ }
48
+
49
+ public getTimeout() {
50
+ return this.timeout;
51
+ }
52
+
53
+ private constructor() {
54
+ this.deferred = new Deferred();
55
+ // Ignore rejection for timeout promise if no one is waiting for it.
56
+ this.deferred.promise.catch(() => {});
57
+ }
58
+
59
+ private resetTimer(runnable: Mocha.Runnable) {
60
+ assert(!this.timer, "clearTimer should have been called before reset");
61
+ assert(!this.deferred.isCompleted, "can't reset a completed TestTimeout");
62
+
63
+ // Check the test timeout setting
64
+ const timeout = runnable.timeout();
65
+ if (!(Number.isFinite(timeout) && timeout > 0)) {
66
+ return;
67
+ }
68
+
69
+ // subtract a buffer
70
+ this.timeout = Math.max(timeout - timeBuffer, 1);
71
+
72
+ // Set up timer to reject near the test timeout.
73
+ this.timer = setTimeout(() => {
74
+ this.deferred.reject(this);
75
+ this.rejected = true;
76
+ }, this.timeout);
77
+ }
78
+ private clearTimer() {
79
+ if (this.timer) {
80
+ clearTimeout(this.timer);
81
+ this.timer = undefined;
82
+ }
83
+ }
84
+ }
85
+
86
+ // only register if we are running with mocha-test-setup loaded
87
+ if (globalThis.getMochaModule !== undefined) {
88
+ // patching resetTimeout and clearTimeout on the runnable object
89
+ // so we can track when test timeout are enforced
90
+ const mochaModule = globalThis.getMochaModule() as typeof Mocha;
91
+ const runnablePrototype = mochaModule.Runnable.prototype;
92
+ // eslint-disable-next-line @typescript-eslint/unbound-method
93
+ const oldResetTimeoutFunc = runnablePrototype.resetTimeout;
94
+ runnablePrototype.resetTimeout = function (this: Mocha.Runnable) {
95
+ oldResetTimeoutFunc.call(this);
96
+ TestTimeout.reset(this);
97
+ };
98
+ // eslint-disable-next-line @typescript-eslint/unbound-method
99
+ const oldClearTimeoutFunc = runnablePrototype.clearTimeout;
100
+ runnablePrototype.clearTimeout = function (this: Mocha.Runnable) {
101
+ TestTimeout.clear();
102
+ oldClearTimeoutFunc.call(this);
103
+ };
104
+ }
105
+
106
+ /**
107
+ * @internal
108
+ */
8
109
  export interface TimeoutWithError {
9
- durationMs?: number;
10
- reject?: true;
11
- errorMsg?: string;
110
+ /**
111
+ * Timeout duration in milliseconds, if it is great than 0 and not Infinity
112
+ * If it is undefined, then it will use test timeout if we are in side the test function
113
+ * Otherwise, there is no timeout
114
+ */
115
+ durationMs?: number;
116
+ reject?: true;
117
+ errorMsg?: string;
12
118
  }
119
+ /**
120
+ * @internal
121
+ */
13
122
  export interface TimeoutWithValue<T = void> {
14
- durationMs?: number;
15
- reject: false;
16
- value: T;
123
+ /**
124
+ * Timeout duration in milliseconds, if it is great than 0 and not Infinity
125
+ * If it is undefined, then it will use test timeout if we are in side the test function
126
+ * Otherwise, there is no timeout
127
+ */
128
+ durationMs?: number;
129
+ reject: false;
130
+ value: T;
17
131
  }
18
132
 
133
+ export type PromiseExecutor<T = void> = (
134
+ resolve: (value: T | PromiseLike<T>) => void,
135
+ reject: (reason?: any) => void,
136
+ ) => void;
137
+
138
+ /**
139
+ * Wraps the given promise around with promise that will complete after a specific timeout if the original promise does
140
+ * not resolve by then. By default, it uses the mocha test timeout and complete the promise just before that so that
141
+ * tests don't time out because of unpredictable awaits.
142
+ * The timeout can be overridden via timeoutOptions but it's recommended to use the default value.
143
+ * @param promise - The promise to be awaited.
144
+ * @param timeoutOptions - Options that can be used to override the timeout and / or define the behavior
145
+ * when the promise is not fulfilled. For example, instead of rejecting the promise, resolve with a
146
+ * specific value.
147
+ * @returns A new promise that will complete when the given promise resolves or the timeout expires.
148
+ * @internal
149
+ */
19
150
  export async function timeoutAwait<T = void>(
20
- promise: PromiseLike<T>,
21
- timeoutOptions: TimeoutWithError | TimeoutWithValue<T> = {},
22
- ) {
23
- return Promise.race([promise, timeoutPromise<T>(() => { }, timeoutOptions)]);
151
+ promise: PromiseLike<T>,
152
+ timeoutOptions: TimeoutWithError | TimeoutWithValue<T> = {},
153
+ ): Promise<T> {
154
+ return Promise.race([promise, timeoutPromise<T>(() => {}, timeoutOptions)]);
24
155
  }
25
156
 
157
+ /**
158
+ * Creates a promise from the given executor that will complete after a specific timeout. By default, it uses the mocha
159
+ * test timeout and complete the promise just before that so that tests don't time out because of unpredictable awaits.
160
+ * The timeout can be overridden via timeoutOptions but it's recommended to use the default value.
161
+ * @param executor - The executor for the promise.
162
+ * @param timeoutOptions - Options that can be used to override the timeout and / or define the behavior when
163
+ * the promise is not fulfilled. For example, instead of rejecting the promise, resolve with a specific value.
164
+ * @returns A new promise that will complete when the given executor resolves or the timeout expires.
165
+ * @internal
166
+ */
26
167
  export async function timeoutPromise<T = void>(
27
- executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void,
28
- timeoutOptions: TimeoutWithError | TimeoutWithValue<T> = {},
168
+ executor: (
169
+ resolve: (value: T | PromiseLike<T>) => void,
170
+ reject: (reason?: any) => void,
171
+ ) => void,
172
+ timeoutOptions: TimeoutWithError | TimeoutWithValue<T> = {},
29
173
  ): Promise<T> {
30
- const timeout =
31
- timeoutOptions.durationMs !== undefined
32
- && Number.isFinite(timeoutOptions.durationMs)
33
- && timeoutOptions.durationMs > 0
34
- ? timeoutOptions.durationMs : defaultTimeoutDurationMs;
35
- // create the timeout error outside the async task, so it's callstack includes
36
- // the original call site, this makes it easier to debug
37
- const err = timeoutOptions.reject === false
38
- ? undefined
39
- : new Error(`${timeoutOptions.errorMsg ?? "Timed out"}(${timeout}ms)`);
40
- return new Promise<T>((resolve, reject) => {
41
- const timer = setTimeout(
42
- () => timeoutOptions.reject === false ? resolve(timeoutOptions.value) : reject(err),
43
- timeout);
44
-
45
- executor(
46
- (value) => {
47
- clearTimeout(timer);
48
- resolve(value);
49
- },
50
- (reason) => {
51
- clearTimeout(timer);
52
- reject(reason);
53
- });
54
- });
174
+ // create the timeout error outside the async task, so its callstack includes
175
+ // the original call site, this makes it easier to debug
176
+ const err =
177
+ timeoutOptions.reject === false
178
+ ? undefined
179
+ : new Error(timeoutOptions.errorMsg ?? "Timed out");
180
+ const executorPromise = getTimeoutPromise(executor, timeoutOptions, err);
181
+
182
+ const currentTestTimeout = TestTimeout.getInstance();
183
+ if (currentTestTimeout === undefined) {
184
+ return executorPromise;
185
+ }
186
+
187
+ return Promise.race([executorPromise, currentTestTimeout.getPromise()]).catch((e) => {
188
+ if (e === currentTestTimeout) {
189
+ if (timeoutOptions.reject !== false) {
190
+ // If the rejection is because of the timeout then
191
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
192
+ const errorObject = err!;
193
+ errorObject.message = `${
194
+ timeoutOptions.errorMsg ?? "Test timed out"
195
+ } (${currentTestTimeout.getTimeout()}ms)`;
196
+ throw errorObject;
197
+ }
198
+ return timeoutOptions.value;
199
+ }
200
+ throw e;
201
+ }) as Promise<T>;
202
+ }
203
+
204
+ // Create a promise based on the timeout options
205
+ async function getTimeoutPromise<T = void>(
206
+ executor: (
207
+ resolve: (value: T | PromiseLike<T>) => void,
208
+ reject: (reason?: any) => void,
209
+ ) => void,
210
+ timeoutOptions: TimeoutWithError | TimeoutWithValue<T>,
211
+ err: Error | undefined,
212
+ ) {
213
+ const timeout = timeoutOptions.durationMs ?? 0;
214
+ if (timeout <= 0 || !Number.isFinite(timeout)) {
215
+ return new Promise(executor);
216
+ }
217
+
218
+ return new Promise<T>((resolve, reject) => {
219
+ const timeoutRejections = () => {
220
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
221
+ const errorObject = err!;
222
+ errorObject.message = `${errorObject.message} (${timeout}ms)`;
223
+ reject(err);
224
+ };
225
+ const timer = setTimeout(
226
+ () =>
227
+ timeoutOptions.reject === false
228
+ ? resolve(timeoutOptions.value)
229
+ : timeoutRejections(),
230
+ timeout,
231
+ );
232
+
233
+ executor(
234
+ (value) => {
235
+ clearTimeout(timer);
236
+ resolve(value);
237
+ },
238
+ (reason) => {
239
+ clearTimeout(timer);
240
+ reject(reason);
241
+ },
242
+ );
243
+ });
55
244
  }
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
  }