Skip to content

Commit

Permalink
[api] Add view function testing to the API tester (#9658)
Browse files Browse the repository at this point in the history
This commit adds a new test which tests a simple view function.
  • Loading branch information
ngkuru committed Aug 22, 2023
1 parent f948c70 commit 7eb7a15
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 21 deletions.
9 changes: 9 additions & 0 deletions crates/aptos-api-tester/src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
// Copyright © Aptos Foundation

use crate::utils::NetworkName;
use once_cell::sync::Lazy;
use std::{env, time::Duration};
use url::Url;

// Node and faucet constants

// TODO: consider making this a CLI argument
pub static NETWORK_NAME: Lazy<NetworkName> = Lazy::new(|| {
env::var("NETWORK_NAME")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(NetworkName::Devnet)
});

pub static DEVNET_NODE_URL: Lazy<Url> =
Lazy::new(|| Url::parse("https://fullnode.devnet.aptoslabs.com").unwrap());

Expand Down
13 changes: 9 additions & 4 deletions crates/aptos-api-tester/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::utils::{NetworkName, TestName};
use anyhow::Result;
use aptos_logger::{info, Level, Logger};
use aptos_push_metrics::MetricsPusher;
use consts::{NUM_THREADS, STACK_SIZE};
use consts::{NETWORK_NAME, NUM_THREADS, STACK_SIZE};
use futures::future::join_all;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::runtime::{Builder, Runtime};
Expand Down Expand Up @@ -59,11 +59,18 @@ async fn test_flows(runtime: &Runtime, network_name: NetworkName) -> Result<()>
TestName::PublishModule.run(network_name, &test_time).await;
});

// Flow 5: View function
let test_time = run_id.clone();
let handle_viewfunction = runtime.spawn(async move {
TestName::ViewFunction.run(network_name, &test_time).await;
});

