diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index eec8e215b833a0..c0f0a32d0ddc7e 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -56,6 +56,7 @@ use { fs::File, io::{self, Read}, mem::transmute, + panic::AssertUnwindSafe, path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, Ordering}, @@ -140,12 +141,25 @@ pub fn invoke_builtin_function( unsafe { deserialize(&mut parameter_bytes.as_slice_mut()[0] as *mut u8) }; // Execute the program - builtin_function(program_id, &account_infos, input).map_err(|err| { - let err = InstructionError::from(u64::from(err)); - stable_log::program_failure(&log_collector, program_id, &err); - let err: Box = Box::new(err); - err - })?; + match std::panic::catch_unwind(AssertUnwindSafe(|| { + builtin_function(program_id, &account_infos, input) + })) { + Ok(program_result) => { + program_result.map_err(|program_error| { + let err = InstructionError::from(u64::from(program_error)); + stable_log::program_failure(&log_collector, program_id, &err); + let err: Box = Box::new(err); + err + })?; + } + Err(_panic_error) => { + let err = InstructionError::ProgramFailedToComplete; + stable_log::program_failure(&log_collector, program_id, &err); + let err: Box = Box::new(err); + Err(err)?; + } + }; + stable_log::program_success(&log_collector, program_id); // Lookup table for AccountInfo @@ -724,8 +738,6 @@ impl ProgramTest { // If SBF is not required (i.e., we were invoked with `test`), use the provided // processor function as is. - // - // TODO: figure out why tests hang if a processor panics when running native code. (false, _, Some(builtin_function)) => { self.add_builtin_program(program_name, program_id, builtin_function) } diff --git a/program-test/tests/panic.rs b/program-test/tests/panic.rs new file mode 100644 index 00000000000000..de8e74b3d40902 --- /dev/null +++ b/program-test/tests/panic.rs @@ -0,0 +1,42 @@ +use { + solana_program_test::{processor, ProgramTest}, + solana_sdk::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{Instruction, InstructionError}, + pubkey::Pubkey, + signature::Signer, + transaction::{Transaction, TransactionError}, + }, +}; + +fn panic(_program_id: &Pubkey, _accounts: &[AccountInfo], _input: &[u8]) -> ProgramResult { + panic!("I panicked"); +} + +#[tokio::test] +async fn panic_test() { + let program_id = Pubkey::new_unique(); + + let program_test = ProgramTest::new("panic", program_id, processor!(panic)); + + let context = program_test.start_with_context().await; + + let instruction = Instruction::new_with_bytes(program_id, &[], vec![]); + + let transaction = Transaction::new_signed_with_payer( + &[instruction], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + assert_eq!( + context + .banks_client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(), + TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete) + ); +}