ordeal 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. ordeal-0.0.0/.github/ISSUE_TEMPLATE/bug_report.yml +48 -0
  2. ordeal-0.0.0/.github/ISSUE_TEMPLATE/config.yml +1 -0
  3. ordeal-0.0.0/.github/ISSUE_TEMPLATE/feature_request.yml +24 -0
  4. ordeal-0.0.0/.github/PULL_REQUEST_TEMPLATE.md +11 -0
  5. ordeal-0.0.0/.github/dependabot.yml +17 -0
  6. ordeal-0.0.0/.github/workflows/ci.yml +51 -0
  7. ordeal-0.0.0/.github/workflows/release.yml +79 -0
  8. ordeal-0.0.0/.gitignore +211 -0
  9. ordeal-0.0.0/.python-version +1 -0
  10. ordeal-0.0.0/AGENTS.md +46 -0
  11. ordeal-0.0.0/CHANGELOG.md +20 -0
  12. ordeal-0.0.0/CLAUDE.md +108 -0
  13. ordeal-0.0.0/CONTRIBUTING.md +42 -0
  14. ordeal-0.0.0/LICENSE +201 -0
  15. ordeal-0.0.0/PKG-INFO +352 -0
  16. ordeal-0.0.0/README.md +314 -0
  17. ordeal-0.0.0/docs/api-reference.md +130 -0
  18. ordeal-0.0.0/docs/cli.md +54 -0
  19. ordeal-0.0.0/docs/configuration.md +78 -0
  20. ordeal-0.0.0/docs/core-concepts.md +82 -0
  21. ordeal-0.0.0/docs/explorer.md +81 -0
  22. ordeal-0.0.0/docs/getting-started.md +92 -0
  23. ordeal-0.0.0/docs/index.md +25 -0
  24. ordeal-0.0.0/docs/integrations.md +66 -0
  25. ordeal-0.0.0/docs/mutations.md +43 -0
  26. ordeal-0.0.0/docs/simulate.md +69 -0
  27. ordeal-0.0.0/ordeal/__init__.py +96 -0
  28. ordeal-0.0.0/ordeal/assertions.py +161 -0
  29. ordeal-0.0.0/ordeal/buggify.py +81 -0
  30. ordeal-0.0.0/ordeal/chaos.py +111 -0
  31. ordeal-0.0.0/ordeal/cli.py +269 -0
  32. ordeal-0.0.0/ordeal/config.py +205 -0
  33. ordeal-0.0.0/ordeal/explore.py +614 -0
  34. ordeal-0.0.0/ordeal/faults/__init__.py +158 -0
  35. ordeal-0.0.0/ordeal/faults/io.py +177 -0
  36. ordeal-0.0.0/ordeal/faults/numerical.py +138 -0
  37. ordeal-0.0.0/ordeal/faults/timing.py +118 -0
  38. ordeal-0.0.0/ordeal/integrations/__init__.py +5 -0
  39. ordeal-0.0.0/ordeal/integrations/atheris_engine.py +172 -0
  40. ordeal-0.0.0/ordeal/integrations/schemathesis_ext.py +184 -0
  41. ordeal-0.0.0/ordeal/invariants.py +179 -0
  42. ordeal-0.0.0/ordeal/mutations.py +435 -0
  43. ordeal-0.0.0/ordeal/plugin.py +129 -0
  44. ordeal-0.0.0/ordeal/py.typed +0 -0
  45. ordeal-0.0.0/ordeal/quickcheck.py +340 -0
  46. ordeal-0.0.0/ordeal/simulate.py +172 -0
  47. ordeal-0.0.0/ordeal/strategies.py +103 -0
  48. ordeal-0.0.0/ordeal/trace.py +331 -0
  49. ordeal-0.0.0/ordeal.egg-info/PKG-INFO +352 -0
  50. ordeal-0.0.0/ordeal.egg-info/SOURCES.txt +72 -0
  51. ordeal-0.0.0/ordeal.egg-info/dependency_links.txt +1 -0
  52. ordeal-0.0.0/ordeal.egg-info/entry_points.txt +5 -0
  53. ordeal-0.0.0/ordeal.egg-info/requires.txt +17 -0
  54. ordeal-0.0.0/ordeal.egg-info/top_level.txt +1 -0
  55. ordeal-0.0.0/ordeal.toml.example +63 -0
  56. ordeal-0.0.0/pyproject.toml +76 -0
  57. ordeal-0.0.0/setup.cfg +4 -0
  58. ordeal-0.0.0/tests/__init__.py +0 -0
  59. ordeal-0.0.0/tests/_explore_target.py +54 -0
  60. ordeal-0.0.0/tests/_mutation_target.py +19 -0
  61. ordeal-0.0.0/tests/test_assertions.py +159 -0
  62. ordeal-0.0.0/tests/test_battle.py +709 -0
  63. ordeal-0.0.0/tests/test_buggify.py +66 -0
  64. ordeal-0.0.0/tests/test_chaos.py +118 -0
  65. ordeal-0.0.0/tests/test_cli.py +45 -0
  66. ordeal-0.0.0/tests/test_config.py +96 -0
  67. ordeal-0.0.0/tests/test_explore.py +225 -0
  68. ordeal-0.0.0/tests/test_faults.py +262 -0
  69. ordeal-0.0.0/tests/test_invariants.py +154 -0
  70. ordeal-0.0.0/tests/test_quickcheck.py +230 -0
  71. ordeal-0.0.0/tests/test_simulate.py +171 -0
  72. ordeal-0.0.0/tests/test_strategies.py +68 -0
  73. ordeal-0.0.0/tests/test_trace.py +202 -0
  74. ordeal-0.0.0/uv.lock +959 -0
