datahaven/test/launcher/utils/checks.ts
Facundo Farall 9b311e00ef
test: 🏗️ Setup e2e testing framework (#104)
## 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>
2025-07-16 18:51:07 +02:00

150 lines
5 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
};