Skip to content

Commit

Permalink
[RPC] add snapshot tests for Sui CLI/RPC
Browse files Browse the repository at this point in the history
A framework that makes it very easy to write tests for RPC behavior:

- tests are sequences of valid Sui CLI commands
- commands are run on a local network, their JSON outputs from RPC are captured in snapshots
- we use simtest to make this deterministic

We will need to add CLI support for a few RPC commands (e.g., get_balance, get_coins) to easily test those using this methodology, but that shouldn't be too bad + CLI parity with the RPC commands is a good thing anyway.
  • Loading branch information
sblackshear committed Apr 20, 2023
1 parent c78c695 commit 9c20a69
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 3 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/sui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ futures = "0.3.23"
prometheus = "0.13.3"
fs_extra = "1.3.0"
indexmap = "1.9.2"

insta = { version = "1.21.1", features = ["json"] }
jsonrpsee = { version = "0.16.2", features = ["jsonrpsee-core"] }

test-utils = { path = "../test-utils" }
rand = "0.8.5"
expect-test = "1.4.0"
move-binary-format.workspace = true
move-package.workspace = true

sui-core = { path = "../sui-core" }
sui-framework = { path = "../sui-framework" }
sui-json-rpc = { path = "../sui-json-rpc" }
Expand Down
37 changes: 36 additions & 1 deletion crates/sui/src/client_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,14 @@ impl SuiClientCommands {
.read_api()
.get_transaction_with_options(
digest,
SuiTransactionBlockResponseOptions::full_content(),
SuiTransactionBlockResponseOptions {
show_input: true,
show_raw_input: false,
show_effects: true,
show_events: true,
show_object_changes: true,
show_balance_changes: false,
},
)
.await?;
SuiClientCommandResult::TransactionBlock(tx_read)
Expand Down Expand Up @@ -1753,6 +1760,34 @@ impl SuiClientCommandResult {
info!("{line}")
}
}

pub fn tx_block_response(&self) -> Option<&SuiTransactionBlockResponse> {
use SuiClientCommandResult::*;
match self {
Upgrade(b)
| Publish(b)
| TransactionBlock(b)
| Call(b)
| Transfer(_, b)
| TransferSui(b)
| Pay(b)
| PaySui(b)
| PayAllSui(b)
| SplitCoin(b)
| MergeCoin(b)
| ExecuteSignedTx(b) => Some(b),
_ => None,
}
}

pub fn objects_response(&self) -> Option<Vec<SuiObjectResponse>> {
use SuiClientCommandResult::*;
match self {
Object(o) | RawObject(o) => Some(vec![o.clone()]),
Objects(o) => Some(o.clone()),
_ => None,
}
}
}

#[derive(Serialize)]
Expand Down
70 changes: 70 additions & 0 deletions crates/sui/tests/snapshot_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use clap::Parser;
use insta::assert_json_snapshot;
use sui::{client_commands::WalletContext, sui_commands::SuiCommand};
use sui_macros::sim_test;
use test_utils::network::TestClusterBuilder;

// special constants for substitution in commands
const ME: &str = "{ME}";

async fn run_one(
test: Vec<&str>,
context: &mut WalletContext,
) -> Result<Vec<serde_json::Value>, anyhow::Error> {
let mut test_output = Vec::new();
let active_addr = context.active_address()?.to_string();
for cli_cmd in test {
let mut cli_cmd_vec = cli_cmd.split(' ').collect::<Vec<&str>>();
for word in cli_cmd_vec.iter_mut() {
if *word == ME {
*word = &active_addr
}
}
test_output.push(serde_json::Value::String(cli_cmd.to_string()));
let c = SuiCommand::try_parse_from(cli_cmd_vec)?;
match c {
SuiCommand::Client { cmd, .. } => {
if let Some(client_cmd) = cmd {
match client_cmd.execute(context).await {
Ok(output) => {
if let Some(block_response) = output.tx_block_response() {
test_output.push(serde_json::to_value(block_response)?);
} else if let Some(objects_response) = output.objects_response() {
test_output.push(serde_json::to_value(objects_response)?)
}
}
Err(e) => test_output.push(serde_json::Value::String(e.to_string())),
}
}
}
SuiCommand::Move {
package_path: _,
build_config: _,
cmd: _,
} => unimplemented!("Supporting Move publish and upgrade commands"),
_ => panic!("Command {:?} not supported by RPC snapshot tests", cli_cmd),
}
}
Ok(test_output)
}

