whatsapp-rust 0.7.0

Rust client for WhatsApp Web
Documentation
name: CodSpeed

on:
  push:
    branches: [main]
    paths-ignore: ["**.md", "docs/**"]
  pull_request:
    branches: [main]
    paths-ignore: ["**.md", "docs/**"]
  # Allows CodSpeed to trigger backtest performance analysis to generate
  # initial baseline data.
  workflow_dispatch:

permissions:
  contents: read
  id-token: write # for OpenID Connect authentication with CodSpeed

concurrency:
  group: codspeed-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

env:
  CARGO_TERM_COLOR: never
  PROTOC_VERSION: "3.25.3"
  # Freeze glibc malloc's adaptive thresholds: mmap/trim/arena decisions vary
  # with allocation history and read as spurious instruction/memory deltas
  # under the deterministic instruments (codspeed.io/docs -> reducing-variance).
  MALLOC_ARENA_MAX: "1"
  MALLOC_MMAP_THRESHOLD_: "131072"
  MALLOC_TRIM_THRESHOLD_: "131072"
  MALLOC_TOP_PAD_: "131072"

jobs:
  benchmarks:
    name: Run CodSpeed benchmarks (${{ matrix.shard.name }})
    runs-on: ubuntu-24.04
    strategy:
      # One shard failing must not cancel the other: CodSpeed merges shards
      # into a single run, and a cancelled shard leaves it incomplete.
      fail-fast: false
      # Shards split by package within one workflow (same OIDC auth), halving
      # the serial wall time; CodSpeed merges shard results into one run
      # (codspeed.io/docs -> sharded-benchmarks).
      matrix:
        shard:
          - name: core
            packages: -p wacore -p wacore-noise
          - name: proto-signal
            packages: -p wacore-binary -p wacore-libsignal -p wacore-appstate
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false

      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: nightly-2026-06-16

      - name: Install tools (protoc, cargo-codspeed)
        uses: taiki-e/install-action@v2
        with:
          tool: protoc@${{ env.PROTOC_VERSION }},cargo-codspeed@5.0.1

      - name: Cache Rust registry
        uses: Swatinem/rust-cache@v2
        with:
          prefix-key: ${{ runner.os }}-cargo-codspeed-${{ matrix.shard.name }}
          cache-targets: "false"

      # simulation and memory share the same instrumented build, so one
      # build covers both instruments.
      - name: Build the benchmark targets
        env:
          SHARD_PACKAGES: ${{ matrix.shard.packages }}
        run: >
          cargo codspeed build -m simulation -m memory
          $SHARD_PACKAGES

      # Both instruments run serially in a single invocation so each
      # benchmark uploads as one run carrying CPU and memory metrics.
      - name: Run the benchmarks
        uses: CodSpeedHQ/action@v4
        env:
          SHARD_PACKAGES: ${{ matrix.shard.packages }}
        with:
          mode: simulation,memory
          run: cargo codspeed run $SHARD_PACKAGES

  # Integration benches drive the real async client against the bartender mock
  # server, so they need the service container + MOCK_SERVER_URL. Kept as a
  # separate job so the unit-bench job above stays fast and mock-server-free.
  integration-benchmarks:
    name: Run CodSpeed integration benchmarks
    # Fork PRs get no secrets, so they can't pull the private mock-server image.
    # `credentials.password` rejects the empty string while the job template is
    # being prepared, so the job fails before any step runs and a step-level
    # `if:` cannot save it — the guard has to sit here. Same condition as e2e.yml.
    if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
    runs-on: ubuntu-24.04
    # These benches drive the real client (connect/reconnect handshakes) under
    # Valgrind, so they are slow; cap the job so a stuck run can't hang CI
    # indefinitely. A healthy run finishes well under this even on slow runners.
    timeout-minutes: 30
    permissions:
      contents: read
      id-token: write # for OpenID Connect authentication with CodSpeed
    services:
      mock-server:
        # Pinned by digest, not `:latest`: send_and_receive measures a live
        # round-trip and the mock server is the other end of it, so a tag moving
        # under us shifts the instruction counts with no code change and
        # pollutes the CodSpeed baseline. Same freeze rationale as the malloc
        # thresholds above. Generated from .github/bartender-image.txt by
        # scripts/ci/sync-bartender-image.sh.
        image: ghcr.io/whiskeysockets-devtools/bartender@sha256:9f3a49dd9f944414fbda50571bba5f66f2ab6fdc48001e6803d4c09a1042c78e
        credentials:
          username: ${{ github.actor }}
          password: ${{ secrets.BARTENDER_GHCR_TOKEN }}
        ports:
          - 8080:8080
        env:
          CHATSTATE_TTL_SECS: "3"
        options: --log-driver none
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false

      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: nightly-2026-06-16

      - name: Install tools (protoc, cargo-codspeed)
        uses: taiki-e/install-action@v2
        with:
          tool: protoc@${{ env.PROTOC_VERSION }},cargo-codspeed@5.0.1

      - name: Cache Rust registry
        uses: Swatinem/rust-cache@v2
        with:
          prefix-key: ${{ runner.os }}-cargo-codspeed-integration
          cache-targets: "false"

      - name: Wait for mock server
        run: |
          for i in $(seq 1 30); do
            if curl -sk https://localhost:8080/ > /dev/null 2>&1; then
              echo "Mock server is ready"
              exit 0
            fi
            sleep 1
          done
          echo "Mock server failed to become ready"
          exit 1

      # Integration benches run only the simulation (instruction-count)
      # instrument, NOT memory. send_and_receive drives a live async round-trip,
      # whose in-flight pipeline buffers (TLS/websocket/noise/decrypt/dispatch)
      # straddle divan sample boundaries, so its peakMemoryBytes tracked runner
      # scheduling/CPU rather than code (freeCalls ~= 2x allocCalls; cross-CPU
      # runs tripped a false memory regression on every send-path PR). Simulation
      # counts are deterministic under Valgrind regardless of host CPU and stay a
      # meaningful end-to-end signal; the deterministic memory signal lives in the
      # single-threaded unit benches (other job), which keep the memory instrument.
      - name: Build the benchmark targets
        run: cargo codspeed build -m simulation -p bench-integration

      # `-p bench-integration` is required: `tests/bench-integration` is not a
      # workspace default-member, so a bare `cargo codspeed run` discovers no
      # benchmarks for it ("No benchmarks found for the simulation mode").
      - name: Run the benchmarks
        uses: CodSpeedHQ/action@v4
        env:
          MOCK_SERVER_URL: "wss://127.0.0.1:8080/ws/chat"
          RUST_LOG: warn
        with:
          mode: simulation
          run: cargo codspeed run -p bench-integration