{"id":48427912,"url":"https://github.com/teilomillet/ordeal","last_synced_at":"2026-04-06T09:35:26.004Z","repository":{"id":348047477,"uuid":"1196180248","full_name":"teilomillet/ordeal","owner":"teilomillet","description":"Automated chaos testing for Python — fault injection, property assertions, and stateful exploration","archived":false,"fork":false,"pushed_at":"2026-03-30T16:25:47.000Z","size":236,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-30T16:35:06.192Z","etag":null,"topics":["chaos-testing","fault-injection","fuzzing","hypothesis","property-testing","python","testing"],"latest_commit_sha":null,"homepage":"https://byordeal.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teilomillet.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-30T12:57:45.000Z","updated_at":"2026-03-30T16:25:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/teilomillet/ordeal","commit_stats":null,"previous_names":["teilomillet/ordeal"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/teilomillet/ordeal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teilomillet%2Fordeal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teilomillet%2Fordeal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teilomillet%2Fordeal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teilomillet%2Fordeal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teilomillet","download_url":"https://codeload.github.com/teilomillet/ordeal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teilomillet%2Fordeal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31466639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-06T08:36:52.050Z","status":"ssl_error","status_checked_at":"2026-04-06T08:36:51.267Z","response_time":112,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chaos-testing","fault-injection","fuzzing","hypothesis","property-testing","python","testing"],"created_at":"2026-04-06T09:35:24.340Z","updated_at":"2026-04-06T09:35:25.997Z","avatar_url":"https://github.com/teilomillet.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ordeal\n\n[![CI](https://github.com/teilomillet/ordeal/actions/workflows/ci.yml/badge.svg)](https://github.com/teilomillet/ordeal/actions/workflows/ci.yml)\n[![Docs](https://github.com/teilomillet/ordeal/actions/workflows/docs.yml/badge.svg)](https://docs.byordeal.com/)\n[![PyPI](https://img.shields.io/pypi/v/ordeal)](https://pypi.org/project/ordeal/)\n[![Python 3.10+](https://img.shields.io/pypi/pyversions/ordeal)](https://pypi.org/project/ordeal/)\n[![License](https://img.shields.io/github/license/teilomillet/ordeal)](LICENSE)\n\n**Your tests pass. Your code still breaks.**\n\nOrdeal finds what you missed — edge cases, untested code paths, bugs that only show up in production. No test code to write. Just point and run.\n\nOpen a terminal and paste this ([uvx](https://docs.astral.sh/uv/guides/tools/) runs Python tools without installing them):\n\n```bash\nuvx ordeal mine ordeal.demo\n```\n\n```\nmine(score): 500 examples\n  ALWAYS  output in [0, 1] (500/500)         ← always returns a value between 0 and 1\n  ALWAYS  monotonically non-decreasing        ← bigger input = bigger output, always\n\nmine(normalize): 500 examples\n     97%  idempotent (29/30)                  ← normalizing twice should give the same result\n                                                 ...but ordeal found a case where it doesn't\n```\n\nNow point it at your code. If you have `myapp/scoring.py`, the module path is `myapp.scoring`:\n\n```bash\nuvx ordeal mine myapp.scoring       # what do my functions actually do?\nuvx ordeal audit myapp.scoring      # what are my tests missing?\n```\n\nOr let your AI assistant do it — open Claude Code, Cursor, or any coding assistant and paste:\n\n\u003e \"Run `uvx ordeal mine` and `uvx ordeal audit` on my main modules. Explain what it finds and fix the issues.\"\n\nordeal ships with an [AGENTS.md](https://github.com/teilomillet/ordeal/blob/main/AGENTS.md) — your AI reads it automatically and knows how to use every command.\n\n```\npip install ordeal                        # or: uv tool install ordeal\n```\n\n## 30-second example\n\n```python\nfrom ordeal import ChaosTest, rule, invariant, always\nfrom ordeal.faults import timing, numerical\n\nclass MyServiceChaos(ChaosTest):\n    faults = [\n        timing.timeout(\"myapp.api.call\"),         # API times out\n        numerical.nan_injection(\"myapp.predict\"),  # model returns NaN\n    ]\n\n    @rule()\n    def call_service(self):\n        result = self.service.process(\"input\")\n        always(result is not None, \"process never returns None\")\n\n    @invariant()\n    def no_corruption(self):\n        for item in self.service.results:\n            always(not math.isnan(item), \"no NaN in output\")\n\nTestMyServiceChaos = MyServiceChaos.TestCase\n```\n\n```bash\npytest --chaos                    # explore fault interleavings\npytest --chaos --chaos-seed 42    # reproduce exactly\nordeal explore                    # coverage-guided, reads ordeal.toml\n```\n\nYou declare what can go wrong (faults), what your system does (rules), and what must stay true (assertions). Ordeal explores the combinations.\n\n## Why ordeal\n\nTesting catches bugs you can imagine. The dangerous bugs are the ones you can't — a timeout *during* a retry, a NaN *inside* a recovery path, a permission error *after* the cache warmed up. These come from combinations, and the space of combinations is too large to explore by hand.\n\nOrdeal automates this. It brings ideas from the most rigorous engineering cultures in the world to Python:\n\n| What | Idea | From |\n|---|---|---|\n| Stateful chaos testing with nemesis | An adversary toggles faults while Hypothesis explores interleavings | [Jepsen](https://jepsen.io) + [Hypothesis](https://hypothesis.works) |\n| Coverage-guided exploration | Save checkpoints at new code paths, branch from productive states | [Antithesis](https://antithesis.com) |\n| Property assertions | `always`, `sometimes`, `reachable`, `unreachable` — accumulate evidence across runs | [Antithesis](https://antithesis.com/docs/properties_assertions/) |\n| Inline fault injection | `buggify()` — no-op in production, probabilistic fault in testing | [FoundationDB](https://apple.github.io/foundationdb/testing.html) |\n| Boundary-biased generation | Test at 0, -1, empty, max-length — where bugs actually cluster | [Jane Street QuickCheck](https://blog.janestreet.com/quickcheck-for-core/) |\n| Mutation testing | Flip `+` to `-`, `\u003c` to `\u003c=` — verify your tests actually catch real bugs | [Meta ACH](https://engineering.fb.com) |\n| Differential testing | Compare two implementations on random inputs — catches regressions | Equivalence testing |\n| Property mining | Discover invariants from execution traces — type, bounds, monotonicity | Specification mining |\n| Metamorphic testing | Check output *relationships* across transformed inputs | [Metamorphic relations](https://en.wikipedia.org/wiki/Metamorphic_testing) |\n\n**Read the full [philosophy](https://docs.byordeal.com/philosophy) to understand why this matters.**\n\n## The ordeal standard\n\nWhen a project uses ordeal and passes, it means something:\n\n- An explorer ran thousands of operation sequences with coverage guidance\n- Faults were injected in combinations no human would write\n- Property assertions held across all runs\n- Mutations were caught\n\nThat's not \"the tests pass.\" That's evidence — much stronger than green unit tests alone — that the code handles adversity.\n\n## Install\n\n```bash\n# From PyPI\npip install ordeal\n\n# With extras\npip install ordeal[atheris]    # coverage-guided fuzzing via Atheris\npip install ordeal[all]        # everything\n\n# As a CLI tool\nuv tool install ordeal         # global install\nuvx ordeal explore             # ephemeral, no install\n\n# Development\ngit clone https://github.com/teilomillet/ordeal\ncd ordeal \u0026\u0026 uv sync --locked --extra dev\nuv run pytest                  # run the full test suite\n```\n\nWhen dependency metadata changes, run `uv lock` and commit `uv.lock` in the\nsame change. Normal development should use `uv sync --locked` so lock drift\nfails early instead of rewriting the lockfile locally.\n\n## What's in the box\n\n### Stateful chaos testing\n\n`ChaosTest` extends Hypothesis's `RuleBasedStateMachine`. You declare faults and rules — ordeal auto-injects a **nemesis** that toggles faults during exploration. The nemesis is just another Hypothesis rule, so the engine explores fault schedules like it explores any other state transition. Shrinking works automatically.\n\n```python\nfrom ordeal import ChaosTest, rule, invariant\nfrom ordeal.faults import io, numerical, timing\n\nclass StorageChaos(ChaosTest):\n    faults = [\n        io.error_on_call(\"myapp.storage.save\", IOError),\n        timing.intermittent_crash(\"myapp.worker.process\", every_n=3),\n        numerical.nan_injection(\"myapp.scoring.predict\"),\n    ]\n    swarm = True  # random fault subsets per run — better aggregate coverage\n\n    @rule()\n    def write_data(self):\n        self.service.save({\"key\": \"value\"})\n\n    @rule()\n    def read_data(self):\n        result = self.service.load(\"key\")\n        always(result is not None, \"reads never return None after write\")\n```\n\nSwarm mode: each run activates a random subset of faults. Over many runs, this covers more fault combinations than always-all-on. Hypothesis handles the subset selection, so shrinking isolates the exact fault combination that triggers a failure.\n\n### Property assertions\n\nFour property types, plus an optional declaration helper for deferred checks:\n\n```python\nfrom ordeal import always, declare, sometimes, reachable, unreachable\n\nalways(len(results) \u003e 0, \"never empty\")          # must hold every time — fails immediately\nsometimes(cache_hit, \"cache is used\")             # must hold at least once — checked at session end\ndeclare(\"error-recovery-path\", \"reachable\")       # declare deferred expectation up front\nreachable(\"error-recovery-path\")                  # code path must execute at least once\nunreachable(\"silent-data-corruption\")             # code path must never execute — fails immediately\n```\n\n`always` and `unreachable` fail instantly, triggering Hypothesis shrinking. `sometimes` and `reachable` accumulate evidence across the full session. Use `declare()` when you want those deferred properties to fail even if the marker was never observed.\n\n### Inline fault injection (BUGGIFY)\n\nPlace `buggify()` gates in your production code. They return `False` normally. During chaos testing, they probabilistically return `True`:\n\n```python\nfrom ordeal.buggify import buggify, buggify_value\n\ndef process(data):\n    if buggify():                                    # sometimes inject delay\n        time.sleep(random.random() * 5)\n    result = compute(data)\n    return buggify_value(result, float('nan'))        # sometimes corrupt output\n```\n\nSeed-controlled. Thread-local. No-op when inactive. This is [FoundationDB's BUGGIFY](https://apple.github.io/foundationdb/testing.html) for Python — the code under test *is* the test harness.\n\n### Coverage-guided exploration\n\nThe Explorer tracks which code paths each run discovers (AFL-style edge hashing). When a run finds new coverage, it saves a checkpoint. Future runs branch from high-value checkpoints, systematically exploring the state space:\n\n```python\nfrom ordeal.explore import Explorer\n\nexplorer = Explorer(\n    MyServiceChaos,\n    target_modules=[\"myapp\"],\n    checkpoint_strategy=\"energy\",  # favor productive checkpoints\n)\nresult = explorer.run(max_time=60)\nprint(result.summary())\n# Exploration: 5000 runs, 52000 steps, 60.0s\n# Coverage: 287 edges, 43 checkpoints\n# Failures found: 2\n#   Run 342, step 15: ValueError (3 steps after shrinking)\n```\n\nFailures are **shrunk** — delta debugging removes unnecessary steps, then fault simplification removes unnecessary faults. You get the minimal sequence that reproduces the bug.\n\nScale with `workers` — each process gets a unique seed for independent exploration, results are aggregated:\n\n```python\nexplorer = Explorer(MyServiceChaos, target_modules=[\"myapp\"], workers=8)\n```\n\n### Configuration\n\n```toml\n# ordeal.toml — one file, human and machine readable\n[explorer]\ntarget_modules = [\"myapp\"]\nmax_time = 60\nseed = 42\ncheckpoint_strategy = \"energy\"\n\n[[tests]]\nclass = \"tests.test_chaos:MyServiceChaos\"\n\n[report]\nformat = \"both\"\ntraces = true\nverbose = true\n```\n\nSee [`ordeal.toml.example`](ordeal.toml.example) for the full schema with every option documented.\n\n### QuickCheck with boundary bias\n\n`@quickcheck` infers strategies from type hints. It biases toward boundary values — 0, -1, empty list, max length — where implementation bugs cluster:\n\n```python\nfrom ordeal.quickcheck import quickcheck\n\n@quickcheck\ndef test_sort_idempotent(xs: list[int]):\n    assert sorted(sorted(xs)) == sorted(xs)\n\n@quickcheck\ndef test_score_bounded(x: float, y: float):\n    result = score(x, y)\n    assert 0 \u003c= result \u003c= 1\n```\n\n### Composable invariants\n\n```python\nfrom ordeal.invariants import no_nan, no_inf, bounded, finite\n\nvalid_score = finite \u0026 bounded(0, 1)\nvalid_score(model_output)  # raises AssertionError with clear message\n```\n\nInvariants compose with `\u0026`. Works with scalars, sequences, and numpy arrays.\n\n### Simulation primitives\n\nDeterministic Clock and FileSystem — no mocks, no real I/O, instant:\n\n```python\nfrom ordeal.simulate import Clock, FileSystem\n\nclock = Clock()\nfs = FileSystem()\n\nclock.advance(3600)                      # instant — no real waiting\nfs.inject_fault(\"/data.json\", \"corrupt\") # reads return random bytes\n```\n\n### Differential testing\n\nCompare two implementations on the same random inputs — catches regressions and validates refactors:\n\n```python\nfrom ordeal.diff import diff\n\nresult = diff(score_v1, score_v2, rtol=1e-6)\nassert result.equivalent, result.summary()\n# diff(score_v1, score_v2): 100 examples, EQUIVALENT\n```\n\n### Mutation testing\n\nValidates that your chaos tests actually catch bugs. If you flip `+` to `-` in the code and your tests still pass, your tests have a blind spot:\n\n```python\nfrom ordeal.mutations import mutate_function_and_test\n\nresult = mutate_function_and_test(\"myapp.scoring.compute\", my_tests)\nprint(result.summary())\n# Mutation score: 15/18 (83%)\n#   SURVIVED  L42:8 + -\u003e -\n#   SURVIVED  L67:4 negate if-condition\n```\n\n### Fault library\n\n```python\nfrom ordeal.faults import io, numerical, timing, network, concurrency\n\n# I/O faults\nio.error_on_call(\"mod.func\")            # raise IOError\nio.disk_full()                           # writes fail with ENOSPC\nio.corrupt_output(\"mod.func\")           # return random bytes\nio.truncate_output(\"mod.func\", 0.5)     # truncate to half\n\n# Numerical faults\nnumerical.nan_injection(\"mod.func\")      # output becomes NaN\nnumerical.inf_injection(\"mod.func\")      # output becomes Inf\nnumerical.wrong_shape(\"mod.func\", (1,512), (1,256))\n\n# Timing faults\ntiming.timeout(\"mod.func\")              # raise TimeoutError\ntiming.slow(\"mod.func\", delay=2.0)      # add delay\ntiming.intermittent_crash(\"mod.func\", every_n=3)\ntiming.jitter(\"mod.func\", magnitude=0.01)\n\n# Network faults\nnetwork.http_error(\"mod.client.post\", status_code=503)\nnetwork.connection_reset(\"mod.client.post\")\nnetwork.rate_limited(\"mod.client.get\", retry_after=60)\nnetwork.dns_failure(\"mod.client.resolve\")\n\n# Concurrency faults\nconcurrency.contended_call(\"mod.pool.acquire\", contention=0.1)\nconcurrency.thread_boundary(\"mod.cache.get\")\nconcurrency.stale_state(service, \"config\", old_config)\n```\n\n### Integrations\n\n```python\n# Atheris — coverage-guided fuzzing steers buggify() decisions\nfrom ordeal.integrations.atheris_engine import fuzz\nfuzz(my_function, max_time=60)\n\n# API chaos testing (built-in, no extra install)\nfrom ordeal.integrations.openapi import chaos_api_test\nchaos_api_test(\"http://localhost:8080/openapi.json\", faults=[...])\nchaos_api_test(\"http://localhost:8080/openapi.json\", faults=[...], stateful=True)\n```\n\n### Audit — justify adoption with data\n\n```bash\nordeal audit myapp.scoring --test-dir tests/\nordeal audit myapp.scoring --validation-mode deep\n```\n\n```\nmyapp.scoring\n  current:   33 tests |   343 lines | 98% coverage [verified]\n  migrated:  12 tests |   130 lines | 96% coverage [verified]\n  saving:   64% fewer tests | 62% less code | same coverage\n  mined:    compute: output in [0, 1] (500/500, \u003e=99% CI)\n  mutation: 14/18 (78%)\n  suggest:\n    - L42 in compute(): test when x \u003c 0\n    - L67 in normalize(): test that ValueError is raised\n```\n\nEvery number is either `[verified]` (measured and cross-checked) or `FAILED: reason` — the audit never silently returns 0%. When `pytest-cov` is available, ordeal uses its JSON report; otherwise it falls back to an internal tracer. Mined properties include Wilson confidence intervals. `--validation-mode fast` replays mined inputs against mutants for speed; `--validation-mode deep` keeps that replay check and then re-mines each mutant for broader search. When there are coverage gaps, it reads the source and tells you exactly what to test. Use `--show-generated` to inspect the test file, `--save-generated` to keep it. For agent workflows, `ordeal benchmark --perf-contract ... --output-json PATH` writes a stable JSON artifact instead of only text output.\n\n## CLI\n\n```bash\nordeal audit myapp.scoring              # compare existing tests vs ordeal\nordeal explore                          # run from ordeal.toml\nordeal explore -w 8                     # parallel with 8 workers\nordeal explore -c ci.toml -v            # custom config, verbose\nordeal explore --max-time 300 --seed 99 # override settings\nordeal replay trace.json                # reproduce a failure\nordeal replay --shrink trace.json       # minimize a failure trace\nordeal explore --generate-tests tests/test_gen.py  # turn traces into pytest tests\n```\n\n## Find what you need\n\nEvery goal maps to a starting point — a command to run, a module to import, and a page to read. Nothing is hidden.\n\n| I want to... | Start here | In the codebase | Docs |\n|---|---|---|---|\n| Find bugs without writing tests | `ordeal mine mymodule` | `ordeal/auto.py` | [Auto Testing](https://docs.byordeal.com/guides/auto) |\n| Check if my tests are good enough | `ordeal audit mymodule` | `ordeal/mutations.py` | [Mutations](https://docs.byordeal.com/guides/mutations) |\n| Write a chaos test | `from ordeal import ChaosTest` | `ordeal/chaos.py` | [Getting Started](https://docs.byordeal.com/getting-started) |\n| Inject specific failures (timeout, NaN, ...) | `from ordeal.faults import timing` | `ordeal/faults/` directory | [Fault Injection](https://docs.byordeal.com/concepts/fault-injection) |\n| Explore all failure combinations | `ordeal explore` | `ordeal/explore.py` | [Explorer](https://docs.byordeal.com/guides/explorer) |\n| Reproduce and shrink a failure | `ordeal replay trace.json` | `ordeal/trace.py` | [Shrinking](https://docs.byordeal.com/concepts/shrinking) |\n| Add fail-safe gates to production code | `from ordeal.buggify import buggify` | `ordeal/buggify.py` | [Fault Injection](https://docs.byordeal.com/concepts/fault-injection) |\n| Make assertions across all runs | `from ordeal import always, sometimes` | `ordeal/assertions.py` | [Assertions](https://docs.byordeal.com/concepts/property-assertions) |\n| Control time / filesystem in tests | `from ordeal.simulate import Clock` | `ordeal/simulate.py` | [Simulation](https://docs.byordeal.com/guides/simulate) |\n| Compare two implementations | `ordeal mine-pair mod.fn1 mod.fn2` | `ordeal/diff.py` | [Auto Testing](https://docs.byordeal.com/guides/auto) |\n| Compose validation rules | `from ordeal.invariants import no_nan` | `ordeal/invariants.py` | [API Reference](https://docs.byordeal.com/reference/api) |\n| Test API endpoints for faults | `from ordeal.integrations.openapi import chaos_api_test` | `ordeal/integrations/openapi.py` | [Integrations](https://docs.byordeal.com/guides/integrations) |\n| Extend ordeal with a new fault | Follow the pattern in `faults/*.py` | `ordeal/faults/` | [Fault Injection](https://docs.byordeal.com/concepts/fault-injection) |\n| Configure reproducible runs | Create `ordeal.toml` | `ordeal/config.py` | [Configuration](https://docs.byordeal.com/guides/configuration) |\n| Discover all faults, assertions, strategies | `from ordeal import catalog; catalog()` | `ordeal/__init__.py` | [API Reference](https://docs.byordeal.com/reference/api) |\n\n\u003e **New to ordeal?** Start with `ordeal mine ordeal.demo` to see it in action, then read [Getting Started](https://docs.byordeal.com/getting-started).\n\u003e **Have existing tests?** Run `ordeal audit mymodule --test-dir tests/` to see how they compare.\n\u003e **Want the full picture?** Browse the [full documentation](https://docs.byordeal.com/).**\n\n## Architecture — code map\n\nEvery module does one thing. When you want to understand, use, or extend ordeal, this tells you where to look.\n\n```\nordeal/\n├── chaos.py           Your tests extend this — ChaosTest base class, nemesis, swarm mode\n├── explore.py         The exploration engine — coverage tracking, checkpoints, energy scheduling\n├── assertions.py      always / sometimes / reachable / unreachable — the assertion model\n├── buggify.py         Inline fault gates — thread-local, seed-controlled, no-op when inactive\n├── quickcheck.py      @quickcheck — type-driven strategies with boundary bias\n├── simulate.py        Deterministic Clock and FileSystem — no mocks, no real I/O\n├── invariants.py      Composable checks — no_nan \u0026 bounded(0, 1), works with numpy\n├── mutations.py       AST mutation testing — 14 operators, count-and-apply pattern\n├── auto.py            Auto-testing — scan_module, fuzz, mine, diff, chaos_for\n├── trace.py           Trace recording, JSON serialization, replay, delta-debugging shrink\n├── config.py          ordeal.toml loader — strict validation\n├── cli.py             CLI entry point — explore, replay, mine, audit\n├── plugin.py          Pytest plugin — --chaos, --chaos-seed, --buggify-prob\n├── strategies.py      Adversarial Hypothesis strategies for fuzzing\n├── faults/            All fault types, organized by what can go wrong:\n│   ├── io.py              disk_full, corrupt_output, permission_denied\n│   ├── numerical.py       nan_injection, inf_injection, wrong_shape\n│   ├── timing.py          timeout, slow, intermittent_crash, jitter\n│   ├── network.py         http_error, connection_reset, dns_failure\n│   └── concurrency.py     contended_call, thread_boundary, stale_state\n└── integrations/      Optional bridges to specialized tools:\n    ├── openapi.py         Built-in API chaos testing (no extra deps)\n    └── atheris_engine.py  Coverage-guided fuzzing (pip install ordeal[atheris])\n```\n\n\u003e **Want to add a new fault?** Look at any function in `ordeal/faults/` — they all follow the same pattern: take a dotted target path, return a `PatchFault` or `LambdaFault`. Adding a new fault type means adding a new function that follows this pattern.\n\u003e\n\u003e **Want to understand how something works?** Every module is self-contained. Read the module you're interested in — the code matches the documentation.\n\n## License\n\nApache 2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteilomillet%2Fordeal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteilomillet%2Fordeal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteilomillet%2Fordeal/lists"}