|
| 1 | +--- |
| 2 | +title: "Testing Processor" |
| 3 | +--- |
| 4 | + |
| 5 | +import { Callout } from "nextra/components" |
| 6 | + |
| 7 | + |
| 8 | +# Overview |
| 9 | +### What Is a Processor? |
| 10 | +A processor is a core component of the Aptos Indexer that handles blockchain transaction processing. It validates, transforms, and stores transactions into a database, enabling downstream applications like analytics, indexing, and querying. Testing the processor ensures that all transactions are correctly handled, maintaining data accuracy and consistency. |
| 11 | + |
| 12 | + |
| 13 | +### What Are We Testing With This? |
| 14 | + |
| 15 | +- **Transaction correctness**: Ensure that each transaction is processed and stored accurately. |
| 16 | +- **Schema consistency**: Verify that the database schema is correctly set up and maintained throughout the tests. |
| 17 | + |
| 18 | +### General Flow of how Processor Testing Works |
| 19 | + |
| 20 | +1. Prepare testing transactions (refer to prior documentations). |
| 21 | +2. Update dependencies as needed. |
| 22 | +3. Import new transactions. |
| 23 | +4. Write test cases. |
| 24 | +5. Generate expected database output and validate. |
| 25 | +6. Merge. |
| 26 | + |
| 27 | +## Prerequisites |
| 28 | +<Callout> |
| 29 | + Key Considerations: |
| 30 | + - Each test runs in an isolated environment using a PostgreSQL container to prevent interference. |
| 31 | + - Proper handling of versions ensures transactions are processed and validated in the correct order. |
| 32 | + - Validation logic must detect changes or issues by comparing processor output with the expected baseline. |
| 33 | +</Callout> |
| 34 | + |
| 35 | + |
| 36 | +1. Ensure Docker is running for PostgreSQL container support. |
| 37 | + - Set up docker engine/daemon on your machine |
| 38 | + - Start Docker if it's not running |
| 39 | +2. Identify the transactions to test. |
| 40 | + - Use imported transactions or write your own custom Move scripts to generate test transactions. Refer to Importing Transaction Guide and Generating Transaction using Move Script Guide for detailed instructions. |
| 41 | +3. Import necessary modules, see example: |
| 42 | + ```rust |
| 43 | + use aptos_indexer_testing_framework::{ |
| 44 | + database::{PostgresTestDatabase, TestDatabase}, |
| 45 | + sdk_test_context::SdkTestContext, |
| 46 | + }; |
| 47 | + ``` |
| 48 | + |
| 49 | +## Steps to Write a Test |
| 50 | + |
| 51 | +### 1. Set Up the Test Environment |
| 52 | +Before setting up the test environment, it’s important to understand the configurations being used in this step: |
| 53 | + |
| 54 | + |
| 55 | +**What Are These Configurations?** |
| 56 | + |
| 57 | +`generate_file_flag` |
| 58 | +- This flag determines whether to run the test in a "diff mode" or not. |
| 59 | +In "diff mode," the system will compare the actual output from the processor with the expected output and highlight differences. |
| 60 | +Use this mode when validating changes or debugging discrepancies. |
| 61 | + |
| 62 | +`custom_output_path` |
| 63 | +- An optional configuration to specify a custom path where the expected database output will be stored. |
| 64 | +If not provided, the test will use the default path defined by DEFAULT_OUTPUT_FOLDER. |
| 65 | + |
| 66 | +`DEFAULT_OUTPUT_FOLDER` |
| 67 | +- This constant defines the default folder where the system stores output files for the tests. |
| 68 | +Example: "sdk_expected_db_output_files". |
| 69 | +Modify this value in your configuration if you prefer a different default directory. |
| 70 | + |
| 71 | + |
| 72 | +```rust |
| 73 | +let (generate_file_flag, custom_output_path) = get_test_config(); |
| 74 | +let output_path = custom_output_path.unwrap_or_else(|| format!("{}/imported_mainnet_txns", DEFAULT_OUTPUT_FOLDER)); |
| 75 | + |
| 76 | +// Setup DB and replace as needed |
| 77 | +let mut db = PostgresTestDatabase::new(); |
| 78 | +db.setup().await.unwrap(); |
| 79 | + |
| 80 | +let mut test_context = SdkTestContext::new(&[CONST_VARIABLE_OF_YOUR_TEST_TRANSACTION]); // Replace with your test transaction |
| 81 | +if test_context.init_mock_grpc().await.is_err() { |
| 82 | + panic!("Failed to initialize mock grpc"); |
| 83 | +}; |
| 84 | +``` |
| 85 | + |
| 86 | +**Explanation of Each Component:** |
| 87 | + |
| 88 | +`get_test_config():` |
| 89 | + |
| 90 | +This function fetches the configurations (diff_flag and custom_output_path) for the test. |
| 91 | +Modify or extend this function if you want to support additional custom flags or configurations. |
| 92 | +output_path: |
| 93 | + |
| 94 | +Combines DEFAULT_OUTPUT_FOLDER with the subfolder imported_mainnet_txns if no custom_output_path is specified. |
| 95 | +This ensures all output files are stored in a predictable location. |
| 96 | + |
| 97 | +`PostgresTestDatabase::new():` |
| 98 | + |
| 99 | +Creates a new PostgreSQL database instance for testing. |
| 100 | +This database is isolated, ensuring no interference with production or other test environments. |
| 101 | + |
| 102 | +`SdkTestContext::new():` |
| 103 | + |
| 104 | +Initializes the test context with the transaction(s) you want to test. |
| 105 | +Replace CONST_VARIABLE_OF_YOUR_TEST_TRANSACTION with the appropriate variable or constant representing the transaction(s) to be tested. |
| 106 | + |
| 107 | +`init_mock_grpc():` |
| 108 | + |
| 109 | +Initializes a mock gRPC service for the test. |
| 110 | +This allows the processor to simulate transactions without interacting with live blockchain data. |
| 111 | + |
| 112 | + |
| 113 | +### 2. Configure the Processor |
| 114 | + |
| 115 | +```rust |
| 116 | +let db_url = db.get_db_url(); |
| 117 | +let transaction_stream_config = test_context.create_transaction_stream_config(); |
| 118 | +let postgres_config = PostgresConfig { |
| 119 | + connection_string: db_url.to_string(), |
| 120 | + db_pool_size: 100, |
| 121 | +}; |
| 122 | + |
| 123 | +let db_config = DbConfig::PostgresConfig(postgres_config); |
| 124 | +let default_processor_config = DefaultProcessorConfig { |
| 125 | + per_table_chunk_sizes: AHashMap::new(), |
| 126 | + channel_size: 100, |
| 127 | + deprecated_tables: HashSet::new(), |
| 128 | +}; |
| 129 | + |
| 130 | +let processor_config = ProcessorConfig::DefaultProcessor(default_processor_config); |
| 131 | +let processor_name = processor_config.name(); |
| 132 | +``` |
| 133 | + |
| 134 | +### 3. Create the Processor |
| 135 | + |
| 136 | +```rust |
| 137 | +let processor = DefaultProcessor::new(indexer_processor_config) |
| 138 | + .await |
| 139 | + .expect("Failed to create processor"); |
| 140 | +``` |
| 141 | +Note: Replace `DefaultProcessor` with the processor you are testing. |
| 142 | + |
| 143 | +### 4. Setup a Query |
| 144 | + |
| 145 | +Set up a query to load data from the local database and compare it with expected results, see [example loading function](https://github.com/aptos-labs/aptos-indexer-processors/blob/a8f9c5915f4e3f1f596ed3412b8eb01feca1aa7b/rust/integration-tests/src/diff_test_helper/default_processor.rs#L45) |
| 146 | + |
| 147 | + |
| 148 | +### 5. Setup a Test Context run function |
| 149 | +Use the test_context.run() function to execute the processor, validate outputs using your query, and optionally generate database output files: |
| 150 | + |
| 151 | +```rust |
| 152 | + let txn_versions: Vec<i64> = test_context |
| 153 | + .get_test_transaction_versions() |
| 154 | + .into_iter() |
| 155 | + .map(|v| v as i64) |
| 156 | + .collect(); |
| 157 | + |
| 158 | + let db_values = test_context |
| 159 | + .run( |
| 160 | + &processor, |
| 161 | + generate_file_flag, |
| 162 | + output_path.clone(), |
| 163 | + custom_file_name, |
| 164 | + move || { |
| 165 | + let mut conn = PgConnection::establish(&db_url).unwrap_or_else(|e| { |
| 166 | + eprintln!("[ERROR] Failed to establish DB connection: {:?}", e); |
| 167 | + panic!("Failed to establish DB connection: {:?}", e); |
| 168 | + }); |
| 169 | + |
| 170 | + let db_values = match load_data(&mut conn, txn_versions.clone()) { |
| 171 | + Ok(db_data) => db_data, |
| 172 | + Err(e) => { |
| 173 | + eprintln!("[ERROR] Failed to load data {}", e); |
| 174 | + return Err(e); |
| 175 | + }, |
| 176 | + }; |
| 177 | + |
| 178 | + if db_values.is_empty() { |
| 179 | + eprintln!("[WARNING] No data found for versions: {:?}", txn_versions); |
| 180 | + } |
| 181 | + |
| 182 | + Ok(db_values) |
| 183 | + }, |
| 184 | + ) |
| 185 | +``` |
| 186 | + |
| 187 | + |
| 188 | +### 6. Run the Processor Test |
| 189 | + |
| 190 | +Once you have your test ready, run the following command to generate the expected output for validation: |
| 191 | + |
| 192 | +```bash |
| 193 | +cargo test sdk_tests -- generate-output |
| 194 | +``` |
| 195 | + |
| 196 | +Arguments: |
| 197 | +generate-output: A custom flag to indicate that expected outputs should be generated. |
| 198 | +output-path: it's an optional argument to specify the output path for the db output. |
| 199 | + |
| 200 | +The expected database output will be saved in the specified output_path or sdk_expected_db_output_files by default. |
| 201 | + |
| 202 | + |
| 203 | +--- |
| 204 | + |
| 205 | +## FAQ |
| 206 | + |
| 207 | +### What Types of Tests Does It Support? |
| 208 | + |
| 209 | +- Database schema output diff. |
| 210 | + |
| 211 | +### What Is `TestContext`? |
| 212 | + |
| 213 | +`TestContext` is a struct that manages: |
| 214 | + |
| 215 | +- `transaction_batches`: A collection of transaction batches. |
| 216 | +- `postgres_container`: A PostgreSQL container for test isolation. |
| 217 | + |
| 218 | +It initializes and manages the database and transaction context for tests. |
| 219 | + |
| 220 | +#### What Does `TestContext.run` Do? |
| 221 | + |
| 222 | +This function executes the processor, applies validation logic, and optionally generates output files. |
| 223 | + |
| 224 | +#### Key Features: |
| 225 | + |
| 226 | +- Flexible Validation: Accepts a user-provided verification function. |
| 227 | +- Multi-Table Support: Handles data across multiple tables. |
| 228 | +- Retries: Uses exponential backoff and timeout for retries. |
| 229 | +- Optional File Generation: Controlled by a flag. |
| 230 | + |
| 231 | +#### Example Usage: |
| 232 | + |
| 233 | +```rust |
| 234 | +pub async fn run<F>( |
| 235 | + &mut self, |
| 236 | + processor: &impl ProcessorTrait, |
| 237 | + txn_version: u64, |
| 238 | + generate_files: bool, // Flag to control file generation |
| 239 | + output_path: String, // Output path |
| 240 | + custom_file_name: Option<String>, // Custom file name |
| 241 | + verification_f: F, // Verification function |
| 242 | +) -> anyhow::Result<HashMap<String, Value>> |
| 243 | +where |
| 244 | +``` |
| 245 | + |
| 246 | +### How to Generate Expected DB Output? |
| 247 | + |
| 248 | +Run the following command: |
| 249 | + |
| 250 | +```bash |
| 251 | +cargo test sdk_tests -- --nocapture generate-output |
| 252 | +``` |
| 253 | + |
| 254 | +Supported Test Args: |
| 255 | + |
| 256 | +1. `generate-output` |
| 257 | +2. `output_path` |
| 258 | + |
| 259 | +--- |
| 260 | + |
| 261 | +## Troubleshooting and Tips |
| 262 | + |
| 263 | +1. **Isolate Tests**: Use Docker containers for database isolation. |
| 264 | +2. **Handle Non-Deterministic Fields**: Use helpers like `remove_inserted_at` to clean up timestamps before validation. |
| 265 | +3. **Enable Debugging**: Use `eprintln!` for detailed error logging. |
| 266 | + |
| 267 | +#### How to Debug Test Failures? |
| 268 | +run following command to get detailed logs: |
| 269 | + |
| 270 | +```bash |
| 271 | +cargo test sdk_tests -- --nocapture |
| 272 | +``` |
| 273 | + |
| 274 | +## Additional Notes |
| 275 | + |
| 276 | +- **Adapting to Other Databases**: |
| 277 | + - Replace PostgreSQL-specific code with relevant database code you intend to use (e.g., MySQL). |
| 278 | + - Update schema initialization and query methods. |
| 279 | +- **Docker Installation**: Follow the [Docker setup guide](https://docs.docker.com/get-docker/). |
| 280 | +- **Referencing Existing Tests**: |
| 281 | + - Example: [Event Processor Tests](https://github.com/aptos-labs/aptos-indexer-processors/blob/main/rust/integration-tests/src/sdk_tests/events_processor_tests.rs#L139). |
0 commit comments