Skip to main content

litesvm_utils/
builder.rs

1//! Builder pattern for simplified test environment setup
2//!
3//! This module provides a fluent API for setting up test environments
4//! with automatic program deployment and configuration.
5
6use litesvm::LiteSVM;
7use solana_program::pubkey::Pubkey;
8
9/// Builder for creating a LiteSVM instance with programs pre-deployed
10///
11/// This provides a more ergonomic way to set up test environments compared to
12/// manually creating LiteSVM instances and deploying programs.
13///
14/// # Example
15/// ```ignore
16/// use litesvm_utils::LiteSVMBuilder;
17/// use solana_program::pubkey::Pubkey;
18///
19/// // Simple single program setup
20/// let program_id = Pubkey::new_unique();
21/// let program_bytes = include_bytes!("../target/deploy/my_program.so");
22/// let mut svm = LiteSVMBuilder::new()
23///     .deploy_program(program_id, program_bytes)
24///     .build();
25///
26/// // Or use the convenience method for single program
27/// let mut svm = LiteSVMBuilder::build_with_program(program_id, program_bytes);
28/// ```
29pub struct LiteSVMBuilder {
30    svm: LiteSVM,
31    programs: Vec<(Pubkey, Vec<u8>)>,
32}
33
34impl LiteSVMBuilder {
35    /// Create a new test environment builder
36    pub fn new() -> Self {
37        Self {
38            svm: LiteSVM::new(),
39            programs: Vec::new(),
40        }
41    }
42
43    /// Add a program to be deployed
44    ///
45    /// Programs are deployed in the order they are added.
46    ///
47    /// # Arguments
48    ///
49    /// * `program_id` - The program ID to deploy at
50    /// * `program_bytes` - The compiled program bytes (.so file contents)
51    ///
52    /// # Example
53    ///
54    /// ```ignore
55    /// builder.deploy_program(program_id, program_bytes)
56    /// ```
57    pub fn deploy_program(mut self, program_id: Pubkey, program_bytes: &[u8]) -> Self {
58        self.programs.push((program_id, program_bytes.to_vec()));
59        self
60    }
61
62    /// Build the LiteSVM instance with all programs deployed
63    ///
64    /// # Returns
65    ///
66    /// Returns the configured LiteSVM instance with all programs deployed
67    ///
68    /// # Example
69    ///
70    /// ```ignore
71    /// let mut svm = builder.build();
72    /// ```
73    pub fn build(mut self) -> LiteSVM {
74        // Deploy all programs
75        for (program_id, program_bytes) in self.programs {
76            self.svm
77                .add_program(program_id, &program_bytes)
78                .expect("Failed to add program");
79        }
80
81        self.svm
82    }
83
84    /// Convenience method to quickly set up a single program
85    ///
86    /// This is equivalent to:
87    /// ```ignore
88    /// LiteSVMBuilder::new()
89    ///     .deploy_program(program_id, program_bytes)
90    ///     .build()
91    /// ```
92    ///
93    /// # Arguments
94    ///
95    /// * `program_id` - The program ID to deploy at
96    /// * `program_bytes` - The compiled program bytes
97    ///
98    /// # Returns
99    ///
100    /// Returns a configured LiteSVM instance with the program deployed
101    ///
102    /// # Example
103    ///
104    /// ```ignore
105    /// let mut svm = LiteSVMBuilder::build_with_program(program_id, program_bytes);
106    /// ```
107    pub fn build_with_program(program_id: Pubkey, program_bytes: &[u8]) -> LiteSVM {
108        Self::new()
109            .deploy_program(program_id, program_bytes)
110            .build()
111    }
112
113    /// Convenience method to quickly set up multiple programs
114    ///
115    /// # Arguments
116    ///
117    /// * `programs` - A slice of (program_id, program_bytes) tuples
118    ///
119    /// # Returns
120    ///
121    /// Returns a configured LiteSVM instance with all programs deployed
122    ///
123    /// # Example
124    ///
125    /// ```ignore
126    /// let programs = vec![
127    ///     (program_id1, program_bytes1),
128    ///     (program_id2, program_bytes2),
129    /// ];
130    /// let mut svm = LiteSVMBuilder::build_with_programs(&programs);
131    /// ```
132    pub fn build_with_programs(programs: &[(Pubkey, &[u8])]) -> LiteSVM {
133        let mut builder = Self::new();
134        for (program_id, program_bytes) in programs {
135            builder = builder.deploy_program(*program_id, program_bytes);
136        }
137        builder.build()
138    }
139}
140
141impl Default for LiteSVMBuilder {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147/// Extension trait for LiteSVM to add program deployment capabilities
148pub trait ProgramTestExt {
149    /// Deploy a program to this LiteSVM instance
150    ///
151    /// # Example
152    /// ```no_run
153    /// # use litesvm_utils::ProgramTestExt;
154    /// # use litesvm::LiteSVM;
155    /// # use solana_program::pubkey::Pubkey;
156    /// # let mut svm = LiteSVM::new();
157    /// # let program_id = Pubkey::new_unique();
158    /// # let program_bytes = vec![];
159    /// svm.deploy_program(program_id, &program_bytes);
160    /// ```
161    fn deploy_program(&mut self, program_id: Pubkey, program_bytes: &[u8]);
162}
163
164impl ProgramTestExt for LiteSVM {
165    fn deploy_program(&mut self, program_id: Pubkey, program_bytes: &[u8]) {
166        self.add_program(program_id, program_bytes)
167            .expect("Failed to deploy program");
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_builder_new() {
177        let builder = LiteSVMBuilder::new();
178        let _svm = builder.build();
179        // Should successfully create a new LiteSVM instance
180    }
181
182    #[test]
183    fn test_builder_default() {
184        let builder = LiteSVMBuilder::default();
185        let _svm = builder.build();
186        // Default should work the same as new()
187    }
188
189    #[test]
190    fn test_builder_deploy_single_program() {
191        let program_id = Pubkey::new_unique();
192        let program_bytes = vec![1, 2, 3, 4];
193
194        // Test that builder fluent API works - don't call build() to avoid validation
195        let mut builder = LiteSVMBuilder::new();
196        builder = builder.deploy_program(program_id, &program_bytes);
197
198        // Verify the program was added to the builder
199        assert_eq!(builder.programs.len(), 1);
200        assert_eq!(builder.programs[0].0, program_id);
201    }
202
203    #[test]
204    fn test_builder_deploy_multiple_programs() {
205        let program_id1 = Pubkey::new_unique();
206        let program_id2 = Pubkey::new_unique();
207        let program_bytes = vec![1, 2, 3, 4];
208
209        // Test that builder accepts multiple programs
210        let builder = LiteSVMBuilder::new()
211            .deploy_program(program_id1, &program_bytes)
212            .deploy_program(program_id2, &program_bytes);
213
214        // Verify both programs were added
215        assert_eq!(builder.programs.len(), 2);
216    }
217
218    #[test]
219    fn test_build_with_programs_empty_list() {
220        let programs: Vec<(Pubkey, &[u8])> = vec![];
221        let _svm = LiteSVMBuilder::build_with_programs(&programs);
222        // Should not panic with empty program list
223    }
224
225    #[test]
226    fn test_builder_chaining() {
227        let program_id1 = Pubkey::new_unique();
228        let program_id2 = Pubkey::new_unique();
229        let program_id3 = Pubkey::new_unique();
230        let program_bytes = vec![1, 2, 3, 4];
231
232        // Test that builder methods can be chained
233        let builder = LiteSVMBuilder::new()
234            .deploy_program(program_id1, &program_bytes)
235            .deploy_program(program_id2, &program_bytes)
236            .deploy_program(program_id3, &program_bytes);
237
238        // Verify all 3 programs were added
239        assert_eq!(builder.programs.len(), 3);
240    }
241}