Skip to content
 
 

Repository files navigation

Floyd

crates.io: floyd crates.io: cargo-floyd license: MIT OR Apache-2.0

Floyd is an open-source MC/DC (Modified Condition/Decision Coverage) engine for Rust, focused on automotive safety-critical code (ISO 26262, ASIL-D, TCL3).

$ cargo floyd test
floyd  runtime MC/DC analysis of Cargo.toml

Tests discovered: 4
Observations:     4
Conditions:       channel_a_ok, channel_b_ok, master_enable

Per-condition MC/DC status:
  ✓ master_enable: EXERCISED  via (channel_a_ok=T, channel_b_ok=T, master_enable=F) -> F   vs   (channel_a_ok=T, channel_b_ok=T, master_enable=T) -> T
  ✓ channel_a_ok:  EXERCISED  via (channel_a_ok=F, channel_b_ok=T, master_enable=T) -> F   vs   (channel_a_ok=T, channel_b_ok=T, master_enable=T) -> T
  ✓ channel_b_ok:  EXERCISED  via (channel_a_ok=T, channel_b_ok=F, master_enable=T) -> F   vs   (channel_a_ok=T, channel_b_ok=T, master_enable=T) -> T

MC/DC coverage: 3 of 3 conditions exercised (100%)

Install

# Nightly toolchain + LLVM coverage tools (one-time).
rustup install nightly
rustup component add llvm-tools-preview --toolchain nightly

# Floyd itself.
cargo install cargo-floyd

Then, inside any cargo project with #[test] functions:

cargo floyd test

Floyd builds the project's tests with -Cinstrument-coverage -Zcoverage-options=branch,condition, runs each test in isolation, ingests its profraw via llvm-profdata + llvm-cov, and reports which decisions the test suite exercises under masking MC/DC.

