Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: move mocha to vitest for beacon-node change #6028

Merged
merged 24 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0591eec
Add jest dependencies
nazarhussain Oct 6, 2023
e471638
Convert beacon node unit tests to jest
nazarhussain Oct 6, 2023
fc2f715
Convert all beacon unit tests to vitest
nazarhussain Oct 9, 2023
861351b
Update dependencies
nazarhussain Oct 9, 2023
a777dbb
Move all e2e tests to vitest
nazarhussain Oct 9, 2023
6f338b7
Fix http but which was causing abort to not work
nazarhussain Oct 9, 2023
c041f77
Update the e2e script for the beacon-node
nazarhussain Oct 9, 2023
87ccec2
Fix the e2e tests
nazarhussain Oct 9, 2023
ef350a0
Update yarn dependencies
nazarhussain Oct 9, 2023
a12f52b
Remove .only filter
nazarhussain Oct 9, 2023
1a1e67a
Fix lint and type errors
nazarhussain Oct 9, 2023
48af4c2
Made close callbacks async
nazarhussain Oct 9, 2023
42725aa
Fix the test path
nazarhussain Oct 9, 2023
d9a4d18
Fix order of resource cleanup
nazarhussain Oct 9, 2023
43ef4f0
Fix the peer manager for agent version
nazarhussain Oct 9, 2023
ee665df
Fix failing unit test
nazarhussain Oct 9, 2023
7141892
Update e2e workflow
nazarhussain Oct 10, 2023
347a633
Add code coverage support for vitest
nazarhussain Oct 10, 2023
1f207d5
Match the code coverage configuration to previous nyc config
nazarhussain Oct 10, 2023
11ca6ef
Fix the formatting for easy code review
nazarhussain Oct 10, 2023
00b495a
Add custom error messages to extremely confusing assertions
nazarhussain Oct 12, 2023
d08b6e8
Add custom matcher support in the vitest
nazarhussain Oct 12, 2023
dc4b282
Merge branch 'unstable' into nh/vitest
nazarhussain Oct 13, 2023
c17be2d
Update code with feedback
nazarhussain Oct 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,7 @@ jobs:
run: scripts/run_e2e_env.sh start