#[sim_test]
async fn basic_read_cmd_snapshot_tests() -> Result<(), anyhow::Error> {
let mut test_cluster = TestClusterBuilder::new().build().await?;
let context = &mut test_cluster.wallet;

let cmds = vec![
"sui client objects {ME}", // valid addr
"sui client objects 0x0000000000000000000000000000000000000000000000000000000000000000", // empty addr
"sui client object 0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee", // valid object
"sui client object 0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee --bcs", // valid object BCS
"sui client object 0x0000000000000000000000000000000000000000000000000000000000000000", // non-existent object
"sui client tx-block Duwr9uSk9ZvNdEa8oDHunx345i6oyrp3e78MYHVAbYdv", // valid tx digest
"sui client tx-block EgMTHQygMi6SRsBqrPHAEKZCNrpShXurCp9rcb9qbSg8", // non-existent tx digest
];
assert_json_snapshot!(run_one(cmds, context).await?);
Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
---
source: crates/sui/tests/snapshot_tests.rs
expression: "run_one(cmds, context).await?"
---
[
"sui client objects {ME}",
[
{
"data": {
"objectId": "0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee",
"version": "1",
"digest": "Duwr9uSk9ZvNdEa8oDHunx345i6oyrp3e78MYHVAbYdv",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"owner": {
"AddressOwner": "0x2768c8927ad2e24ad90fb2aa51f771dade8d86f634a37a59d7ed9ee6a5173f2d"
},
"previousTransaction": "349PK126tFq8YPg7ZirL1LksiiSNxJa8mNrym192zwwr",
"storageRebate": "0",
"content": {
"dataType": "moveObject",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"hasPublicTransfer": true,
"fields": {
"balance": "30000000000000000",
"id": {
"id": "0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee"
}
}
}
}
},
{
"data": {
"objectId": "0x4a774dbe830238303947f68bc747002de401d04aa62068f703c943b15d47e763",
"version": "1",
"digest": "2oXM6mXeQqM8SDV8RBbTRoK69sNn2x5EvSPs4D2Afopz",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"owner": {
"AddressOwner": "0x2768c8927ad2e24ad90fb2aa51f771dade8d86f634a37a59d7ed9ee6a5173f2d"
},
"previousTransaction": "349PK126tFq8YPg7ZirL1LksiiSNxJa8mNrym192zwwr",
"storageRebate": "0",
"content": {
"dataType": "moveObject",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"hasPublicTransfer": true,
"fields": {
"balance": "30000000000000000",
"id": {
"id": "0x4a774dbe830238303947f68bc747002de401d04aa62068f703c943b15d47e763"
}
}
}
}
},
{
"data": {
"objectId": "0xdb58c0d2c616fc597bfef6ae1ab472d1f03a0a60cc2c42525f9d0ce6ec829ade",
"version": "1",
"digest": "8Z7STKsxC67mMXWRPvup8Vbk4Yr42Popef742rqvj7Qr",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"owner": {
"AddressOwner": "0x2768c8927ad2e24ad90fb2aa51f771dade8d86f634a37a59d7ed9ee6a5173f2d"
},
"previousTransaction": "349PK126tFq8YPg7ZirL1LksiiSNxJa8mNrym192zwwr",
"storageRebate": "0",
"content": {
"dataType": "moveObject",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"hasPublicTransfer": true,
"fields": {
"balance": "30000000000000000",
"id": {
"id": "0xdb58c0d2c616fc597bfef6ae1ab472d1f03a0a60cc2c42525f9d0ce6ec829ade"
}
}
}
}
},
{
"data": {
"objectId": "0xf21e2cea96545de14d03676163c87ce3b6997a37e409498aaa35202175051d69",
"version": "1",
"digest": "GzMbSYLnJgifCj15N5B2aQ6vkyDe5ueuzfBWyqcmptEz",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"owner": {
"AddressOwner": "0x2768c8927ad2e24ad90fb2aa51f771dade8d86f634a37a59d7ed9ee6a5173f2d"
},
"previousTransaction": "349PK126tFq8YPg7ZirL1LksiiSNxJa8mNrym192zwwr",
"storageRebate": "0",
"content": {
"dataType": "moveObject",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"hasPublicTransfer": true,
"fields": {
"balance": "30000000000000000",
"id": {
"id": "0xf21e2cea96545de14d03676163c87ce3b6997a37e409498aaa35202175051d69"
}
}
}
}
},
{
"data": {
"objectId": "0xf7a469812c6387f21eb9d5969899bb9b42b7669ce188be077f2c9fed018eb0c3",
"version": "1",
"digest": "AwrnajMHQwQFJuuLju14ELk5BcZdSWaHNrZtEf6wPVGb",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"owner": {
"AddressOwner": "0x2768c8927ad2e24ad90fb2aa51f771dade8d86f634a37a59d7ed9ee6a5173f2d"
},
"previousTransaction": "349PK126tFq8YPg7ZirL1LksiiSNxJa8mNrym192zwwr",
"storageRebate": "0",
"content": {
"dataType": "moveObject",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"hasPublicTransfer": true,
"fields": {
"balance": "30000000000000000",
"id": {
"id": "0xf7a469812c6387f21eb9d5969899bb9b42b7669ce188be077f2c9fed018eb0c3"
}
}
}
}
}
],
"sui client objects 0x0000000000000000000000000000000000000000000000000000000000000000",
[],
"sui client object 0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee",
[
{
"data": {
"objectId": "0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee",
"version": "1",
"digest": "Duwr9uSk9ZvNdEa8oDHunx345i6oyrp3e78MYHVAbYdv",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"owner": {
"AddressOwner": "0x2768c8927ad2e24ad90fb2aa51f771dade8d86f634a37a59d7ed9ee6a5173f2d"
},
"previousTransaction": "349PK126tFq8YPg7ZirL1LksiiSNxJa8mNrym192zwwr",
"storageRebate": "0",
"content": {
"dataType": "moveObject",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"hasPublicTransfer": true,
"fields": {
"balance": "30000000000000000",
"id": {
"id": "0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee"
}
}
}
}
}
],
"sui client object 0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee --bcs",
[
{
"data": {
"objectId": "0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee",
"version": "1",
"digest": "Duwr9uSk9ZvNdEa8oDHunx345i6oyrp3e78MYHVAbYdv",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"owner": {
"AddressOwner": "0x2768c8927ad2e24ad90fb2aa51f771dade8d86f634a37a59d7ed9ee6a5173f2d"
},
"previousTransaction": "349PK126tFq8YPg7ZirL1LksiiSNxJa8mNrym192zwwr",
"storageRebate": "0",
"bcs": {
"dataType": "moveObject",
"type": "0x2::coin::Coin<0x2::sui::SUI>",
"hasPublicTransfer": true,
"version": {
"$serde_json::private::Number": "1"
},
"bcsBytes": "O1EhoGA+96tMtXgn/OyhfbMzjvLNdhJswVI7aB3yfO4AAENP15RqAA=="
}
}
}
],
"sui client object 0x0000000000000000000000000000000000000000000000000000000000000000",
[
{
"error": {
"code": "notExists",
"object_id": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
}
],
"sui client tx-block Duwr9uSk9ZvNdEa8oDHunx345i6oyrp3e78MYHVAbYdv",
"RPC call failed: ErrorObject { code: ServerError(-32000), message: \"Could not find the referenced transaction [TransactionDigest(Duwr9uSk9ZvNdEa8oDHunx345i6oyrp3e78MYHVAbYdv)].\", data: None }",
"sui client tx-block EgMTHQygMi6SRsBqrPHAEKZCNrpShXurCp9rcb9qbSg8",
"RPC call failed: ErrorObject { code: ServerError(-32000), message: \"Could not find the referenced transaction [TransactionDigest(EgMTHQygMi6SRsBqrPHAEKZCNrpShXurCp9rcb9qbSg8)].\", data: None }"
]

0 comments on commit 9c20a69

Please sign in to comment.