Skip to content

Commit d038087

Browse files
authored
test: keep e2e tests artifacts on failure (#4214)
1 parent da519e7 commit d038087

7 files changed

Lines changed: 321 additions & 48 deletions

File tree

crates/e2e-tests/README.md

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ indexer state may be corrupt).
172172
The entry point for tests. `MpcCluster::start(config)` does everything:
173173

174174
1. Create `TestPorts` via `TestPorts::e2e_tests(config.port_seed)`.
175-
2. Create a per-test temp directory.
175+
2. Create a per-test artifact directory (`TestDir`, kept on failure, see
176+
[Debugging a failure](#debugging-a-failure)).
176177
3. Start the `NearSandbox`.
177178
4. Build a `NearBlockchain` signed as the sandbox root.
178179
5. Generate deterministic signing keys for each node (near signer, p2p, operator,
@@ -213,8 +214,9 @@ The returned cluster exposes:
213214
`wait_for_foreign_chains_registrations`, `wait_for_available_foreign_chains`
214215
- **User accounts:** `user_client`, `default_user_account`.
215216

216-
`Drop` kills all running nodes; the temp directory is held via `test_dir` and
217-
removed when the cluster is dropped.
217+
`Drop` kills all running nodes; the artifact directory is held via `test_dir` and
218+
removed when the cluster is dropped, unless the test failed (see
219+
[Debugging a failure](#debugging-a-failure)).
218220

219221
```rust
220222
pub struct MpcClusterConfig {
@@ -361,6 +363,30 @@ CI runs the same task via the `mpc-e2e-tests` job.
361363

362364
---
363365

366+
## Debugging a failure
367+
368+
Each test gets an artifact directory (`/tmp/mpc-e2e-<random>` by default) with a
369+
`node<i>/` subdirectory per node holding its config, secrets, RocksDB data, the
370+
embedded neard home, `stdout.log` (the node's tracing output) and `stderr.log`
371+
(panics only). `TestDir`
372+
(`src/test_dir.rs`) keeps it when the test failed and prints the path to the
373+
test's stderr; a startup failure also carries it in the error:
374+
375+
```
376+
failed to start cluster: cluster artifacts preserved in /tmp/mpc-e2e-AbC123:
377+
mpc-node 0 exited early, check /tmp/mpc-e2e-AbC123/node0 (stdout.log holds its
378+
logs, stderr.log any panic)
379+
```
380+
381+
| Variable | Effect |
382+
|---|---|
383+
| `E2E_KEEP_TMP=1` | Keep artifacts for passing tests too. `0`, `false`, `no` or `off` (any case) deletes them even for failing ones. |
384+
| `E2E_HOME_BASE=<dir>` | Parent of the artifact directories, created if missing. Same as `MpcClusterConfig::home_base`, without editing the test. |
385+
| `MPC_NODE_LOG=<filter>` | `RUST_LOG` for the spawned mpc-node processes (default `DEBUG`). |
386+
| `MPC_NODE_BACKTRACE=<0\|1\|full>` | `RUST_BACKTRACE` for the spawned mpc-node processes (default `1`). |
387+
388+
---
389+
364390
## Test layout conventions
365391

366392
- Follow the `<subject>__should_<assertion>` or `<subject>__<scenario>` naming
@@ -370,8 +396,8 @@ CI runs the same task via the `mpc-e2e-tests` job.
370396
- Prefer `common::must_setup_cluster` over calling `MpcCluster::start` directly;
371397
it initialises `tracing_subscriber` and waits for presignatures.
372398
- Tests must be deterministic across parallel execution. Use the port
373-
allocator, the per-cluster temp directory, and the deterministic key
374-
generation rather than creating state outside the cluster.
399+
allocator, the per-cluster artifact directory (`cluster.test_dir`), and the
400+
deterministic key generation rather than creating state outside the cluster.
375401
- Arithmetic in tests uses raw `+`/`-`/`*`/`/`; overflow panics are the
376402
desired failure mode (see `CLAUDE.md`).
377403

crates/e2e-tests/src/cluster.rs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::blockchain::{DeployedContract, NearBlockchain};
3030
use crate::caller::{CallMpc, WithWaitLevel};
3131
use crate::mpc_node::{MpcNode, MpcNodeSetup, MpcNodeSetupArgs, NodePorts};
3232
use crate::near_sandbox::NearSandbox;
33+
use crate::test_dir::TestDir;
3334
use test_port_allocator::TestPorts;
3435

3536
const SANDBOX_ROOT_ACCOUNT: &str = "sandbox";
@@ -97,7 +98,8 @@ pub struct MpcClusterConfig {
9798
pub presignatures_to_buffer: usize,
9899
/// Version of the `near-sandbox` binary (e.g. `"2.6.3"`, `"2.10.4"`).
99100
pub sandbox_version: String,
100-
/// Root directory for all test artifacts (logs, configs, DB). If `None`, a temp dir is created.
101+
/// Parent of the directory holding all test artifacts (logs, configs, DB).
102+
/// If `None`, `E2E_HOME_BASE` is used, else the system temp dir.
101103
pub home_base: Option<PathBuf>,
102104
/// Indices (into the node array) of nodes that are initial participants.
103105
/// An empty vec means all nodes are participants. Set to a subset to start
@@ -250,8 +252,7 @@ pub struct MpcCluster {
250252
pub threshold: usize,
251253
pub user_accounts: HashMap<AccountId, SigningKey>,
252254
pub ports: TestPorts,
253-
/// Held to keep the temp directory alive for the lifetime of the cluster.
254-
pub test_dir: tempfile::TempDir,
255+
pub test_dir: TestDir,
255256
}
256257

257258
impl MpcCluster {
@@ -263,9 +264,19 @@ impl MpcCluster {
263264
/// binaries, and wait for Running state.
264265
pub async fn start(config: MpcClusterConfig) -> anyhow::Result<Self> {
265266
config.validate()?;
267+
let test_dir = TestDir::new(config.home_base.as_deref())?;
268+
Self::start_in(&test_dir, config).await.map_err(|error| {
269+
test_dir.keep();
270+
error.context(format!(
271+
"cluster artifacts preserved in {}",
272+
test_dir.path().display()
273+
))
274+
})
275+
}
276+
277+
async fn start_in(test_dir: &TestDir, config: MpcClusterConfig) -> anyhow::Result<Self> {
266278
let threshold = config.threshold;
267279
let ports = TestPorts::e2e_tests(config.port_seed);
268-
let test_dir = create_test_dir(&config.home_base)?;
269280

270281
let sandbox = NearSandbox::start(&ports, &config.sandbox_version).await?;
271282
let root_secret_key: near_kit::SecretKey = SANDBOX_ROOT_SECRET_KEY
@@ -371,7 +382,7 @@ impl MpcCluster {
371382
threshold,
372383
user_accounts,
373384
ports,
374-
test_dir,
385+
test_dir: test_dir.clone(),
375386
})
376387
}
377388

@@ -1192,16 +1203,6 @@ impl MpcNodeState {
11921203
}
11931204
}
11941205

1195-
fn create_test_dir(home_base: &Option<PathBuf>) -> anyhow::Result<tempfile::TempDir> {
1196-
match home_base {
1197-
Some(base) => {
1198-
std::fs::create_dir_all(base)?;
1199-
Ok(tempfile::tempdir_in(base)?)
1200-
}
1201-
None => Ok(tempfile::tempdir()?),
1202-
}
1203-
}
1204-
12051206
fn generate_signing_keys(
12061207
num_nodes: u64,
12071208
) -> (
@@ -1637,8 +1638,9 @@ async fn ensure_nodes_alive(nodes: &mut [MpcNodeState]) -> anyhow::Result<()> {
16371638
if let MpcNodeState::Running(n) = node {
16381639
anyhow::ensure!(
16391640
!n.has_exited(),
1640-
"mpc-node {i} exited earlycheck {}/{}",
1641+
"mpc-node {i} exited early, check {} ({} holds its logs, {} any panic)",
16411642
n.setup().home_dir().display(),
1643+
crate::mpc_node::STDOUT_LOG,
16421644
crate::mpc_node::STDERR_LOG
16431645
);
16441646
}

crates/e2e-tests/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod foreign_chain_mock;
66
pub mod metrics;
77
pub mod mpc_node;
88
pub mod near_sandbox;
9+
pub mod test_dir;
910

1011
pub use blockchain::{DeployedContract, NearBlockchain};
1112
pub use caller::NearKitCaller;
@@ -14,4 +15,5 @@ pub use cluster::{
1415
MpcClusterConfig, MpcNodeState,
1516
};
1617
pub use near_sandbox::NearSandbox;
18+
pub use test_dir::TestDir;
1719
pub use test_port_allocator::{E2eTestPorts, TestPorts};

crates/e2e-tests/src/mpc_node.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ impl MpcNode {
150150
}
151151
}
152152

153+
pub const STDOUT_LOG: &str = "stdout.log";
153154
pub const STDERR_LOG: &str = "stderr.log";
154155

155156
/// Guard that kills the child process on drop.
@@ -362,9 +363,9 @@ impl MpcNodeSetup {
362363
"starting mpc-node"
363364
);
364365

365-
let stdout_file = std::fs::File::create(self.home_dir.join("stdout.log"))
366+
let stdout_file = std::fs::File::create(self.home_dir.join(STDOUT_LOG))
366367
.context("failed to create stdout log")?;
367-
let stderr_file = std::fs::File::create(self.home_dir.join("stderr.log"))
368+
let stderr_file = std::fs::File::create(self.home_dir.join(STDERR_LOG))
368369
.context("failed to create stderr log")?;
369370

370371
let child = Command::new(&self.binary_path)

0 commit comments

Comments
 (0)