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}