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>
150 lines
5 KiB
TypeScript
150 lines
5 KiB
TypeScript
import { $ } from "bun";
|
||
import { logger } from "utils";
|
||
|
||
// Minimum Bun version required
|
||
const MIN_BUN_VERSION = { major: 1, minor: 1 };
|
||
|
||
/**
|
||
* Checks if all base dependencies are installed and available.
|
||
* These checks are needed for both CLI and test environments.
|
||
*/
|
||
export const checkBaseDependencies = async (): Promise<void> => {
|
||
if (!(await checkKurtosisInstalled())) {
|
||
logger.error("Kurtosis CLI is required to be installed: https://docs.kurtosis.com/install");
|
||
throw Error("❌ Kurtosis CLI application not found.");
|
||
}
|
||
|
||
logger.success("Kurtosis CLI found");
|
||
|
||
if (!(await checkBunVersion())) {
|
||
logger.error(
|
||
`Bun version must be ${MIN_BUN_VERSION.major}.${MIN_BUN_VERSION.minor} or higher: https://bun.sh/docs/installation#upgrading`
|
||
);
|
||
throw Error("❌ Bun version is too old.");
|
||
}
|
||
|
||
logger.success("Bun is installed and up to date");
|
||
|
||
if (!(await checkDockerRunning())) {
|
||
logger.error("Is Docker Running? Unable to make connection to docker daemon");
|
||
throw Error("❌ Error connecting to Docker");
|
||
}
|
||
|
||
logger.success("Docker is running");
|
||
|
||
if (!(await checkForgeInstalled())) {
|
||
logger.error("Is foundry installed? https://book.getfoundry.sh/getting-started/installation");
|
||
throw Error("❌ Forge binary not found in PATH");
|
||
}
|
||
|
||
logger.success("Forge is installed");
|
||
};
|
||
|
||
/**
|
||
* Checks if Bun version meets minimum requirements
|
||
*/
|
||
export const checkBunVersion = async (): Promise<boolean> => {
|
||
const bunVersion = Bun.version;
|
||
const [major, minor] = bunVersion.split(".").map(Number);
|
||
|
||
// Check if version meets minimum requirements
|
||
const isVersionValid =
|
||
major > MIN_BUN_VERSION.major ||
|
||
(major === MIN_BUN_VERSION.major && minor >= MIN_BUN_VERSION.minor);
|
||
|
||
if (!isVersionValid) {
|
||
logger.debug(`Bun version: ${bunVersion} (too old)`);
|
||
return false;
|
||
}
|
||
|
||
logger.debug(`Bun version: ${bunVersion}`);
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* Checks if Kurtosis CLI is installed
|
||
*/
|
||
export const checkKurtosisInstalled = async (): Promise<boolean> => {
|
||
const { exitCode, stderr, stdout } = await $`kurtosis version`.nothrow().quiet();
|
||
if (exitCode !== 0) {
|
||
logger.debug(`Kurtosis check failed: ${stderr.toString()}`);
|
||
return false;
|
||
}
|
||
logger.debug(`Kurtosis version: ${stdout.toString().trim()}`);
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* Checks if Docker daemon is running
|
||
*/
|
||
export const checkDockerRunning = async (): Promise<boolean> => {
|
||
const { exitCode, stderr } = await $`docker system info`.nothrow().quiet();
|
||
if (exitCode !== 0) {
|
||
logger.debug(`Docker check failed: ${stderr.toString()}`);
|
||
return false;
|
||
}
|
||
logger.debug("Docker daemon is running");
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* Checks if Forge (Foundry) is installed
|
||
*/
|
||
export const checkForgeInstalled = async (): Promise<boolean> => {
|
||
const { exitCode, stderr, stdout } = await $`forge --version`.nothrow().quiet();
|
||
if (exitCode !== 0) {
|
||
logger.debug(`Forge check failed: ${stderr.toString()}`);
|
||
return false;
|
||
}
|
||
logger.debug(`Forge version: ${stdout.toString().trim()}`);
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* Checks if the Kurtosis cluster type that is configured is compatible with the expected type
|
||
* @param kubernetes - Whether the cluster is expected to be a Kubernetes cluster
|
||
* @returns true if the cluster type is compatible, false otherwise
|
||
*/
|
||
export const checkKurtosisCluster = async (kubernetes?: boolean): Promise<boolean> => {
|
||
// First check if kurtosis cluster get works
|
||
const { exitCode, stderr, stdout } = await $`kurtosis cluster get`.nothrow().quiet();
|
||
|
||
if (exitCode !== 0) {
|
||
logger.warn(`⚠️ Kurtosis cluster get failed: ${stderr.toString()}`);
|
||
logger.info("ℹ️ Assuming local launch mode and continuing.");
|
||
return true;
|
||
}
|
||
|
||
const currentCluster = stdout.toString().trim();
|
||
logger.debug(`Current Kurtosis cluster: ${currentCluster}`);
|
||
|
||
// Try to get the cluster type from config, but don't fail if config path is not reachable
|
||
const clusterTypeResult =
|
||
await $`CURRENT_CLUSTER=${currentCluster} && sed -n "/^ $CURRENT_CLUSTER:$/,/^ [^ ]/p" "$(kurtosis config path)" | grep "type:" | sed 's/.*type: "\(.*\)"/\1/'`
|
||
.nothrow()
|
||
.quiet();
|
||
|
||
if (clusterTypeResult.exitCode !== 0) {
|
||
logger.warn("⚠️ Failed to read Kurtosis cluster type from config");
|
||
logger.debug(clusterTypeResult.stderr.toString());
|
||
logger.info("ℹ️ Assuming local launch mode and continuing gracefully");
|
||
return true; // Continue gracefully for local launch
|
||
}
|
||
|
||
const clusterType = clusterTypeResult.stdout.toString().trim();
|
||
logger.debug(`Kurtosis cluster type: ${clusterType}`);
|
||
|
||
// Validate cluster type against expected type
|
||
if (kubernetes && clusterType !== "kubernetes") {
|
||
logger.error(`❌ Kurtosis cluster type is "${clusterType}" but kubernetes is required`);
|
||
return false;
|
||
}
|
||
|
||
if (!kubernetes && clusterType !== "docker") {
|
||
logger.error(`❌ Kurtosis cluster type is "${clusterType}" but docker is required`);
|
||
return false;
|
||
}
|
||
|
||
logger.success(`Kurtosis cluster type "${clusterType}" is compatible`);
|
||
return true;
|
||
};
|