datahaven/test/scripts/cargo-crossbuild.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

143 lines
4.6 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { $ } from "bun";
import { logger } from "utils";
const LOG_LEVEL = Bun.env.LOG_LEVEL || "info";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export const cargoCrossbuild = async (options: {
datahavenBuildExtraArgs?: string;
networkId?: string;
}) => {
logger.info("🔀 Cross-building DataHaven node for Linux AMD64");
const ARCH = (await $`uname -m`.text()).trim();
const OS = (await $`uname -s`.text()).trim();
// Case: Apple Silicon
if (ARCH === "arm64" && OS === "Darwin") {
logger.info("🍎 Apple Silicon detected. Proceeding with cross-building...");
if (!isCommandAvailable("zig")) {
logger.error("Zig is not installed. Please install Zig to proceed.");
logger.info(
"Instructions to install can be found here: https://ziglang.org/learn/getting-started/"
);
throw new Error("Zig is not installed");
}
await installCargoZigbuild();
const target = "x86_64-unknown-linux-gnu";
await addRustupTarget(target);
// Build and copy libpq.so before cargo zigbuild
await buildAndCopyLibpq(target, options.networkId);
// Get additional arguments from command line
const additionalArgs = options.datahavenBuildExtraArgs ?? "";
const command = `cargo zigbuild --target ${target} --release ${additionalArgs}`;
logger.debug(`Running build command: ${command}`);
if (LOG_LEVEL === "debug") {
await $`sh -c "${command}"`.cwd(`${process.cwd()}/../operator`);
} else {
await $`sh -c "${command}"`.cwd(`${process.cwd()}/../operator`).quiet();
}
// Case: Linux x86
} else if (ARCH === "x86_64" && OS === "Linux") {
logger.info("🖥️ Linux AMD64 detected. Proceeding with cross-building...");
const target = "x86_64-unknown-linux-gnu";
await addRustupTarget(target);
// Get additional arguments from command line
const additionalArgs = options.datahavenBuildExtraArgs ?? "";
const command = `cargo build --target ${target} --release ${additionalArgs}`;
logger.debug(`Running build command: ${command}`);
if (LOG_LEVEL === "debug") {
await $`sh -c "${command}"`.cwd(`${process.cwd()}/../operator`);
} else {
await $`sh -c "${command}"`.cwd(`${process.cwd()}/../operator`).quiet();
}
// Case: Unsupported architecture or OS
} else {
logger.error("🚨 Unsupported architecture or OS. Please use Apple Silicon or Linux AMD64.");
logger.info(`Architecture: ${ARCH}; OS: ${OS}`);
throw new Error("Unsupported architecture or OS");
}
};
const isCommandAvailable = async (command: string): Promise<boolean> => {
try {
await $`command -v ${command}`.text();
return true;
} catch {
return false;
}
};
const installCargoZigbuild = async (): Promise<void> => {
if (!(await $`cargo install --list`.text()).includes("cargo-zigbuild")) {
await $`cargo install cargo-zigbuild --locked`.text();
}
};
const addRustupTarget = async (target: string): Promise<void> => {
if (!(await $`rustup target list --installed`.text()).includes(target)) {
logger.debug(await $`rustup target add ${target}`.text());
}
};
// Updated function to build and copy libpq.so
const buildAndCopyLibpq = async (target: string, networkId?: string): Promise<void> => {
logger.info("🏗️ Building and copying libpq.so...");
// Set Docker platform
process.env.DOCKER_DEFAULT_PLATFORM = "linux/amd64";
// Build Docker image
const dockerfilePath = path.join(__dirname, "../docker/crossbuild-mac-libpq.dockerfile");
logger.debug(
await $`docker build -f ${dockerfilePath} -t crossbuild-libpq ${path.join(__dirname, "..", "..")}`.text()
);
// Create container with unique name
const containerName = networkId ? `linux-libpq-container-${networkId}` : "linux-libpq-container";
logger.debug(await $`docker create --name ${containerName} crossbuild-libpq`.text());
const destPath = path.join(
__dirname,
"..",
"..",
"operator",
"target",
target,
"release",
"deps"
);
// Ensure the destination directory exists
fs.mkdirSync(destPath, { recursive: true });
logger.debug(
await $`docker cp ${containerName}:/artifacts/libpq.so ${path.join(destPath, "libpq.so")}`.text()
);
// Remove container
logger.debug(await $`docker rm ${containerName}`.text());
// Set RUSTFLAGS with the correct library path
process.env.RUSTFLAGS = `-C link-arg=-Wl,-rpath,$ORIGIN/../release/deps -L ${destPath}`;
logger.trace(`RUSTFLAGS set to: ${process.env.RUSTFLAGS}`);
logger.success(`libpq.so has been copied to ${destPath}`);
};