join_all(vec![
handle_newaccount,
handle_cointransfer,
handle_nfttransfer,
handle_publishmodule,
handle_viewfunction,
])
.await;
Ok(())
Expand All @@ -82,10 +89,8 @@ fn main() -> Result<()> {
let _mp = MetricsPusher::start_for_local_run("api-tester");

// run tests
// TODO: separate the running of the two networks
runtime.block_on(async {
let _ = test_flows(&runtime, NetworkName::Testnet).await;
let _ = test_flows(&runtime, NetworkName::Devnet).await;
let _ = test_flows(&runtime, *NETWORK_NAME).await;
});

Ok(())
Expand Down
4 changes: 4 additions & 0 deletions crates/aptos-api-tester/src/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub const FAIL_WRONG_TOKEN_DATA: &str = "wrong token data";

// Error messages

pub const ERROR_BAD_BALANCE_STRING: &str = "bad balance string";
pub const ERROR_COULD_NOT_BUILD_PACKAGE: &str = "failed to build package";
pub const ERROR_COULD_NOT_CHECK: &str = "persistency check never started";
pub const ERROR_COULD_NOT_CREATE_ACCOUNT: &str = "failed to create account";
Expand All @@ -21,8 +22,10 @@ pub const ERROR_COULD_NOT_CREATE_AND_SUBMIT_TRANSACTION: &str =
pub const ERROR_COULD_NOT_FINISH_TRANSACTION: &str = "failed to finish transaction";
pub const ERROR_COULD_NOT_FUND_ACCOUNT: &str = "failed to fund account";
pub const ERROR_COULD_NOT_SERIALIZE: &str = "failed to serialize";
pub const ERROR_COULD_NOT_VIEW: &str = "view function failed";
pub const ERROR_NO_ACCOUNT_DATA: &str = "can't find account data";
pub const ERROR_NO_BALANCE: &str = "can't find account balance";
pub const ERROR_NO_BALANCE_STRING: &str = "the API did not return a balance string";
pub const ERROR_NO_BYTECODE: &str = "can't find bytecode";
pub const ERROR_NO_COLLECTION_DATA: &str = "can't find collection data";
pub const ERROR_NO_MESSAGE: &str = "can't find message";
Expand Down Expand Up @@ -53,3 +56,4 @@ pub const PUBLISH_MODULE: &str = "publish_module";
pub const CHECK_MODULE_DATA: &str = "check_module_data";
pub const SET_MESSAGE: &str = "set_message";
pub const CHECK_MESSAGE: &str = "check_message";
pub const CHECK_VIEW_ACCOUNT_BALANCE: &str = "check_view_account_balance";
1 change: 1 addition & 0 deletions crates/aptos-api-tester/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ pub mod coin_transfer;
pub mod new_account;
pub mod publish_module;
pub mod tokenv1_transfer;
pub mod view_function;
177 changes: 177 additions & 0 deletions crates/aptos-api-tester/src/tests/view_function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright © Aptos Foundation

use crate::{
consts::FUND_AMOUNT,
persistent_check,
strings::{
CHECK_ACCOUNT_DATA, CHECK_VIEW_ACCOUNT_BALANCE, ERROR_BAD_BALANCE_STRING,
ERROR_COULD_NOT_FUND_ACCOUNT, ERROR_COULD_NOT_VIEW, ERROR_NO_BALANCE_STRING,
FAIL_WRONG_BALANCE, SETUP,
},
time_fn,
utils::{
check_balance, create_and_fund_account, emit_step_metrics, NetworkName, TestFailure,
TestName,
},
};
use anyhow::anyhow;
use aptos_api_types::{ViewRequest, U64};
use aptos_logger::error;
use aptos_rest_client::Client;
use aptos_sdk::types::LocalAccount;
use aptos_types::account_address::AccountAddress;

/// Tests view function use. Checks that:
/// - view function returns correct value
pub async fn test(network_name: NetworkName, run_id: &str) -> Result<(), TestFailure> {
// setup
let (client, account) = emit_step_metrics(
time_fn!(setup, network_name),
TestName::ViewFunction,
SETUP,
network_name,
run_id,
)?;

// check account data persistently
emit_step_metrics(
time_fn!(
persistent_check::address,
CHECK_ACCOUNT_DATA,
check_account_data,
&client,
account.address()
),
TestName::ViewFunction,
CHECK_ACCOUNT_DATA,
network_name,
run_id,
)?;

// check account balance from view function persistently
emit_step_metrics(
time_fn!(
persistent_check::address,
CHECK_VIEW_ACCOUNT_BALANCE,
check_view_account_balance,
&client,
account.address()
),
TestName::ViewFunction,
CHECK_VIEW_ACCOUNT_BALANCE,
network_name,
run_id,
)?;

Ok(())
}

// Steps

async fn setup(network_name: NetworkName) -> Result<(Client, LocalAccount), TestFailure> {
// spin up clients
let client = network_name.get_client();
let faucet_client = network_name.get_faucet_client();

// create account
let account = match create_and_fund_account(&faucet_client, TestName::ViewFunction).await {
Ok(account) => account,
Err(e) => {
error!(
"test: {} part: {} ERROR: {}, with error {:?}",
TestName::ViewFunction.to_string(),
SETUP,
ERROR_COULD_NOT_FUND_ACCOUNT,
e
);
return Err(e.into());
},
};

Ok((client, account))
}

async fn check_account_data(client: &Client, account: AccountAddress) -> Result<(), TestFailure> {
check_balance(TestName::ViewFunction, client, account, U64(FUND_AMOUNT)).await?;

Ok(())
}

async fn check_view_account_balance(
client: &Client,
address: AccountAddress,
) -> Result<(), TestFailure> {
// expected
let expected = U64(FUND_AMOUNT);

// actual

// get client response
let response = match client
.view(
&ViewRequest {
function: "0x1::coin::balance".parse()?,
type_arguments: vec!["0x1::aptos_coin::AptosCoin".parse()?],
arguments: vec![serde_json::Value::String(address.to_hex_literal())],
},
None,
)
.await
{
Ok(response) => response,
Err(e) => {
error!(
"test: {} part: {} ERROR: {}, with error {:?}",
TestName::ViewFunction.to_string(),
CHECK_VIEW_ACCOUNT_BALANCE,
ERROR_COULD_NOT_VIEW,
e
);
return Err(e.into());
},
};

// get the string value from the serde_json value
let value = match response.inner()[0].as_str() {
Some(value) => value,
None => {
error!(
"test: {} part: {} ERROR: {}, with error {:?}",
TestName::ViewFunction.to_string(),
CHECK_VIEW_ACCOUNT_BALANCE,
ERROR_NO_BALANCE_STRING,
response.inner()
);
return Err(anyhow!(ERROR_NO_BALANCE_STRING).into());
},
};

// parse the string into a U64
let actual = match value.parse::<u64>() {
Ok(value) => U64(value),
Err(e) => {
error!(
"test: {} part: {} ERROR: {}, with error {:?}",
TestName::ViewFunction.to_string(),
CHECK_VIEW_ACCOUNT_BALANCE,
ERROR_BAD_BALANCE_STRING,
e
);
return Err(e.into());
},
};

// compare
if expected != actual {
error!(
"test: {} part: {} FAIL: {}, expected {:?}, got {:?}",
TestName::ViewFunction.to_string(),
CHECK_VIEW_ACCOUNT_BALANCE,
FAIL_WRONG_BALANCE,
expected,
actual
);
}

Ok(())
}
55 changes: 38 additions & 17 deletions crates/aptos-api-tester/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ use crate::{
},
counters::{test_error, test_fail, test_latency, test_step_latency, test_success},
strings::{ERROR_NO_BALANCE, FAIL_WRONG_BALANCE},
tests::{coin_transfer, new_account, publish_module, tokenv1_transfer},
tests::{coin_transfer, new_account, publish_module, tokenv1_transfer, view_function},
time_fn,
};
use anyhow::Result;
use anyhow::{anyhow, Error, Result};
use aptos_api_types::U64;
use aptos_logger::{error, info};
use aptos_rest_client::{error::RestError, Client, FaucetClient};
use aptos_sdk::types::LocalAccount;
use aptos_types::account_address::AccountAddress;
use std::env;
use std::{env, num::ParseIntError, str::FromStr};

