From b05dbe0557df55c2b156f2892bb5c0e8e5c2526f Mon Sep 17 00:00:00 2001 From: ealush Date: Sat, 20 Jul 2024 00:59:26 +0300 Subject: [PATCH 1/5] patch(vest): Reduce the number of times hasPending checks happen on test run --- packages/vest/src/core/VestBus/VestBus.ts | 29 ++++++++++++------- .../vestjs-runtime/src/Isolate/Isolate.ts | 1 + packages/vestjs-runtime/src/RuntimeEvents.ts | 3 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/vest/src/core/VestBus/VestBus.ts b/packages/vest/src/core/VestBus/VestBus.ts index 704ce8d8d..04e8ec5b3 100644 --- a/packages/vest/src/core/VestBus/VestBus.ts +++ b/packages/vest/src/core/VestBus/VestBus.ts @@ -2,7 +2,7 @@ import { CB, ValueOf } from 'vest-utils'; import { Bus, RuntimeEvents, TIsolate } from 'vestjs-runtime'; import { Events } from 'BusEvents'; -import { TIsolateTest } from 'IsolateTest'; +// import { TIsolateTest } from 'IsolateTest'; import { useExpireSuiteResultCache, useResetCallbacks, @@ -22,15 +22,7 @@ export function useInitVestBus() { // Report a the completion of a test. There may be other tests with the same // name that are still running, or not yet started. - on(Events.TEST_COMPLETED, (testObject: TIsolateTest) => { - if (VestTest.isCanceled(testObject)) { - return; - } - - const { fieldName } = VestTest.getData(testObject); - - useRunFieldCallbacks(fieldName); - }); + on(Events.TEST_COMPLETED, () => {}); on(Events.TEST_RUN_STARTED, () => { /* Let's just invalidate the suite cache for now */ @@ -50,9 +42,19 @@ export function useInitVestBus() { } VestIsolate.setDone(isolate); + }); + + on(RuntimeEvents.ASYNC_ISOLATE_DONE, (isolate: TIsolate) => { + if (VestTest.is(isolate)) { + if (!VestTest.isCanceled(isolate)) { + const { fieldName } = VestTest.getData(isolate); + + useRunFieldCallbacks(fieldName); + } + } if (!SuiteWalker.hasPending()) { - // When no more tests are running, emit the done event + // When no more async tests are running, emit the done event VestBus.emit(Events.ALL_RUNNING_TESTS_FINISHED); } }); @@ -82,6 +84,11 @@ export function useInitVestBus() { }); on(Events.SUITE_CALLBACK_RUN_FINISHED, () => { + if (!SuiteWalker.hasPending()) { + // When no more async tests are running, emit the done event + VestBus.emit(Events.ALL_RUNNING_TESTS_FINISHED); + } + useOmitOptionalFields(); }); diff --git a/packages/vestjs-runtime/src/Isolate/Isolate.ts b/packages/vestjs-runtime/src/Isolate/Isolate.ts index b3c6bf669..2ec4874d8 100644 --- a/packages/vestjs-runtime/src/Isolate/Isolate.ts +++ b/packages/vestjs-runtime/src/Isolate/Isolate.ts @@ -100,6 +100,7 @@ function useRunAsNew( } emit(RuntimeEvents.ISOLATE_DONE, current); + emit(RuntimeEvents.ASYNC_ISOLATE_DONE, current); }); } else { emit(RuntimeEvents.ISOLATE_DONE, current); diff --git a/packages/vestjs-runtime/src/RuntimeEvents.ts b/packages/vestjs-runtime/src/RuntimeEvents.ts index 90e8150c1..8de49c837 100644 --- a/packages/vestjs-runtime/src/RuntimeEvents.ts +++ b/packages/vestjs-runtime/src/RuntimeEvents.ts @@ -1,5 +1,6 @@ export const RuntimeEvents = { + ASYNC_ISOLATE_DONE: 'ASYNC_ISOLATE_DONE', + ISOLATE_DONE: 'ISOLATE_DONE', ISOLATE_ENTER: 'ISOLATE_ENTER', ISOLATE_PENDING: 'ISOLATE_PENDING', - ISOLATE_DONE: 'ISOLATE_DONE', }; From a9243124bad78e0fe09df5992330f9cd371d5444 Mon Sep 17 00:00:00 2001 From: ealush Date: Sat, 20 Jul 2024 02:52:38 +0300 Subject: [PATCH 2/5] minor(vest-utils): Add FindAll to IsolateWalker --- packages/vestjs-runtime/src/IsolateWalker.ts | 21 +++++++++++ .../src/__tests__/IsolateWalker.test.ts | 35 ++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/vestjs-runtime/src/IsolateWalker.ts b/packages/vestjs-runtime/src/IsolateWalker.ts index be716a0fc..907fd3440 100644 --- a/packages/vestjs-runtime/src/IsolateWalker.ts +++ b/packages/vestjs-runtime/src/IsolateWalker.ts @@ -142,6 +142,27 @@ export function find( return found; } +// this function acts like find, but returns an array of all matching nodes +export function findAll( + startNode: TIsolate, + predicate: (node: TIsolate) => boolean, + visitOnly?: VisitOnlyPredicate, +): TIsolate[] { + const found: TIsolate[] = []; + + walk( + startNode, + node => { + if (predicate(node)) { + found.push(node); + } + }, + visitOnly, + ); + + return found; +} + // This function returns true if the given predicate function returns true for every Isolate object in the tree. // If visitOnly is provided, only Isolate objects that satisfy the predicate are visited. export function every( diff --git a/packages/vestjs-runtime/src/__tests__/IsolateWalker.test.ts b/packages/vestjs-runtime/src/__tests__/IsolateWalker.test.ts index ba5d61792..e86cb3a30 100644 --- a/packages/vestjs-runtime/src/__tests__/IsolateWalker.test.ts +++ b/packages/vestjs-runtime/src/__tests__/IsolateWalker.test.ts @@ -1,5 +1,5 @@ import { TIsolate } from 'Isolate'; -import { walk, reduce } from 'IsolateWalker'; +import { walk, reduce, findAll } from 'IsolateWalker'; type WalkedNode = TIsolate<{ id: string }>; @@ -171,3 +171,36 @@ describe('reduce', () => { }); }); }); + +describe('findAll', () => { + let node = {} as unknown as TIsolate<{ value: number }>; + beforeEach(() => { + node = { + data: { value: 100 }, + children: [ + { + data: { value: 2 }, + children: [ + { data: { value: 100 }, $type: 's' }, + { + data: { value: 2 }, + children: [{ data: { value: 0 } }, { data: { value: 100 } }], + }, + { data: { value: 1 }, $type: 's' }, + ], + }, + { data: { value: 0 } }, + ], + } as unknown as TIsolate<{ value: number }>; + }); + + it('Should return all nodes that satisfy the predicate', () => { + const output = findAll(node, isolate => isolate.data.value === 100); + + expect(output).toEqual([ + node.children?.[0]?.children?.[0], + node.children?.[0]?.children?.[1]?.children?.[1], + node, + ]); + }); +}); From 9a43b3f7d9b58217fe1826ea8957da8c9a93c2e1 Mon Sep 17 00:00:00 2001 From: ealush Date: Sat, 20 Jul 2024 03:34:44 +0300 Subject: [PATCH 3/5] patch(vest): Cache useHasPending Call --- packages/vest/src/core/Runtime.ts | 18 ++++++++-- packages/vest/src/core/VestBus/VestBus.ts | 4 +-- .../__tests__/hasRemainingTests.test.ts | 20 +++++------ packages/vest/src/suite/SuiteWalker.ts | 36 +++++++++++++------ packages/vest/src/suite/runCallbacks.ts | 2 +- .../selectors/shouldAddValidProperty.ts | 20 +++++------ .../vest/src/suiteResult/suiteRunResult.ts | 2 +- 7 files changed, 65 insertions(+), 37 deletions(-) diff --git a/packages/vest/src/core/Runtime.ts b/packages/vest/src/core/Runtime.ts index 3665b3a3e..6c5909ba8 100644 --- a/packages/vest/src/core/Runtime.ts +++ b/packages/vest/src/core/Runtime.ts @@ -7,7 +7,7 @@ import { seq, tinyState, } from 'vest-utils'; -import { IRecociler, VestRuntime } from 'vestjs-runtime'; +import { IRecociler, TIsolate, VestRuntime } from 'vestjs-runtime'; import { TIsolateSuite } from 'IsolateSuite'; import { @@ -27,8 +27,10 @@ type StateExtra = { suiteName: Maybe; suiteId: string; suiteResultCache: CacheApi>; + pendingCache: CacheApi; }; const suiteResultCache = cache>(); +const pendingCache = cache(); export function useCreateVestState({ suiteName, @@ -40,6 +42,7 @@ export function useCreateVestState({ const stateRef: StateExtra = { doneCallbacks: tinyState.createTinyState(() => []), fieldCallbacks: tinyState.createTinyState(() => ({})), + pendingCache, suiteId: seq(), suiteName, suiteResultCache, @@ -69,16 +72,27 @@ export function useSuiteId() { } export function useSuiteResultCache( - action: CB> + action: CB>, ): SuiteResult { const suiteResultCache = useX().suiteResultCache; return suiteResultCache([useSuiteId()], action) as SuiteResult; } +export function usePendingCache(action: CB) { + const pendingCache = useX().pendingCache; + + return pendingCache([useSuiteId()], action); +} + export function useExpireSuiteResultCache() { const suiteResultCache = useX().suiteResultCache; suiteResultCache.invalidate([useSuiteId()]); + + // whenever we invalidate the entire result, we also want to invalidate the pending cache + // so that we do not get stale results there. + // there may be a better place to do this, but for now, this should work. + pendingCache.invalidate([useSuiteId()]); } export function useResetCallbacks() { diff --git a/packages/vest/src/core/VestBus/VestBus.ts b/packages/vest/src/core/VestBus/VestBus.ts index 04e8ec5b3..aa22a0b8e 100644 --- a/packages/vest/src/core/VestBus/VestBus.ts +++ b/packages/vest/src/core/VestBus/VestBus.ts @@ -53,7 +53,7 @@ export function useInitVestBus() { } } - if (!SuiteWalker.hasPending()) { + if (!SuiteWalker.useHasPending()) { // When no more async tests are running, emit the done event VestBus.emit(Events.ALL_RUNNING_TESTS_FINISHED); } @@ -84,7 +84,7 @@ export function useInitVestBus() { }); on(Events.SUITE_CALLBACK_RUN_FINISHED, () => { - if (!SuiteWalker.hasPending()) { + if (!SuiteWalker.useHasPending()) { // When no more async tests are running, emit the done event VestBus.emit(Events.ALL_RUNNING_TESTS_FINISHED); } diff --git a/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts b/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts index 22680eb95..a9079cbff 100644 --- a/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts +++ b/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts @@ -3,7 +3,7 @@ import wait from 'wait'; import { SuiteWalker } from 'SuiteWalker'; import * as vest from 'vest'; -describe('SuiteWalker.hasRemainingWithTestNameMatching', () => { +describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { let hasRemaining: boolean | null = null; let count = 0; @@ -15,7 +15,7 @@ describe('SuiteWalker.hasRemainingWithTestNameMatching', () => { describe('When no remaining tests', () => { it('should return false', () => { vest.create(() => { - hasRemaining = SuiteWalker.hasRemainingWithTestNameMatching(); + hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); })(); expect(hasRemaining).toBe(false); }); @@ -27,7 +27,7 @@ describe('SuiteWalker.hasRemainingWithTestNameMatching', () => { vest.test('f1', async () => { await wait(100); }); - hasRemaining = SuiteWalker.hasRemainingWithTestNameMatching(); + hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); })(); expect(hasRemaining).toBe(true); @@ -40,7 +40,7 @@ describe('SuiteWalker.hasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = SuiteWalker.hasRemainingWithTestNameMatching(); + hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); }); suite(); suite(); @@ -58,7 +58,7 @@ describe('SuiteWalker.hasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = SuiteWalker.hasRemainingWithTestNameMatching(); + hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); }); suite(); @@ -73,7 +73,7 @@ describe('SuiteWalker.hasRemainingWithTestNameMatching', () => { describe('When no remaining tests', () => { it('Should return false', () => { vest.create(() => { - hasRemaining = SuiteWalker.hasRemainingWithTestNameMatching('f1'); + hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching('f1'); })(); expect(hasRemaining).toBe(false); }); @@ -85,7 +85,7 @@ describe('SuiteWalker.hasRemainingWithTestNameMatching', () => { vest.test('f1', async () => { await wait(100); }); - hasRemaining = SuiteWalker.hasRemainingWithTestNameMatching('f1'); + hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching('f1'); })(); expect(hasRemaining).toBe(true); }); @@ -97,7 +97,7 @@ describe('SuiteWalker.hasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = SuiteWalker.hasRemainingWithTestNameMatching('f1'); + hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching('f1'); }); suite(); suite(); @@ -116,8 +116,8 @@ describe('SuiteWalker.hasRemainingWithTestNameMatching', () => { }); count++; hasRemaining = - SuiteWalker.hasRemainingWithTestNameMatching('f1') && - SuiteWalker.hasRemainingWithTestNameMatching('f2'); + SuiteWalker.useHasRemainingWithTestNameMatching('f1') && + SuiteWalker.useHasRemainingWithTestNameMatching('f2'); }); suite(); diff --git a/packages/vest/src/suite/SuiteWalker.ts b/packages/vest/src/suite/SuiteWalker.ts index 8522f97ed..83452103f 100644 --- a/packages/vest/src/suite/SuiteWalker.ts +++ b/packages/vest/src/suite/SuiteWalker.ts @@ -1,7 +1,8 @@ import { Predicate, Predicates, isNullish } from 'vest-utils'; -import { VestRuntime, Walker } from 'vestjs-runtime'; +import { TIsolate, VestRuntime, Walker } from 'vestjs-runtime'; import { TIsolateTest } from 'IsolateTest'; +import { usePendingCache } from 'Runtime'; import { TFieldName } from 'SuiteResultTypes'; import { VestIsolate } from 'VestIsolate'; import { VestTest } from 'VestTest'; @@ -10,32 +11,45 @@ import { matchesOrHasNoFieldName } from 'matchingFieldName'; export class SuiteWalker { static defaultRoot = VestRuntime.useAvailableRoot; - static hasPending(predicate?: Predicate): boolean { + static useHasPending(predicate?: Predicate): boolean { const root = SuiteWalker.defaultRoot(); if (!root) { return false; } - return Walker.some( - root, - Predicates.all(VestIsolate.isPending, predicate ?? true) - ); + const allPending = usePendingCache(this.findAllPending); + + if (!allPending.length) { + return false; + } + + return allPending.some(Predicates.all(predicate ?? true)); + } + + static findAllPending(): TIsolate[] { + const root = SuiteWalker.defaultRoot(); + + if (!root) { + return []; + } + + return Walker.findAll(root, VestIsolate.isPending); } // Checks whether there are pending isolates in the tree. // If a fieldname is provided, will only check tests with a matching fieldname. - static hasRemainingWithTestNameMatching(fieldName?: TFieldName): boolean { - return SuiteWalker.hasPending( + static useHasRemainingWithTestNameMatching(fieldName?: TFieldName): boolean { + return SuiteWalker.useHasPending( Predicates.any( isNullish(fieldName), Predicates.all(VestTest.is, (testObject: TIsolateTest) => { return matchesOrHasNoFieldName( VestTest.getData(testObject), - fieldName + fieldName, ); - }) - ) + }), + ), ); } } diff --git a/packages/vest/src/suite/runCallbacks.ts b/packages/vest/src/suite/runCallbacks.ts index 474a4107d..813579805 100644 --- a/packages/vest/src/suite/runCallbacks.ts +++ b/packages/vest/src/suite/runCallbacks.ts @@ -12,7 +12,7 @@ export function useRunFieldCallbacks(fieldName?: TFieldName): void { if ( fieldName && - !SuiteWalker.hasRemainingWithTestNameMatching(fieldName) && + !SuiteWalker.useHasRemainingWithTestNameMatching(fieldName) && isArray(fieldCallbacks[fieldName]) ) { callEach(fieldCallbacks[fieldName]); diff --git a/packages/vest/src/suiteResult/selectors/shouldAddValidProperty.ts b/packages/vest/src/suiteResult/selectors/shouldAddValidProperty.ts index aadd394bb..f1f2016cc 100644 --- a/packages/vest/src/suiteResult/selectors/shouldAddValidProperty.ts +++ b/packages/vest/src/suiteResult/selectors/shouldAddValidProperty.ts @@ -44,7 +44,7 @@ export function useShouldAddValidProperty(fieldName?: TFieldName): boolean { export function useShouldAddValidPropertyInGroup( groupName: TGroupName, - fieldName: TFieldName + fieldName: TFieldName, ): boolean { if (useIsOptionalFieldApplied(fieldName)) { return true; @@ -64,30 +64,30 @@ export function useShouldAddValidPropertyInGroup( // Does the given field have any pending tests that are not optional? function useHasNonOptionalIncomplete(fieldName?: TFieldName) { - return SuiteWalker.hasPending( + return SuiteWalker.useHasPending( Predicates.all( VestTest.is, (testObject: TIsolateTest) => !nonMatchingFieldName(VestTest.getData(testObject), fieldName), - () => !useIsOptionalFieldApplied(fieldName) - ) + () => !useIsOptionalFieldApplied(fieldName), + ), ); } // Do the given group/field have any pending tests that are not optional? function useHasNonOptionalIncompleteByGroup( groupName: TGroupName, - fieldName: TFieldName + fieldName: TFieldName, ): boolean { - return SuiteWalker.hasPending( + return SuiteWalker.useHasPending( Predicates.all( VestTest.is, (testObject: TIsolateTest) => !nonMatchingGroupName(testObject, groupName), (testObject: TIsolateTest) => !nonMatchingFieldName(VestTest.getData(testObject), fieldName), - () => !useIsOptionalFieldApplied(fieldName) - ) + () => !useIsOptionalFieldApplied(fieldName), + ), ); } @@ -102,7 +102,7 @@ function useNoMissingTests(fieldName?: string): boolean { // Does the group have no missing tests? function useNoMissingTestsByGroup( groupName: TGroupName, - fieldName?: TFieldName + fieldName?: TFieldName, ): boolean { return TestWalker.everyTest(testObject => { if (nonMatchingGroupName(testObject, groupName)) { @@ -115,7 +115,7 @@ function useNoMissingTestsByGroup( function useNoMissingTestsLogic( testObject: TIsolateTest, - fieldName?: TFieldName + fieldName?: TFieldName, ): boolean { if (nonMatchingFieldName(VestTest.getData(testObject), fieldName)) { return true; diff --git a/packages/vest/src/suiteResult/suiteRunResult.ts b/packages/vest/src/suiteResult/suiteRunResult.ts index e60a18566..fd6f7357d 100644 --- a/packages/vest/src/suiteResult/suiteRunResult.ts +++ b/packages/vest/src/suiteResult/suiteRunResult.ts @@ -41,7 +41,7 @@ function done( return output; } const useDoneCallback = () => callback(useCreateSuiteResult()); - if (!SuiteWalker.hasRemainingWithTestNameMatching(fieldName)) { + if (!SuiteWalker.useHasRemainingWithTestNameMatching(fieldName)) { useDoneCallback(); return output; } From 67162bc16ed0b7898c8a05c2e6ba6b3502d5d59b Mon Sep 17 00:00:00 2001 From: ealush Date: Sun, 21 Jul 2024 02:28:33 +0300 Subject: [PATCH 4/5] patch(vest): Add a shared cache for preaggs --- packages/vest/src/core/Runtime.ts | 25 +++++--- .../__tests__/hasRemainingTests.test.ts | 37 ++++------- packages/vest/src/suite/SuiteWalker.ts | 62 +++++++++++++++---- .../selectors/hasFailuresByTestObjects.ts | 22 +++++-- 4 files changed, 96 insertions(+), 50 deletions(-) diff --git a/packages/vest/src/core/Runtime.ts b/packages/vest/src/core/Runtime.ts index 6c5909ba8..caadb302d 100644 --- a/packages/vest/src/core/Runtime.ts +++ b/packages/vest/src/core/Runtime.ts @@ -10,6 +10,7 @@ import { import { IRecociler, TIsolate, VestRuntime } from 'vestjs-runtime'; import { TIsolateSuite } from 'IsolateSuite'; +import { Severity } from 'Severity'; import { SuiteName, SuiteResult, @@ -20,6 +21,14 @@ import { export type DoneCallback = (res: SuiteResult) => void; type FieldCallbacks = Record; type DoneCallbacks = Array; +type FailuresCache = { + [Severity.ERRORS]: Record; + [Severity.WARNINGS]: Record; +}; +export type PreAggCache = { + pending: TIsolate[]; + failures: FailuresCache; +}; type StateExtra = { doneCallbacks: TinyState; @@ -27,10 +36,10 @@ type StateExtra = { suiteName: Maybe; suiteId: string; suiteResultCache: CacheApi>; - pendingCache: CacheApi; + preAggCache: CacheApi; }; const suiteResultCache = cache>(); -const pendingCache = cache(); +const preAggCache = cache(); export function useCreateVestState({ suiteName, @@ -42,7 +51,7 @@ export function useCreateVestState({ const stateRef: StateExtra = { doneCallbacks: tinyState.createTinyState(() => []), fieldCallbacks: tinyState.createTinyState(() => ({})), - pendingCache, + preAggCache, suiteId: seq(), suiteName, suiteResultCache, @@ -79,20 +88,20 @@ export function useSuiteResultCache( return suiteResultCache([useSuiteId()], action) as SuiteResult; } -export function usePendingCache(action: CB) { - const pendingCache = useX().pendingCache; +export function usePreAggCache(action: CB) { + const preAggCache = useX().preAggCache; - return pendingCache([useSuiteId()], action); + return preAggCache([useSuiteId()], action); } export function useExpireSuiteResultCache() { const suiteResultCache = useX().suiteResultCache; suiteResultCache.invalidate([useSuiteId()]); - // whenever we invalidate the entire result, we also want to invalidate the pending cache + // whenever we invalidate the entire result, we also want to invalidate the preagg cache // so that we do not get stale results there. // there may be a better place to do this, but for now, this should work. - pendingCache.invalidate([useSuiteId()]); + preAggCache.invalidate([useSuiteId()]); } export function useResetCallbacks() { diff --git a/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts b/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts index a9079cbff..11de5a269 100644 --- a/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts +++ b/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts @@ -14,23 +14,20 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { describe('When no field specified', () => { describe('When no remaining tests', () => { it('should return false', () => { - vest.create(() => { - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); - })(); - expect(hasRemaining).toBe(false); + const suite = vest.create(() => {})(); + expect(suite.isPending()).toBe(false); }); }); describe('When there are remaining tests', () => { it('pending tests return true', () => { - vest.create(() => { + const suite = vest.create(() => { vest.test('f1', async () => { await wait(100); }); - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); })(); - expect(hasRemaining).toBe(true); + expect(suite.isPending()).toBe(true); }); it('lagging tests return true', () => { @@ -40,12 +37,11 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); }); suite(); suite(); - expect(hasRemaining).toBe(true); + expect(suite.isPending()).toBe(true); }); it('lagging and pending tests return true', () => { @@ -58,13 +54,12 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); }); suite(); suite(); - expect(hasRemaining).toBe(true); + expect(suite.isPending()).toBe(true); }); }); }); @@ -72,22 +67,19 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { describe('When field specified', () => { describe('When no remaining tests', () => { it('Should return false', () => { - vest.create(() => { - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching('f1'); - })(); - expect(hasRemaining).toBe(false); + const suite = vest.create(() => {})(); + expect(suite.isPending('f1')).toBe(false); }); }); describe('When remaining tests', () => { it('pending tests return true', () => { - vest.create(() => { + const suite = vest.create(() => { vest.test('f1', async () => { await wait(100); }); - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching('f1'); })(); - expect(hasRemaining).toBe(true); + expect(suite.isPending('f1')).toBe(true); }); it('lagging tests return true', () => { @@ -97,12 +89,11 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching('f1'); }); suite(); suite(); - expect(hasRemaining).toBe(true); + expect(suite.isPending('f1')).toBe(true); }); it('lagging and pending tests return true', () => { @@ -115,15 +106,13 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = - SuiteWalker.useHasRemainingWithTestNameMatching('f1') && - SuiteWalker.useHasRemainingWithTestNameMatching('f2'); }); suite(); suite(); - expect(hasRemaining).toBe(true); + expect(suite.isPending('f1')).toBe(true); + expect(suite.isPending('f2')).toBe(true); }); }); }); diff --git a/packages/vest/src/suite/SuiteWalker.ts b/packages/vest/src/suite/SuiteWalker.ts index 83452103f..1ebb15231 100644 --- a/packages/vest/src/suite/SuiteWalker.ts +++ b/packages/vest/src/suite/SuiteWalker.ts @@ -1,8 +1,8 @@ -import { Predicate, Predicates, isNullish } from 'vest-utils'; +import { Predicate, Predicates, isEmpty, isNullish } from 'vest-utils'; import { TIsolate, VestRuntime, Walker } from 'vestjs-runtime'; import { TIsolateTest } from 'IsolateTest'; -import { usePendingCache } from 'Runtime'; +import { PreAggCache, usePreAggCache } from 'Runtime'; import { TFieldName } from 'SuiteResultTypes'; import { VestIsolate } from 'VestIsolate'; import { VestTest } from 'VestTest'; @@ -18,23 +18,17 @@ export class SuiteWalker { return false; } - const allPending = usePendingCache(this.findAllPending); + const allPending = SuiteWalker.usePreAggs().pending; - if (!allPending.length) { + if (isEmpty(allPending)) { return false; } return allPending.some(Predicates.all(predicate ?? true)); } - static findAllPending(): TIsolate[] { - const root = SuiteWalker.defaultRoot(); - - if (!root) { - return []; - } - - return Walker.findAll(root, VestIsolate.isPending); + static usePreAggs() { + return usePreAggCache(buildPreAggCache); } // Checks whether there are pending isolates in the tree. @@ -53,3 +47,47 @@ export class SuiteWalker { ); } } + +function buildPreAggCache(): PreAggCache { + const root = SuiteWalker.defaultRoot(); + + const base: PreAggCache = { + pending: [], + failures: { + errors: {}, + warnings: {}, + }, + }; + + if (!root) { + return base; + } + + return Walker.reduce( + root, + // eslint-disable-next-line complexity, max-statements + (agg, isolate: TIsolate) => { + if (VestIsolate.isPending(isolate)) { + agg.pending.push(isolate); + } + + if (VestTest.is(isolate)) { + const fieldName = VestTest.getData(isolate).fieldName; + + if (VestTest.isWarning(isolate)) { + agg.failures.warnings[fieldName] = + agg.failures.warnings[fieldName] ?? []; + agg.failures.warnings[fieldName].push(isolate); + } + + if (VestTest.isFailing(isolate)) { + agg.failures.errors[fieldName] = agg.failures.errors[fieldName] ?? []; + agg.failures.errors[fieldName].push(isolate); + } + } + + return agg; + }, + base, + ); +} diff --git a/packages/vest/src/suiteResult/selectors/hasFailuresByTestObjects.ts b/packages/vest/src/suiteResult/selectors/hasFailuresByTestObjects.ts index 98418a9cf..d83df1e25 100644 --- a/packages/vest/src/suiteResult/selectors/hasFailuresByTestObjects.ts +++ b/packages/vest/src/suiteResult/selectors/hasFailuresByTestObjects.ts @@ -1,7 +1,9 @@ +import { isEmpty } from 'vest-utils'; import { TIsolateTest } from 'IsolateTest'; import { Severity } from 'Severity'; import { TFieldName, TGroupName } from 'SuiteResultTypes'; +import { SuiteWalker } from 'SuiteWalker'; import { TestWalker } from 'TestWalker'; import { VestTest } from 'VestTest'; import { nonMatchingFieldName } from 'matchingFieldName'; @@ -19,17 +21,25 @@ export function hasErrorsByTestObjects(fieldName?: TFieldName): boolean { function hasFailuresByTestObjects( severityKey: Severity, - fieldName?: TFieldName + fieldName?: TFieldName, ): boolean { - return TestWalker.someTests(testObject => { - return hasFailuresByTestObject(testObject, severityKey, fieldName); - }); + const allFailures = SuiteWalker.usePreAggs().failures; + + if (isEmpty(allFailures[severityKey])) { + return false; + } + + if (fieldName) { + return !isEmpty(allFailures[severityKey][fieldName]); + } + + return true; } export function hasGroupFailuresByTestObjects( severityKey: Severity, groupName: TGroupName, - fieldName?: TFieldName + fieldName?: TFieldName, ): boolean { return TestWalker.someTests(testObject => { if (nonMatchingGroupName(testObject, groupName)) { @@ -46,7 +56,7 @@ export function hasGroupFailuresByTestObjects( export function hasFailuresByTestObject( testObject: TIsolateTest, severityKey: Severity, - fieldName?: TFieldName + fieldName?: TFieldName, ): boolean { if (!VestTest.hasFailures(testObject)) { return false; From cca717f6e882de67b1ef5ea26384cd2b8ddd61e6 Mon Sep 17 00:00:00 2001 From: ealush Date: Sun, 21 Jul 2024 12:20:53 +0300 Subject: [PATCH 5/5] patch(vest): Reduce Unneeded cache invalidations --- packages/vest/src/core/VestBus/VestBus.ts | 15 +++++---------- .../src/core/test/testLevelFlowControl/runTest.ts | 4 ++-- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/vest/src/core/VestBus/VestBus.ts b/packages/vest/src/core/VestBus/VestBus.ts index aa22a0b8e..ae106fb97 100644 --- a/packages/vest/src/core/VestBus/VestBus.ts +++ b/packages/vest/src/core/VestBus/VestBus.ts @@ -20,15 +20,10 @@ import { useRunDoneCallbacks, useRunFieldCallbacks } from 'runCallbacks'; export function useInitVestBus() { const VestBus = Bus.useBus(); - // Report a the completion of a test. There may be other tests with the same - // name that are still running, or not yet started. on(Events.TEST_COMPLETED, () => {}); + // on(Events.TEST_RUN_STARTED, () => {}); - on(Events.TEST_RUN_STARTED, () => { - /* Let's just invalidate the suite cache for now */ - }); - - on(RuntimeEvents.ISOLATE_PENDING, (isolate: TIsolate) => { + VestBus.on(RuntimeEvents.ISOLATE_PENDING, (isolate: TIsolate) => { if (VestTest.is(isolate)) { VestTest.setPending(isolate); } @@ -36,7 +31,7 @@ export function useInitVestBus() { VestIsolate.setPending(isolate); }); - on(RuntimeEvents.ISOLATE_DONE, (isolate: TIsolate) => { + VestBus.on(RuntimeEvents.ISOLATE_DONE, (isolate: TIsolate) => { if (VestTest.is(isolate)) { VestBus.emit(Events.TEST_COMPLETED, isolate); } @@ -44,7 +39,7 @@ export function useInitVestBus() { VestIsolate.setDone(isolate); }); - on(RuntimeEvents.ASYNC_ISOLATE_DONE, (isolate: TIsolate) => { + VestBus.on(RuntimeEvents.ASYNC_ISOLATE_DONE, (isolate: TIsolate) => { if (VestTest.is(isolate)) { if (!VestTest.isCanceled(isolate)) { const { fieldName } = VestTest.getData(isolate); @@ -64,7 +59,7 @@ export function useInitVestBus() { }); // Called when all the tests, including async, are done running - on(Events.ALL_RUNNING_TESTS_FINISHED, () => { + VestBus.on(Events.ALL_RUNNING_TESTS_FINISHED, () => { // Small optimization. We don't need to run this if there are no async tests // The reason is that we run this function immediately after the suite callback // is run, so if the suite is only comprised of sync tests, we don't need to diff --git a/packages/vest/src/core/test/testLevelFlowControl/runTest.ts b/packages/vest/src/core/test/testLevelFlowControl/runTest.ts index 8aafa2eca..ffff5ac8b 100644 --- a/packages/vest/src/core/test/testLevelFlowControl/runTest.ts +++ b/packages/vest/src/core/test/testLevelFlowControl/runTest.ts @@ -24,7 +24,7 @@ export function useAttemptRunTest(testObject: TIsolateTest) { deferThrow( text(ErrorStrings.UNEXPECTED_TEST_REGISTRATION_ERROR, { testObject: JSON.stringify(testObject), - }) + }), ); } } @@ -77,7 +77,7 @@ function useRunTest(testObject: TIsolateTest): Promise | undefined { text(ErrorStrings.UNEXPECTED_TEST_REGISTRATION_ERROR, { testObject: JSON.stringify(testObject), error: e, - }) + }), ); } }