Skip to content

Commit

Permalink
[wip] adding a shared cache for preaggs
Browse files Browse the repository at this point in the history
  • Loading branch information
ealush committed Jul 20, 2024
1 parent 002ee3e commit 253c327
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 50 deletions.
25 changes: 17 additions & 8 deletions packages/vest/src/core/Runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { IRecociler, TIsolate, VestRuntime } from 'vestjs-runtime';

import { TIsolateSuite } from 'IsolateSuite';
import { Severity } from 'Severity';
import {
SuiteName,
SuiteResult,
Expand All @@ -20,17 +21,25 @@ import {
export type DoneCallback = (res: SuiteResult<TFieldName, TGroupName>) => void;
type FieldCallbacks = Record<string, DoneCallbacks>;
type DoneCallbacks = Array<DoneCallback>;
type FailuresCache = {
[Severity.ERRORS]: Record<TFieldName, TIsolate[]>;
[Severity.WARNINGS]: Record<TFieldName, TIsolate[]>;
};
export type PreAggCache = {
pending: TIsolate[];
failures: FailuresCache;
};

type StateExtra = {
doneCallbacks: TinyState<DoneCallbacks>;
fieldCallbacks: TinyState<FieldCallbacks>;
suiteName: Maybe<string>;
suiteId: string;
suiteResultCache: CacheApi<SuiteResult<TFieldName, TGroupName>>;
pendingCache: CacheApi<TIsolate[]>;
preAggCache: CacheApi<PreAggCache>;
};
const suiteResultCache = cache<SuiteResult<TFieldName, TGroupName>>();
const pendingCache = cache<TIsolate[]>();
const preAggCache = cache<PreAggCache>();

export function useCreateVestState({
suiteName,
Expand All @@ -42,7 +51,7 @@ export function useCreateVestState({
const stateRef: StateExtra = {
doneCallbacks: tinyState.createTinyState<DoneCallbacks>(() => []),
fieldCallbacks: tinyState.createTinyState<FieldCallbacks>(() => ({})),
pendingCache,
preAggCache,
suiteId: seq(),
suiteName,
suiteResultCache,
Expand Down Expand Up @@ -79,20 +88,20 @@ export function useSuiteResultCache<F extends TFieldName, G extends TGroupName>(
return suiteResultCache([useSuiteId()], action) as SuiteResult<F, G>;
}

export function usePendingCache(action: CB<TIsolate[]>) {
const pendingCache = useX().pendingCache;
export function usePreAggCache(action: CB<PreAggCache>) {
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -58,36 +54,32 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => {
await wait(100);
});
count++;
hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching();
});

suite();
suite();

expect(hasRemaining).toBe(true);
expect(suite.isPending()).toBe(true);
});
});
});

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', () => {
Expand All @@ -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', () => {
Expand All @@ -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);
});
});
});
Expand Down
62 changes: 50 additions & 12 deletions packages/vest/src/suite/SuiteWalker.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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.
Expand All @@ -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,
);
}
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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)) {
Expand All @@ -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;
Expand Down

0 comments on commit 253c327

Please sign in to comment.