// Test failure

Expand All @@ -27,15 +27,21 @@ pub enum TestFailure {
Error(anyhow::Error),
}

impl From<anyhow::Error> for TestFailure {
fn from(e: anyhow::Error) -> TestFailure {
TestFailure::Error(e)
}
}

impl From<RestError> for TestFailure {
fn from(e: RestError) -> TestFailure {
TestFailure::Error(e.into())
}
}

impl From<anyhow::Error> for TestFailure {
fn from(e: anyhow::Error) -> TestFailure {
TestFailure::Error(e)
impl From<ParseIntError> for TestFailure {
fn from(e: ParseIntError) -> TestFailure {
TestFailure::Error(e.into())
}
}

Expand All @@ -47,17 +53,7 @@ pub enum TestName {
CoinTransfer,
TokenV1Transfer,
PublishModule,
}

impl ToString for TestName {
fn to_string(&self) -> String {
match &self {
TestName::NewAccount => "new_account".to_string(),
TestName::CoinTransfer => "coin_transfer".to_string(),
TestName::TokenV1Transfer => "tokenv1_transfer".to_string(),
TestName::PublishModule => "publish_module".to_string(),
}
}
ViewFunction,
}

impl TestName {
Expand All @@ -67,12 +63,25 @@ impl TestName {
TestName::CoinTransfer => time_fn!(coin_transfer::test, network_name, run_id),
TestName::TokenV1Transfer => time_fn!(tokenv1_transfer::test, network_name, run_id),
TestName::PublishModule => time_fn!(publish_module::test, network_name, run_id),
TestName::ViewFunction => time_fn!(view_function::test, network_name, run_id),
};

emit_test_metrics(output, *self, network_name, run_id);
}
}

impl ToString for TestName {
fn to_string(&self) -> String {
match &self {
TestName::NewAccount => "new_account".to_string(),
TestName::CoinTransfer => "coin_transfer".to_string(),
TestName::TokenV1Transfer => "tokenv1_transfer".to_string(),
TestName::PublishModule => "publish_module".to_string(),
TestName::ViewFunction => "view_function".to_string(),
}
}
}

// Network name

#[derive(Clone, Copy)]
Expand All @@ -90,6 +99,18 @@ impl ToString for NetworkName {
}
}

impl FromStr for NetworkName {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"testnet" => Ok(NetworkName::Testnet),
"devnet" => Ok(NetworkName::Devnet),
_ => Err(anyhow!("invalid network name")),
}
}
}

impl NetworkName {
/// Create a REST client.
pub fn get_client(&self) -> Client {
Expand Down

0 comments on commit 7eb7a15

Please sign in to comment.