@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.
Files changed (40) hide show
  1. package/dist/TestSummaryUtils.d.ts +0 -1
  2. package/dist/TestSummaryUtils.d.ts.map +1 -1
  3. package/dist/TestSummaryUtils.js +7 -13
  4. package/dist/TestSummaryUtils.js.map +1 -1
  5. package/dist/containerUtils.d.ts +38 -0
  6. package/dist/containerUtils.d.ts.map +1 -0
  7. package/dist/containerUtils.js +56 -0
  8. package/dist/containerUtils.js.map +1 -0
  9. package/dist/index.d.ts +3 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/loaderContainerTracker.d.ts +0 -4
  14. package/dist/loaderContainerTracker.d.ts.map +1 -1
  15. package/dist/loaderContainerTracker.js +2 -11
  16. package/dist/loaderContainerTracker.js.map +1 -1
  17. package/dist/packageVersion.d.ts +1 -1
  18. package/dist/packageVersion.js +1 -1
  19. package/dist/packageVersion.js.map +1 -1
  20. package/dist/testContainerRuntimeFactory.d.ts +2 -2
  21. package/dist/testContainerRuntimeFactory.js +1 -2
  22. package/dist/testContainerRuntimeFactory.js.map +1 -1
  23. package/dist/testObjectProvider.d.ts +2 -0
  24. package/dist/testObjectProvider.d.ts.map +1 -1
  25. package/dist/testObjectProvider.js +3 -5
  26. package/dist/testObjectProvider.js.map +1 -1
  27. package/dist/timeoutUtils.d.ts +11 -0
  28. package/dist/timeoutUtils.d.ts.map +1 -1
  29. package/dist/timeoutUtils.js +120 -11
  30. package/dist/timeoutUtils.js.map +1 -1
  31. package/package.json +33 -26
  32. package/src/TestSummaryUtils.ts +5 -11
  33. package/src/containerUtils.ts +59 -0
  34. package/src/index.ts +4 -2
  35. package/src/loaderContainerTracker.ts +0 -11
  36. package/src/packageVersion.ts +1 -1
  37. package/src/testContainerRuntimeFactory.ts +1 -1
  38. package/src/testObjectProvider.ts +17 -15
  39. package/src/timeoutUtils.ts +151 -14
  40. package/tsconfig.json +5 -2
@@ -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
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/test-utils";
9
- export const pkgVersion = "2.0.0-internal.2.3.1";
9
+ export const pkgVersion = "2.0.0-internal.3.0.0";
@@ -67,7 +67,7 @@ export const createTestContainerRuntimeFactory = (containerRuntimeCtor: typeof C
67
67
  ...this.requestHandlers,
68
68
  ),
69
69
  this.runtimeOptions,
70
- undefined, // containerScope
70
+ context.scope,
71
71
  existing,
72
72
  );
73
73
 
@@ -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(... orderedExpectedEvents: ITelemetryGenericEvent[]) {
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(... orderedExpectedEvents.map((event, index) => ({ index, event })));
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
- all: {
234
- driverType: this.driver.type,
235
- driverEndpointName: this.driver.endpointName,
236
- driverTenantName: this.driver.tenantName,
237
- driverUserIndex: this.driver.userIndex,
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
- ... loaderProps,
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
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
408
- return !timeoutDuration
409
- ? this._loaderContainerTracker.ensureSynchronized()
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) {
@@ -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
- export async function timeoutPromise<T = void>(
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
- ): 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)`);
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) : reject(err),
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
+ }
package/tsconfig.json CHANGED
@@ -6,8 +6,11 @@
6
6
  "compilerOptions": {
7
7
  "rootDir": "./src",
8
8
  "outDir": "./dist",
9
- "types": ["node"],
10
- "composite": true
9
+ "composite": true,
10
+ "types": [
11
+ "mocha",
12
+ "node"
13
+ ]
11
14
  },
12
15
  "include": [
13
16
  "src/**/*"