admixture-harness 0.1.0

Test harness with context lifecycle management and grouped execution
Documentation
# admixture-harness

Test harness for integration tests with admixture contexts.

## Overview

`admixture-harness` provides the `#[admixture_test]` macro for writing integration tests with automatic context lifecycle management. It uses the `inventory` crate to collect tests at compile-time and run them sequentially with proper setup and teardown.

## Features

- **`#[admixture_test]` macro**: Automatically manages context lifecycle for each test
- **Context reuse**: Tests sharing the same context type reuse the same context instance (major performance improvement!)
- **Inventory-based test collection**: Tests are collected at compile-time and grouped by context type
- **Sequential execution within context groups**: Tests run sequentially per context type
- **Flexible return types**: Supports both `Result<(), E>` and `()` return types
- **Works with Docker**: Integrates seamlessly with `admixture-docker` for container-based tests

## Usage

### Basic Example

```rust
use admixture::context;
use admixture_harness::prelude::*;

// Define your test context with inline configuration
context! {
    MyTestContext {
        database: MockDatabaseSetup = MockDatabaseSetup::new(),
        cache: MockCacheSetup = MockCacheSetup::new(),
    }
}

// Write tests with the harness
#[admixture_test(context = MyTestContext)]
async fn test_with_result(ctx: &MyTestContext) -> Result<(), TestError> {
    let db = ctx.database().client().await?;
    // Test logic here
    Ok(())
}

#[admixture_test(context = MyTestContext)]
async fn test_with_panics(ctx: &MyTestContext) {
    let cache = ctx.cache().client().await.unwrap();
    assert_eq!(cache.get("key").await, Some("value"));
}

// Generate the test runner
admixture_harness::test_runner!();
```

### Docker/PostgreSQL Example

```rust
use admixture::context;
use admixture_docker::SqlxPostgresServiceSetup;
use admixture_harness::prelude::*;
use testcontainers_modules::postgres::Postgres;

context! {
    PostgresTestContext {
        postgres: SqlxPostgresServiceSetup = Postgres::default(),
    }
}

#[admixture_test(context = PostgresTestContext)]
async fn test_database_query(ctx: &PostgresTestContext) -> Result<(), TestError> {
    let client = ctx.postgres().client().await
        .map_err(|e| TestError::Other(e.to_string()))?;
    
    let result: i32 = sqlx::query_scalar("SELECT 1")
        .fetch_one(&client)
        .await
        .map_err(|e| TestError::Other(e.to_string()))?;
    
    assert_eq!(result, 1);
    Ok(())
}

admixture_harness::test_runner!();
```

## How It Works

1. **`#[admixture_test]` macro**:
   - Registers each test with the `inventory` crate
   - Includes the context type information for grouping
   - Generates a wrapper that can receive a context reference
   - Calls the test function with the context

2. **`test_runner!()` macro**:
   - Generates a single `#[tokio::test]` function
   - Collects all registered tests and groups them by context type
   - For each context type:
     - Starts the context once
     - Runs all tests for that context sequentially
     - Stops the context
   - Reports results for all tests

3. **Test execution with context reuse**:
   - **Context is started once per context type** (not per test!)
   - All tests sharing the same context type reuse the same context instance
   - This dramatically improves performance, especially with Docker containers
   - Tests still run sequentially within each context group
   - Context is always torn down after all tests in the group complete

### Example: Context Reuse in Action

If you have 3 tests all using `PostgresTestContext`:
```rust
#[admixture_test(context = PostgresTestContext)]
async fn test_one(ctx: &PostgresTestContext) { /* ... */ }

#[admixture_test(context = PostgresTestContext)]
async fn test_two(ctx: &PostgresTestContext) { /* ... */ }

#[admixture_test(context = PostgresTestContext)]
async fn test_three(ctx: &PostgresTestContext) { /* ... */ }
```

The harness will:
1. Start PostgresTestContext once
2. Run test_one with the context
3. Run test_two with the **same** context
4. Run test_three with the **same** context
5. Stop the context once

This means the PostgreSQL Docker container is started **only once** for all three tests!

### Visual Output Example

When you run tests with `--nocapture`, you'll see clear visual output showing context reuse:

```
================================================================================
🚀 Starting Integration Test Run
   Total tests: 7
   Context types: 3
================================================================================

📦 Context: CacheContext
   Tests in group: 2
   ⏳ Starting context...
      🔧 Starting Cache service
   ✅ Context started successfully

   [1/2] Running: test_cache_get_1 ... ✅ PASSED
   [2/2] Running: test_cache_get_2 ... ✅ PASSED

   ⏳ Stopping context... ✅ Stopped

📦 Context: DatabaseContext
   Tests in group: 2
   ⏳ Starting context...
      🔧 Starting Database service
   ✅ Context started successfully

   [1/2] Running: test_database_query_1 ... ✅ PASSED
   [2/2] Running: test_database_query_2 ... ✅ PASSED

   ⏳ Stopping context... ✅ Stopped

📦 Context: FullContext
   Tests in group: 3
   ⏳ Starting context...
      🔧 Starting Database service
      🔧 Starting Cache service
   ✅ Context started successfully

   [1/3] Running: test_full_integration_1 ... ✅ PASSED
   [2/3] Running: test_full_integration_2 ... ✅ PASSED
   [3/3] Running: test_full_integration_3 ... ✅ PASSED

   ⏳ Stopping context... ✅ Stopped

================================================================================
📊 Test Results Summary
================================================================================
   ✅ All tests passed!

   Total:  7
   ✅ Passed: 7
   ❌ Failed: 0
================================================================================
```

Notice how:
- Each context type is started **once**
- All tests for that context run sequentially
- The context is stopped **once** after all tests complete
- You can clearly see the progress: `[1/3]`, `[2/3]`, `[3/3]`

## Requirements

- Test functions must be `async`
- Test functions must have exactly one parameter: `ctx: &<ContextType>`
- The context must provide service configurations either:
  - Using inline config syntax: `service: ServiceType = config_expr`
  - Or service configs must implement `Default`
- Tests must return either `()` or `Result<(), E>`

## Future Enhancements

- **Test isolation options**: Allow opting into fresh context per test when needed
- **Lifecycle hooks**: `before_each`, `after_each`, `before_all`, `after_all`
- **Parallel context groups**: Run different context groups in parallel
- **Test filtering**: Run specific tests by name or pattern
- **Custom context setup**: Allow non-Default context initialization
- **Test dependencies**: Specify test execution order within a context group

## Running Tests

```bash
# Run all harness tests
cargo test --package admixture-harness

# Run specific test file
cargo test --test harness_basic_tests

# Run with output
cargo test --test harness_basic_tests -- --nocapture
```

## License

Same as parent `admixture` project.