chore: add reliability tests package with longevity tests (#2361)

* chore: add reliability tests package with longevity tests

* chore: add reliability tests package with longevity tests

* chore: fix ci errors

* chore: fix

* chore: fix timeout
This commit is contained in:
fbarbu15 2025-06-05 11:19:00 +03:00 committed by GitHub
parent 49f26d89a8
commit 4997440225
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 464 additions and 0 deletions

37
.github/workflows/test-reliability.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: Run Reliability Test
on:
workflow_dispatch:
push:
branches:
- "chore/longevity-tests"
env:
NODE_JS: "20"
jobs:
node:
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
checks: write
steps:
- uses: actions/checkout@v3
with:
repository: waku-org/js-waku
- name: Remove unwanted software
uses: ./.github/actions/prune-vm
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_JS }}
- uses: ./.github/actions/npm
- run: npm run build:esm
- name: Run tests
timeout-minutes: 150
run: npm run test:longevity

55
package-lock.json generated
View File

@ -18,6 +18,7 @@
"packages/sds",
"packages/rln",
"packages/tests",
"packages/reliability-tests",
"packages/headless-tests",
"packages/browser-tests",
"packages/build-utils",
@ -11751,6 +11752,10 @@
"resolved": "packages/relay",
"link": true
},
"node_modules/@waku/reliability-tests": {
"resolved": "packages/reliability-tests",
"link": true
},
"node_modules/@waku/rln": {
"resolved": "packages/rln",
"link": true
@ -44165,6 +44170,56 @@
}
}
},
"packages/reliability-tests": {
"name": "@waku/reliability-tests",
"version": "0.0.1",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@libp2p/interface-compliance-tests": "^6.0.1",
"@libp2p/peer-id": "^5.0.1",
"@waku/core": "*",
"@waku/enr": "*",
"@waku/interfaces": "*",
"@waku/utils": "*",
"app-root-path": "^3.1.0",
"chai-as-promised": "^7.1.1",
"debug": "^4.3.4",
"dockerode": "^4.0.2",
"fast-check": "^3.19.0",
"p-retry": "^6.1.0",
"p-timeout": "^6.1.0",
"portfinder": "^1.0.32",
"sinon": "^18.0.0",
"tail": "^2.2.6"
},
"devDependencies": {
"@libp2p/bootstrap": "^11.0.1",
"@libp2p/crypto": "^5.0.1",
"@types/chai": "^4.3.11",
"@types/dockerode": "^3.3.19",
"@types/mocha": "^10.0.6",
"@types/sinon": "^17.0.3",
"@types/tail": "^2.2.3",
"@waku/discovery": "*",
"@waku/message-encryption": "*",
"@waku/relay": "*",
"@waku/sdk": "*",
"allure-commandline": "^2.27.0",
"allure-mocha": "^2.9.2",
"chai": "^4.3.10",
"cspell": "^8.6.1",
"datastore-core": "^10.0.2",
"debug": "^4.3.4",
"interface-datastore": "^8.2.10",
"libp2p": "2.1.8",
"mocha": "^10.3.0",
"mocha-multi-reporters": "^1.5.1",
"npm-run-all": "^4.1.5"
},
"engines": {
"node": ">=20"
}
},
"packages/rln": {
"name": "@waku/rln",
"version": "0.1.5",

View File

@ -15,6 +15,7 @@
"packages/sds",
"packages/rln",
"packages/tests",
"packages/reliability-tests",
"packages/headless-tests",
"packages/browser-tests",
"packages/build-utils",
@ -33,6 +34,7 @@
"test": "NODE_ENV=test npm run test --workspaces --if-present",
"test:browser": "NODE_ENV=test npm run test:browser --workspaces --if-present",
"test:node": "NODE_ENV=test npm run test:node --workspaces --if-present",
"test:longevity": "npm --prefix packages/reliability-tests run test:longevity",
"proto": "npm run proto --workspaces --if-present",
"deploy": "node ci/deploy.js",
"doc": "run-s doc:*",

View File

@ -0,0 +1,12 @@
module.exports = {
parserOptions: {
tsconfigRootDir: __dirname,
project: "./tsconfig.dev.json"
},
rules: {
"@typescript-eslint/no-non-null-assertion": "off"
},
globals: {
process: true
}
};

View File

@ -0,0 +1,13 @@
const config = {
extension: ['ts'],
require: ['ts-node/register', 'isomorphic-fetch'],
loader: 'ts-node/esm',
'node-option': [
'experimental-specifier-resolution=node',
'loader=ts-node/esm'
],
exit: true,
retries: 0
};
module.exports = config;

View File

@ -0,0 +1,26 @@
# Reliability Tests
This package contains reliability and stability tests for the [js-waku](https://github.com/waku-org/js-waku) project.
These tests are designed to run realistic message scenarios in loops over extended periods, helping identify edge cases, memory leaks, network inconsistencies, or message delivery issues that may appear over time.
## 📄 Current Tests
### `longevity.spec.ts`
This is the first test in the suite. It runs a js-waku<->nwaku filter scenario in a loop for 2 hours, sending and receiving messages continuously.
The test records:
- Message ID
- Timestamp
- Send/receive status
- Any errors during transmission
At the end, a summary report is printed and any failures cause the test to fail.
## 🚀 How to Run
From the **project root**:
```bash
npm run test:longevity

View File

@ -0,0 +1,92 @@
{
"name": "@waku/reliability-tests",
"private": true,
"version": "0.0.1",
"description": "Waku reliability tests",
"types": "./dist/index.d.ts",
"module": "./dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"type": "module",
"author": "Waku Team",
"homepage": "https://github.com/waku-org/js-waku/tree/master/packages/reliability-tests#readme",
"repository": {
"type": "git",
"url": "https://github.com/waku-org/js-waku.git"
},
"bugs": {
"url": "https://github.com/waku-org/js-waku/issues"
},
"license": "MIT OR Apache-2.0",
"keywords": [
"waku",
"decentralized",
"secure",
"communication",
"web3",
"ethereum",
"dapps",
"privacy"
],
"scripts": {
"build": "run-s build:**",
"build:esm": "tsc",
"fix": "run-s fix:*",
"fix:lint": "eslint src tests --fix",
"check": "run-s check:*",
"check:lint": "eslint src tests",
"check:spelling": "cspell \"{README.md,{tests,src}/**/*.ts}\"",
"check:tsc": "tsc -p tsconfig.dev.json",
"test:longevity": "NODE_ENV=test node ./src/run-tests.js tests/longevity.spec.ts",
"reset-hard": "git clean -dfx -e .idea && git reset --hard && npm i && npm run build"
},
"engines": {
"node": ">=20"
},
"dependencies": {
"@libp2p/interface-compliance-tests": "^6.0.1",
"@libp2p/peer-id": "^5.0.1",
"@waku/core": "*",
"@waku/enr": "*",
"@waku/interfaces": "*",
"@waku/utils": "*",
"app-root-path": "^3.1.0",
"chai-as-promised": "^7.1.1",
"debug": "^4.3.4",
"dockerode": "^4.0.2",
"fast-check": "^3.19.0",
"p-retry": "^6.1.0",
"p-timeout": "^6.1.0",
"portfinder": "^1.0.32",
"sinon": "^18.0.0",
"tail": "^2.2.6"
},
"devDependencies": {
"@libp2p/bootstrap": "^11.0.1",
"@libp2p/crypto": "^5.0.1",
"@types/chai": "^4.3.11",
"@types/dockerode": "^3.3.19",
"@types/mocha": "^10.0.6",
"@types/sinon": "^17.0.3",
"@types/tail": "^2.2.3",
"@waku/discovery": "*",
"@waku/message-encryption": "*",
"@waku/relay": "*",
"@waku/sdk": "*",
"allure-commandline": "^2.27.0",
"allure-mocha": "^2.9.2",
"chai": "^4.3.10",
"cspell": "^8.6.1",
"datastore-core": "^10.0.2",
"debug": "^4.3.4",
"interface-datastore": "^8.2.10",
"libp2p": "2.1.8",
"mocha": "^10.3.0",
"mocha-multi-reporters": "^1.5.1",
"npm-run-all": "^4.1.5"
}
}

View File

@ -0,0 +1,50 @@
import { exec, spawn } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
const WAKUNODE_IMAGE = process.env.WAKUNODE_IMAGE || "wakuorg/nwaku:v0.35.1";
async function main() {
try {
await execAsync(`docker inspect ${WAKUNODE_IMAGE}`);
console.log(`Using local image ${WAKUNODE_IMAGE}`);
} catch (error) {
console.log(`Pulling image ${WAKUNODE_IMAGE}`);
await execAsync(`docker pull ${WAKUNODE_IMAGE}`);
console.log("Image pulled");
}
const mochaArgs = [
"mocha",
"--require",
"ts-node/register",
"--project",
"./tsconfig.dev.json",
...process.argv.slice(2)
];
// Run mocha tests
const mocha = spawn("npx", mochaArgs, {
stdio: "inherit",
env: {
...process.env,
NODE_ENV: "test"
}
});
mocha.on("error", (error) => {
console.log(`Error running mocha tests: ${error.message}`);
process.exit(1);
});
mocha.on("exit", (code) => {
console.log(`Mocha tests exited with code ${code}`);
process.exit(code || 0);
});
}
main().catch((error) => {
console.log(error);
process.exit(1);
});

View File

@ -0,0 +1,164 @@
import { LightNode, Protocols } from "@waku/interfaces";
import {
createDecoder,
createEncoder,
createLightNode,
utf8ToBytes
} from "@waku/sdk";
import {
delay,
shardInfoToPubsubTopics,
singleShardInfosToShardInfo,
singleShardInfoToPubsubTopic
} from "@waku/utils";
import { expect } from "chai";
import {
afterEachCustom,
beforeEachCustom,
makeLogFileName,
MessageCollector,
ServiceNode,
tearDownNodes
} from "../../tests/src/index.js";
const ContentTopic = "/waku/2/content/test.js";
describe("Longevity", function () {
const testDurationMs = 2 * 60 * 60 * 1000; // 2 hours
this.timeout(testDurationMs + 5 * 60 * 1000);
let waku: LightNode;
let nwaku: ServiceNode;
let messageCollector: MessageCollector;
beforeEachCustom(this, async () => {
nwaku = new ServiceNode(makeLogFileName(this.ctx));
messageCollector = new MessageCollector(nwaku);
});
afterEachCustom(this, async () => {
await tearDownNodes(nwaku, waku);
});
it("Filter - 2 hours", async function () {
const singleShardInfo = { clusterId: 0, shard: 0 };
const shardInfo = singleShardInfosToShardInfo([singleShardInfo]);
const testStart = new Date();
const testEnd = Date.now() + testDurationMs;
const report: {
messageId: number;
timestamp: string;
sent: boolean;
received: boolean;
error?: string;
}[] = [];
await nwaku.start(
{
store: true,
filter: true,
relay: true,
clusterId: 0,
shard: [0],
contentTopic: [ContentTopic]
},
{ retries: 3 }
);
await nwaku.ensureSubscriptions(shardInfoToPubsubTopics(shardInfo));
waku = await createLightNode({ networkConfig: shardInfo });
await waku.start();
await waku.dial(await nwaku.getMultiaddrWithId());
await waku.waitForPeers([Protocols.Filter]);
const decoder = createDecoder(ContentTopic, singleShardInfo);
const { error } = await waku.filter.subscribe(
[decoder],
messageCollector.callback
);
if (error) throw error;
const encoder = createEncoder({
contentTopic: ContentTopic,
pubsubTopicShardInfo: singleShardInfo
});
expect(encoder.pubsubTopic).to.eq(
singleShardInfoToPubsubTopic(singleShardInfo)
);
let messageId = 0;
while (Date.now() < testEnd) {
const now = new Date();
const message = `ping-${messageId}`;
let sent = false;
let received = false;
let err: string | undefined;
try {
await nwaku.sendMessage(
ServiceNode.toMessageRpcQuery({
contentTopic: ContentTopic,
payload: utf8ToBytes(message)
})
);
sent = true;
received = await messageCollector.waitForMessages(1, {
timeoutDuration: 5000
});
if (received) {
messageCollector.verifyReceivedMessage(0, {
expectedMessageText: message,
expectedContentTopic: ContentTopic,
expectedPubsubTopic: shardInfoToPubsubTopics(shardInfo)[0]
});
}
} catch (e: any) {
err = e.message || String(e);
}
report.push({
messageId,
timestamp: now.toISOString(),
sent,
received,
error: err
});
messageId++;
messageCollector.list = []; // clearing the message collector
await delay(400);
}
const failedMessages = report.filter(
(m) => !m.sent || !m.received || m.error
);
console.log("\n=== Longevity Test Summary ===");
console.log("Start time:", testStart.toISOString());
console.log("End time:", new Date().toISOString());
console.log("Total messages:", report.length);
console.log("Failures:", failedMessages.length);
if (failedMessages.length > 0) {
console.log("\n--- Failed Messages ---");
for (const fail of failedMessages) {
console.log(
`#${fail.messageId} @ ${fail.timestamp} | sent: ${fail.sent} | received: ${fail.received} | error: ${fail.error || "N/A"}`
);
}
}
expect(
failedMessages.length,
`Some messages failed: ${failedMessages.length}`
).to.eq(0);
});
});

View File

@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.dev"
}

View File

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"allowJs": true,
"outDir": "dist/",
"rootDir": "src",
"tsBuildInfoFile": "dist/.tsbuildinfo"
},
"include": ["src"]
}