@@ -0,0 +1,48 @@
1
+ name: Bug Report
2
+ description: Report a bug in ordeal
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: description
7
+ attributes:
8
+ label: Description
9
+ description: What happened?
10
+ validations:
11
+ required: true
12
+
13
+ - type: textarea
14
+ id: reproduction
15
+ attributes:
16
+ label: Minimal reproduction
17
+ description: Smallest code snippet or steps that trigger the bug.
18
+ render: python
19
+
20
+ - type: textarea
21
+ id: expected
22
+ attributes:
23
+ label: Expected behavior
24
+ description: What should have happened instead?
25
+
26
+ - type: input
27
+ id: ordeal-version
28
+ attributes:
29
+ label: ordeal version
30
+ placeholder: "0.1.0"
31
+
32
+ - type: input
33
+ id: python-version
34
+ attributes:
35
+ label: Python version
36
+ placeholder: "3.12.0"
37
+
38
+ - type: input
39
+ id: os
40
+ attributes:
41
+ label: Operating system
42
+ placeholder: "Ubuntu 24.04 / macOS 15 / Windows 11"
43
+
44
+ - type: textarea
45
+ id: logs
46
+ attributes:
47
+ label: Relevant log output
48
+ render: text
@@ -0,0 +1 @@
1
+ blank_issues_enabled: true
@@ -0,0 +1,24 @@
1
+ name: Feature Request
2
+ description: Suggest a new feature or improvement
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: Problem or use case
9
+ description: What are you trying to do? Why does ordeal not solve it today?
10
+ validations:
11
+ required: true
12
+
13
+ - type: textarea
14
+ id: solution
15
+ attributes:
16
+ label: Proposed solution
17
+ description: How should ordeal solve this? Include example API if applicable.
18
+ render: python
19
+
20
+ - type: textarea
21
+ id: alternatives
22
+ attributes:
23
+ label: Alternatives considered
24
+ description: What other approaches did you consider?
@@ -0,0 +1,11 @@
1
+ ## Summary
2
+
3
+ <!-- What does this PR do? Why? -->
4
+
5
+ ## Test plan
6
+
7
+ <!-- How was this tested? -->
8
+
9
+ - [ ] Tests pass (`uv run pytest`)
10
+ - [ ] Lint passes (`uv run ruff check .`)
11
+ - [ ] New tests added for new functionality
@@ -0,0 +1,17 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ groups:
8
+ dependencies:
9
+ patterns: ["*"]
10
+
11
+ - package-ecosystem: "github-actions"
12
+ directory: "/"
13
+ schedule:
14
+ interval: "weekly"
15
+ groups:
16
+ actions:
17
+ patterns: ["*"]
@@ -0,0 +1,51 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ lint:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: astral-sh/setup-uv@v4
18
+
19
+ - run: uv python install 3.13
20
+
21
+ - run: uv sync --extra dev
22
+
23
+ - name: Ruff check
24
+ run: uv run ruff check .
25
+
26
+ - name: Ruff format
27
+ run: uv run ruff format --check .
28
+
29
+ test:
30
+ runs-on: ${{ matrix.os }}
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ os: [ubuntu-latest]
35
+ python-version: ["3.12", "3.13"]
36
+ include:
37
+ - os: macos-latest
38
+ python-version: "3.13"
39
+ - os: windows-latest
40
+ python-version: "3.13"
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+
44
+ - uses: astral-sh/setup-uv@v4
45
+
46
+ - run: uv python install ${{ matrix.python-version }}
47
+
48
+ - run: uv sync --extra dev
49
+
50
+ - name: Run tests
51
+ run: uv run pytest --tb=short -q
@@ -0,0 +1,79 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+
8
+ permissions: {}
9
+
10
+ jobs:
11
+ # ── Auto-tag: bump patch on every push to main ──────────────────────
12
+ auto-tag:
13
+ if: github.ref == 'refs/heads/main'
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: write
17
+ outputs:
18
+ new_tag: ${{ steps.bump.outputs.new_tag }}
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0
23
+
24
+ - name: Bump patch version
25
+ id: bump
26
+ run: |
27
+ # If HEAD already carries a version tag, skip (manual tag push).
28
+ if git tag --points-at HEAD | grep -qE '^v[0-9]'; then
29
+ echo "new_tag=" >> "$GITHUB_OUTPUT"
30
+ exit 0
31
+ fi
32
+
33
+ # Find the latest clean semver tag (ignore pre-release suffixes).
34
+ LATEST=$(git tag -l 'v*' --sort=-v:refname \
35
+ | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
36
+
37
+ if [ -z "$LATEST" ]; then
38
+ NEW_TAG="v0.0.1"
39
+ else
40
+ VERSION=${LATEST#v}
41
+ IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
42
+ NEW_TAG="v${MAJOR}.${MINOR}.$((PATCH + 1))"
43
+ fi
44
+
45
+ git tag "$NEW_TAG"
46
+ git push origin "$NEW_TAG"
47
+ echo "new_tag=$NEW_TAG" >> "$GITHUB_OUTPUT"
48
+
49
+ # ── Publish to PyPI ─────────────────────────────────────────────────
50
+ publish:
51
+ needs: [auto-tag]
52
+ # Run when: (a) triggered by a manual tag push, OR
53
+ # (b) auto-tag just created a new tag.
54
+ if: >-
55
+ always() && (
56
+ startsWith(github.ref, 'refs/tags/v') ||
57
+ needs.auto-tag.outputs.new_tag != ''
58
+ )
59
+ runs-on: ubuntu-latest
60
+ environment: pypi
61
+ permissions:
62
+ id-token: write # OIDC for PyPI trusted publisher
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+ with:
66
+ fetch-depth: 0 # setuptools-scm needs full history + tags
67
+
68
+ - uses: actions/setup-python@v5
69
+ with:
70
+ python-version: "3.12"
71
+
72
+ - name: Install build tools
73
+ run: pip install build setuptools-scm
74
+
75
+ - name: Build sdist + wheel
76
+ run: python -m build
77
+
78
+ - name: Publish to PyPI
79
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,211 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
208
+
209
+ # Ordeal
210
+ .ordeal/
211
+ ordeal-report.json
@@ -0,0 +1 @@
1
+ 3.12
ordeal-0.0.0/AGENTS.md ADDED
@@ -0,0 +1,46 @@
1
+ # AGENTS.md
2
+
3
+ Conventions for AI agents working on ordeal.
4
+
5
+ ## Build & test
6
+
7
+ ```bash
8
+ uv sync # install deps
9
+ uv run pytest tests/ -q # run all 205 tests (~7s)
10
+ uv run pytest tests/ --chaos # with property reporting
11
+ uv run ordeal explore -c demo.toml # run explorer (needs PYTHONPATH if tests/ is target)
12
+ ```
13
+
14
+ ## Project structure
15
+
16
+ - `ordeal/` — library source (14 modules + faults/ + integrations/)
17
+ - `tests/` — 13 test files, includes `test_battle.py` (ordeal testing itself)
18
+ - `docs/` — 9 markdown docs, `index.md` is the hub
19
+ - `ordeal.toml.example` — annotated config reference
20
+
21
+ ## Rules
22
+
23
+ - Python 3.12+. Use `match/case`, `type | None` syntax, `from __future__ import annotations`.
24
+ - Every public function has a docstring. Every parameter is typed. No untyped `Any` except at system boundaries (wrapping unknown functions, JSON codecs, optional deps).
25
+ - Tests go in `tests/test_<module>.py`. Battle tests (ordeal testing itself) go in `tests/test_battle.py`.
26
+ - Faults are in `ordeal/faults/{io,numerical,timing}.py`. New fault types go in the matching file or a new one.
27
+ - No emojis in code or docs.
28
+ - Keep docs under 130 lines each. Example-first, minimal prose.
29
+
30
+ ## Architecture decisions
31
+
32
+ - `ChaosTest` extends Hypothesis's `RuleBasedStateMachine`. The nemesis rule is auto-injected.
33
+ - The Explorer is separate from Hypothesis — it drives rules manually with coverage feedback.
34
+ - `buggify()` is zero-cost in production (thread-local `_state.active` check).
35
+ - Assertions use a global `PropertyTracker` — thread-safe, activated by `--chaos` flag or `auto_configure()`.
36
+ - TOML config (`ordeal.toml`) is the interface between humans/agents and the Explorer.
37
+ - Traces are JSON. Replay uses recorded param values, not re-drawing from strategies.
38
+
39
+ ## When adding a new feature
40
+
41
+ 1. Write the module in `ordeal/`.
42
+ 2. Add tests in `tests/test_<module>.py`.
43
+ 3. Add a battle test in `tests/test_battle.py` if the feature has interesting state.
44
+ 4. Export from `ordeal/__init__.py` if it's public API.
45
+ 5. Add a doc in `docs/` (under 130 lines).
46
+ 6. Update `docs/api-reference.md`.
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ Initial release.
6
+
7
+ - **ChaosTest** — stateful chaos testing with auto-injected nemesis, swarm mode
8
+ - **Faults** — io, numerical, timing fault primitives + PatchFault/LambdaFault base
9
+ - **Assertions** — always/sometimes/reachable/unreachable (Antithesis model)
10
+ - **Invariants** — composable named checks (no_nan & bounded(0,1))
11
+ - **Buggify** — FoundationDB-style inline fault injection
12
+ - **QuickCheck** — @quickcheck decorator with boundary-biased generation
13
+ - **Simulate** — Clock and FileSystem for no-mock testing
14
+ - **Mutations** — AST-based mutation testing (arithmetic, comparison, negate, return_none)
15
+ - **Explorer** — coverage-guided exploration with checkpointing, energy scheduling, shrinking
16
+ - **Traces** — JSON recording, replay, delta-debugging shrinking
17
+ - **CLI** — `ordeal explore` and `ordeal replay`
18
+ - **Config** — `ordeal.toml` driven configuration
19
+ - **Plugin** — pytest integration (--chaos, --chaos-seed, @pytest.mark.chaos)
20
+ - **Integrations** — Atheris (coverage-guided fuzzing), Schemathesis (API chaos)
ordeal-0.0.0/CLAUDE.md ADDED
@@ -0,0 +1,108 @@
1
+ # CLAUDE.md
2
+
3
+ ## Project
4
+
5
+ ordeal — Automated chaos testing for Python. Fault injection, property assertions, coverage-guided exploration, and stateful testing in one library.
6
+
7
+ Built on ideas from Antithesis (deterministic exploration), FoundationDB (BUGGIFY inline faults), Jepsen (nemesis interleaving), Hypothesis (stateful property testing), Jane Street QuickCheck (boundary-biased generation), and Meta ACH (mutation validation).
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ uv sync # install dependencies
13
+ uv run pytest # run all tests (~205 tests)
14
+ uv run pytest tests/test_X.py # single module
15
+ uv run pytest -x # stop on first failure
16
+ uv run pytest --chaos # enable chaos mode in tests
17
+ uv run pytest --chaos-seed 42 # reproducible chaos
18
+ uv run ruff check . # lint
19
+ uv run ruff format --check . # check formatting
20
+ uv run ruff format . # auto-format
21
+ uv run ordeal explore # run coverage-guided explorer (reads ordeal.toml)
22
+ uv run ordeal replay <trace> # replay a failure trace
23
+ ```
24
+
25
+ ## Architecture
26
+
27
+ ```
28
+ ordeal/
29
+ ├── __init__.py Public API: ChaosTest, rule, invariant, always, sometimes, buggify, etc.
30
+ ├── chaos.py ChaosTest base class (extends Hypothesis RuleBasedStateMachine)
31
+ ├── explore.py Coverage-guided explorer — AFL-style edge hashing, energy scheduling
32
+ ├── assertions.py always / sometimes / reachable / unreachable (Antithesis model)
33
+ ├── buggify.py Inline fault injection (FoundationDB BUGGIFY model)
34
+ ├── quickcheck.py @quickcheck decorator with boundary-biased type-driven strategies
35
+ ├── simulate.py Deterministic simulation primitives: Clock, FileSystem
36
+ ├── invariants.py Composable invariants with & operator: no_nan & bounded(0, 1)
37
+ ├── mutations.py AST-based mutation testing with 4 operators
38
+ ├── trace.py Trace recording, JSON serialization, replay, delta-debugging shrink
39
+ ├── config.py ordeal.toml loader with strict validation
40
+ ├── cli.py CLI entry point: ordeal explore / ordeal replay
41
+ ├── plugin.py Pytest plugin: --chaos, --chaos-seed, --buggify-prob flags
42
+ ├── strategies.py Adversarial Hypothesis strategies for fuzzing
43
+ ├── faults/
44
+ │ ├── __init__.py Fault / PatchFault / LambdaFault base abstractions
45
+ │ ├── io.py error_on_call, disk_full, permission_denied, corrupt/truncate output
46
+ │ ├── numerical.py nan_injection, inf_injection, wrong_shape, corrupted_floats
47
+ │ └── timing.py timeout, slow, intermittent_crash, jitter
48
+ └── integrations/
49
+ ├── atheris_engine.py Coverage-guided fuzzing bridge (optional: atheris)
50
+ └── schemathesis_ext.py API chaos testing bridge (optional: schemathesis)
51
+ ```
52
+
53
+ ## Key design decisions
54
+
55
+ - **ChaosTest** extends `hypothesis.stateful.RuleBasedStateMachine`. A **nemesis rule** is auto-injected to toggle faults during exploration. Hypothesis explores rule interleavings + fault schedules.
56
+ - **Swarm mode**: Each test run uses a random subset of faults. Better aggregate coverage than all-faults-always-on.
57
+ - **Energy scheduling**: Checkpoints that led to new edge coverage get higher selection probability. Constants: reward=2.0, decay=0.95, min=0.01.
58
+ - **Assertions**: `always`/`unreachable` raise immediately (triggers Hypothesis shrinking). `sometimes`/`reachable` are deferred — checked at session end via PropertyTracker.
59
+ - **buggify()**: No-op when chaos mode is inactive. Thread-local RNG, seed-controlled, zero overhead in production.
60
+ - **PatchFault**: Resolves a dotted path (e.g. `"myapp.api.call"`) and wraps the target function with fault behavior. Activate/deactivate cycle managed by ChaosTest.
61
+ - **Optional deps**: atheris, schemathesis, numpy are behind try/except imports with helpful error messages.
62
+
63
+ ## Conventions
64
+
65
+ - Python >= 3.12. Type hints throughout.
66
+ - `ruff` for lint and format. Line length 99. Rules: E, F, I, W.
67
+ - Tests in `tests/`. Test files prefixed `test_*.py`. Helper modules prefixed `_*.py`.
68
+ - Configuration via `ordeal.toml` — see `ordeal.toml.example` for full schema.
69
+ - Hypothesis stateful API (`rule`, `invariant`, `initialize`, `precondition`, `Bundle`) is re-exported from `ordeal.__init__`.
70
+ - Version derived from git tags via `setuptools-scm`.
71
+
72
+ ## Dependencies
73
+
74
+ - **Required**: hypothesis >= 6.100.0, pytest >= 8.0.0
75
+ - **Optional extras**:
76
+ - `ordeal[atheris]` — coverage-guided fuzzing via Google Atheris
77
+ - `ordeal[api]` — API chaos testing via Schemathesis
78
+ - `ordeal[all]` — everything including numpy
79
+ - **Dev**: ruff, pytest-cov (`pip install ordeal[dev]`)
80
+
81
+ ## Common tasks
82
+
83
+ ### Add a new fault type
84
+
85
+ 1. Add a function in the appropriate `ordeal/faults/*.py` module that returns a `PatchFault` or `LambdaFault`.
86
+ 2. The function takes a `target` dotted path and fault-specific parameters.
87
+ 3. Add tests in `tests/test_faults.py`.
88
+ 4. Document in `docs/api-reference.md`.
89
+
90
+ ### Add a new assertion
91
+
92
+ 1. Add the function in `ordeal/assertions.py`.
93
+ 2. If deferred (like `sometimes`), register with the global `tracker: PropertyTracker`.
94
+ 3. Export from `ordeal/__init__.py` and add to `__all__`.
95
+ 4. Add tests in `tests/test_assertions.py`.
96
+
97
+ ### Add a new invariant
98
+
99
+ 1. Add a function in `ordeal/invariants.py` that returns an `Invariant` instance.
100
+ 2. Invariants compose with `&`. Numpy support is optional — guard with try/except.
101
+ 3. Add tests in `tests/test_invariants.py`.
102
+
103
+ ### Add a new mutation operator
104
+
105
+ 1. Create an AST `NodeTransformer` subclass in `ordeal/mutations.py`.
106
+ 2. Register it in `_APPLICATORS` dict with a string key.
107
+ 3. The applicator yields `Mutant` objects with location and description.
108
+ 4. Add tests in `tests/test_battle.py` or a dedicated test file.
@@ -0,0 +1,42 @@
1
+ # Contributing to ordeal
2
+
3
+ ## Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/teilomillet/ordeal
7
+ cd ordeal
8
+ uv sync --extra dev
9
+ ```
10
+
11
+ ## Development workflow
12
+
13
+ ```bash
14
+ uv run pytest # run tests
15
+ uv run pytest tests/test_X.py # single module
16
+ uv run pytest -x # stop on first failure
17
+ uv run ruff check . # lint
18
+ uv run ruff format . # format
19
+ ```
20
+
21
+ ## Submitting changes
22
+
23
+ 1. Fork the repo and create a branch from `main`.
24
+ 2. Write tests for your changes.
25
+ 3. Ensure `uv run pytest` and `uv run ruff check .` pass.
26
+ 4. Open a pull request.
27
+
28
+ ## Project structure
29
+
30
+ See [CLAUDE.md](CLAUDE.md) for architecture, conventions, and common contribution recipes.
31
+
32
+ ## Adding a new fault type
33
+
34
+ 1. Create a function in `ordeal/faults/` that returns a `PatchFault` or `LambdaFault`.
35
+ 2. Add tests in `tests/test_faults.py`.
36
+ 3. Document in `docs/api-reference.md`.
37
+
38
+ ## Adding a new assertion
39
+
40
+ 1. Add the function in `ordeal/assertions.py`.
41
+ 2. Export from `ordeal/__init__.py` and add to `__all__`.
42
+ 3. Add tests in `tests/test_assertions.py`.