mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
## Implement E2E Testing Framework with Isolated Networks ### Summary Refactors the existing E2E testing infrastructure to provide isolated test environments with parallel execution support. Each test suite now runs in its own network namespace, preventing resource conflicts. ### Key Changes - **New Testing Framework** (`test/framework/`): Base classes for test lifecycle management with automatic setup/teardown - **Launcher Module** (`test/launcher/`): Extracted network orchestration logic from CLI handlers for reusability - **Parallel Execution**: Added `test-parallel.ts` script with concurrency limits to prevent resource exhaustion - **Test Isolation**: Each suite gets unique network IDs (format: `suiteName-timestamp`) and Docker networks - **Improved Test Organization**: Migrated tests to new framework, deprecated old test structure ### Test Improvements - Added 4 new test suites demonstrating framework usage. : - `contracts.test.ts` - Smart contract deployment/interaction - `datahaven-substrate.test.ts` - Substrate API operations - `cross-chain.test.ts` - Snowbridge cross-chain messaging - `ethereum-basic.test.ts` - Ethereum network operations > [!WARNING] The test suites themselves are bad and shouldn't be consider examples of good tests. They were AI generated just to test the concurrency of test runners ### Documentation - Added comprehensive framework overview (`E2E_FRAMEWORK_OVERVIEW.md`) - Updated README with parallel testing commands - Added test patterns and best practices ### Breaking Changes - Old test suites moved to `e2e - DEPRECATED/` directory - Test execution now requires extending `BaseTestSuite` class ### Testing Run tests with: `bun test:e2e` or `bun test:e2e:parallel` (with concurrency limits) ### TODO - [ ] Implement good test examples. - [ ] Implement useful test utils (like waiting for an event to show up in DataHaven or Ethereum). - [ ] Enforce tests with CI (currently cannot be done due to intermittent error when sending a transaction with PAPI). --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: undercover-cactus <lola@moonsonglabs.com>
83 lines
2.3 KiB
TypeScript
83 lines
2.3 KiB
TypeScript
import { logger } from "utils";
|
|
|
|
export interface TestSuiteRegistry {
|
|
suiteId: string;
|
|
networkId: string;
|
|
startTime: number;
|
|
status: "running" | "completed" | "failed";
|
|
}
|
|
|
|
/**
|
|
* Manager for tracking running test suites and ensuring cleanup
|
|
*/
|
|
export class TestSuiteManager {
|
|
private static instance: TestSuiteManager;
|
|
private suites: Map<string, TestSuiteRegistry> = new Map();
|
|
|
|
private constructor() {
|
|
// Set up process exit handlers to ensure cleanup
|
|
process.on("exit", () => this.cleanupAll());
|
|
process.on("SIGINT", () => this.cleanupAll());
|
|
process.on("SIGTERM", () => this.cleanupAll());
|
|
}
|
|
|
|
static getInstance(): TestSuiteManager {
|
|
if (!TestSuiteManager.instance) {
|
|
TestSuiteManager.instance = new TestSuiteManager();
|
|
}
|
|
return TestSuiteManager.instance;
|
|
}
|
|
|
|
registerSuite(suiteId: string, networkId: string): void {
|
|
if (this.suites.has(suiteId)) {
|
|
throw new Error(`Test suite ${suiteId} is already registered`);
|
|
}
|
|
|
|
this.suites.set(suiteId, {
|
|
suiteId,
|
|
networkId,
|
|
startTime: Date.now(),
|
|
status: "running"
|
|
});
|
|
|
|
logger.debug(`Registered test suite: ${suiteId} with network: ${networkId}`);
|
|
}
|
|
|
|
completeSuite(suiteId: string): void {
|
|
const suite = this.suites.get(suiteId);
|
|
if (suite) {
|
|
suite.status = "completed";
|
|
const duration = ((Date.now() - suite.startTime) / 1000).toFixed(1);
|
|
logger.debug(`Test suite ${suiteId} completed in ${duration}s`);
|
|
}
|
|
}
|
|
|
|
failSuite(suiteId: string): void {
|
|
const suite = this.suites.get(suiteId);
|
|
if (suite) {
|
|
suite.status = "failed";
|
|
logger.debug(`Test suite ${suiteId} failed`);
|
|
}
|
|
}
|
|
|
|
getRunningCount(): number {
|
|
return Array.from(this.suites.values()).filter((s) => s.status === "running").length;
|
|
}
|
|
|
|
getRunningNetworkIds(): string[] {
|
|
return Array.from(this.suites.values())
|
|
.filter((s) => s.status === "running")
|
|
.map((s) => s.networkId);
|
|
}
|
|
|
|
private cleanupAll(): void {
|
|
const runningSuites = Array.from(this.suites.values()).filter((s) => s.status === "running");
|
|
|
|
if (runningSuites.length > 0) {
|
|
logger.warn(`⚠️ Process exiting with ${runningSuites.length} test suite(s) still running`);
|
|
runningSuites.forEach((suite) => {
|
|
logger.warn(` - ${suite.suiteId} (network: ${suite.networkId})`);
|
|
});
|
|
}
|
|
}
|
|
}
|