bbtree 0.1.1

Blessed B-tree.
Documentation
# Named for its README badge.
name: fuzzing

# Fuzzing (doc/08-fuzzing-verification.md), weekly and for every release
# (ci.yml): every target in turn, each for its share of the run, continuing
# from the corpora the last run saved, so that the runs add up. A crash fails
# the run and is saved as an artifact.
on:
  schedule:
    # Wednesdays at 10:17 UTC: 5:17 in the morning in Madison under daylight
    # time, 4:17 otherwise. Off the hour, when GitHub delays scheduled runs
    # the most.
    - cron: "17 10 * * 3"
  workflow_dispatch:
    inputs:
      minutes:
        description: Fuzzing time, in minutes, shared among the targets
        type: number
        default: 90
  workflow_call:
    inputs:
      minutes:
        description: Fuzzing time, in minutes, shared among the targets
        type: number
        required: true

permissions:
  contents: read
  # To fetch the corpora that the last run saved.
  actions: read

# Weekly and manual runs that overlap would both start from the same corpora.
# A release's run is in a group of its own, so that it never waits behind
# them; ci.yml cancels it when a newer commit replaces the release's.
concurrency:
  group: fuzz-${{ github.event_name == 'pull_request' && github.ref || 'corpora' }}
  cancel-in-progress: false

env:
  CARGO_TERM_COLOR: always

jobs:
  fuzz:
    name: fuzz
    runs-on: ubuntu-latest
    # The longest run is a release's, 120 minutes; the rest is building and
    # saving.
    timeout-minutes: 180
    env:
      # The cargo-fuzz that install-action fetches is a musl build, which
      # builds the targets for musl unless told otherwise, and AddressSanitizer
      # refuses musl's statically linked libc.
      FUZZ_TRIPLE: x86_64-unknown-linux-gnu
    steps:
      - uses: actions/checkout@v4

      # Keep this exact nightly aligned with flake.nix.
      - name: Install Rust (nightly-2026-10-07)
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: nightly-2026-10-07

      - name: Install cargo-fuzz
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-fuzz

      - name: Fetch the corpora of the last run
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          mkdir -p fuzz/corpus
          # The newest corpora that a run saved, weekly or a release's (those
          # are ci.yml's runs); the first run starts from nothing.
          run_id=$(gh api "repos/$GITHUB_REPOSITORY/actions/artifacts?name=fuzz-corpora&per_page=100" \
            --jq '[.artifacts[] | select(.expired | not)] | max_by(.created_at) | .workflow_run.id // empty')
          if [ -n "$run_id" ]; then
            gh run download "$run_id" --repo "$GITHUB_REPOSITORY" \
              --name fuzz-corpora --dir fuzz/corpus
            echo "Continuing from the corpora of run $run_id"
          fi

      # With AddressSanitizer, cargo-fuzz's default, and the library's debug
      # assertions, its own cross-checks.
      - name: Build the targets
        run: cargo fuzz build --debug-assertions --target "$FUZZ_TRIPLE"

      - name: Fuzz
        env:
          RUN_MINUTES: ${{ inputs.minutes || 90 }}
        run: |
          # Each target's minutes in the weekly run of 90, by how much code a
          # bug there would reach and how much harm it would do
          # (doc/08-fuzzing-verification.md); other runs scale them.
          budget=(
            "map_heap_byte_strings 20"
            "user_code_panics 20"
            "map_integers 12"
            "map_user_inline_keys 12"
            "map_plain_keys 10"
            "set_algebra 10"
            "map_byte_arrays 6"
          )
          failed=()
          echo "| target | seconds | result |" >> "$GITHUB_STEP_SUMMARY"
          echo "|---|---|---|" >> "$GITHUB_STEP_SUMMARY"
          for entry in "${budget[@]}"; do
            read -r target weekly_minutes <<< "$entry"
            seconds=$(( weekly_minutes * 60 * RUN_MINUTES / 90 ))
            echo "::group::$target, $seconds s"
            # A crash ends the target's run, not the others'. An input that
            # runs over a minute is reported as a hang.
            if cargo fuzz run --debug-assertions --target "$FUZZ_TRIPLE" "$target" -- \
                -max_total_time="$seconds" -fork="$(nproc)" -timeout=60; then
              result=clean
            else
              result="**crashed**"
              failed+=("$target")
            fi
            echo "::endgroup::"
            echo "| $target | $seconds | $result |" >> "$GITHUB_STEP_SUMMARY"
          done
          if [ ${#failed[@]} -gt 0 ]; then
            {
              echo
              echo "The crashing inputs are in the \`fuzz-crashes\` artifact. To replay"
              echo "one, minimize it, and keep it once fixed:"
              echo
              echo '```'
              echo "cargo fuzz run --debug-assertions <target> <input>"
              echo "cargo fuzz tmin --debug-assertions <target> <input>"
              echo "cp <minimized input> fuzz/regressions/<target>/<what it found>"
              echo '```'
            } >> "$GITHUB_STEP_SUMMARY"
            echo "::error::crashed: ${failed[*]}"
            exit 1
          fi

      - name: Save the corpora
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: fuzz-corpora
          path: fuzz/corpus
          # Three weeks: the next run, or the one after a skipped week,
          # continues from them.
          retention-days: 21

      - name: Save the crashing inputs
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: fuzz-crashes
          path: fuzz/artifacts
          retention-days: 90