# Dynamic Tests

For tests generated at runtime (e.g. from data files), use `TestRunner` instead of `run_all()`.

## Basic usage

```rust
#[skuld::label] const DATA: skuld::Label;

fn main() {
    let mut runner = skuld::TestRunner::new();

    for file in std::fs::read_dir("test_data").unwrap() {
        let path = file.unwrap().path();
        runner.add(
            path.display().to_string(),
            &[DATA],           // labels
            false,             // ignored
            move || {
                let content = std::fs::read_to_string(&path).unwrap();
                assert!(!content.is_empty());
            },
        );
    }

    runner.run();  // Runs both dynamic and #[skuld::test] tests, then exits.
}
```

Dynamic tests are collected alongside `#[skuld::test]`-registered tests. They support labels and label filtering.

## Serial dynamic tests

Use `add_serial` for dynamic tests that need the serial mutex:

```rust
#[skuld::label] const INTEGRATION: skuld::Label;

runner.add_serial(
    "env-sensitive test",
    &[INTEGRATION],
    false,
    || { /* body */ },
);
```

## Filtered serial dynamic tests

Use `add_serial_with` for dynamic tests that need serial execution with a filter expression:

```rust
#[skuld::label] const DATABASE: skuld::Label;
#[skuld::label] const FAST: skuld::Label;

runner.add_serial_with(
    "filtered serial test",
    &[DATABASE],
    false,
    DATABASE & !FAST,   // LabelFilter via operator overloads
    || { /* body */ },
);
```

The filter argument is a `LabelFilter`, built from `Label` constants using `&` (AND), `|` (OR), and `!` (NOT).

## Custom CLI arguments

If your test binary accepts custom flags that would otherwise be rejected by the argument parser, strip them:

```rust
let mut runner = skuld::TestRunner::new();
runner.strip_args(&["--no-sandbox", "--headless"]);
// ... add tests ...
runner.run();
```

## `run()` vs `run_tests()`

| Method        | Returns      | Use case                                                                              |
| ------------- | ------------ | ------------------------------------------------------------------------------------- |
| `run()`       | `!` (exits)  | Normal usage — runs tests and exits with the appropriate code.                        |
| `run_tests()` | `Conclusion` | When you need post-run assertions before exiting. Call `conclusion.exit()` when done. |

```rust
fn main() {
    let conclusion = skuld::TestRunner::new().run_tests();

    // Post-run checks...
    assert!(some_condition());

    conclusion.exit();
}
```
