Skip to main content

forest/dev/subcommands/
devnet_cmd.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4//! Integration suites that run against the local docker devnet. Unlike the unit
5//! suite, these need a running devnet with both a Forest and a Lotus node reachable and the
6//! test harness environment wired up. [`preflight`] fails early with actionable errors when
7//! that environment is missing, rather than letting a suite surface it as an opaque mid-run error.
8
9mod eth_gas;
10
11use crate::dev::subcommands::tests_cmd::helpers::{docker, forest_client, lotus_client};
12use crate::rpc::prelude::*;
13use anyhow::{Context as _, ensure};
14
15/// Integration tests that require the docker devnet
16#[derive(Debug, clap::Subcommand)]
17pub enum DevnetCommand {
18    EthGas(eth_gas::EthGasTestCommand),
19}
20
21impl DevnetCommand {
22    pub async fn run(self) -> anyhow::Result<()> {
23        preflight().await.context("devnet pre-flight failed")?;
24        match self {
25            Self::EthGas(cmd) => cmd.run().await,
26        }
27    }
28}
29
30async fn preflight() -> anyhow::Result<()> {
31    for container in ["forest", "lotus"] {
32        let running = docker(&["inspect", "-f", "{{.State.Running}}", container]).with_context(
33            || format!("could not query container `{container}`; is docker running and the local docker devnet up?"),
34        )?;
35        ensure!(
36            running.trim() == "true",
37            "devnet container `{container}` is not running; bring the local docker devnet up first"
38        );
39    }
40
41    for var in [
42        "FULLNODE_API_INFO",
43        "FOREST_TEST_PRELOADED_ADDRESS",
44        "LOTUS_RPC_PORT",
45    ] {
46        ensure!(
47            std::env::var_os(var).is_some(),
48            "{var} is not set; source the devnet test harness and run `devnet_test_env_init` first"
49        );
50    }
51
52    // Probe eth RPC specifically, not just `ChainHead`: the suites need it, and a devnet with eth
53    // RPC disabled would otherwise pass here and fail opaquely mid-suite.
54    for (node, client) in [("forest", forest_client()?), ("lotus", lotus_client()?)] {
55        EthBlockNumber::call(&client, ()).await.with_context(|| {
56            format!("{node} eth RPC is not reachable; is the local docker devnet up (with eth RPC enabled) and synced?")
57        })?;
58    }
59
60    Ok(())
61}