Skip to main content

Crate thruput_testkit

Crate thruput_testkit 

Source
Expand description

A harness for writing multi-agent integration tests as ordinary application code.

A test builds a Scenario against an explicit NetworkConfig, hands it a set of Agents, and asserts on what they observe. Test scenarios are run in #[tokio::test] functions.

Each agent receives a deterministic native account derived from the scenario seed. Agents deploy contracts with explicit funding and optional initialization, call them, await receipts, and read chain state, in addition to coordinating with other agents off-chain and using the virtual clock and deterministic randomness.

use thruput_testkit::{Agent, DeployOptions, LocalNetworkConfig, NetworkConfig, Scenario};

let mut config = LocalNetworkConfig::default();
config.hog_devnode_binary = Some("/path/to/supporting/hog-devnode".into());
config.contract_artifact_directories.push("/path/to/built_contracts".into());
let scenario = Scenario::builder("counter-test")
    .network(NetworkConfig::local(config))
    .artifact("counter", "target/thruput/counter.rv32a")
    .build()
    .await?;

scenario.run_agents([Agent::new("alice", |mut ctx| async move {
    ctx.fund(1_000_000_000).await?; // 100 native tokens from the bootstrap faucet
    let deployed = ctx.deploy_artifact("counter", DeployOptions::funded(100_000_000)).await?;
    assert!(deployed.registered);
    Ok(())
})]).await?;
scenario.stop().await?;

§Networks and accounts

NetworkConfig::existing attaches to a running node, NetworkConfig::local launches and owns an ephemeral hog-devnode, and NetworkConfig::offline runs agents with locally-derived accounts and no chain (for coordination/timing/messaging tests). Scenario::run_agents returns an error when any agent returns an error, so the ordinary .await? test path cannot silently ignore agent failures.

Before using a live network, the scenario checks that the endpoint is compatible and serves the network hash named in its runtime manifest, so nothing is signed for a different network.

The scenario owns its fixture accounts’ keys, derived from its seed; ScenarioBuilder::accounts sets how many there are. A local node receives only their public allocations, of LocalNetworkConfig::initial_account_tokens raw native token units each (100 native tokens by default). On an existing network the accounts start empty: fund them explicitly with AgentContext::fund or AgentContext::submit_fund.

§Manual blocks and uncertain submissions

AgentContext::submit_fund, AgentContext::submit_deploy_artifact and the submit forms of calls return handles without waiting, for tests that drive blocks by hand (see BlockDriverConfig): await pending, produce a block, then await the receipt. Receipt waits are read-only.

Each submission reserves its nonce before dispatch. When a submission’s outcome is uncertain, the harness never resends it: the error retains the original transaction hash and exact signed bytes for reconciliation (TestkitError::Submission, or TestkitError::ReceiptWait for a wait that stopped), as does a SubmittedTransaction. In particular:

§Typed calls

With thruput-sdk’s remote feature (typically on a dev-dependency), #[contract] generates an <Iface>Remote proxy for each interface. Bound to an agent’s Caller, each of its methods encodes the arguments and returns a prepared invocation: a ContractView for a method declared pure or readOnly, which awaiting simulates with the agent as msg.sender, or otherwise a ContractCall, which awaiting submits through the agent’s wallet. Either decodes the method’s return type. A method with no effect annotation gets the same proxy method as one declared stateChanging, so the annotation can be omitted; the omission is only visible in the contract’s spec, where the effect is recorded as undeclared.

mod counter {
    use thruput_sdk::contract;

    #[contract(version = "0.1.0")]
    pub trait Counter {
        /// Add `by` to the count, returning the new count.
        fn increment(&mut self, by: u64) -> u64;

        #[contract(effect = "readOnly")]
        fn get(&self) -> u64;
    }
}

use counter::CounterRemote;
use thruput_testkit::Agent;
use thruput_testkit::xdr::types::ContractAddress;

fn alice(address: ContractAddress) -> Agent {
    Agent::new("alice", move |ctx| async move {
        let counter = CounterRemote::new(ctx.caller().clone(), &address);
        let count = counter.increment(1).await?; // submitted
        assert_eq!(counter.get().await?, count); // simulated
        Ok(())
    })
}

See contract for receipts, deferred submission, and reads through methods without a declared effect, and Contract for calling a method by name when no interface crate is at hand.

Re-exports§

pub use agent::Agent;
pub use agent::AgentContext;
pub use agent::AgentId;
pub use caller::Caller;
pub use clock::Clock;
pub use contract::Contract;
pub use contract::ContractCall;
pub use contract::ContractView;
pub use contract::SubmittedContractCall;
pub use contract::entrypoint_id;
pub use contract::try_entrypoint_id;
pub use error::AgentFailure;
pub use error::AgentFailures;
pub use error::Result;
pub use error::TestkitError;
pub use mailbox::Envelope;
pub use mailbox::Mailbox;
pub use mailbox::Message;
pub use network::LocalNetworkConfig;
pub use network::Network;
pub use network::NetworkConfig;
pub use network::SubmittedTransaction;
pub use rng::AgentRng;
pub use scenario::AgentOutcome;
pub use scenario::Scenario;
pub use scenario::ScenarioBuilder;
pub use scenario::ScenarioReport;
pub use thruput_json_rpc_types as rpc_types;
pub use thruput_xdr as xdr;
pub use xdrs;

Modules§

agent
Agents and the context the harness hands them.
caller
A cloneable handle for submitting transactions from an agent’s account.
clock
Virtual clock owned by the harness.
contract
Contract method helpers.
error
Error type for the test harness.
mailbox
Off-chain messaging between agents.
network
The network a scenario runs against: how to reach it (NetworkConfig) and the live handle agents use (Network).
rng
Deterministic per-agent randomness.
scenario
Scenario builder, runner, and result types.

Structs§

BlockDriverConfig
Local block production policy used by the hogd-dev supervisor.
ContractEntrypointID
8-byte identifier for a contract method (entrypoint).
DeployOptions
Explicit funding and optional initialization in the deployment transaction.
Deployed
Result of a contract deploy.
ProducedBlock
Metadata returned after the scheduler commits a manually-produced block.
SubmittedDeploy
A submitted deployment whose receipt can be awaited after manual block production.
WalletInitCall
Optional initialization of the newly deployed contract, called by the wallet. A failed invocation rolls back funding and deployment; return bytes are discarded.

Enums§

ArtifactKind
Which artifact format a contract binary is in.
BlockDriverMode
Block driver wake mode.
BlockProductionError
Errors returned while manually producing a local block.
LogLevel
Log verbosity level for hog-devnode

Functions§

init_tracing
Install a stderr tracing subscriber that prints forwarded hog-devnode logs and supervisor diagnostics.