What works (and what doesn't)

Floyd recovers logical decisions from rustc MIR and analyses them against the masking MC/DC variant (the modern qualified default). The engine is correct on what it claims to recover — and when it doesn't recognise a decision, it returns no decision rather than wrong output. The corpus/ directory pins the ground-truth analyses Floyd's output is checked against (see "Qualification stance" below).

What works

Shape Example
Boolean expressions over named bool values a && b, a || b, !a, arbitrary nesting
Inline comparisons in a decision if speed > 50 && brake { ... }, code == 1, value >= 0 && value <= 100 (Eq, Ne, Lt, Le, Gt, Ge)
if let with a binding if let Some(x) = opt { x && b } else { false }
? operator (skip-through to the success path) let x = opt?; x && b
Literal match on integer scrutinees match n { 0 => ..., 1 => ..., _ => ... }
Bool match match b { true => ..., false => ... }
Enum match without binding match state { State::Idle => ..., State::Running => ... } — condition names use the rustc discriminant index (e.g. state == State::variant_0); use if let with a binding to get source variant names instead
Boolean derived from a let-bound expression let fast = speed > 50; fast && brake
Closures capturing outer booleans let f = |x: bool| x && b; f(a) — works for by-value (move ||) and by-reference captures; FnMut closures with internal mutation are out of MVP scope
JSON output cargo floyd test --format=json
JUnit XML output cargo floyd test --format=junit — one <testcase> per condition (pass = exercised, failure = unexercised); rendered natively by Jenkins, GitLab CI, GitHub Actions, Bazel/Buck2, etc.

What doesn't (yet)

The current v0.1.0 line targets pure boolean-variable decisions. Several shapes that appear regularly in real safety code are declined; the v0.2.0 line (engine-correctness milestone — see ADR-0004) closes the biggest gaps. The current declines:

Shape Workaround / status
Functions composing multiple decisions into a non-bool output, e.g. `fn dual(a, b, c) -> (bool, bool) { (a && b, a
Match guards (match n { 0 if c => ... }) Not in MVP scope.
Pattern destructuring beyond a single bound value (if let Some((a, b)) = ...) Not in MVP scope.
async desugaring, macro-expansion provenance Not in MVP scope.
FnMut closures with internal mutation The Fn / move-closure capture forms recover; closures that mutate captured state internally (b = b && x) produce a different MIR shape that's not yet handled.

Most bool-returning safety-critical Rust functions recover correctly. The remaining gaps in the "what doesn't" table are either explicit Phase 2 deliverables or items that sit in the commercial tier per ADR-0004 — the OSS engine's surface is the v0.2.x line as listed in "what works" above.

Using Floyd in CI

Floyd's --format=junit emits JUnit XML, which every CI in regulated industries renders natively. The snippets below are copy-paste starting points; adapt to your project's existing toolchain conventions. They are documentation, not packaged integrations — anyone using them is doing the integration themselves.

GitHub Actions

name: MC/DC coverage

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  floyd:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@nightly
        with:
          components: llvm-tools-preview
      - run: cargo install cargo-floyd
      - name: Run Floyd
        run: cargo floyd test --format=junit > floyd-mcdc.xml
      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: floyd-mcdc-report
          path: floyd-mcdc.xml

GitLab CI

floyd-mcdc:
  image: rustlang/rust:nightly
  before_script:
    - rustup component add llvm-tools-preview
    - cargo install cargo-floyd
  script:
    - cargo floyd test --format=junit > floyd-mcdc.xml
  artifacts:
    when: always
    reports:
      junit: floyd-mcdc.xml

GitLab's native reports: junit: field surfaces the per-condition results directly in the merge-request UI; unexercised conditions appear alongside failing tests.

Jenkins and Buildkite

Both ingest JUnit XML out of the box. In a Jenkins declarative pipeline, run cargo floyd test --format=junit > floyd-mcdc.xml in a stage and add junit 'floyd-mcdc.xml' in post { always { ... } }. In Buildkite, run the same command and use the junit-annotate plugin (or call buildkite-agent artifact upload floyd-mcdc.xml).

Failing the build on unexercised conditions

Floyd's exit code today reports the engine's own success, not whether every condition was exercised. To fail a CI run on incomplete MC/DC coverage, post-process the JUnit XML — any <failure> element with type="UnexercisedCondition" indicates a missing pair. The CI-native test-result steps (reports: junit: on GitLab, junit '...' on Jenkins, JUnit-aware actions on GitHub) all surface these as failing tests in their respective UIs.

Why MC/DC, why this engine

DO-178C, ISO 26262, and IEC 61508 require MC/DC analysis at the highest safety levels. Until recently rustc shipped partial MC/DC instrumentation behind -Zcoverage-options=mcdc, but that implementation was removed in August 2025 (PR #144999). No cargo-native open-source MC/DC tool exists at the time of writing. Floyd fills that gap.

The closest neighbouring tool is AdaCore's gnatcov, which is qualified for DO-178C Level A and ISO 26262 ASIL-D — but only on Ada and SPARK, and via the AdaCore toolchain. Floyd takes the qualification-by-validation route on rustc's existing instrumentation, with the differential corpus in this repo as the load-bearing evidence artifact.

Qualification stance

Floyd's qualification target is ISO 26262 TCL3 via the qualification-by-validation path. The corpus/ directory pins every decision shape Floyd claims to handle, alongside the hand-computed MC/DC analysis a qualified reviewer would produce. CI re-runs the corpus on every commit; any drift between engine output and pinned ground truth fails the build. This is the substrate auditors use to trust the engine without reading its source.

See ADR-0001 for the architectural commitment (external engine, no rustc fork, automotive-first scope) and ADR-0002 for the runtime pipeline design.

Layout

.
├── floyd/              the engine (library crate)
├── cargo-floyd/        cargo subcommand driver (binary)
├── corpus/             qualification evidence
│   ├── v0/             synthetic decision patterns
│   └── v1/             safety-critical decision patterns
├── architecture/       workflow + per-tool design graphs
│   ├── workflow.toml     top-level pipeline (stages + typed contracts)
│   ├── decisions/        accepted architecture decision records
│   └── tools/            per-tool sub-graphs
└── tests/end_to_end_001.rs    gated full-pipeline integration test

License

Dual-licensed under either:

at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project shall be dual licensed as above, without any additional terms or conditions.

Contributing

See CONTRIBUTING.md for how to add corpus patterns, file bug reports, and propose engine changes. The short version: corpus patterns are the highest-value drop-in contribution; architectural changes that alter Floyd's internal data model need a design issue first per ADR-0004's OSS/commercial scope boundary.

About

Open-source MC/DC coverage engine for Rust

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages