# Testing Guide
This document describes the testing strategy for pg_exporter to prevent production issues.
## Testing Philosophy
All collectors MUST be tested with:
1. **Extension/Feature availability tests** - Handle missing extensions gracefully
2. **Edge case tests** - NULL values, empty results, utility statements
3. **Type compatibility tests** - Ensure SQL types match Rust types
4. **Realistic workload tests** - Test with actual data and queries
## Running Tests
### Local Testing
```bash
# Set up PostgreSQL with pg_stat_statements
export PG_EXPORTER_DSN="postgresql://postgres:postgres@localhost:5432/postgres"
# Enable pg_stat_statements in postgresql.conf:
# shared_preload_libraries = 'pg_stat_statements'
# Then in psql:
# CREATE EXTENSION pg_stat_statements;
# Run all tests
cargo test
# Run specific collector tests
cargo test --test collectors_tests statements
# Run with output
cargo test -- --nocapture
```
For rootless Podman with `testcontainers`, export:
```bash
export DOCKER_HOST="unix:///run/user/$UID/podman/podman.sock"
```
### CI Testing
The CI pipeline automatically:
- Tests against PostgreSQL 16, 17, and 18
- Installs and configures pg_stat_statements extension
- Runs all integration tests
## Writing Collector Tests
When adding a new collector, you MUST include these test categories:
### 1. Registration Test
```rust
#[tokio::test]
async fn test_collector_registers_without_error() -> Result<()> {
let collector = MyCollector::new();
let registry = Registry::new();
collector.register_metrics(®istry)?;
Ok(())
}
```
### 2. Extension/Feature Availability Test
```rust
#[tokio::test]
async fn test_collector_handles_missing_extension() -> Result<()> {
let pool = common::create_test_pool().await?;
let collector = MyCollector::new();
let registry = Registry::new();
collector.register_metrics(®istry)?;
let result = collector.collect(&pool).await;
// Should not panic
assert!(result.is_ok());
Ok(())
}
```
### 3. Edge Case Tests
Test for common edge cases that cause panics:
```rust
#[tokio::test]
async fn test_collector_handles_null_values() -> Result<()> {
// Test queries that may return NULL
// Utility statements (VACUUM, ANALYZE)
// Empty result sets
// Zero values
}
#[tokio::test]
async fn test_collector_handles_type_mismatches() -> Result<()> {
// Ensure SQL types (NUMERIC, BIGINT) match Rust types
// Use explicit casts in SQL if needed: value::bigint
}
```
### 4. Realistic Workload Test
```rust
#[tokio::test]
async fn test_collector_with_realistic_data() -> Result<()> {
// Create test data
// Generate realistic workload
// Verify metrics are collected correctly
}
```
## Common Pitfalls and Solutions
**Detailed code examples with inline comments are in `src/collectors/statements/pg_statements.rs`.**
### 1. Type Mismatches (CRITICAL)
**Problem:** PostgreSQL NUMERIC type doesn't match Rust i64/f64
**Solution:** Always cast in SQL: `SELECT column::bigint FROM table`
### 2. NULL Values (CRITICAL)
**Problem:** Using `row.get()` panics on NULL
**Solution:** Use `row.try_get()` with fallbacks (see code examples)
### 3. Missing Extensions
**Problem:** Assuming extensions are installed
**Solution:** Check with `fetch_optional()` and handle gracefully
### 4. Division by Zero
**Problem:** Dividing without checking denominator
**Solution:** Check `if total > 0` before division
**See `src/collectors/statements/pg_statements.rs` for production examples of all these patterns.**
## Test Coverage Requirements
Before merging:
- [ ] All new collectors have registration tests
- [ ] All new collectors have extension availability tests
- [ ] Edge cases (NULL, zero, empty) are tested
- [ ] Type conversions are tested with realistic data
- [ ] CI passes on all PostgreSQL versions
## Debugging Test Failures
```bash
# Run single test with output
cargo test test_name -- --nocapture
# Run with RUST_LOG for detailed tracing
RUST_LOG=debug cargo test test_name -- --nocapture
# Connect to test database to inspect state
psql $PG_EXPORTER_DSN
# Check pg_stat_statements data
SELECT * FROM pg_stat_statements LIMIT 5;
# Check extension installation
SELECT * FROM pg_extension;
```
## PostgreSQL Version Compatibility
`pg_exporter` supports PostgreSQL 14 and newer.
The test matrix currently covers PostgreSQL 16, 17, and 18. Some features may vary across supported versions:
- `pg_stat_checkpointer` - Added in PostgreSQL 17
- Use explicit version guards only for features that vary across supported versions
- Always use COALESCE for nullable or version-specific columns when applicable:
```sql
COALESCE(wal_bytes, 0)::bigint as wal_bytes
```
## When to Skip Tests
Tests should be skipped (not fail) when:
- Required extension is not installed
- PostgreSQL version doesn't support a feature within the supported PostgreSQL 14+ range
- Running in a restricted environment
```rust
if ext_check.is_none() {
println!("Extension not available, skipping test");
return Ok(());
}
```
## Continuous Improvement
After any production panic:
1. Add a test that reproduces the panic
2. Fix the code
3. Verify the test now passes
4. Update this guide with lessons learned