Tests: More flexible test context construction

This commit is contained in:
Christian Kamm 2022-03-21 10:45:55 +01:00
parent a6a31f226c
commit 27f882a333
8 changed files with 119 additions and 83 deletions

View File

@ -1,4 +1,7 @@
#![allow(dead_code)]
use std::cell::RefCell; use std::cell::RefCell;
use std::str::FromStr;
use std::{sync::Arc, sync::RwLock}; use std::{sync::Arc, sync::RwLock};
use log::*; use log::*;
@ -73,29 +76,22 @@ impl Log for LoggerWrapper {
fn flush(&self) {} fn flush(&self) {}
} }
pub struct TestContext { pub struct MarginTradeCookie {
pub solana: Arc<SolanaCookie>, pub program: Pubkey,
pub mints: Vec<MintCookie>, pub token_account: Keypair,
pub users: Vec<UserCookie>, pub token_account_owner: Pubkey,
pub quote_index: usize, pub token_account_bump: u8,
pub serum: Arc<SerumCookie>,
} }
impl TestContext { pub struct TestContextBuilder {
pub async fn new( test: ProgramTest,
test_opt: Option<ProgramTest>, program_log_capture: Arc<RwLock<Vec<String>>>,
margin_trade_program_id: Option<&Pubkey>, mint0: Pubkey,
margin_trade_token_account: Option<&Keypair>, }
mtta_owner: Option<&Pubkey>,
) -> Self {
let mut test = if test_opt.is_some() {
test_opt.unwrap()
} else {
ProgramTest::new("mango_v4", mango_v4::id(), processor!(mango_v4::entry))
};
let serum_program_id = anchor_spl::dex::id(); impl TestContextBuilder {
test.add_program("serum_dex", serum_program_id, None); pub fn new() -> Self {
let mut test = ProgramTest::new("mango_v4", mango_v4::id(), processor!(mango_v4::entry));
// We need to intercept logs to capture program log output // We need to intercept logs to capture program log output
let log_filter = "solana_rbpf=trace,\ let log_filter = "solana_rbpf=trace,\
@ -115,9 +111,18 @@ impl TestContext {
// intentionally set to half the limit, to catch potential problems early // intentionally set to half the limit, to catch potential problems early
test.set_compute_max_units(100000); test.set_compute_max_units(100000);
// Setup the environment Self {
test,
program_log_capture,
mint0: Pubkey::new_unique(),
}
}
// Mints pub fn test(&mut self) -> &mut ProgramTest {
&mut self.test
}
pub fn create_mints(&mut self) -> Vec<MintCookie> {
let mut mints: Vec<MintCookie> = vec![ let mut mints: Vec<MintCookie> = vec![
MintCookie { MintCookie {
index: 0, index: 0,
@ -125,7 +130,7 @@ impl TestContext {
unit: 10u64.pow(6) as f64, unit: 10u64.pow(6) as f64,
base_lot: 100 as f64, base_lot: 100 as f64,
quote_lot: 10 as f64, quote_lot: 10 as f64,
pubkey: Pubkey::default(), pubkey: self.mint0,
authority: Keypair::new(), authority: Keypair::new(),
}, // symbol: "MNGO".to_string() }, // symbol: "MNGO".to_string()
]; ];
@ -150,7 +155,7 @@ impl TestContext {
} }
mints[mint_index].pubkey = mint_pk; mints[mint_index].pubkey = mint_pk;
test.add_packable_account( self.test.add_packable_account(
mint_pk, mint_pk,
u32::MAX as u64, u32::MAX as u64,
&Mint { &Mint {
@ -162,36 +167,16 @@ impl TestContext {
&spl_token::id(), &spl_token::id(),
); );
} }
let quote_index = mints.len() - 1;
// margin trade mints
if margin_trade_program_id.is_some() { }
test.add_program(
"margin_trade",
*margin_trade_program_id.unwrap(),
std::option::Option::None,
);
test.add_packable_account(
margin_trade_token_account.unwrap().pubkey(),
u32::MAX as u64,
&Account {
mint: mints[0].pubkey,
owner: *mtta_owner.unwrap(),
amount: 0,
state: AccountState::Initialized,
is_native: COption::None,
..Account::default()
},
&spl_token::id(),
);
}
// Users pub fn create_users(&mut self, mints: &[MintCookie]) -> Vec<UserCookie> {
let num_users = 4; let num_users = 4;
let mut users = Vec::new(); let mut users = Vec::new();
for _ in 0..num_users { for _ in 0..num_users {
let user_key = Keypair::new(); let user_key = Keypair::new();
test.add_account( self.test.add_account(
user_key.pubkey(), user_key.pubkey(),
solana_sdk::account::Account::new( solana_sdk::account::Account::new(
u32::MAX as u64, u32::MAX as u64,
@ -205,7 +190,7 @@ impl TestContext {
let mut token_accounts = Vec::new(); let mut token_accounts = Vec::new();
for mint_index in 0..mints.len() { for mint_index in 0..mints.len() {
let token_key = Pubkey::new_unique(); let token_key = Pubkey::new_unique();
test.add_packable_account( self.test.add_packable_account(
token_key, token_key,
u32::MAX as u64, u32::MAX as u64,
&spl_token::state::Account { &spl_token::state::Account {
@ -226,14 +211,51 @@ impl TestContext {
}); });
} }
let mut context = test.start_with_context().await; users
let rent = context.banks_client.get_rent().await.unwrap(); }
let solana = Arc::new(SolanaCookie { pub fn add_serum_program(&mut self) -> Pubkey {
context: RefCell::new(context), let serum_program_id = anchor_spl::dex::id();
rent, self.test.add_program("serum_dex", serum_program_id, None);
program_log: program_log_capture.clone(), serum_program_id
}); }
pub fn add_margin_trade_program(&mut self) -> MarginTradeCookie {
let program = Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap();
let token_account = Keypair::new();
let (token_account_owner, token_account_bump) =
Pubkey::find_program_address(&[b"MarginTrade"], &program);
self.test
.add_program("margin_trade", program, std::option::Option::None);
self.test.add_packable_account(
token_account.pubkey(),
u32::MAX as u64,
&Account {
mint: self.mint0,
owner: token_account_owner,
amount: 0,
state: AccountState::Initialized,
is_native: COption::None,
..Account::default()
},
&spl_token::id(),
);
MarginTradeCookie {
program,
token_account,
token_account_owner,
token_account_bump,
}
}
pub async fn start_default(mut self) -> TestContext {
let mints = self.create_mints();
let users = self.create_users(&mints);
let serum_program_id = self.add_serum_program();
let solana = self.start().await;
let serum = Arc::new(SerumCookie { let serum = Arc::new(SerumCookie {
solana: solana.clone(), solana: solana.clone(),
@ -244,8 +266,33 @@ impl TestContext {
solana: solana.clone(), solana: solana.clone(),
mints, mints,
users, users,
quote_index,
serum, serum,
} }
} }
pub async fn start(self) -> Arc<SolanaCookie> {
let mut context = self.test.start_with_context().await;
let rent = context.banks_client.get_rent().await.unwrap();
let solana = Arc::new(SolanaCookie {
context: RefCell::new(context),
rent,
program_log: self.program_log_capture.clone(),
});
solana
}
}
pub struct TestContext {
pub solana: Arc<SolanaCookie>,
pub mints: Vec<MintCookie>,
pub users: Vec<UserCookie>,
pub serum: Arc<SerumCookie>,
}
impl TestContext {
pub async fn new() -> Self {
TestContextBuilder::new().start_default().await
}
} }

View File

@ -13,7 +13,7 @@ mod program_test;
// that they work in principle. It should be split up / renamed. // that they work in principle. It should be split up / renamed.
#[tokio::test] #[tokio::test]
async fn test_basic() -> Result<(), TransportError> { async fn test_basic() -> Result<(), TransportError> {
let context = TestContext::new(Option::None, Option::None, Option::None, Option::None).await; let context = TestContext::new().await;
let solana = &context.solana.clone(); let solana = &context.solana.clone();
let admin = &Keypair::new(); let admin = &Keypair::new();

View File

@ -14,7 +14,7 @@ mod program_test;
// that they work in principle. It should be split up / renamed. // that they work in principle. It should be split up / renamed.
#[tokio::test] #[tokio::test]
async fn test_group_address_lookup_tables() -> Result<()> { async fn test_group_address_lookup_tables() -> Result<()> {
let context = TestContext::new(None, None, None, None).await; let context = TestContext::new().await;
let solana = &context.solana.clone(); let solana = &context.solana.clone();
let admin = &Keypair::new(); let admin = &Keypair::new();

View File

@ -10,7 +10,7 @@ mod program_test;
// Try to reach compute limits in health checks by having many different tokens in an account // Try to reach compute limits in health checks by having many different tokens in an account
#[tokio::test] #[tokio::test]
async fn test_health_compute_tokens() -> Result<(), TransportError> { async fn test_health_compute_tokens() -> Result<(), TransportError> {
let context = TestContext::new(Option::None, Option::None, Option::None, Option::None).await; let context = TestContext::new().await;
let solana = &context.solana.clone(); let solana = &context.solana.clone();
let admin = &Keypair::new(); let admin = &Keypair::new();
@ -73,7 +73,7 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> {
// Try to reach compute limits in health checks by having many serum markets in an account // Try to reach compute limits in health checks by having many serum markets in an account
#[tokio::test] #[tokio::test]
async fn test_health_compute_serum() -> Result<(), TransportError> { async fn test_health_compute_serum() -> Result<(), TransportError> {
let context = TestContext::new(Option::None, Option::None, Option::None, Option::None).await; let context = TestContext::new().await;
let solana = &context.solana.clone(); let solana = &context.solana.clone();
let admin = &Keypair::new(); let admin = &Keypair::new();

View File

@ -2,11 +2,9 @@
use anchor_lang::InstructionData; use anchor_lang::InstructionData;
use fixed::types::I80F48; use fixed::types::I80F48;
use solana_program::pubkey::Pubkey;
use solana_program_test::*; use solana_program_test::*;
use solana_sdk::signature::Signer; use solana_sdk::signature::Signer;
use solana_sdk::{signature::Keypair, transport::TransportError}; use solana_sdk::{signature::Keypair, transport::TransportError};
use std::str::FromStr;
use mango_v4::state::*; use mango_v4::state::*;
use program_test::*; use program_test::*;
@ -17,18 +15,9 @@ mod program_test;
// that they work in principle. It should be split up / renamed. // that they work in principle. It should be split up / renamed.
#[tokio::test] #[tokio::test]
async fn test_margin_trade() -> Result<(), TransportError> { async fn test_margin_trade() -> Result<(), TransportError> {
let margin_trade_program_id = let mut builder = TestContextBuilder::new();
Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); let margin_trade = builder.add_margin_trade_program();
let margin_trade_token_account = Keypair::new(); let context = builder.start_default().await;
let (mtta_owner, mtta_bump_seeds) =
Pubkey::find_program_address(&[b"MarginTrade"], &margin_trade_program_id);
let context = TestContext::new(
Option::None,
Some(&margin_trade_program_id),
Some(&margin_trade_token_account),
Some(&mtta_owner),
)
.await;
let solana = &context.solana.clone(); let solana = &context.solana.clone();
let admin = &Keypair::new(); let admin = &Keypair::new();
@ -117,14 +106,14 @@ async fn test_margin_trade() -> Result<(), TransportError> {
account, account,
owner, owner,
mango_token_vault: vault, mango_token_vault: vault,
margin_trade_program_id, margin_trade_program_id: margin_trade.program,
deposit_account: margin_trade_token_account.pubkey(), deposit_account: margin_trade.token_account.pubkey(),
deposit_account_owner: mtta_owner, deposit_account_owner: margin_trade.token_account_owner,
margin_trade_program_ix_cpi_data: { margin_trade_program_ix_cpi_data: {
let ix = margin_trade::instruction::MarginTrade { let ix = margin_trade::instruction::MarginTrade {
amount_from: 2, amount_from: 2,
amount_to: 1, amount_to: 1,
deposit_account_owner_bump_seeds: mtta_bump_seeds, deposit_account_owner_bump_seeds: margin_trade.token_account_bump,
}; };
ix.data() ix.data()
}, },
@ -139,7 +128,7 @@ async fn test_margin_trade() -> Result<(), TransportError> {
); );
assert_eq!( assert_eq!(
solana solana
.token_account_balance(margin_trade_token_account.pubkey()) .token_account_balance(margin_trade.token_account.pubkey())
.await, .await,
withdraw_amount - deposit_amount withdraw_amount - deposit_amount
); );

View File

@ -9,7 +9,7 @@ mod program_test;
#[tokio::test] #[tokio::test]
async fn test_perp() -> Result<(), TransportError> { async fn test_perp() -> Result<(), TransportError> {
let context = TestContext::new(Option::None, Option::None, Option::None, Option::None).await; let context = TestContext::new().await;
let solana = &context.solana.clone(); let solana = &context.solana.clone();
let admin = &Keypair::new(); let admin = &Keypair::new();

View File

@ -12,7 +12,7 @@ mod program_test;
// Check opening and closing positions // Check opening and closing positions
#[tokio::test] #[tokio::test]
async fn test_position_lifetime() -> Result<()> { async fn test_position_lifetime() -> Result<()> {
let context = TestContext::new(None, None, None, None).await; let context = TestContext::new().await;
let solana = &context.solana.clone(); let solana = &context.solana.clone();
let admin = &Keypair::new(); let admin = &Keypair::new();

View File

@ -11,7 +11,7 @@ mod program_test;
#[tokio::test] #[tokio::test]
async fn test_serum() -> Result<(), TransportError> { async fn test_serum() -> Result<(), TransportError> {
let context = TestContext::new(Option::None, Option::None, Option::None, Option::None).await; let context = TestContext::new().await;
let solana = &context.solana.clone(); let solana = &context.solana.clone();
let admin = &Keypair::new(); let admin = &Keypair::new();