litesvm_testing/lib.rs
1//! # LiteSVM Testing Utilities
2//! Copyright (C) 2024 LiteSVM Testing Framework Contributors - Licensed under GPL v3.0-or-later
3//!
4//! A comprehensive testing framework for Solana programs using LiteSVM. Provides ergonomic,
5//! type-safe assertions for transaction results, logs, and all levels of Solana errors.
6//!
7//! ## Core Features
8//!
9//! - **📋 Log Assertions**: Verify program logs contain expected content
10//! - **🎯 Error Testing**: Complete coverage of transaction, instruction, and system errors
11//! - **🔧 Dual API Styles**: Direct function calls and fluent method chaining
12//! - **⚡ Precision Control**: "Anywhere" matching vs surgical instruction-index targeting
13//! - **🛡️ Type Safety**: Work with SystemError enums instead of raw error codes
14//! - **📚 Educational Examples**: Learn API progression from verbose to elegant
15//!
16//! ## API Styles
17//!
18//! **Direct Functions** (traditional):
19//! ```text
20//! demand_logs_contain("Hello!", result);
21//! demand_system_error(SystemError::ResultWithNegativeLamports, result);
22//! ```
23//!
24//! **Fluent Methods** (chainable):
25//! ```text
26//! result.demand_logs_contain("Hello!")
27//! .demand_system_error(SystemError::ResultWithNegativeLamports);
28//! ```
29//!
30//! ## Error Testing Hierarchy
31//!
32//! **🏗️ Transaction Level**: Validation errors before execution
33//! - `demand_transaction_error(TransactionError::AlreadyProcessed, result)`
34//!
35//! **📍 Instruction Level**: Errors during instruction execution
36//! - `demand_instruction_error(InstructionError::Custom(1), result)`
37//! - `demand_instruction_error_at_index(1, InstructionError::Custom(1), result)`
38//!
39//! **⚙️ System Level**: Type-safe system program errors
40//! - `demand_system_error(SystemError::ResultWithNegativeLamports, result)` (anywhere)
41//! - `demand_system_error_at_index(1, SystemError::ResultWithNegativeLamports, result)` (surgical)
42//!
43//! ## Complete Examples
44//!
45//! **API Progression Tutorial**:
46//! - [`test_system_error_insufficient_funds.rs`](crates/litesvm-testing/tests/test_system_error_insufficient_funds.rs) - Shows Good → Better → Best → Best+ approaches
47//!
48//! **Framework Integration**:
49//! - **Anchor**: [`examples/anchor/simple-anchor-tests/`](examples/anchor/simple-anchor-tests/) - Complete Anchor program testing with IDL integration
50//! - **Pinocchio**: [`examples/pinocchio/simple-pinocchio-tests/`](examples/pinocchio/simple-pinocchio-tests/) - Lightweight testing with minimal boilerplate
51
52#[cfg(feature = "anchor")]
53pub mod anchor_testing;
54
55#[cfg(any(feature = "anchor", feature = "pinocchio"))]
56mod build_internal;
57
58#[cfg(feature = "cu_bench")]
59pub mod cu_bench;
60
61#[cfg(feature = "pinocchio")]
62pub mod pinocchio_testing;
63
64// #[cfg(feature = "token")]
65// pub mod token_testing;
66
67// #[cfg(feature = "steel")]
68// pub mod steel_testing;
69
70use num_traits::FromPrimitive;
71use solana_keypair::Keypair;
72use solana_signer::Signer;
73use solana_system_interface::error::SystemError;
74
75/// Convenient re-exports for LiteSVM testing.
76///
77/// This prelude module provides all the commonly needed types and functions for testing
78/// Solana programs with LiteSVM. Import everything with:
79///
80/// ```text
81/// use litesvm_testing::prelude::*;
82/// ```
83///
84/// ## What's included:
85///
86/// **Core Solana types**:
87/// - `litesvm` - The LiteSVM runtime for testing
88/// - `solana_*` - Transaction, instruction, keypair, and error types
89///
90/// **Testing assertions**:
91/// - `demand_logs_contain` - Assert transaction logs contain expected content
92/// - `demand_transaction_error` - Assert transaction-level errors
93/// - `demand_instruction_error` - Assert instruction-level errors
94/// - `demand_system_error` - Assert system program errors (type-safe)
95/// - `DemandFluency` - Trait for fluent method chaining
96pub mod prelude {
97 pub use litesvm;
98 pub use solana_compute_budget_interface;
99 pub use solana_instruction;
100 pub use solana_keypair;
101 pub use solana_pubkey;
102 pub use solana_signer;
103 pub use solana_system_interface;
104 pub use solana_transaction;
105 pub use solana_transaction_error;
106 pub use spl_associated_token_account;
107 pub use spl_token;
108
109 pub use solana_keypair::Keypair;
110 pub use solana_pubkey::Pubkey;
111 pub use solana_signer::Signer;
112 pub use solana_system_interface::program as system_program;
113
114 pub use super::{
115 demand_instruction_error, //
116 demand_instruction_error_at_index,
117 demand_logs_contain,
118 demand_logs_contain_at_index,
119 demand_system_error,
120 demand_system_error_at_index,
121 demand_transaction_error,
122 DemandFluency,
123 };
124}
125
126// "demanding solana"
127// - transaction errors
128// - instruction errors
129// - custom errors (the special case instruction error)
130// - anchor errors
131// - anchor events
132// - cu limits, etc, etc, etc
133
134use litesvm::{types::TransactionResult, LiteSVM};
135use solana_instruction::error::InstructionError;
136use solana_transaction_error::TransactionError;
137
138/// Trait for fluent assertions on transaction results.
139///
140/// This trait extends [`TransactionResult`] with assertion methods that provide
141/// detailed error messages when conditions are not met. The fluent API allows
142/// for readable test code that chains naturally from transaction execution.
143///
144/// See the working examples in the repository for complete usage patterns.
145pub trait DemandFluency<T> {
146 fn demand_instruction_error(self, expected_error: InstructionError);
147 fn demand_instruction_error_at_index(
148 self,
149 expected_index: u8,
150 expected_error: InstructionError,
151 );
152 fn demand_logs_contain(self, expected: &str);
153 fn demand_system_error(self, expected_error: SystemError);
154 fn demand_system_error_at_index(self, expected_index: u8, expected_error: SystemError);
155 fn demand_transaction_error(self, expected_error: TransactionError);
156}
157
158impl DemandFluency<TransactionResult> for TransactionResult {
159 fn demand_instruction_error(self, expected_error: InstructionError) {
160 demand_instruction_error(expected_error, self);
161 }
162
163 fn demand_instruction_error_at_index(
164 self,
165 expected_index: u8,
166 expected_error: InstructionError,
167 ) {
168 demand_instruction_error_at_index(expected_index, expected_error, self);
169 }
170
171 fn demand_logs_contain(self, expected: &str) {
172 demand_logs_contain(expected, self);
173 }
174
175 fn demand_system_error(self, expected_error: SystemError) {
176 demand_system_error(expected_error, self);
177 }
178
179 fn demand_system_error_at_index(self, expected_index: u8, expected_error: SystemError) {
180 demand_system_error_at_index(expected_index, expected_error, self);
181 }
182
183 fn demand_transaction_error(self, expected_error: TransactionError) {
184 demand_transaction_error(expected_error, self);
185 }
186}
187
188// FUTURE IDEA: support for chaining methods on the result:
189// pub trait DemandChaining<T> {
190// fn demand_logs_contain_and(self, expected: &str) -> Self;
191// fn demand_system_error_and(self, expected_error: SystemError) -> Self;
192// fn accept(self); // terminal method to consume the result
193// }
194
195/// Asserts that a transaction's logs contain a specific string.
196///
197/// This function is designed for testing Solana programs with LiteSVM. It searches through
198/// all log entries produced by a transaction and panics with a detailed error message if
199/// the expected string is not found.
200///
201/// # Arguments
202///
203/// * `expected` - The string to search for within the transaction logs
204/// * `result` - The result of executing a transaction via [`litesvm::LiteSVM::send_transaction`]
205///
206/// # Panics
207///
208/// Panics if `expected` is not found in any of the transaction logs. The panic message
209/// includes:
210/// - The string that was expected but not found
211/// - The total number of log entries searched
212/// - All log entries with their indices for debugging
213///
214/// # Usage
215///
216/// Direct function call:
217/// ```text
218/// demand_logs_contain("Hello from anchor!", result);
219/// ```
220///
221/// Fluent trait method:
222/// ```text
223/// result.demand_logs_contain("Hello from anchor!");
224/// ```
225///
226/// For complete working examples, see:
227/// - **Anchor**: `examples/anchor/simple-anchor-tests/tests/test_simple_anchor_program.rs`
228/// - **Pinocchio**: `examples/pinocchio/simple-pinocchio-tests/tests/test_simple_pinocchio_program.rs`
229///
230/// These examples show the full setup including build scripts, program loading, and test structure.
231///
232/// ## Error output example
233///
234/// When the assertion fails, you'll see output like:
235///
236/// ```text
237/// Expected to find: "Hello from my program!" in one of 4 log entries:
238/// [0]: Program 11111111111111111111111111111111 invoke [1]
239/// [1]: Program log: Hello from pinocchio! [12, 10, 227, ...]
240/// [2]: Program 11111111111111111111111111111111 consumed 3258 of 200000 compute units
241/// [3]: Program 11111111111111111111111111111111 success
242/// ```
243///
244/// # Note
245///
246/// This function works with both successful and failed transactions. For failed transactions,
247/// it searches through the logs in the error metadata.
248pub fn demand_logs_contain(expected: &str, result: TransactionResult) {
249 let logs = match &result {
250 Ok(meta) => &meta.logs,
251 Err(meta) => &meta.meta.logs,
252 };
253
254 if logs.iter().any(|log| log.contains(expected)) {
255 return;
256 }
257
258 panic!(
259 "Expected {:?} among {} log entries: {}",
260 expected,
261 logs.len(),
262 logs.iter()
263 .enumerate()
264 .map(|(i, log)| format!("[{}]: {}", i, log))
265 .collect::<Vec<_>>()
266 .join(", ")
267 );
268}
269
270/// Asserts that a specific log entry contains an expected string.
271///
272/// Unlike [`demand_logs_contain`] which searches all logs, this function checks
273/// only the log at the specified index. Useful when you need to verify specific
274/// program log ordering or validate that a particular instruction produces
275/// expected log output.
276///
277/// # Arguments
278///
279/// * `expected` - The string to search for within the specific log entry
280/// * `expected_index` - The index of the log entry to check (0-based)
281/// * `result` - The result of executing a transaction via [`litesvm::LiteSVM::send_transaction`]
282///
283/// # Panics
284///
285/// Panics if:
286/// - The log index is out of bounds (not enough log entries)
287/// - The log entry at the specified index doesn't contain the expected string
288///
289/// # Example
290///
291/// ```text
292/// // Verify the second log entry contains specific content
293/// demand_logs_contain_at_index("Hello from instruction 1", 1, result);
294/// ```
295pub fn demand_logs_contain_at_index(
296 expected: &str,
297 expected_index: usize,
298 result: TransactionResult,
299) {
300 let logs = match &result {
301 Ok(meta) => &meta.logs,
302 Err(meta) => &meta.meta.logs,
303 };
304
305 let Some(log_entry) = logs.get(expected_index) else {
306 panic!(
307 "Log index {} out of bounds, only {} entries available",
308 expected_index,
309 logs.len()
310 );
311 };
312
313 if !log_entry.contains(expected) {
314 panic!(
315 "Expected {:?} at log index {} but found: {:?}",
316 expected, expected_index, log_entry
317 );
318 }
319}
320
321/// Asserts that a transaction fails with a specific instruction error, regardless of index.
322///
323/// This is the "anywhere" version of instruction error testing - it matches the specified
324/// instruction error without caring which instruction in the transaction produced it.
325/// Perfect for single-instruction transactions or when you just need to verify the error type.
326///
327/// For precise instruction-index control, use [`demand_instruction_error_at_index`].
328///
329/// # Arguments
330///
331/// * `expected_error` - The expected instruction error
332/// * `result` - The result of executing a transaction via [`litesvm::LiteSVM::send_transaction`]
333///
334/// # Panics
335///
336/// Panics if:
337/// - The transaction succeeds (no error)
338/// - The error is not an instruction error
339/// - The instruction error doesn't match the expected error
340///
341/// # Example
342///
343/// ```text
344/// demand_instruction_error(
345/// InstructionError::Custom(1),
346/// result
347/// );
348/// ```
349pub fn demand_instruction_error(expected_error: InstructionError, result: TransactionResult) {
350 let Err(e) = result else {
351 panic!("Expected {} but transaction succeeded", expected_error);
352 };
353
354 let TransactionError::InstructionError(_, observed_error) = &e.err else {
355 panic!("Expected {} but got: {}", expected_error, e.err);
356 };
357
358 if *observed_error != expected_error {
359 panic!("Expected {} but got {}", expected_error, observed_error);
360 }
361}
362
363/// Asserts that a specific instruction fails with a specific error.
364///
365/// This is the "surgical" version of instruction error testing - it validates both the
366/// error type AND which instruction produced it. Use this for multi-instruction transactions
367/// where you need to verify that a specific instruction fails with a specific error.
368///
369/// For "anywhere" matching (don't care about index), use [`demand_instruction_error`].
370///
371/// # Arguments
372///
373/// * `expected_index` - The index of the instruction that should fail (0-based)
374/// * `expected_error` - The expected instruction error
375/// * `result` - The result of executing a transaction via [`litesvm::LiteSVM::send_transaction`]
376///
377/// # Panics
378///
379/// Panics if:
380/// - The transaction succeeds (no error)
381/// - The error is not an instruction error
382/// - The error occurs at a different instruction index
383/// - The instruction error doesn't match the expected error
384///
385/// # Example
386///
387/// ```text
388/// // Expect the second instruction (index 1) to fail with Custom(42)
389/// demand_instruction_error_at_index(
390/// 1,
391/// InstructionError::Custom(42),
392/// result
393/// );
394/// ```
395pub fn demand_instruction_error_at_index(
396 expected_index: u8,
397 expected_error: InstructionError,
398 result: TransactionResult,
399) {
400 let Err(e) = result else {
401 panic!(
402 "Expected {} at index {} but transaction succeeded",
403 expected_error, expected_index
404 );
405 };
406
407 let TransactionError::InstructionError(observed_index, observed_error) = &e.err else {
408 panic!(
409 "Expected {} at index {} but got: {}",
410 expected_error, expected_index, e.err
411 );
412 };
413
414 if *observed_index != expected_index {
415 panic!(
416 "Expected {} at index {} but got error at index {}",
417 expected_error, expected_index, observed_index
418 );
419 }
420
421 if *observed_error != expected_error {
422 panic!(
423 "Expected {} at index {} but got {} at index {}",
424 expected_error, expected_index, observed_error, observed_index
425 );
426 }
427}
428
429/// Asserts that a transaction error matches the expected error.
430///
431/// This function tests for transaction-level errors that occur before instruction execution,
432/// such as `AlreadyProcessed`, `InsufficientFundsForFee`, `AccountNotFound`, etc.
433///
434/// # Important Distinction
435///
436/// **Transaction-level errors** occur during transaction validation/processing:
437/// - `TransactionError::AlreadyProcessed` - Transaction already seen
438/// - `TransactionError::InsufficientFundsForFee` - Can't pay transaction fees
439/// - `TransactionError::AccountNotFound` - Referenced account doesn't exist
440///
441/// **Instruction-level errors** occur during instruction execution:
442/// - `InstructionError::Custom(1)` - System program insufficient funds for transfer
443/// - `InstructionError::Custom(3)` - System program invalid data length
444///
445/// Use [`demand_instruction_error`] for instruction-level errors.
446///
447/// # Arguments
448///
449/// * `expected` - The expected transaction error
450/// * `result` - The result of executing a transaction via [`litesvm::LiteSVM::send_transaction`]
451///
452pub fn demand_transaction_error(expected: TransactionError, result: TransactionResult) {
453 let Err(e) = result else {
454 panic!("Expected {} but transaction succeeded", expected);
455 };
456
457 if e.err != expected {
458 panic!("Expected {} but got {}", expected, e.err);
459 }
460}
461
462/// Asserts that a system error occurs, regardless of which instruction index produced it.
463///
464/// This is the "anywhere" version that matches system errors without caring about instruction index.
465/// When a transaction fails, there's exactly one error - this function checks if that error matches
466/// the expected system error, ignoring which instruction caused it.
467///
468/// **When to use**: Use this when you know the error should occur but don't care which instruction
469/// produced it. Perfect for single-instruction transactions or when testing general error conditions.
470///
471/// **When not to use**: When you need to verify the error occurs at a specific instruction index
472/// to test precise error handling - use [`demand_system_error_at_index`] instead.
473///
474/// # Arguments
475///
476/// * `expected_error` - The expected system error
477/// * `result` - The result of executing a transaction via [`litesvm::LiteSVM::send_transaction`]
478///
479pub fn demand_system_error(expected_error: SystemError, result: TransactionResult) {
480 let Err(e) = &result else {
481 panic!("Expected {} but transaction succeeded", expected_error);
482 };
483
484 let TransactionError::InstructionError(_, InstructionError::Custom(observed_code)) = &e.err
485 else {
486 panic!("Expected {} but got: {}", expected_error, e.err);
487 };
488
489 let Some(observed_error) = SystemError::from_u64(*observed_code as u64) else {
490 panic!(
491 "Expected {} but got invalid code {}",
492 expected_error, observed_code
493 );
494 };
495
496 if observed_error != expected_error {
497 panic!("Expected {} but got: {}", expected_error, observed_error);
498 }
499}
500
501/// Asserts that a system error occurs at a specific instruction index.
502///
503/// This is the "surgical" version for when you need precise control over which
504/// instruction should produce the error. Use this for multi-instruction transactions
505/// or when you want to be explicit about the instruction index.
506///
507/// # Arguments
508///
509/// * `expected_index` - The index of the instruction that should produce the error
510/// * `expected_error` - The expected system error
511/// * `result` - The result of executing a transaction via [`litesvm::LiteSVM::send_transaction`]
512///
513pub fn demand_system_error_at_index(
514 expected_index: u8,
515 expected_error: SystemError,
516 result: TransactionResult,
517) {
518 let Err(e) = &result else {
519 panic!(
520 "Expected {} at index {} but transaction succeeded",
521 expected_error, expected_index
522 );
523 };
524
525 let TransactionError::InstructionError(observed_index, InstructionError::Custom(observed_code)) =
526 &e.err
527 else {
528 panic!(
529 "Expected {} at index {} but got: {:?}",
530 expected_error, expected_index, e.err
531 );
532 };
533
534 if *observed_index != expected_index {
535 panic!(
536 "Expected {} at index {} but got error at index {}",
537 expected_error, expected_index, observed_index
538 );
539 }
540
541 let Some(observed_error) = SystemError::from_u64(*observed_code as u64) else {
542 panic!(
543 "Expected {} at index {} but got invalid code {} at index {}",
544 expected_error, expected_index, observed_code, observed_index
545 );
546 };
547
548 if observed_error != expected_error {
549 panic!(
550 "Expected {} at index {} but got {} at index {}",
551 expected_error, expected_index, observed_error, observed_index
552 );
553 }
554}
555
556/// Sets up a fresh LiteSVM instance with a funded fee payer account.
557///
558/// This is a convenience function for getting started quickly with LiteSVM testing.
559/// It creates a new SVM runtime and funds a fee payer account with 100 SOL, which
560/// should be sufficient for most testing scenarios.
561///
562/// # Returns
563///
564/// A tuple containing:
565/// - `LiteSVM` - A fresh SVM runtime instance
566/// - `Keypair` - A fee payer account funded with 100 SOL
567///
568/// # Example
569///
570/// ```text
571/// let (mut svm, fee_payer) = setup_svm_and_fee_payer();
572///
573/// // Use svm for testing...
574/// let result = svm.send_transaction(tx);
575/// ```
576///
577/// # Note
578///
579/// This function is primarily intended for examples and getting started. For production
580/// tests, you may want more control over the setup process.
581pub fn setup_svm_and_fee_payer() -> (LiteSVM, Keypair) {
582 const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
583
584 let mut svm = LiteSVM::new();
585
586 let fee_payer = Keypair::new();
587 svm.airdrop(&fee_payer.pubkey(), 100 * LAMPORTS_PER_SOL)
588 .expect("airdrop failed");
589
590 (svm, fee_payer)
591}