Skip to content

Commit a0d892b

Browse files
committed
testing processor
1 parent d958d58 commit a0d892b

File tree

3 files changed

+291
-0
lines changed

3 files changed

+291
-0
lines changed

apps/nextra/next.config.mjs

+7
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,13 @@ export default withBundleAnalyzer(
473473
"/en/build/indexer/indexer-sdk/documentation/advanced-tutorials/txn-script",
474474
permanent: true,
475475
},
476+
{
477+
source:
478+
"/indexer/indexer-sdk/documentation/advanced-tutorials/processor-test",
479+
destination:
480+
"/en/build/indexer/indexer-sdk/documentation/advanced-tutorials/processor-test",
481+
permanent: true,
482+
},
476483
{
477484
source: "/indexer/txn-stream/labs-hosted",
478485
destination: "/en/build/indexer/api/labs-hosted",

apps/nextra/pages/en/build/indexer/indexer-sdk/advanced-tutorials/_meta.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ export default {
88
"txn-script": {
99
title: "Generating Transactions with Move Scripts",
1010
},
11+
"processor-test": {
12+
title: "Testing Your Processor",
13+
},
1114
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
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

Comments
 (0)