How to use p4Builder method in fast-check-monorepo

Best JavaScript code snippet using fast-check-monorepo

SchedulerImplem.spec.ts

Source:SchedulerImplem.spec.ts Github

copy

Full Screen

1import fc from 'fast-check';2import {3 ScheduledTask,4 SchedulerImplem,5 TaskSelector,6} from '../../../../../src/arbitrary/_internals/implementations/SchedulerImplem';7import { Scheduler } from '../../../../../src/arbitrary/_internals/interfaces/Scheduler';8import { cloneMethod, hasCloneMethod } from '../../../../../src/check/symbols';9function beforeEachHook() {10 jest.resetModules();11 jest.restoreAllMocks();12 fc.configureGlobal({ beforeEach: beforeEachHook });13}14beforeEach(beforeEachHook);15const buildUnresolved = () => {16 let resolved = false;17 let resolve = () => {};18 const p = new Promise<void>(19 (r) =>20 (resolve = () => {21 resolved = true;22 r();23 })24 );25 return { p, resolve, hasBeenResolved: () => resolved };26};27const delay = () => new Promise((r) => setTimeout(r, 0));28describe('SchedulerImplem', () => {29 describe('waitOne', () => {30 it('should throw when there is no scheduled promise in the pipe', async () => {31 // Arrange32 const act = jest.fn().mockImplementation((f) => f());33 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex: jest.fn() };34 // Act35 const s = new SchedulerImplem(act, taskSelector);36 // Assert37 await expect(s.waitOne()).rejects.toMatchInlineSnapshot(`[Error: No task scheduled]`);38 });39 it('should wrap waitOne call using act whenever specified', async () => {40 // Arrange41 const act = jest.fn().mockImplementation((f) => f());42 const nextTaskIndexLengths: number[] = [];43 const nextTaskIndex = jest.fn().mockImplementation((tasks: ScheduledTask<unknown>[]) => {44 // `tasks` pointer being re-used from one call to another (mutate)45 // we cannot rely on toHaveBeenCalledWith46 nextTaskIndexLengths.push(tasks.length);47 return 0;48 });49 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };50 // Act51 const s = new SchedulerImplem(act, taskSelector);52 s.schedule(Promise.resolve(42));53 // Assert54 expect(act).not.toHaveBeenCalled();55 expect(nextTaskIndex).not.toHaveBeenCalled();56 await s.waitOne();57 expect(act).toHaveBeenCalledTimes(1);58 expect(nextTaskIndex).toHaveBeenCalledTimes(1);59 expect(nextTaskIndexLengths).toEqual([1]); // only one task scheduled60 });61 it('should wait the end of act before resolving waitOne', async () => {62 // Arrange63 const p1 = buildUnresolved();64 const p2 = buildUnresolved();65 const act = jest.fn().mockImplementation(async (f) => {66 await p1.p;67 await f();68 await p2.p;69 });70 const nextTaskIndex = jest.fn().mockReturnValue(0);71 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };72 // Act73 let promiseResolved = false;74 let waitOneResolved = false;75 const s = new SchedulerImplem(act, taskSelector);76 s.schedule(Promise.resolve(1)).then(() => (promiseResolved = true));77 // Assert78 s.waitOne().then(() => (waitOneResolved = true));79 await delay();80 expect(promiseResolved).toBe(false);81 expect(waitOneResolved).toBe(false);82 p1.resolve();83 await delay();84 expect(promiseResolved).toBe(true);85 expect(waitOneResolved).toBe(false);86 p2.resolve();87 await delay();88 expect(promiseResolved).toBe(true);89 expect(waitOneResolved).toBe(true);90 });91 });92 describe('waitAll', () => {93 it('should not throw when there is no scheduled promise in the pipe', async () => {94 // Arrange95 const act = jest.fn().mockImplementation((f) => f());96 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex: jest.fn() };97 // Act98 const s = new SchedulerImplem(act, taskSelector);99 // Assert100 await s.waitAll();101 });102 it('should wrap waitAll call using act whenever specified', async () =>103 fc.assert(104 fc.asyncProperty(fc.infiniteStream(fc.nat()), async (seeds) => {105 // Arrange106 const act = jest.fn().mockImplementation((f) => f());107 const nextTaskIndexLengths: number[] = [];108 const nextTaskIndex = buildSeededNextTaskIndex(seeds, nextTaskIndexLengths);109 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };110 // Act111 const s = new SchedulerImplem(act, taskSelector);112 s.schedule(Promise.resolve(1));113 s.schedule(Promise.resolve(8));114 s.schedule(Promise.resolve(42));115 // Assert116 expect(act).not.toHaveBeenCalled();117 expect(nextTaskIndex).not.toHaveBeenCalled();118 await s.waitAll();119 expect(act).toHaveBeenCalledTimes(3);120 expect(nextTaskIndex).toHaveBeenCalledTimes(3);121 expect(nextTaskIndexLengths).toEqual([3, 2, 1]);122 })123 ));124 it('should wait the end of act before moving to the next task', async () =>125 fc.assert(126 fc.asyncProperty(fc.infiniteStream(fc.nat()), async (seeds) => {127 // Arrange128 let locked = false;129 const updateLocked = (newLocked: boolean) => (locked = newLocked);130 const act = jest.fn().mockImplementation(async (f) => {131 expect(locked).toBe(false);132 updateLocked(true); // equivalent to: locked = true133 await f();134 updateLocked(false); // equivalent to: locked = false135 });136 const nextTaskIndex = buildSeededNextTaskIndex(seeds);137 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };138 // Act139 const s = new SchedulerImplem(act, taskSelector);140 s.schedule(Promise.resolve(1));141 s.schedule(Promise.resolve(8));142 s.schedule(Promise.resolve(42));143 // Assert144 await s.waitAll();145 expect(locked).toBe(false);146 })147 ));148 });149 describe('waitFor', () => {150 it('should not release any of the scheduled promises if the task has already been resolved', async () => {151 // Arrange152 const p1 = buildUnresolved();153 const p2 = buildUnresolved();154 const nextTaskIndex = jest.fn();155 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };156 const awaitedTaskValue = Symbol();157 const awaitedTask = Promise.resolve(awaitedTaskValue);158 // Act159 const s = new SchedulerImplem((f) => f(), taskSelector);160 s.schedule(p1.p);161 s.schedule(p2.p);162 // Assert163 const value = await s.waitFor(awaitedTask);164 expect(value).toBe(awaitedTaskValue);165 expect(nextTaskIndex).not.toHaveBeenCalled();166 expect(s.count()).toBe(2); // Still two pending scheduled tasks (none released yet)167 });168 it('should stop releasing scheduled promises as soon as the task resolves', async () => {169 // Arrange170 const p1 = buildUnresolved();171 const p2 = buildUnresolved();172 const p3 = buildUnresolved();173 const nextTaskIndexParams: unknown[] = [];174 const nextTaskIndex = jest175 .fn()176 .mockImplementationOnce((scheduledTasks) => {177 nextTaskIndexParams.push([...scheduledTasks]); // need to clone it as it will be altered178 return 1;179 })180 .mockImplementationOnce((scheduledTasks) => {181 nextTaskIndexParams.push([...scheduledTasks]);182 return 0;183 });184 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };185 // Act186 const s = new SchedulerImplem((f) => f(), taskSelector);187 const sp1 = s.schedule(p1.p);188 s.schedule(p2.p);189 s.schedule(p3.p);190 const awaitedTask = sp1.then(() => Symbol());191 // Assert192 let waitForEnded = false;193 s.waitFor(awaitedTask).finally(() => (waitForEnded = true));194 await delay();195 expect(waitForEnded).toBe(false);196 expect(nextTaskIndex).toHaveBeenCalledTimes(1);197 expect(nextTaskIndexParams[0]).toEqual([198 expect.objectContaining({ original: p1.p, taskId: 1 }),199 expect.objectContaining({ original: p2.p, taskId: 2 }),200 expect.objectContaining({ original: p3.p, taskId: 3 }),201 ]);202 // nextTaskIndex returned 1, so p2 scheduled203 p2.resolve();204 await delay();205 expect(waitForEnded).toBe(false);206 expect(nextTaskIndex).toHaveBeenCalledTimes(2);207 expect(nextTaskIndexParams[1]).toEqual([208 expect.objectContaining({ original: p1.p, taskId: 1 }),209 expect.objectContaining({ original: p3.p, taskId: 3 }),210 ]);211 // nextTaskIndex returned 1, so p1 scheduled212 // We do not wait for p3 as it is not needed for the computation of awaitedTask213 p1.resolve();214 await delay();215 expect(waitForEnded).toBe(true);216 expect(nextTaskIndex).toHaveBeenCalledTimes(2); // no other call received217 expect(s.count()).toBe(1); // Still one pending scheduled task218 });219 it('should wait any released scheduled task to end even if the one we waited for resolved on its own', async () => {220 // Arrange221 const p1 = buildUnresolved();222 const pAwaited = buildUnresolved();223 const nextTaskIndex = jest.fn().mockReturnValueOnce(0);224 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };225 // Act226 const s = new SchedulerImplem((f) => f(), taskSelector);227 s.schedule(p1.p);228 // Assert229 let waitForEnded = false;230 s.waitFor(pAwaited.p).finally(() => (waitForEnded = true));231 expect(waitForEnded).toBe(false);232 expect(nextTaskIndex).not.toHaveBeenCalled(); // not called synchronously233 await delay();234 expect(waitForEnded).toBe(false);235 expect(nextTaskIndex).toHaveBeenCalledTimes(1);236 // Let's resolve waiated task237 pAwaited.resolve();238 await delay();239 expect(waitForEnded).toBe(false); // should not be visible yet as we need to wait the running one240 // Let's resolve running one241 p1.resolve();242 await delay();243 expect(waitForEnded).toBe(true);244 expect(s.count()).toBe(0); // No pending scheduled task245 });246 it('should wait and release scheduled tasks coming after the call to waitFor', async () => {247 // Arrange248 const p1 = buildUnresolved();249 const nextTaskIndex = jest.fn().mockReturnValueOnce(0);250 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };251 let resolveAwaitedTask: () => void;252 const awaitedTask = new Promise<void>((r) => (resolveAwaitedTask = r));253 // Act254 const s = new SchedulerImplem((f) => f(), taskSelector);255 // Assert256 let waitForEnded = false;257 s.waitFor(awaitedTask).finally(() => (waitForEnded = true));258 await delay();259 expect(waitForEnded).toBe(false);260 expect(nextTaskIndex).not.toHaveBeenCalled(); // nothing scheduled yet261 // Schedule p1 a requirement for our resulting task262 s.schedule(p1.p).then(() => resolveAwaitedTask());263 expect(waitForEnded).toBe(false);264 expect(nextTaskIndex).not.toHaveBeenCalled(); // no synchronous trigger265 await delay();266 expect(waitForEnded).toBe(false);267 expect(nextTaskIndex).toHaveBeenCalledTimes(1);268 // Releasing p1 should release the main task269 p1.resolve();270 await delay();271 expect(waitForEnded).toBe(true);272 expect(nextTaskIndex).toHaveBeenCalledTimes(1);273 expect(s.count()).toBe(0); // No more pending scheduled task, all have been released274 });275 it('should accept multiple tasks to be scheduled together after the call to waitFor', async () => {276 // Arrange277 const p1 = buildUnresolved();278 const p2 = buildUnresolved();279 const nextTaskIndexParams: unknown[] = [];280 const nextTaskIndex = jest.fn().mockImplementation((scheduledTasks) => {281 nextTaskIndexParams.push([...scheduledTasks]); // We need to clone it as it will be altered after the call282 return 0;283 });284 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };285 let resolveAwaitedTask: () => void;286 const awaitedTask = new Promise<void>((r) => (resolveAwaitedTask = r));287 // Act288 const s = new SchedulerImplem((f) => f(), taskSelector);289 // Assert290 let waitForEnded = false;291 s.waitFor(awaitedTask).finally(() => (waitForEnded = true));292 await delay();293 expect(waitForEnded).toBe(false);294 expect(nextTaskIndex).not.toHaveBeenCalled(); // nothing scheduled yet295 // Schedule p1 and p2, requirements for our resulting task296 Promise.all([s.schedule(p1.p), s.schedule(p2.p)]).then(() => resolveAwaitedTask());297 expect(waitForEnded).toBe(false);298 expect(nextTaskIndex).not.toHaveBeenCalled(); // no synchronous trigger299 await delay();300 expect(waitForEnded).toBe(false);301 expect(nextTaskIndex).toHaveBeenCalledTimes(1);302 expect(nextTaskIndexParams[0]).toEqual([303 expect.objectContaining({ original: p1.p }),304 expect.objectContaining({ original: p2.p }),305 ]);306 // Releasing p1 should trigger the call to schedule p2 (main not released yet)307 p1.resolve();308 await delay();309 expect(waitForEnded).toBe(false);310 expect(nextTaskIndex).toHaveBeenCalledTimes(2);311 expect(nextTaskIndexParams[1]).toEqual([expect.objectContaining({ original: p2.p })]);312 // Both p1 and p2 have been released so main has also been released313 p2.resolve();314 await delay();315 expect(waitForEnded).toBe(true);316 expect(nextTaskIndex).toHaveBeenCalledTimes(2);317 expect(s.count()).toBe(0); // No more pending scheduled task, all have been released318 });319 it('should accept multiple tasks to be scheduled together even if coming from distinct immediately resolved promises after the call to waitFor', async () => {320 // Arrange321 const p1 = Promise.resolve(1);322 const p2 = Promise.resolve(2);323 const nextTaskIndexParams: unknown[] = [];324 const nextTaskIndex = jest.fn().mockImplementation((scheduledTasks) => {325 nextTaskIndexParams.push([...scheduledTasks]); // We need to clone it as it will be altered after the call326 return 0;327 });328 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };329 let resolveAwaitedTask: () => void;330 const awaitedTask = new Promise<void>((r) => (resolveAwaitedTask = r));331 // Act332 const s = new SchedulerImplem((f) => f(), taskSelector);333 // Assert334 let waitForEnded = false;335 s.waitFor(awaitedTask).finally(() => (waitForEnded = true));336 await delay();337 expect(waitForEnded).toBe(false);338 expect(nextTaskIndex).not.toHaveBeenCalled(); // nothing scheduled yet339 // Schedule p1 and p2 in a very close futur, requirements for our resulting task340 const delayedP1Scheduling = Promise.resolve().then(() => s.schedule(p1));341 const delayedP2Scheduling = Promise.resolve().then(() => s.schedule(p2));342 Promise.all([delayedP1Scheduling, delayedP2Scheduling]).then(() => resolveAwaitedTask());343 expect(waitForEnded).toBe(false);344 expect(nextTaskIndex).not.toHaveBeenCalled(); // no synchronous trigger345 await delay();346 expect(waitForEnded).toBe(true);347 expect(nextTaskIndex).toHaveBeenCalledTimes(2);348 expect(nextTaskIndexParams[0]).toEqual([349 expect.objectContaining({ original: p1 }),350 expect.objectContaining({ original: p2 }),351 ]);352 expect(nextTaskIndexParams[1]).toEqual([expect.objectContaining({ original: p2 })]);353 expect(s.count()).toBe(0); // No more pending scheduled task, all have been released354 });355 it('should consider separately tasks scheduled via different timeouts after the call to waitFor', async () => {356 // Arrange357 const p1 = Promise.resolve(1);358 const p2 = Promise.resolve(2);359 const nextTaskIndexParams: unknown[] = [];360 const nextTaskIndex = jest.fn().mockImplementation((scheduledTasks) => {361 nextTaskIndexParams.push([...scheduledTasks]); // We need to clone it as it will be altered after the call362 return 0;363 });364 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };365 let resolveAwaitedTask: () => void;366 const awaitedTask = new Promise<void>((r) => (resolveAwaitedTask = r));367 // Act368 const s = new SchedulerImplem((f) => f(), taskSelector);369 // Assert370 let waitForEnded = false;371 s.waitFor(awaitedTask).finally(() => (waitForEnded = true));372 await delay();373 expect(waitForEnded).toBe(false);374 expect(nextTaskIndex).not.toHaveBeenCalled(); // nothing scheduled yet375 // Schedule p1 and p2 in a very close futur, requirements for our resulting task376 const delayedP1Scheduling = delay().then(() => s.schedule(p1));377 const delayedP2Scheduling = delay().then(() => s.schedule(p2));378 Promise.all([delayedP1Scheduling, delayedP2Scheduling]).then(() => resolveAwaitedTask());379 expect(waitForEnded).toBe(false);380 expect(nextTaskIndex).not.toHaveBeenCalled(); // no synchronous trigger381 await delay();382 expect(waitForEnded).toBe(true);383 expect(nextTaskIndex).toHaveBeenCalledTimes(2);384 expect(nextTaskIndexParams[0]).toEqual([expect.objectContaining({ original: p1 })]);385 expect(nextTaskIndexParams[1]).toEqual([expect.objectContaining({ original: p2 })]);386 expect(s.count()).toBe(0); // No more pending scheduled task, all have been released387 });388 it('should forward exception thrown by the awaited task', async () => {389 // Arrange390 const nextTaskIndex = jest.fn();391 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };392 const awaitedTaskValue = new Error('thrown by awaited');393 const awaitedTask = Promise.reject(awaitedTaskValue);394 // Act395 const s = new SchedulerImplem((f) => f(), taskSelector);396 // Assert397 await expect(() => s.waitFor(awaitedTask)).rejects.toThrow(awaitedTaskValue);398 });399 it('should be able to wait for a task being itself a scheduled one (no priority to release it first)', async () => {400 // Arrange401 const p1 = Promise.resolve(1);402 const p2 = Promise.resolve(2);403 const nextTaskIndex = jest.fn().mockReturnValueOnce(0).mockReturnValueOnce(1);404 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };405 const awaitedTaskValue = Symbol();406 const awaitedTask = Promise.resolve(awaitedTaskValue);407 // Act408 const s = new SchedulerImplem((f) => f(), taskSelector);409 s.schedule(p1);410 s.schedule(p2);411 // Assert412 const value = await s.waitFor(s.schedule(awaitedTask));413 expect(value).toBe(awaitedTaskValue);414 expect(nextTaskIndex).toHaveBeenCalledTimes(2);415 expect(s.count()).toBe(1); // Still one pending scheduled task (only p1 has been released before the one we waited for)416 });417 it('should not release multiple scheduled tasks at the same time even if called via multiple waitFor', async () => {418 // Arrange419 const p1 = buildUnresolved();420 const p2 = buildUnresolved();421 const p3 = buildUnresolved();422 const p4 = buildUnresolved();423 const nextTaskIndex = jest424 .fn()425 .mockReturnValueOnce(1) // releasing p2 in [p1,p2,p3,p4]426 .mockReturnValueOnce(2) // releasing p4 in [p1,p3,p4]427 .mockReturnValueOnce(0); // releasing p1 in [p1,p4]428 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };429 // Act430 const s = new SchedulerImplem((f) => f(), taskSelector);431 const sp1 = s.schedule(p1.p);432 const sp2 = s.schedule(p2.p);433 s.schedule(p3.p);434 s.schedule(p4.p);435 const awaitedTask1 = sp1.then(() => Symbol());436 const awaitedTask2 = sp2.then(() => Symbol());437 // Assert438 let waitForEnded1 = false;439 let waitForEnded2 = false;440 s.waitFor(awaitedTask1).finally(() => (waitForEnded1 = true));441 s.waitFor(awaitedTask2).finally(() => (waitForEnded2 = true));442 await delay();443 expect(waitForEnded1).toBe(false);444 expect(waitForEnded2).toBe(false);445 expect(nextTaskIndex).toHaveBeenCalledTimes(1);446 // p2 released, let's resolve it447 p2.resolve();448 await delay();449 expect(waitForEnded1).toBe(false);450 expect(waitForEnded2).toBe(true);451 expect(nextTaskIndex).toHaveBeenCalledTimes(2);452 // p4 released, let's resolve it453 p4.resolve();454 await delay();455 expect(waitForEnded1).toBe(false);456 expect(waitForEnded2).toBe(true);457 expect(nextTaskIndex).toHaveBeenCalledTimes(3);458 // p1 released, let's resolve it459 p1.resolve();460 await delay();461 expect(waitForEnded1).toBe(true);462 expect(waitForEnded2).toBe(true);463 expect(nextTaskIndex).toHaveBeenCalledTimes(3); // no other call received464 expect(s.count()).toBe(1); // Still one pending scheduled task for p3465 });466 it('should end whenever possible while never launching multiple tasks at the same time', async () => {467 const schedulingTypeArb = fc.constantFrom(...(['none', 'init'] as const));468 const dependenciesArbFor = (currentItem: number) =>469 fc.uniqueArray(fc.nat({ max: currentItem - 2 }), { maxLength: currentItem - 1 });470 const buildAndAddScheduled = (471 s: SchedulerImplem<unknown>,472 unscheduled: Promise<unknown>,473 allPs: Promise<unknown>[],474 dependencies: number[],475 schedulingType: 'none' | 'init'476 ) => {477 const label = `p${allPs.length + 1}:${JSON.stringify(dependencies)}`;478 const deps = dependencies.map((id) => allPs[id]);479 let self: Promise<unknown>;480 if (schedulingType === 'init') {481 if (deps.length === 0) {482 self = s.schedule(unscheduled, label);483 } else {484 self = Promise.all(deps).then(() => s.schedule(unscheduled, label));485 }486 } else {487 self = deps.length !== 0 ? Promise.all([...deps, unscheduled]) : unscheduled;488 }489 allPs.push(self);490 };491 const scheduleResolution = (492 wrappingScheduler: fc.Scheduler<unknown>,493 p: ReturnType<typeof buildUnresolved>,494 pName: string,495 directResolved: boolean,496 ctx: fc.ContextValue497 ): void => {498 if (directResolved) {499 ctx.log(`${pName} resolved (direct)`);500 p.resolve();501 return;502 }503 wrappingScheduler.schedule(Promise.resolve(`Resolve ${pName}`)).then(() => {504 ctx.log(`${pName} resolved`);505 p.resolve();506 });507 };508 await fc.assert(509 fc.asyncProperty(510 // Scheduler not being tested itself, it will be used to schedule when task will resolve511 fc.scheduler(),512 // Stream of positive integers used to tell which task should be selected by `nextTaskIndex`513 // whenever asked for the next task to be scheduled514 fc.infiniteStream(fc.nat()),515 // Define the tree of pre-requisite tasks:516 // - type of scheduling: should they be scheduled within the Schduler under test?517 // - when should they resolved: on init or when `fc.scheduler` decides it?518 // - what are the dependencies needed for this task?519 fc.tuple(schedulingTypeArb, fc.boolean()),520 fc.tuple(schedulingTypeArb, fc.boolean(), dependenciesArbFor(2)),521 fc.tuple(schedulingTypeArb, fc.boolean(), dependenciesArbFor(3)),522 fc.tuple(schedulingTypeArb, fc.boolean(), dependenciesArbFor(4)),523 fc.tuple(schedulingTypeArb, fc.boolean(), dependenciesArbFor(5)),524 fc.tuple(fc.boolean(), dependenciesArbFor(6)),525 fc.tuple(fc.boolean(), dependenciesArbFor(6)),526 fc.tuple(fc.boolean(), dependenciesArbFor(6)),527 // Extra boolean values used to add some delays between resolved Promises and next ones528 fc.infiniteStream(fc.boolean()),529 // Logger for easier troubleshooting530 fc.context(),531 async (532 wrappingScheduler,533 nextTaskIndexSeed,534 [schedulingType1, directResolve1],535 [schedulingType2, directResolve2, dependencies2],536 [schedulingType3, directResolve3, dependencies3],537 [schedulingType4, directResolve4, dependencies4],538 [schedulingType5, directResolve5, dependencies5],539 [directResolveA, finalDependenciesA],540 [directResolveB, finalDependenciesB],541 [directResolveC, finalDependenciesC],542 addExtraDelays,543 ctx544 ) => {545 // Arrange546 const p1 = buildUnresolved();547 const p2 = buildUnresolved();548 const p3 = buildUnresolved();549 const p4 = buildUnresolved();550 const p5 = buildUnresolved();551 const rawAllPs = [p1, p2, p3, p4, p5];552 const pAwaitedA = buildUnresolved();553 const pAwaitedB = buildUnresolved();554 const pAwaitedC = buildUnresolved();555 let unknownTaskReleased = false;556 let multipleTasksReleasedAtTheSameTime: string | undefined = undefined;557 let alreadyRunningPx: typeof p1 | undefined = undefined;558 const taskSelector: TaskSelector<unknown> = {559 clone: jest.fn(),560 nextTaskIndex: (scheduledTasks) => {561 const selectedId = nextTaskIndexSeed.next().value % scheduledTasks.length;562 const selectedPx = rawAllPs.find((p) => p.p === scheduledTasks[selectedId].original);563 const newPx = `p${rawAllPs.indexOf(selectedPx!) + 1}`;564 ctx.log(`Releasing ${newPx}${selectedPx!.hasBeenResolved() ? ' (resolved)' : ''}`);565 if (566 multipleTasksReleasedAtTheSameTime === undefined &&567 alreadyRunningPx !== undefined &&568 !alreadyRunningPx.hasBeenResolved()569 ) {570 const oldPx = `p${rawAllPs.indexOf(alreadyRunningPx) + 1}`;571 multipleTasksReleasedAtTheSameTime = `${oldPx} already running when releasing ${newPx}`;572 }573 alreadyRunningPx = selectedPx;574 unknownTaskReleased = unknownTaskReleased || alreadyRunningPx === undefined;575 return selectedId;576 },577 };578 const s = new SchedulerImplem((f) => f(), taskSelector);579 const allPs: Promise<unknown>[] = [];580 buildAndAddScheduled(s, p1.p, allPs, [], schedulingType1);581 buildAndAddScheduled(s, p2.p, allPs, dependencies2, schedulingType2);582 buildAndAddScheduled(s, p3.p, allPs, dependencies3, schedulingType3);583 buildAndAddScheduled(s, p4.p, allPs, dependencies4, schedulingType4);584 buildAndAddScheduled(s, p5.p, allPs, dependencies5, schedulingType5);585 const awaitedTaskA = Promise.all([...finalDependenciesA.map((id) => allPs[id]), pAwaitedA.p]);586 let resolvedA = false;587 s.waitFor(awaitedTaskA).then(() => {588 ctx.log(`A ended`);589 resolvedA = true;590 });591 const awaitedTaskB = Promise.all([...finalDependenciesB.map((id) => allPs[id]), pAwaitedB.p]);592 let resolvedB = false;593 s.waitFor(awaitedTaskB).then(() => {594 ctx.log(`B ended`);595 resolvedB = true;596 });597 const awaitedTaskC = Promise.all([...finalDependenciesC.map((id) => allPs[id]), pAwaitedC.p]);598 let resolvedC = false;599 s.waitFor(awaitedTaskC).then(() => {600 ctx.log(`C ended`);601 resolvedC = true;602 });603 // Act604 scheduleResolution(wrappingScheduler, p1, 'p1', directResolve1, ctx);605 scheduleResolution(wrappingScheduler, p2, 'p2', directResolve2, ctx);606 scheduleResolution(wrappingScheduler, p3, 'p3', directResolve3, ctx);607 scheduleResolution(wrappingScheduler, p4, 'p4', directResolve4, ctx);608 scheduleResolution(wrappingScheduler, p5, 'p5', directResolve5, ctx);609 scheduleResolution(wrappingScheduler, pAwaitedA, 'pAwaitedA', directResolveA, ctx);610 scheduleResolution(wrappingScheduler, pAwaitedB, 'pAwaitedB', directResolveB, ctx);611 scheduleResolution(wrappingScheduler, pAwaitedC, 'pAwaitedC', directResolveC, ctx);612 while (wrappingScheduler.count() > 0) {613 // Extra delays based on timeouts of 0ms can potentially trigger unwanted bugs: let's try to add some before waitOne.614 if (addExtraDelays.next().value) {615 await delay();616 }617 await wrappingScheduler.waitOne();618 }619 // Extra delay done after all the scheduling as wrappingScheduler only schedules triggers to resolve tasks620 // and never waits for their associated Promises to really resolve.621 await delay();622 // Assert623 // All awaited tasks should have resolved624 expect(resolvedA).toBe(true);625 expect(resolvedB).toBe(true);626 expect(resolvedC).toBe(true);627 // Only one scheduled task awaited by the scheduler at a given point in time628 expect(multipleTasksReleasedAtTheSameTime).toBe(undefined);629 // Only known tasks could be scheduled630 expect(unknownTaskReleased).toBe(false);631 }632 )633 );634 });635 });636 describe('schedule', () => {637 it('should postpone completion of promise but call it with right parameters in case of success', async () => {638 // Arrange639 const expectedThenValue = 123;640 const thenFunction = jest.fn();641 const act = jest.fn().mockImplementation((f) => f());642 const nextTaskIndexLengths: number[] = [];643 const nextTaskIndex = jest.fn().mockImplementationOnce((tasks: ScheduledTask<unknown>[]) => {644 nextTaskIndexLengths.push(tasks.length); // tasks are mutated, toHaveBeenCalledWith cannot be used645 return 0;646 });647 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };648 // Act649 const s = new SchedulerImplem(act, taskSelector);650 s.schedule(Promise.resolve(expectedThenValue)).then(thenFunction);651 // Assert652 expect(s.count()).toBe(1);653 expect(thenFunction).not.toHaveBeenCalled();654 await s.waitOne();655 expect(thenFunction).toHaveBeenCalled();656 expect(thenFunction).toHaveBeenCalledTimes(1);657 expect(thenFunction).toHaveBeenCalledWith(expectedThenValue);658 expect(act).toHaveBeenCalledTimes(1);659 expect(nextTaskIndex).toHaveBeenCalledTimes(1);660 expect(nextTaskIndexLengths).toEqual([1]); // only one task scheduled661 });662 it('should postpone completion of promise but call it with right parameters in case of failure', async () => {663 // Arrange664 const expectedThenValue = 123;665 const catchFunction = jest.fn();666 const act = jest.fn().mockImplementation((f) => f());667 const nextTaskIndex = jest.fn().mockReturnValue(0);668 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };669 // Act670 const s = new SchedulerImplem(act, taskSelector);671 s.schedule(Promise.reject(expectedThenValue)).then(() => {}, catchFunction);672 // Assert673 expect(s.count()).toBe(1);674 expect(catchFunction).not.toHaveBeenCalled();675 await s.waitOne();676 expect(catchFunction).toHaveBeenCalled();677 expect(catchFunction).toHaveBeenCalledTimes(1);678 expect(catchFunction).toHaveBeenCalledWith(expectedThenValue);679 });680 it('should be able to schedule multiple promises', async () => {681 // Arrange682 const tasks = [Promise.resolve(1), Promise.resolve(8), Promise.resolve(2)];683 const act = jest.fn().mockImplementation((f) => f());684 const nextTaskIndex = jest685 .fn()686 .mockImplementationOnce((scheduledTasks) => {687 expect(scheduledTasks).toEqual([688 expect.objectContaining({ original: tasks[0] }), // selected as nextTaskIndex returns 0689 expect.objectContaining({ original: tasks[1] }),690 expect.objectContaining({ original: tasks[2] }),691 ]);692 return 0;693 })694 .mockImplementationOnce((scheduledTasks) => {695 expect(scheduledTasks).toEqual([696 expect.objectContaining({ original: tasks[1] }),697 expect.objectContaining({ original: tasks[2] }), // selected as nextTaskIndex returns 1698 ]);699 return 1;700 })701 .mockImplementationOnce((scheduledTasks) => {702 expect(scheduledTasks).toEqual([703 expect.objectContaining({ original: tasks[1] }), // selected as nextTaskIndex returns 0704 ]);705 return 0;706 });707 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };708 // Act709 const s = new SchedulerImplem(act, taskSelector);710 for (const t of tasks) {711 s.schedule(t);712 }713 // Assert714 expect(s.count()).toBe(tasks.length);715 await s.waitAll();716 expect(s.count()).toBe(0);717 expect(act).toHaveBeenCalledTimes(3);718 expect(nextTaskIndex).toHaveBeenCalledTimes(3);719 });720 it('should be able to waitAll promises scheduling others', async () =>721 fc.assert(722 fc.asyncProperty(fc.infiniteStream(fc.nat()), async (seeds) => {723 // Arrange724 const status = { done: false };725 const nothingResolved = {726 1: false,727 2: false,728 3: false,729 4: false,730 5: false,731 };732 const resolved = { ...nothingResolved };733 const act = jest.fn().mockImplementation((f) => f());734 const nextTaskIndex = buildSeededNextTaskIndex(seeds);735 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };736 // Act737 const s = new SchedulerImplem(act, taskSelector);738 s.schedule(Promise.resolve(1)).then(() => {739 resolved[1] = true;740 Promise.all([741 s.schedule(Promise.resolve(2)).then(() => {742 resolved[2] = true;743 }),744 s.schedule(Promise.resolve(3)).then(() => {745 resolved[3] = true;746 s.schedule(Promise.resolve(4)).then(() => {747 resolved[4] = true;748 });749 }),750 ]).then(() => {751 s.schedule(Promise.resolve(5)).then(() => {752 resolved[5] = true;753 status.done = true;754 });755 });756 });757 // Assert758 expect(status.done).toBe(false);759 expect(resolved).toEqual(nothingResolved);760 await s.waitAll();761 expect(status.done).toBe(true);762 expect(resolved).toEqual({763 1: true,764 2: true,765 3: true,766 4: true,767 5: true,768 });769 })770 ));771 it('should show both resolved, rejected and pending promises in toString', async () => {772 // Arrange773 const act = jest.fn().mockImplementation((f) => f());774 const nextTaskIndex = jest775 .fn()776 .mockReturnValueOnce(5) // task#6 resolved, state was: [0,1,2,3,4,5,6,7,8,9]777 .mockReturnValueOnce(5) // task#7 resolved, state was: [0,1,2,3,4,6,7,8,9]778 .mockReturnValueOnce(1) // task#2 resolved, state was: [0,1,2,3,4,7,8,9]779 .mockReturnValueOnce(0) // task#1 resolved, state was: [0,2,3,4,7,8,9]780 .mockReturnValueOnce(1) // task#4 resolved, state was: [2,3,4,7,8,9]781 .mockReturnValueOnce(2) // task#8 resolved, state was: [2,4,7,8,9]782 .mockReturnValueOnce(3) // task#10 resolved, state was: [2,4,8,9]783 .mockReturnValueOnce(0) // task#3 resolved, state was: [2,4,8]784 .mockReturnValueOnce(0) // task#5 resolved, state was: [4,8]785 .mockReturnValueOnce(0); // task#9 resolved, state was: [8]786 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };787 // Act788 const s = new SchedulerImplem(act, taskSelector);789 for (let idx = 0; idx !== 10; ++idx) {790 if (idx % 2 === 0) s.schedule(Promise.resolve(idx));791 else s.schedule(Promise.reject(idx));792 }793 // Assert794 expect(s.count()).toBe(10);795 expect(s.toString()).toMatchInlineSnapshot(`796 "schedulerFor()\`797 -> [task\${1}] promise pending798 -> [task\${2}] promise pending799 -> [task\${3}] promise pending800 -> [task\${4}] promise pending801 -> [task\${5}] promise pending802 -> [task\${6}] promise pending803 -> [task\${7}] promise pending804 -> [task\${8}] promise pending805 -> [task\${9}] promise pending806 -> [task\${10}] promise pending\`"807 `);808 await s.waitOne();809 await s.waitOne();810 await s.waitOne();811 expect(s.toString()).toMatchInlineSnapshot(`812 "schedulerFor()\`813 -> [task\${6}] promise rejected with value 5814 -> [task\${7}] promise resolved with value 6815 -> [task\${2}] promise rejected with value 1816 -> [task\${1}] promise pending817 -> [task\${3}] promise pending818 -> [task\${4}] promise pending819 -> [task\${5}] promise pending820 -> [task\${8}] promise pending821 -> [task\${9}] promise pending822 -> [task\${10}] promise pending\`"823 `);824 await s.waitOne();825 await s.waitOne();826 await s.waitOne();827 expect(s.toString()).toMatchInlineSnapshot(`828 "schedulerFor()\`829 -> [task\${6}] promise rejected with value 5830 -> [task\${7}] promise resolved with value 6831 -> [task\${2}] promise rejected with value 1832 -> [task\${1}] promise resolved with value 0833 -> [task\${4}] promise rejected with value 3834 -> [task\${8}] promise rejected with value 7835 -> [task\${3}] promise pending836 -> [task\${5}] promise pending837 -> [task\${9}] promise pending838 -> [task\${10}] promise pending\`"839 `);840 await s.waitOne();841 await s.waitOne();842 await s.waitOne();843 await s.waitOne();844 expect(s.toString()).toMatchInlineSnapshot(`845 "schedulerFor()\`846 -> [task\${6}] promise rejected with value 5847 -> [task\${7}] promise resolved with value 6848 -> [task\${2}] promise rejected with value 1849 -> [task\${1}] promise resolved with value 0850 -> [task\${4}] promise rejected with value 3851 -> [task\${8}] promise rejected with value 7852 -> [task\${10}] promise rejected with value 9853 -> [task\${3}] promise resolved with value 2854 -> [task\${5}] promise resolved with value 4855 -> [task\${9}] promise resolved with value 8\`"856 `);857 expect(s.count()).toBe(0);858 });859 it('should properly replay schedule on cloned instance', async () => {860 // Arrange861 const promises = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];862 const then1Function = jest.fn();863 const then2Function = jest.fn();864 const act = jest.fn().mockImplementation((f) => f());865 const nextTaskIndex1 = jest.fn().mockReturnValueOnce(2).mockReturnValueOnce(1).mockReturnValueOnce(0);866 const nextTaskIndex2 = jest.fn().mockReturnValueOnce(2).mockReturnValueOnce(1).mockReturnValueOnce(0);867 const taskSelector2: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex: nextTaskIndex2 };868 const taskSelector1: TaskSelector<unknown> = {869 clone: jest.fn().mockReturnValueOnce(taskSelector2),870 nextTaskIndex: nextTaskIndex1,871 };872 // Act873 const s1 = new SchedulerImplem(act, taskSelector1);874 for (const p of promises) {875 s1.schedule(p).then(then1Function);876 }877 await s1.waitAll();878 if (!hasCloneMethod(s1)) {879 throw new Error('Expected s1 to be cloneable');880 }881 const s2 = s1[cloneMethod]();882 for (const p of promises) {883 s2.schedule(p).then(then2Function);884 }885 await s2.waitAll();886 // Assert887 expect(then1Function.mock.calls).toEqual(then2Function.mock.calls);888 });889 it('should attach passed metadata into the report', async () => {890 // Arrange891 const expectedMetadata = Symbol('123');892 const act = jest.fn().mockImplementation((f) => f());893 const nextTaskIndex = jest.fn().mockReturnValue(0);894 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };895 // Act896 const s = new SchedulerImplem(act, taskSelector);897 s.schedule(Promise.resolve(), 'label', expectedMetadata);898 // Assert899 await s.waitAll();900 const report = s.report();901 expect(report).toHaveLength(1);902 expect(report[0].status).toBe('resolved');903 expect(report[0].metadata).toBe(expectedMetadata);904 });905 it('should attach passed metadata into the report even if not executed', async () => {906 // Arrange907 const expectedMetadata = Symbol('123');908 const act = jest.fn().mockImplementation((f) => f());909 const nextTaskIndex = jest.fn().mockReturnValue(0);910 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };911 // Act912 const s = new SchedulerImplem(act, taskSelector);913 s.schedule(Promise.resolve(), 'label', expectedMetadata);914 // Assert915 const report = s.report();916 expect(report).toHaveLength(1);917 expect(report[0].status).toBe('pending');918 expect(report[0].metadata).toBe(expectedMetadata);919 });920 type ExecutionPlan = { name: string; children: ExecutionPlan[] };921 it('should be able to schedule new tasks from other tasks and wait them all with waitAll', async () =>922 fc.assert(923 fc.asyncProperty(924 fc.array(925 fc.letrec((tie) => ({926 self: fc.record({927 name: fc.hexaString({ minLength: 4, maxLength: 4 }).noBias(),928 children: fc.oneof(929 fc.constant<ExecutionPlan[]>([]),930 fc.array(tie('self') as fc.Arbitrary<ExecutionPlan>)931 ),932 }),933 })).self,934 { minLength: 1 }935 ),936 fc.infiniteStream(fc.nat()),937 async (plan, seeds) => {938 // Arrange939 const act = jest.fn().mockImplementation((f) => f());940 const nextTaskIndex = buildSeededNextTaskIndex(seeds);941 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };942 const computeTasksInPlan = (tasks: ExecutionPlan[]) => {943 let count = 0;944 for (const t of tasks) {945 count += 1 + computeTasksInPlan(t.children);946 }947 return count;948 };949 const schedulePlan = (s: Scheduler, tasks: ExecutionPlan[]) => {950 for (const t of tasks) {951 s.schedule(Promise.resolve(t.name), t.name).then(() => schedulePlan(s, t.children));952 }953 };954 // Act955 const s = new SchedulerImplem(act, taskSelector);956 schedulePlan(s, plan);957 // Assert958 await s.waitAll();959 expect(s.count()).toBe(0);960 expect(s.report()).toHaveLength(computeTasksInPlan(plan));961 }962 )963 ));964 });965 describe('scheduleFunction', () => {966 it('should schedule a new promise when calling a scheduled function', async () => {967 // Arrange968 const firstCallInput = 1;969 const expectedThenValue = 123;970 const thenFunction = jest.fn();971 const act = jest.fn().mockImplementation((f) => f());972 const nextTaskIndex = jest.fn().mockReturnValue(0);973 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };974 // Act975 const s = new SchedulerImplem(act, taskSelector);976 const scheduledFun = s.scheduleFunction(async (id) => id + expectedThenValue);977 // Assert978 expect(s.count()).toBe(0);979 expect(thenFunction).not.toHaveBeenCalled();980 scheduledFun(firstCallInput).then(thenFunction);981 expect(s.count()).toBe(1);982 expect(thenFunction).not.toHaveBeenCalled();983 await s.waitOne();984 expect(thenFunction).toHaveBeenCalled();985 expect(thenFunction).toHaveBeenCalledTimes(1);986 expect(thenFunction).toHaveBeenCalledWith(firstCallInput + expectedThenValue);987 });988 it('should be able to call a scheduled function multiple times', async () => {989 // Arrange990 const firstCallInput = 1;991 const secondCallInput = 10;992 const expectedThenValue = 123;993 const thenFunction = jest.fn();994 const then2Function = jest.fn();995 const act = jest.fn().mockImplementation((f) => f());996 const nextTaskIndex = jest997 .fn()998 .mockReturnValueOnce(1) // resolving then2Function first999 .mockReturnValueOnce(0); // resolving thenFunction second1000 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1001 // Act1002 const s = new SchedulerImplem(act, taskSelector);1003 const scheduledFun = s.scheduleFunction(async (id) => id + expectedThenValue);1004 // Assert1005 expect(s.count()).toBe(0);1006 scheduledFun(firstCallInput).then(thenFunction);1007 scheduledFun(secondCallInput).then(then2Function);1008 expect(s.count()).toBe(2);1009 expect(thenFunction).not.toHaveBeenCalled();1010 expect(then2Function).not.toHaveBeenCalled();1011 await s.waitAll();1012 expect(thenFunction).toHaveBeenCalledWith(firstCallInput + expectedThenValue);1013 expect(then2Function).toHaveBeenCalledWith(secondCallInput + expectedThenValue);1014 });1015 it('should be able to waitAll for a scheduled function calling itself', async () => {1016 // Arrange1017 const firstCallInput = 10;1018 const thenFunction = jest.fn();1019 const thenImplem = (remaining: number) => {1020 thenFunction();1021 if (remaining <= 0) return;1022 scheduledFun(remaining - 1).then(thenImplem);1023 };1024 const act = jest.fn().mockImplementation((f) => f());1025 const nextTaskIndex = jest.fn().mockReturnValue(0);1026 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1027 // Act1028 const s = new SchedulerImplem(act, taskSelector);1029 const scheduledFun = s.scheduleFunction(async (id) => id);1030 // Assert1031 expect(s.count()).toBe(0);1032 scheduledFun(firstCallInput).then(thenImplem);1033 await s.waitAll();1034 expect(thenFunction).toHaveBeenCalledTimes(firstCallInput + 1);1035 });1036 it('should show both resolved, rejected and pending promises in toString', async () => {1037 // Arrange1038 const calls: [number, number][] = [1039 [0, 3],1040 [1, 4],1041 [6, 0],1042 ];1043 const act = jest.fn().mockImplementation((f) => f());1044 const nextTaskIndex = jest1045 .fn()1046 .mockReturnValueOnce(2) // task#3 resolved, state was: [0,1,2]1047 .mockReturnValueOnce(0) // task#1 resolved, state was: [0,1]1048 .mockReturnValueOnce(0); // task#2 resolved, state was: [1]1049 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1050 // Act1051 const s = new SchedulerImplem(act, taskSelector);1052 const scheduledFun = s.scheduleFunction(async (a: number, b: number) => {1053 if (a >= b) throw new Error(`Unexpected: ${a} >= ${b}`);1054 return a;1055 });1056 for (const ins of calls) {1057 scheduledFun(...ins);1058 }1059 // Assert1060 expect(s.count()).toBe(calls.length);1061 expect(s.toString()).toMatchInlineSnapshot(`1062 "schedulerFor()\`1063 -> [task\${1}] function::(0,3) pending1064 -> [task\${2}] function::(1,4) pending1065 -> [task\${3}] function::(6,0) pending\`"1066 `);1067 await s.waitOne();1068 await s.waitOne();1069 expect(s.toString()).toMatchInlineSnapshot(`1070 "schedulerFor()\`1071 -> [task\${3}] function::(6,0) rejected with value new Error("Unexpected: 6 >= 0")1072 -> [task\${1}] function::(0,3) resolved with value 01073 -> [task\${2}] function::(1,4) pending\`"1074 `);1075 await s.waitOne();1076 expect(s.toString()).toMatchInlineSnapshot(`1077 "schedulerFor()\`1078 -> [task\${3}] function::(6,0) rejected with value new Error("Unexpected: 6 >= 0")1079 -> [task\${1}] function::(0,3) resolved with value 01080 -> [task\${2}] function::(1,4) resolved with value 1\`"1081 `);1082 expect(s.count()).toBe(0);1083 });1084 it('should show function name if any in toString', async () => {1085 // Arrange1086 const act = jest.fn().mockImplementation((f) => f());1087 const nextTaskIndex = jest1088 .fn()1089 .mockReturnValueOnce(2) // task#3 resolved, state was: [0,1,2]1090 .mockReturnValueOnce(0) // task#1 resolved, state was: [0,1]1091 .mockReturnValueOnce(0); // task#2 resolved, state was: [1]1092 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1093 // Act1094 const s = new SchedulerImplem(act, taskSelector);1095 s.scheduleFunction(async function taskA() {1096 return { response: 'dummy response for task A' };1097 })();1098 s.scheduleFunction(async function anotherTaskNameForB(_input: number) {1099 return 3;1100 })(42);1101 s.scheduleFunction(async function somethingElseForC(_complexInstance: any, _anotherInput: number) {1102 return 'c';1103 })({ a: { b: 5 }, c: 0 }, 4);1104 // Assert1105 expect(s.count()).toBe(3);1106 expect(s.toString()).toMatchInlineSnapshot(`1107 "schedulerFor()\`1108 -> [task\${1}] function::taskA() pending1109 -> [task\${2}] function::anotherTaskNameForB(42) pending1110 -> [task\${3}] function::somethingElseForC({"a":{"b":5},"c":0},4) pending\`"1111 `);1112 await s.waitAll();1113 expect(s.toString()).toMatchInlineSnapshot(`1114 "schedulerFor()\`1115 -> [task\${3}] function::somethingElseForC({"a":{"b":5},"c":0},4) resolved with value "c"1116 -> [task\${1}] function::taskA() resolved with value {"response":"dummy response for task A"}1117 -> [task\${2}] function::anotherTaskNameForB(42) resolved with value 3\`"1118 `);1119 expect(s.count()).toBe(0);1120 });1121 });1122 describe('scheduleSequence', () => {1123 it('should accept empty sequences', async () => {1124 // Arrange1125 const act = jest.fn().mockImplementation((f) => f());1126 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex: jest.fn() };1127 // Act1128 const s = new SchedulerImplem(act, taskSelector);1129 const status = s.scheduleSequence([]);1130 // Assert1131 expect(s.count()).toBe(0);1132 expect(status.done).toBe(true);1133 expect(status.faulty).toBe(false);1134 });1135 it('should consider a sequence as a serie of tasks and not parallel tasks', async () => {1136 // Arrange1137 const p1Builder = jest.fn().mockResolvedValue(1);1138 const p2Builder = jest.fn().mockResolvedValue(2);1139 const p3Builder = jest.fn().mockResolvedValue(3);1140 const p4Builder = jest.fn().mockResolvedValue(4);1141 const act = jest.fn().mockImplementation((f) => f());1142 const nextTaskIndex = jest.fn().mockReturnValue(0);1143 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1144 // Act1145 const s = new SchedulerImplem(act, taskSelector);1146 s.scheduleSequence([1147 { builder: p1Builder, label: 'p1' },1148 { builder: p2Builder, label: 'p2' },1149 { builder: p3Builder, label: 'p3' },1150 { builder: p4Builder, label: 'p4' },1151 ]);1152 // Assert1153 expect(s.count()).toBe(1);1154 await s.waitAll();1155 expect(s.count()).toBe(0);1156 });1157 it('should mark schedule as done at the end of the sequence but not faulty', async () => {1158 // Arrange1159 const p1Builder = jest.fn().mockResolvedValue(1);1160 const p2Builder = jest.fn().mockResolvedValue(2);1161 const p3Builder = jest.fn().mockResolvedValue(3);1162 const p4Builder = jest.fn().mockResolvedValue(4);1163 const act = jest.fn().mockImplementation((f) => f());1164 const nextTaskIndex = jest.fn().mockReturnValue(0);1165 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1166 // Act1167 const s = new SchedulerImplem(act, taskSelector);1168 const status = s.scheduleSequence([1169 { builder: p1Builder, label: 'p1' },1170 { builder: p2Builder, label: 'p2' },1171 { builder: p3Builder, label: 'p3' },1172 { builder: p4Builder, label: 'p4' },1173 ]);1174 // Assert1175 while (s.count() > 0) {1176 expect(status.done).toBe(false);1177 expect(status.faulty).toBe(false);1178 await s.waitOne();1179 }1180 expect(status.done).toBe(true);1181 expect(status.faulty).toBe(false);1182 });1183 it('should mark faulty schedule as not done but as faulty', async () => {1184 // Arrange1185 const p1Builder = jest.fn().mockResolvedValue(1);1186 const p2Builder = jest.fn().mockResolvedValue(2);1187 const p3Builder = jest.fn().mockRejectedValue(3);1188 const p4Builder = jest.fn().mockResolvedValue(4);1189 const act = jest.fn().mockImplementation((f) => f());1190 const nextTaskIndex = jest.fn().mockReturnValue(0);1191 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1192 // Act1193 const s = new SchedulerImplem(act, taskSelector);1194 const status = s.scheduleSequence([1195 { builder: p1Builder, label: 'p1' },1196 { builder: p2Builder, label: 'p2' },1197 { builder: p3Builder, label: 'p3' },1198 { builder: p4Builder, label: 'p4' },1199 ]);1200 // Assert1201 while (s.count() > 0) {1202 expect(status.done).toBe(false);1203 await s.waitOne();1204 }1205 expect(status.done).toBe(false);1206 expect(status.faulty).toBe(true);1207 });1208 it('should execute schedule up to the first faulty task', async () => {1209 // Arrange1210 const p1Builder = jest.fn().mockResolvedValue(1);1211 const p2Builder = jest.fn().mockResolvedValue(2);1212 const p3Builder = jest.fn().mockRejectedValue(3);1213 const p4Builder = jest.fn().mockResolvedValue(4);1214 const act = jest.fn().mockImplementation((f) => f());1215 const nextTaskIndex = jest.fn().mockReturnValue(0);1216 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1217 // Act1218 const s = new SchedulerImplem(act, taskSelector);1219 s.scheduleSequence([1220 { builder: p1Builder, label: 'p1' },1221 { builder: p2Builder, label: 'p2' },1222 { builder: p3Builder, label: 'p3' },1223 { builder: p4Builder, label: 'p4' },1224 ]);1225 // Assert1226 await s.waitAll();1227 expect(p1Builder).toHaveBeenCalled();1228 expect(p2Builder).toHaveBeenCalled();1229 expect(p3Builder).toHaveBeenCalled();1230 expect(p4Builder).not.toHaveBeenCalled();1231 });1232 it('should execute sequence in order', async () => {1233 // Arrange1234 const p1Builder = jest.fn().mockResolvedValue(1);1235 const p2Builder = jest.fn().mockResolvedValue(2);1236 const p3Builder = jest.fn().mockResolvedValue(3);1237 const p4Builder = jest.fn().mockResolvedValue(4);1238 const act = jest.fn().mockImplementation((f) => f());1239 const nextTaskIndex = jest.fn().mockReturnValue(0);1240 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1241 // Act1242 const s = new SchedulerImplem(act, taskSelector);1243 s.scheduleSequence([1244 { builder: p1Builder, label: 'p1' },1245 { builder: p2Builder, label: 'p2' },1246 { builder: p3Builder, label: 'p3' },1247 { builder: p4Builder, label: 'p4' },1248 ]);1249 // Assert1250 expect(p1Builder).not.toHaveBeenCalled();1251 expect(p2Builder).not.toHaveBeenCalled();1252 expect(p3Builder).not.toHaveBeenCalled();1253 expect(p4Builder).not.toHaveBeenCalled();1254 await s.waitOne();1255 expect(p1Builder).toHaveBeenCalled();1256 expect(p2Builder).not.toHaveBeenCalled();1257 expect(p3Builder).not.toHaveBeenCalled();1258 expect(p4Builder).not.toHaveBeenCalled();1259 await s.waitOne();1260 expect(p1Builder).toHaveBeenCalled();1261 expect(p2Builder).toHaveBeenCalled();1262 expect(p3Builder).not.toHaveBeenCalled();1263 expect(p4Builder).not.toHaveBeenCalled();1264 await s.waitOne();1265 expect(p1Builder).toHaveBeenCalled();1266 expect(p2Builder).toHaveBeenCalled();1267 expect(p3Builder).toHaveBeenCalled();1268 expect(p4Builder).not.toHaveBeenCalled();1269 await s.waitOne();1270 expect(p1Builder).toHaveBeenCalled();1271 expect(p2Builder).toHaveBeenCalled();1272 expect(p3Builder).toHaveBeenCalled();1273 expect(p4Builder).toHaveBeenCalled();1274 expect(s.count()).toBe(0);1275 });1276 it('should wait the full completion of items coming from the scheduled sequence before taking any other scheduled promise', async () => {1277 // Arrange1278 const delay = () => new Promise((resolve) => setTimeout(resolve, 0));1279 const p1BuilderSteps = { a: false, b: false, c: false, d: false };1280 const p2Builder = jest.fn().mockResolvedValue(2);1281 const act = jest.fn().mockImplementation((f) => f());1282 const nextTaskIndex = jest.fn().mockReturnValue(0);1283 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1284 // Act1285 const s = new SchedulerImplem(act, taskSelector);1286 s.scheduleSequence([1287 {1288 builder: () => {1289 const complexSequenceItem = async () => {1290 p1BuilderSteps.a = true;1291 await delay();1292 p1BuilderSteps.b = true;1293 await delay();1294 p1BuilderSteps.c = true;1295 await delay();1296 p1BuilderSteps.d = true;1297 };1298 return complexSequenceItem();1299 },1300 label: 'p1',1301 },1302 { builder: p2Builder, label: 'p2' },1303 ]);1304 // Assert1305 expect(p1BuilderSteps).toEqual({ a: false, b: false, c: false, d: false });1306 expect(p2Builder).not.toHaveBeenCalled();1307 await s.waitOne();1308 expect(p1BuilderSteps).toEqual({ a: true, b: true, c: true, d: true });1309 expect(p2Builder).not.toHaveBeenCalled();1310 await s.waitAll();1311 });1312 it('should show item name declared in sequence in toString', async () => {1313 // Arrange1314 const act = jest.fn().mockImplementation((f) => f());1315 const nextTaskIndex = jest.fn().mockReturnValue(0);1316 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1317 // Act1318 const s = new SchedulerImplem(act, taskSelector);1319 s.scheduleSequence([1320 { builder: () => Promise.resolve(42), label: 'firstStep' },1321 function anotherStep() {1322 return Promise.resolve(48);1323 },1324 { builder: () => Promise.reject(1), label: 'rejectedStep' },1325 { builder: () => Promise.resolve(8), label: 'neverCalled' },1326 ]);1327 // Assert1328 await s.waitAll();1329 expect(s.toString()).toMatchInlineSnapshot(`1330 "schedulerFor()\`1331 -> [task\${1}] sequence::firstStep resolved with value 421332 -> [task\${2}] sequence::anotherStep resolved with value 481333 -> [task\${3}] sequence::rejectedStep rejected with value 1\`"1334 `);1335 expect(s.count()).toBe(0);1336 });1337 it('should issue a task that resolves when the sequence ends successfully', async () => {1338 // Arrange1339 const act = jest.fn().mockImplementation((f) => f());1340 const nextTaskIndex = jest.fn().mockReturnValue(0);1341 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1342 // Act1343 let taskResolvedValue: { done: boolean; faulty: boolean } | null = null;1344 const s = new SchedulerImplem(act, taskSelector);1345 const { task } = s.scheduleSequence([1346 { builder: () => Promise.resolve(42), label: 'firstStep' },1347 { builder: () => Promise.resolve(8), label: 'secondStep' },1348 ]);1349 task.then((v) => (taskResolvedValue = v));1350 // Assert1351 while (s.count() !== 0) {1352 expect(taskResolvedValue).toBe(null);1353 await s.waitOne();1354 }1355 expect(taskResolvedValue).toEqual({ done: true, faulty: false });1356 });1357 it('should issue a task that resolves when the sequence fails', async () => {1358 // Arrange1359 const act = jest.fn().mockImplementation((f) => f());1360 const nextTaskIndex = jest.fn().mockReturnValue(0);1361 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1362 // Act1363 let taskResolvedValue: { done: boolean; faulty: boolean } | null = null;1364 const s = new SchedulerImplem(act, taskSelector);1365 const { task } = s.scheduleSequence([1366 { builder: () => Promise.resolve(42), label: 'firstStep' },1367 { builder: () => Promise.reject(8), label: 'secondStep' },1368 { builder: () => Promise.resolve(8), label: 'neverCalledStep' },1369 ]);1370 task.then((v) => (taskResolvedValue = v));1371 // Assert1372 while (s.count() !== 0) {1373 expect(taskResolvedValue).toBe(null);1374 await s.waitOne();1375 }1376 expect(taskResolvedValue).toEqual({ done: false, faulty: true });1377 });1378 it('should attach passed metadata into the report', async () => {1379 // Arrange1380 const expectedMetadataFirst = Symbol('123');1381 const expectedMetadataSecond = Symbol('1234');1382 const act = jest.fn().mockImplementation((f) => f());1383 const nextTaskIndex = jest.fn().mockReturnValue(0);1384 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1385 // Act1386 const s = new SchedulerImplem(act, taskSelector);1387 s.scheduleSequence([1388 { builder: () => Promise.resolve(42), label: 'firstStep', metadata: expectedMetadataFirst },1389 { builder: () => Promise.reject(8), label: 'secondStep', metadata: expectedMetadataSecond },1390 ]);1391 // Assert1392 await s.waitAll();1393 const report = s.report();1394 expect(report).toHaveLength(2);1395 expect(report[0].status).toBe('resolved');1396 expect(report[0].metadata).toBe(expectedMetadataFirst);1397 expect(report[1].status).toBe('rejected');1398 expect(report[1].metadata).toBe(expectedMetadataSecond);1399 });1400 it('should attach passed metadata into the report even if not executed', async () => {1401 // Arrange1402 const expectedMetadataFirst = Symbol('123');1403 const expectedMetadataSecond = Symbol('1234');1404 const act = jest.fn().mockImplementation((f) => f());1405 const nextTaskIndex = jest.fn().mockReturnValue(0);1406 const taskSelector: TaskSelector<unknown> = { clone: jest.fn(), nextTaskIndex };1407 // Act1408 const s = new SchedulerImplem(act, taskSelector);1409 s.scheduleSequence([1410 { builder: () => Promise.resolve(42), label: 'firstStep', metadata: expectedMetadataFirst },1411 { builder: () => Promise.reject(8), label: 'secondStep', metadata: expectedMetadataSecond },1412 ]);1413 // Assert1414 const report = s.report();1415 expect(report).toHaveLength(1); // second task cannot be scheduled as first one is still pending1416 expect(report[0].status).toBe('pending');1417 expect(report[0].metadata).toBe(expectedMetadataFirst);1418 });1419 });1420});1421// Helpers1422function buildSeededNextTaskIndex(seeds: fc.Stream<number>, nextTaskIndexLengths: number[] = []) {1423 const nextTaskIndex = jest.fn().mockImplementation((scheduledTasks: ScheduledTask<unknown>[]) => {1424 const seed = seeds.next();1425 if (seed.done) {1426 throw new Error('Stream for seeds exhausted');1427 }1428 if (scheduledTasks.length === 0) {1429 throw new Error('Called without any task');1430 }1431 // `tasks` pointer being re-used from one call to another (mutate)1432 // we cannot rely on toHaveBeenCalledWith1433 nextTaskIndexLengths.push(scheduledTasks.length);1434 return seed.value % scheduledTasks.length;1435 });1436 return nextTaskIndex;...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1const { p4Builder } = require('fast-check-monorepo');2const { p4 } = p4Builder();3const { p4Builder } = require('fast-check');4const { p4 } = p4Builder();5const { p4Builder } = require('fast-check-monorepo');6const { p4 } = p4Builder();7const { p4Builder } = require('fast-check');8const { p4 } = p4Builder();9const { p4Builder } = require('fast-check-monorepo');10const { p4 } = p4Builder();11const { p4Builder } = require('fast-check');12const { p4 } = p4Builder();13const { p4Builder } = require('fast-check-monorepo');14const { p4 } = p4Builder();15const { p4Builder } = require('fast-check');16const { p4 } = p4Builder();17const { p4Builder } = require('fast-check-monorepo');18const { p4 } = p4Builder();19const { p4Builder } = require('fast-check');20const { p4 } = p4Builder();21const { p4Builder } = require('fast-check-monorepo');22const { p4 } = p4Builder();23const { p4Builder } = require('fast-check');24const { p4 } = p4Builder();25const { p4Builder } = require('fast-check-monorepo');26const { p

Full Screen

Using AI Code Generation

copy

Full Screen

1const { p4Builder } = require('fast-check-monorepo');2const p4 = p4Builder();3p4.check(4 p4.property(p4.integer(), p4.integer(), (a, b) => {5 return a + b === b + a;6 })7);8const { p4Builder } = require('fast-check-monorepo');9const p4 = p4Builder();10p4.check(11 p4.property(p4.integer(), p4.integer(), (a, b) => {12 return a + b === b + a;13 })14);15const { p4Builder } = require('fast-check-monorepo');16const p4 = p4Builder();17p4.check(18 p4.property(p4.integer(), p4.integer(), (a, b) => {19 return a + b === b + a;20 })21);22const { p4Builder } = require('fast-check-monorepo');23const p4 = p4Builder();24p4.check(25 p4.property(p4.integer(), p4.integer(), (a, b) => {26 return a + b === b + a;27 })28);29const { p4Builder } = require('fast-check-monorepo');30const p4 = p4Builder();31p4.check(32 p4.property(p4.integer(), p4.integer(), (a, b) => {33 return a + b === b + a;34 })35);36const { p4Builder } = require('fast-check-monorepo');37const p4 = p4Builder();38p4.check(39 p4.property(p4.integer(), p4.integer(), (a, b) => {40 return a + b === b + a;41 })42);43const { p4Builder } = require('fast-check-mon

Full Screen

Using AI Code Generation

copy

Full Screen

1import { p4Builder } from 'fast-check-monorepo';2const p4 = p4Builder();3console.log(p4);4import { p4Builder } from 'fast-check-monorepo';5const p4 = p4Builder();6console.log(p4);7import { p4Builder } from 'fast-check-monorepo';8const p4 = p4Builder();9console.log(p4);10import { p4Builder } from 'fast-check-monorepo';11const p4 = p4Builder();12console.log(p4);13import { p4Builder } from 'fast-check-monorepo';14const p4 = p4Builder();15console.log(p4);

Full Screen

Using AI Code Generation

copy

Full Screen

1const { p4Builder } = require('fast-check-monorepo')2const { fc } = p4Builder()3import { p4Builder } from 'fast-check'4const { fc } = p4Builder()5const { p4Builder } = require('fast-check')6const { fc } = p4Builder()7import { p4Builder } from 'fast-check'8const { fc } = p4Builder()9const { p4Builder } = require('fast-check')10const { fc } = p4Builder()11import { p4Builder } from 'fast-check'12const { fc } = p4Builder()13const { p4Builder } = require('fast-check')14const { fc } = p4Builder()15import { p4Builder } from 'fast-check'16const { fc } = p4Builder()17const { p4Builder } = require('fast-check')18const { fc } = p4Builder()19import { p4Builder } from 'fast-check'20const { fc } = p4Builder()21const { p4Builder } = require('fast-check')22const { fc } = p4Builder()23import { p4Builder } from 'fast-check'24const { fc } = p4Builder()25const { p4Builder } = require('fast-check')26const { fc } = p4Builder()27import { p4Builder } from 'fast-check'28const { fc } = p4Builder()29const { p4Builder } = require('fast-check')30const { fc } = p4Builder()31import { p4Builder } from

Full Screen

Using AI Code Generation

copy

Full Screen

1import { p4Builder } from 'fast-check-monorepo';2describe('test', () => {3 it('should work', () => {4 expect(p4Builder()).toBe('p4');5 });6});7import { p4Builder } from 'fast-check-monorepo';8describe('test', () => {9 it('should work', () => {10 expect(p4Builder()).toBe('p4');11 });12});13{14 "compilerOptions": {15 },16}17module.exports = {18 collectCoverageFrom: ['src/**/*.{js,ts}'],19};20{21 "compilerOptions": {22 },23}

Full Screen

Using AI Code Generation

copy

Full Screen

1const { p4Builder } = require('fast-check-monorepo');2const fc = require('fast-check');3const myBuilder = p4Builder(fc.integer, fc.string, fc.boolean, fc.float);4const myArbitrary = myBuilder.build();5fc.assert(6 fc.property(myArbitrary, ([a, b, c, d]) => {7 })8);9const { p4Builder } = require('fast-check-monorepo');10const fc = require('fast-check');11const myBuilder = p4Builder(fc.integer, fc.string, fc.boolean, fc.float);12const myArbitrary = myBuilder.build();13fc.assert(14 fc.property(myArbitrary, ([a, b, c, d]) => {15 })16);17const { p4Builder } = require('fast-check-monorepo');18const fc = require('fast-check');19const myBuilder = p4Builder(fc.integer, fc.string, fc.boolean, fc.float);20const myArbitrary = myBuilder.build();21fc.assert(22 fc.property(myArbitrary, ([a, b, c, d]) => {23 })24);25const { p4Builder } = require('fast-check-monorepo');26const fc = require('fast-check');27const myBuilder = p4Builder(fc.integer, fc.string, fc.boolean, fc.float);28const myArbitrary = myBuilder.build();29fc.assert(30 fc.property(myArbitrary, ([a, b, c, d]) => {31 })32);

Full Screen

Using AI Code Generation

copy

Full Screen

1import { p4Builder } from 'fast-check-monorepo';2import { p4 } from 'fast-check';3const p4Builder = p4Builder();4import { p4 } from 'fast-check';5import { p4Builder } from 'fast-check';6import { p4 } from 'fast-check-monorepo';7import { p4Builder } from 'fast-check-monorepo';8import { p4 } from 'fast-check';9import { p4Builder } from 'fast-check';10import { p4 } from 'fast-check-monorepo';11import { p4Builder } from 'fast-check-monorepo';12import { p4 } from 'fast-check';13import { p4Builder } from 'fast-check';14import { p4 } from 'fast-check-monorepo';15import { p4Builder } from 'fast-check-monorepo';16import { p4 } from 'fast-check';17import { p4Builder } from 'fast-check';18import { p4 } from 'fast-check-monorepo';19import { p4Builder } from 'fast-check-monorepo';20import { p4 } from 'fast-check';21import { p4Builder } from 'fast-check';22import { p4 } from '

Full Screen

Using AI Code Generation

copy

Full Screen

1const { p4Builder } = require('fast-check-monorepo');2const { p4 } = p4Builder();3console.log(p4(1, 2));4const { p4Builder } = require('fast-check-monorepo');5const { p4 } = p4Builder();6console.log(p4(1, 2));7const { p4Builder } = require('fast-check-monorepo');8const { p4 } = p4Builder();9console.log(p4(1, 2));10const { p4Builder } = require('fast-check-monorepo');11const { p4 } = p4Builder();12console.log(p4(1, 2));13const { p4Builder } = require('fast-check-monorepo');14const { p4 } = p4Builder();15console.log(p4(1, 2));16const { p4Builder } = require('fast-check-monorepo');17const { p4 } = p4Builder();18console.log(p4(1, 2));19const { p4Builder } = require('fast-check-monorepo');20const { p4 } = p4Builder();21console.log(p4(1, 2));22const { p4Builder } = require('fast-check-monorepo');23const { p4 } = p4Builder();24console.log(p4(1, 2));

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run fast-check-monorepo automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful