Skip to content

Commit

Permalink
add test harness
Browse files Browse the repository at this point in the history
  • Loading branch information
2501babe committed Mar 13, 2024
1 parent 55c3bae commit b7cf06e
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 10 deletions.
19 changes: 10 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,20 @@ borsh = { version = "1.2.1", features = ["derive", "unstable__schema"] }
num-derive = "0.4"
num-traits = "0.2"
num_enum = "0.7.1"
solana-program = { version = "1.18.0", path = "/home/hana/work/solana/solana/sdk/program" }
solana-program = "1.18.0"
thiserror = "1.0.52"
tokio = "1.29.1"

[dev-dependencies]
solana-program-test = { version = "1.18.0", path = "/home/hana/work/solana/solana/program-test" }
solana-sdk = { version = "1.18.0", path = "/home/hana/work/solana/solana/sdk" }
solana-vote-program = { version = "1.18.0", path = "/home/hana/work/solana/solana/programs/vote" }
serial_test = "3.0.0"
solana-cli-config = "1.18.0"
solana-logger = "1.18.0"
solana-rpc-client = "1.18.0"
solana-sdk = "1.18.0"
solana-test-validator = "1.18.0"
spl-token-client = "0.8"
tempfile = "3.10.1"
test-case = "3.3"

[patch.crates-io]
solana-program = { path = "/home/hana/work/solana/solana/sdk/program" }
solana-sdk = { path = "/home/hana/work/solana/solana/sdk" }
solana-zk-token-sdk = { path = "/home/hana/work/solana/solana/zk-token-sdk" }

[lib]
crate-type = ["cdylib", "lib"]
2 changes: 1 addition & 1 deletion src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub enum SkeletonInstruction {
}

