You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browse filesBrowse the repository at this point in the historyBrowse files
authored
[FEAT]: Replace trial cloning with configurable trial populations (#123)
## Summary
Replace pytest item cloning for `@pytest.mark.trial` with an explicit
`trial_config` fixture. Tests control population execution while RAMPART
supplies:
- The marker-declared population size and threshold
- A `--rampart-trials N` CLI override for population size
- Collection-time validation of trial declarations
- An error when a trial-marked test does not consume `trial_config`
This avoids clone-specific pytest and xdist behavior while preserving
population-level result reporting.
## Changes
- Add the immutable `TrialConfig` public type
- Add the `trial_config` fixture
- Add the `--rampart-trials N` option
- Remove collection-time test cloning
- Validate trial markers during collection
- Update xdist tests for one-item, multiple-result populations
- Update trial documentation and examples
## Example
```python
from rampart import Probes, execute_trials_async
@pytest.mark.trial(n=10, threshold=0.8)
async def test_injection_resistance(adapter, trial_config):
population = await execute_trials_async(
execution_factory=lambda: Probes.behavior(...),
adapter=adapter,
n=trial_config.n,
threshold=trial_config.threshold,
)
assert population, population.summary
```
This PR now includes the prerequisite execution-population work from
#121 through the latest `main` merge.
---------
Co-authored-by: behnamousat <behnamousat@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 30c39e4d-e693-4158-8685-2fbf7607101d
-**`@pytest.mark.harm(...)`** — Groups results by harm category in the terminal summary and reports.
130
-
-**`@pytest.mark.trial(n=3, threshold=0.8)`** — Runs 3 independent trials; passes if ≥ 80% are SAFE. LLM agents are non-deterministic, so a single run may not be representative.
137
+
-**`@pytest.mark.trial(n=3, threshold=0.8)`** — Declares population defaults consumed through `trial_config`. LLM agents are non-deterministic, so a single run may not be representative.
131
138
132
139
!!! tip "Execution-level trials"
133
140
Pass `execute_trials_async` a factory that constructs the complete execution
Copy file name to clipboardExpand all lines: docs/glossary.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -88,7 +88,7 @@ Terms used throughout the RAMPART documentation.
88
88
: An implementation of [`Surface`][rampart.core.injection.Surface]. Represents an injectable data source. See [Surfaces](api/surfaces.md).
89
89
90
90
**Trial**
91
-
: A repeated execution of a test for statistical confidence, configured via`@pytest.mark.trial(n=...)`. See [pytest Markers & Fixtures](usage/pytest-integration.md).
91
+
: A population execution configured by`@pytest.mark.trial(n=...)` and consumed through `trial_config`. See [pytest Markers & Fixtures](usage/pytest-integration.md).
92
92
93
93
**Turn**
94
94
: One prompt-response exchange. Immutable. See [`Turn`][rampart.core.types.Turn].
Copy file name to clipboardExpand all lines: docs/usage/authoring-tests.md
+6-4Lines changed: 6 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -299,7 +299,7 @@ evaluator = ~ResponseContains("I cannot help with that")
299
299
`&` and `|` record every operand they ran that came back `UNDETERMINED`, one distinct reason per entry, in `undetermined_operands` on [`EvalResult`][rampart.core.types.EvalResult], and `~` carries its inner result's entries through. Recording does not move the `EvalOutcome` the operands settled. Where the run resolves `SAFE`, the result remains `SAFE`, but its summary names the parts of the evaluation that were undetermined. Only an operand that actually ran can be recorded, so put the evaluator that depends on adapter observability on the left of `&`, where the `NOT_DETECTED` short-circuit cannot skip it. Under `RESPONSE_ONLY`, `ToolCalled("x") & ResponseContains("absent")` records the tool call gap; the same pair written the other way round reaches the same verdict with nothing recorded. `|` skips its right operand once the left detects, so it has the same limit and the opposite pull from the tip above: the cheap evaluator on the left is faster, the observability-dependent one on the left is better recorded.
300
300
301
301
!!! warning "A recorded gap does not change the verdict"
302
-
`SAFE` is the only status that passes, and a run that reaches it is graded a plain pass: `bool(result)` is `True`, the result line reads `PASS`, a trial group counts it toward the pass rate, and pytest exits zero. On such a run the summary and `undetermined_operands` are the only places the gap shows; any other status fails the test on its own account, not because of the gap. To fail a passing run that carries one, read the operands yourself: see [Observability Gaps on a Passing Run](results-and-reporting.md#observability-gaps-on-a-passing-run). XPIA has one separate backstop that does move the verdict, described in [Observability Adjustment](../attacks/xpia.md#observability-adjustment).
302
+
`SAFE` is the only status that passes, and a run that reaches it is graded a plain pass: `bool(result)` is `True`, the result line reads `PASS`, an execution population counts it toward the pass rate, and pytest exits zero. On such a run the summary and `undetermined_operands` are the only places the gap shows; any other status fails the test on its own account, not because of the gap. To fail a passing run that carries one, read the operands yourself: see [Observability Gaps on a Passing Run](results-and-reporting.md#observability-gaps-on-a-passing-run). XPIA has one separate backstop that does move the verdict, described in [Observability Adjustment](../attacks/xpia.md#observability-adjustment).
303
303
304
304
---
305
305
@@ -397,18 +397,20 @@ def adapter():
397
397
398
398
### Class-Based Test Organization
399
399
400
-
Group related tests in a class:
400
+
Group related tests in a class. Use `trial_config` to resolve each declaration against CLI overrides:
Copy file name to clipboardExpand all lines: docs/usage/ci-integration.md
+12-12Lines changed: 12 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,7 +25,7 @@ pip install pytest-xdist
25
25
pytest tests/ -n auto
26
26
```
27
27
28
-
RAMPART aggregates results across worker processes and emits a single unified report under **any**`--dist` mode. The default `--dist=load` spreads `@trial` clones across all workers and is usually fastest. Add `--dist=loadgroup` only when a trial group needs to stay on one worker (e.g. clones share a session fixture or per-group worker state). See [Choosing `loadgroup` vs `load`](xdist.md#choosing-loadgroup-vs-load) for details and security considerations.
28
+
RAMPART aggregates results across worker processes and emits a single unified report under **any**`--dist` mode. Trial markers do not affect xdist scheduling because they do not clone tests.
29
29
30
30
---
31
31
@@ -34,20 +34,20 @@ RAMPART aggregates results across worker processes and emits a single unified re
34
34
Use `@pytest.mark.trial(n=, threshold=)` for tests where a single run is not conclusive:
35
35
36
36
```python
37
+
from rampart import Attacks, execute_trials_async
38
+
37
39
@pytest.mark.trial(n=10, threshold=0.8)
38
-
asyncdeftest_injection_resistance(adapter):
39
-
result =await Attacks.xpia(...).execute_async(adapter=adapter)
Copy file name to clipboardExpand all lines: docs/usage/configuration.md
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,14 +4,17 @@ RAMPART's configurable components: [`LLMConfig`][rampart.core.llm.LLMConfig] for
4
4
5
5
---
6
6
7
-
## Parallel-execution tuning
7
+
## Pytest execution options
8
8
9
-
RAMPART exposes one pytest option for parallel-execution tuning. Other components (LLM endpoints, agent configuration) typically have their own configuration conventions.
9
+
RAMPART exposes pytest options for trial depth and parallel-execution tuning. Other components (LLM endpoints, agent configuration) typically have their own configuration conventions.
10
10
11
11
| Option | Default | Description |
12
12
|--------|---------|-------------|
13
+
|`--rampart-trials N`| marker `n`| Override `trial_config.n` for tests marked `@pytest.mark.trial`. The marker's `threshold` is unchanged. |
13
14
|`--rampart-xdist-max-bytes` (CLI) / `rampart_xdist_max_bytes` (ini) |`16777216` (16 MiB) | Maximum size of each serialized Result when running under [`pytest-xdist`](xdist.md). Oversized Results are replaced by truncation markers and recorded as incomplete in `TestRunReport.metadata`. |
14
15
16
+
For example, `pytest --rampart-trials=50 -m trial` supplies `n=50` to each selected test's `trial_config` fixture while retaining its declared correctness threshold. Invalid or non-positive overrides are rejected during command-line parsing.
0 commit comments