- name: E2E tests
# E2E tests are sometimes stalling until timeout is reached but we know that
# after 15 minutes those should have passed already if there are no failed test cases.
# In this case, just set the job status to passed as there was likely no actual issue.
# See https://github.com/ChainSafe/lodestar/issues/5913
run: timeout 15m yarn test:e2e || { test $? -eq 124 || exit 1; }
run: yarn test:e2e
env:
GOERLI_RPC_URL: ${{ secrets.GOERLI_RPC_URL!=0 && secrets.GOERLI_RPC_URL || env.GOERLI_RPC_DEFAULT_URL }}

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@
"typescript": "^5.2.2",
"typescript-docs-verifier": "^2.5.0",
"webpack": "^5.88.2",
"wait-port": "^1.1.0"
"wait-port": "^1.1.0",
"vitest": "^0.34.6",
"vitest-when": "^0.2.0",
"@vitest/coverage-v8": "^0.34.6"
},
"resolutions": {
"dns-over-http-resolver": "^2.1.1"
Expand Down
4 changes: 2 additions & 2 deletions packages/beacon-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@
"lint:fix": "yarn run lint --fix",
"pretest": "yarn run check-types",
"test": "yarn test:unit && yarn test:e2e",
"test:unit:minimal": "nyc --cache-dir .nyc_output/.cache -e .ts mocha 'test/unit/**/*.test.ts'",
"test:unit:minimal": "vitest --run --dir test/unit/ --coverage",
"test:unit:mainnet": "LODESTAR_PRESET=mainnet nyc --cache-dir .nyc_output/.cache -e .ts mocha 'test/unit-mainnet/**/*.test.ts'",
"test:unit": "yarn test:unit:minimal && yarn test:unit:mainnet",
"test:e2e": "LODESTAR_PRESET=minimal mocha 'test/e2e/**/*.test.ts'",
"test:e2e": "LODESTAR_PRESET=minimal vitest --run --single-thread --dir test/e2e",
"test:sim": "mocha 'test/sim/**/*.test.ts'",
"test:sim:merge-interop": "mocha 'test/sim/merge-interop.test.ts'",
"test:sim:mergemock": "mocha 'test/sim/mergemock.test.ts'",
Expand Down
8 changes: 7 additions & 1 deletion packages/beacon-node/src/chain/bls/multithread/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import path from "node:path";
import {spawn, Worker} from "@chainsafe/threads";
// `threads` library creates self global variable which breaks `timeout-abort-controller` https://github.com/jacobheun/timeout-abort-controller/issues/9
// Don't add an eslint disable here as a reminder that this has to be fixed eventually
Expand Down Expand Up @@ -28,6 +29,9 @@ import {
jobItemWorkReq,
} from "./jobItem.js";

// Worker constructor consider the path relative to the current working directory
const workerDir = process.env.NODE_ENV === "test" ? "../../../../lib/chain/bls/multithread" : "./";

export type BlsMultiThreadWorkerPoolModules = {
logger: Logger;
metrics: Metrics | null;
Expand Down Expand Up @@ -263,7 +267,9 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier {

for (let i = 0; i < poolSize; i++) {
const workerData: WorkerData = {implementation, workerId: i};
const worker = new Worker("./worker.js", {workerData} as ConstructorParameters<typeof Worker>[1]);
const worker = new Worker(path.join(workerDir, "worker.js"), {
workerData,
} as ConstructorParameters<typeof Worker>[1]);

const workerDescriptor: WorkerDescriptor = {
worker,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient {
retries: opts?.retryAttempts ?? this.opts?.retryAttempts ?? 1,
retryDelay: opts?.retryDelay ?? this.opts?.retryDelay ?? 0,
shouldRetry: opts?.shouldRetry,
signal: this.opts?.signal,
}
);
return parseRpcResponse(res, payload);
Expand Down Expand Up @@ -279,7 +280,6 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient {
return bodyJson;
} catch (e) {
this.metrics?.requestErrors.inc({routeId});

if (controller.signal.aborted) {
// controller will abort on both parent signal abort + timeout of this specific request
if (this.opts?.signal?.aborted) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path from "node:path";
import worker_threads from "node:worker_threads";
import {PeerScoreStatsDump} from "@chainsafe/libp2p-gossipsub/dist/src/score/peer-score.js";
import {PublishOpts} from "@chainsafe/libp2p-gossipsub/types";
Expand Down Expand Up @@ -28,6 +29,9 @@ import {
} from "./events.js";
import {INetworkCore, MultiaddrStr, NetworkWorkerApi, NetworkWorkerData, PeerIdStr} from "./types.js";

// Worker constructor consider the path relative to the current working directory
const workerDir = process.env.NODE_ENV === "test" ? "../../../lib/network/core/" : "./";

export type WorkerNetworkCoreOpts = NetworkOptions & {
metricsEnabled: boolean;
peerStoreDir?: string;
Expand Down Expand Up @@ -114,7 +118,7 @@ export class WorkerNetworkCore implements INetworkCore {
loggerOpts: modules.logger.toOpts(),
};

const worker = new Worker("./networkCoreWorker.js", {
const worker = new Worker(path.join(workerDir, "networkCoreWorker.js"), {
workerData,
/**
* maxYoungGenerationSizeMb defaults to 152mb through the cli option defaults.
Expand Down
23 changes: 14 additions & 9 deletions packages/beacon-node/src/network/peers/peerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {BitArray} from "@chainsafe/ssz";
import {SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
import {BeaconConfig} from "@lodestar/config";
import {allForks, altair, phase0} from "@lodestar/types";
import {withTimeout} from "@lodestar/utils";
import {retry, withTimeout} from "@lodestar/utils";
import {LoggerNode} from "@lodestar/logger/node";
import {GoodByeReasonCode, GOODBYE_KNOWN_CODES, Libp2pEvent} from "../../constants/index.js";
import {IClock} from "../../util/clock.js";
Expand Down Expand Up @@ -610,14 +610,19 @@ export class PeerManager {
// AgentVersion was set in libp2p IdentifyService, 'peer:connect' event handler
// since it's not possible to handle it async, we have to wait for a while to set AgentVersion
// See https://github.com/libp2p/js-libp2p/pull/1168
setTimeout(async () => {
const agentVersionBytes = (await this.libp2p.peerStore.get(peerData.peerId)).metadata.get("AgentVersion");
if (agentVersionBytes) {
const agentVersion = new TextDecoder().decode(agentVersionBytes) || "N/A";
peerData.agentVersion = agentVersion;
peerData.agentClient = clientFromAgentVersion(agentVersion);
}
}, 1000);
retry(
async () => {
const agentVersionBytes = (await this.libp2p.peerStore.get(peerData.peerId)).metadata.get("AgentVersion");
if (agentVersionBytes) {
const agentVersion = new TextDecoder().decode(agentVersionBytes) || "N/A";
peerData.agentVersion = agentVersion;
peerData.agentClient = clientFromAgentVersion(agentVersion);
}
},
{retries: 3, retryDelay: 1000}
).catch((err) => {
this.logger.error("Error setting agentVersion for the peer", {peerId: peerData.peerId.toString()}, err);
});
Comment on lines +613 to +625
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following change fixes the following problem. I still suggest that we pass the top level AbortSignal to the PeerManager to be used on multiple places to terminate any running timeout.

Serialized Error: { code: 'ERR_NOT_FOUND' }
This error originated in "test/e2e/network/gossipsub.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Error ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Error: Unexpected message on Worker: {
  type: 'uncaughtError',
  error: {
    __error_marker: '$$error',
    message: 'Not Found',
    name: 'Error',
    stack: 'Error: Not Found\n' +
      '    at Module.notFoundError (file:///lodestar-review-2/node_modules/libp2p/node_modules/datastore-core/src/errors.ts:24:16)\n' +
      '    at MemoryDatastore.get (file:///lodestar-review-2/node_modules/libp2p/node_modules/datastore-core/src/memory.ts:26:20)\n' +
      '    at PersistentStore.load (file:///lodestar-review-2/node_modules/@libp2p/peer-store/src/store.ts:89:38)\n' +
      '    at PersistentPeerStore.get (file:///lodestar-review-2/node_modules/@libp2p/peer-store/src/index.ts:105:31)\n' +
      '    at Timeout._onTimeout (/lodestar-review-2/packages/beacon-node/src/network/peers/peerManager.ts:614:34)'
  }
}

};

/**
Expand Down
44 changes: 44 additions & 0 deletions packages/beacon-node/test/__mocks__/apiMocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {config} from "@lodestar/config/default";
import {ChainForkConfig} from "@lodestar/config";
import {getBeaconBlockApi} from "../../src/api/impl/beacon/blocks/index.js";
import {getMockedBeaconChain, MockedBeaconChain} from "./mockedBeaconChain.js";
import {MockedBeaconSync, getMockedBeaconSync} from "./beaconSyncMock.js";
import {MockedBeaconDb, getMockedBeaconDb} from "./mockedBeaconDb.js";
import {MockedNetwork, getMockedNetwork} from "./mockedNetwork.js";

export type ApiImplTestModules = {
forkChoiceStub: MockedBeaconChain["forkChoice"];
chainStub: MockedBeaconChain;
syncStub: MockedBeaconSync;
dbStub: MockedBeaconDb;
networkStub: MockedNetwork;
blockApi: ReturnType<typeof getBeaconBlockApi>;
config: ChainForkConfig;
};

export function setupApiImplTestServer(): ApiImplTestModules {
const chainStub = getMockedBeaconChain();
const forkChoiceStub = chainStub.forkChoice;
const syncStub = getMockedBeaconSync();
const dbStub = getMockedBeaconDb();
const networkStub = getMockedNetwork();

const blockApi = getBeaconBlockApi({
chain: chainStub,
config,
db: dbStub,
network: networkStub,
metrics: null,
});

return {
forkChoiceStub,
chainStub,
syncStub,
dbStub,
networkStub,
blockApi,
config,
};
}
27 changes: 27 additions & 0 deletions packages/beacon-node/test/__mocks__/beaconSyncMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {MockedObject, vi} from "vitest";
import {BeaconSync} from "../../src/sync/index.js";

export type MockedBeaconSync = MockedObject<BeaconSync>;

vi.mock("../../src/sync/index.js", async (requireActual) => {
const mod = await requireActual<typeof import("../../src/sync/index.js")>();

const BeaconSync = vi.fn().mockImplementation(() => {
const sync = {};
Object.defineProperty(sync, "state", {value: undefined, configurable: true});

return sync;
});

return {
...mod,
BeaconSync,
};
});

export function getMockedBeaconSync(): MockedBeaconSync {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
return vi.mocked(new BeaconSync({})) as MockedBeaconSync;
}
14 changes: 14 additions & 0 deletions packages/beacon-node/test/__mocks__/loggerMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {vi, MockedObject} from "vitest";
import {Logger} from "@lodestar/logger";

export type MockedLogger = MockedObject<Logger>;

export function getMockedLogger(): MockedLogger {
return {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
verbose: vi.fn(),
};
}
114 changes: 114 additions & 0 deletions packages/beacon-node/test/__mocks__/mockedBeaconChain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {vi, MockedObject, Mock} from "vitest";
import {ForkChoice} from "@lodestar/fork-choice";
import {config as defaultConfig} from "@lodestar/config/default";
import {ChainForkConfig} from "@lodestar/config";
import {BeaconChain} from "../../src/chain/index.js";
import {ExecutionEngineHttp} from "../../src/execution/engine/http.js";
import {Eth1ForBlockProduction} from "../../src/eth1/index.js";
import {OpPool} from "../../src/chain/opPools/opPool.js";
import {AggregatedAttestationPool} from "../../src/chain/opPools/aggregatedAttestationPool.js";
import {BeaconProposerCache} from "../../src/chain/beaconProposerCache.js";
import {QueuedStateRegenerator} from "../../src/chain/regen/index.js";
import {LightClientServer} from "../../src/chain/lightClient/index.js";
import {Clock} from "../../src/util/clock.js";
import {getMockedLogger} from "./loggerMock.js";

export type MockedBeaconChain = MockedObject<BeaconChain> & {
getHeadState: Mock<[]>;
forkChoice: MockedObject<ForkChoice>;
executionEngine: MockedObject<ExecutionEngineHttp>;
eth1: MockedObject<Eth1ForBlockProduction>;
opPool: MockedObject<OpPool>;
aggregatedAttestationPool: MockedObject<AggregatedAttestationPool>;
beaconProposerCache: MockedObject<BeaconProposerCache>;
regen: MockedObject<QueuedStateRegenerator>;
bls: {
verifySignatureSets: Mock<[boolean]>;
verifySignatureSetsSameMessage: Mock<[boolean]>;
close: Mock;
canAcceptWork: Mock<[boolean]>;
};
lightClientServer: MockedObject<LightClientServer>;
};
vi.mock("@lodestar/fork-choice");
vi.mock("../../src/execution/engine/http.js");
vi.mock("../../src/eth1/index.js");
vi.mock("../../src/chain/opPools/opPool.js");
vi.mock("../../src/chain/opPools/aggregatedAttestationPool.js");
vi.mock("../../src/chain/beaconProposerCache.js");
vi.mock("../../src/chain/regen/index.js");
vi.mock("../../src/chain/lightClient/index.js");
vi.mock("../../src/chain/index.js", async (requireActual) => {
const mod = await requireActual<typeof import("../../src/chain/index.js")>();

const BeaconChain = vi.fn().mockImplementation(({clock, genesisTime, config}: MockedBeaconChainOptions) => {
return {
config,
opts: {},
genesisTime,
clock:
clock === "real"
? new Clock({config, genesisTime: 0, signal: new AbortController().signal})
: {
currentSlot: undefined,
currentSlotWithGossipDisparity: undefined,
isCurrentSlotGivenGossipDisparity: vi.fn(),
},
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
forkChoice: new ForkChoice(),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
executionEngine: new ExecutionEngineHttp(),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
eth1: new Eth1ForBlockProduction(),
opPool: new OpPool(),
aggregatedAttestationPool: new AggregatedAttestationPool(),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
beaconProposerCache: new BeaconProposerCache(),
produceBlock: vi.fn(),
getCanonicalBlockAtSlot: vi.fn(),
recomputeForkChoiceHead: vi.fn(),
getHeadStateAtCurrentEpoch: vi.fn(),
getHeadState: vi.fn(),
updateBuilderStatus: vi.fn(),
processBlock: vi.fn(),
close: vi.fn(),
logger: getMockedLogger(),
regen: new QueuedStateRegenerator({} as any),
lightClientServer: new LightClientServer({} as any, {} as any),
bls: {
verifySignatureSets: vi.fn().mockResolvedValue(true),
verifySignatureSetsSameMessage: vi.fn().mockResolvedValue([true]),
close: vi.fn().mockResolvedValue(true),
canAcceptWork: vi.fn().mockReturnValue(true),
},
emitter: new mod.ChainEventEmitter(),
};
});

return {
...mod,
BeaconChain,
};
});

type MockedBeaconChainOptions = {
clock: "real" | "fake";
genesisTime: number;
config: ChainForkConfig;
};

export function getMockedBeaconChain(opts?: Partial<MockedBeaconChainOptions>): MockedBeaconChain {
const {clock, genesisTime, config} = opts ?? {};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
return new BeaconChain({
clock: clock ?? "fake",
genesisTime: genesisTime ?? 0,
config: config ?? defaultConfig,
}) as MockedBeaconChain;
}
Loading
Loading