/// Creates a `DoSomething` instruction.
pub fn initialize_pool(program_id: &Pubkey, some_account: &Pubkey) -> Instruction {
pub fn do_something(program_id: &Pubkey, some_account: &Pubkey) -> Instruction {
let data = borsh::to_vec(&SkeletonInstruction::DoSomething).unwrap();
let accounts = vec![AccountMeta::new_readonly(*some_account, false)];

Expand Down
205 changes: 205 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#![allow(dead_code)]
#![allow(unused_imports)]

use {
serial_test::serial,
skeleton::{
id,
instruction::{self, SkeletonInstruction},
},
solana_cli_config::Config as SolanaConfig,
solana_rpc_client::nonblocking::rpc_client::RpcClient,
solana_sdk::{
bpf_loader_upgradeable,
clock::Epoch,
epoch_schedule::{EpochSchedule, MINIMUM_SLOTS_PER_EPOCH},
native_token::LAMPORTS_PER_SOL,
pubkey::Pubkey,
signature::{write_keypair_file, Keypair, Signer},
stake::{
self,
state::{Authorized, Lockup, StakeStateV2},
},
system_instruction, system_program,
transaction::Transaction,
vote::{
self,
instruction::CreateVoteAccountConfig,
state::{VoteInit, VoteState, VoteStateVersions},
},
},
solana_test_validator::{TestValidator, TestValidatorGenesis, UpgradeableProgramInfo},
spl_token_client::client::{ProgramClient, ProgramRpcClient, ProgramRpcClientSendTransaction},
std::{path::PathBuf, process::Command, str::FromStr, sync::Arc, time::Duration},
tempfile::NamedTempFile,
test_case::test_case,
tokio::time::sleep,
};

type PClient = Arc<dyn ProgramClient<ProgramRpcClientSendTransaction>>;

#[allow(dead_code)]
pub struct Env {
pub rpc_client: Arc<RpcClient>,
pub program_client: PClient,
pub payer: Keypair,
pub keypair_file_path: String,
pub config_file_path: String,
pub vote_account: Pubkey,

// persist in struct so they dont scope out but callers dont need to make them
validator: TestValidator,
keypair_file: NamedTempFile,
config_file: NamedTempFile,
}

async fn setup() -> Env {
// start test validator
let (validator, payer) = start_validator().await;

// make clients
let rpc_client = Arc::new(validator.get_async_rpc_client());
let program_client: PClient = Arc::new(ProgramRpcClient::new(
rpc_client.clone(),
ProgramRpcClientSendTransaction,
));

// write the payer to disk
let keypair_file = NamedTempFile::new().unwrap();
write_keypair_file(&payer, &keypair_file).unwrap();

// write a full config file with our rpc and payer to disk
let config_file = NamedTempFile::new().unwrap();
let config_file_path = config_file.path().to_str().unwrap();
let solana_config = SolanaConfig {
json_rpc_url: validator.rpc_url(),
websocket_url: validator.rpc_pubsub_url(),
keypair_path: keypair_file.path().to_str().unwrap().to_string(),
..SolanaConfig::default()
};
solana_config.save(config_file_path).unwrap();

// make vote account
let vote_account = create_vote_account(&program_client, &payer, &payer.pubkey()).await;

Env {
rpc_client,
program_client,
payer,
keypair_file_path: keypair_file.path().to_str().unwrap().to_string(),
config_file_path: config_file_path.to_string(),
vote_account,
validator,
keypair_file,
config_file,
}
}

async fn start_validator() -> (TestValidator, Keypair) {
solana_logger::setup();
let mut test_validator_genesis = TestValidatorGenesis::default();

test_validator_genesis.epoch_schedule(EpochSchedule::custom(
MINIMUM_SLOTS_PER_EPOCH,
MINIMUM_SLOTS_PER_EPOCH,
false,
));

test_validator_genesis.add_upgradeable_programs_with_path(&[UpgradeableProgramInfo {
program_id: id(),
loader: bpf_loader_upgradeable::id(),
program_path: PathBuf::from("target/deploy/skeleton.so"),
upgrade_authority: Pubkey::default(),
}]);

test_validator_genesis.start_async().await
}

async fn wait_for_next_epoch(rpc_client: &RpcClient) -> Epoch {
let current_epoch = rpc_client.get_epoch_info().await.unwrap().epoch;
println!("current epoch {}, advancing to next...", current_epoch);
loop {
let epoch_info = rpc_client.get_epoch_info().await.unwrap();
if epoch_info.epoch > current_epoch {
return epoch_info.epoch;
}

sleep(Duration::from_millis(200)).await;
}
}

async fn create_vote_account(
program_client: &PClient,
payer: &Keypair,
withdrawer: &Pubkey,
) -> Pubkey {
let validator = Keypair::new();
let vote_account = Keypair::new();
let voter = Keypair::new();

let zero_rent = program_client
.get_minimum_balance_for_rent_exemption(0)
.await
.unwrap();

let vote_rent = program_client
.get_minimum_balance_for_rent_exemption(VoteState::size_of() * 2)
.await
.unwrap();

let blockhash = program_client.get_latest_blockhash().await.unwrap();

let mut instructions = vec![system_instruction::create_account(
&payer.pubkey(),
&validator.pubkey(),
zero_rent,
0,
&system_program::id(),
)];
instructions.append(&mut vote::instruction::create_account_with_config(
&payer.pubkey(),
&vote_account.pubkey(),
&VoteInit {
node_pubkey: validator.pubkey(),
authorized_voter: voter.pubkey(),
authorized_withdrawer: *withdrawer,
..VoteInit::default()
},
vote_rent,
CreateVoteAccountConfig {
space: VoteStateVersions::vote_state_size_of(true) as u64,
..Default::default()
},
));

let mut transaction = Transaction::new_with_payer(&instructions, Some(&payer.pubkey()));

transaction
.try_partial_sign(&vec![payer], blockhash)
.unwrap();
transaction
.try_partial_sign(&vec![&validator, &vote_account], blockhash)
.unwrap();

program_client.send_transaction(&transaction).await.unwrap();

vote_account.pubkey()
}

#[tokio::test]
async fn test_something() {
let env = setup().await;

let account = Pubkey::new_unique();
let instruction = instruction::do_something(&id(), &account);

let mut transaction = Transaction::new_with_payer(&[instruction], Some(&env.payer.pubkey()));

let blockhash = env.program_client.get_latest_blockhash().await.unwrap();
transaction
.try_partial_sign(&vec![&env.payer], blockhash)
.unwrap();

let res = env.program_client.send_transaction(&transaction).await;
println!("HANA res: {:#?}", res);
}

0 comments on commit b7cf06e

Please sign in to comment.