waterkit 0.2.0

Kit utilities and helpers
name: Test

on:
  workflow_call:

env:
  CARGO_INCREMENTAL: "0"
  CARGO_PROFILE_DEV_DEBUG: "0"
  CARGO_PROFILE_TEST_DEBUG: "0"
  CARGO_TERM_COLOR: always

jobs:
  test:
    name: ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    # The slowest job takes about 9 minutes; a hung step must fail the
    # run instead of holding a runner until the 6-hour default.
    timeout-minutes: 30
    strategy:
      # Each OS runs a different set of platform backends, so one OS failing
      # says nothing about the others; cancelling them would hide their
      # failures until the first one is fixed.
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    steps:
      - uses: actions/checkout@v4
      # `shaderloom` compiles WGSL to DXIL on Windows and shells out to `dxc`,
      # which the runner image does not carry. WaterUI's Windows job installs it
      # the same way.
      - name: Install DirectX Shader Compiler
        if: runner.os == 'Windows'
        uses: tracel-ai/github-actions/install-dxc@295f2fdbae5271f4f8ad56d634e4ff6a95daafc8
      - name: Install Linux dependencies
        if: runner.os == 'Linux'
        uses: ./.github/actions/setup-linux-deps
      - name: Start an X server for the clipboard tests
        if: runner.os == 'Linux'
        uses: ./.github/actions/setup-x11-display
      - name: Start a Wayland compositor without data-control
        id: weston
        if: runner.os == 'Linux'
        uses: ./.github/actions/setup-wayland-compositor
      - name: Start a Wayland compositor with data-control
        id: sway
        if: runner.os == 'Linux'
        uses: ./.github/actions/setup-wayland-data-control
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: test-${{ matrix.os }}
      - uses: taiki-e/install-action@nextest
      - run: cargo nextest list --workspace --all-targets
      # `--no-fail-fast`, and running the doctests even when a unit test failed:
      # report every failing test and doctest of the run, not only the first.
      - id: unit_tests
        run: cargo nextest run --workspace --all-features --no-fail-fast
      - if: ${{ success() || steps.unit_tests.outcome == 'failure' }}
        run: cargo test --doc --workspace --all-features --no-fail-fast
      # CLIPBOARD and PRIMARY in a Wayland session without data-control must
      # fail even with the X server above reachable. Same build flags as the
      # unit tests, so nothing is rebuilt.
      - name: Check the selections never fall through from Wayland to X11
        if: ${{ !cancelled() && steps.weston.outcome == 'success' }}
        env:
          WAYLAND_DISPLAY: ${{ steps.weston.outputs.wayland-display }}
        run: >
          cargo nextest run --workspace --all-features --run-ignored only
          -E 'package(waterkit-clipboard) & test(=wayland_without_data_control_never_uses_x11)'
      # The unit-test run above covers the selections on X11; this runs the
      # same round trips through the compositor's data-control protocol, with
      # the X server still reachable.
      - name: Run the selection round trips on Wayland
        if: ${{ !cancelled() && steps.sway.outcome == 'success' }}
        env:
          WAYLAND_DISPLAY: ${{ steps.sway.outputs.wayland-display }}
        run: >
          cargo nextest run --workspace --all-features
          -E 'package(waterkit-clipboard) & (test(=clipboard_round_trip) | test(=primary_selection_round_trip))'
      - name: Run macOS sensor integration harness
        if: runner.os == 'macOS'
        run: cargo run -p waterkit-test -- macos tests/macos/sensor
      - name: Run macOS location integration harness
        if: runner.os == 'macOS'
        run: cargo run -p waterkit-test -- macos tests/macos/location
      - name: Run macOS passkey integration harness
        if: runner.os == 'macOS'
        run: cargo run -p waterkit-test -- macos tests/macos/passkey
      - name: Run macOS menu integration harness
        if: runner.os == 'macOS'
        run: cargo run -p waterkit-test -- macos tests/macos/menu

  android-device-e2e:
    name: Android device E2E
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: aarch64-linux-android,x86_64-linux-android
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '17'
      # The action's default `packages` list starts with `tools`, the legacy
      # SDK package Google has removed from the repository, so sdkmanager now
      # exits 1 with "Failed to find package 'tools'". The runner image already
      # carries the platforms, build-tools and emulator this job needs; the
      # only package the harness invokes by name is adb.
      - uses: android-actions/setup-android@v3
        with:
          packages: platform-tools
      - uses: nttld/setup-ndk@v1
        id: setup-ndk
        with:
          ndk-version: r27c
          add-to-path: true
      - name: Set Android NDK environment
        run: echo "ANDROID_NDK_HOME=${{ steps.setup-ndk.outputs.ndk-path }}" >> $GITHUB_ENV
      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: android-device-e2e
      - name: Install cargo-ndk
        run: cargo install cargo-ndk --locked
      # GitHub's Linux runners expose /dev/kvm only to root until a udev rule
      # opens it; without this the emulator silently falls back to software
      # emulation (7-minute boot, `am start -W` times out) and the harness
      # never gets to run. This is the documented prerequisite for hardware
      # acceleration on hosted runners.
      - name: Enable KVM for the emulator
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm
      # `.` is the workspace root crate `waterkit`, which `get_crate_feature`
      # maps to `full`: every harness case runs on the one emulator boot.
      - name: Run Android full harness on emulator
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 35
          arch: x86_64
          script: cargo run -p waterkit-test -- android .

  ios-device-e2e:
    name: iOS device E2E
    # StoreKit Test cannot load a configuration outside an Xcode debug
    # session on the iOS 26.3-26.5 simulator runtimes (FB22237318), the
    # newest on macos-latest; the xcode-27 image carries the iOS 27 runtime.
    runs-on: xcode-27
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: aarch64-apple-ios-sim
      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: ios-device-e2e
      # Xcode 27 ships the Metal toolchain as a separate component; the
      # harness's Rust crates compile Metal shaders.
      - name: Install the Metal toolchain
        run: xcodebuild -downloadComponent MetalToolchain
      - name: Boot an iPhone simulator on the newest iOS runtime
        run: |
          read -r SIMULATOR_ID SIMULATOR_STATE SIMULATOR_RUNTIME < <(xcrun simctl list devices available -j | ruby -rjson -e '
            runtimes = JSON.parse($stdin.read)["devices"].select { |runtime, _| runtime.include?(".SimRuntime.iOS-") }
            runtime, devices = runtimes.max_by { |runtime, _| Gem::Version.new(runtime[/iOS-(.*)\z/, 1].tr("-", ".")) }
            abort("no iOS simulator runtime") unless runtime
            iphones = devices.select { |device| device["isAvailable"] && device["name"].include?("iPhone") }
            device = iphones.find { |candidate| candidate["state"] == "Shutdown" } || iphones.first
            abort("no available iPhone simulator on #{runtime}") unless device
            puts "#{device["udid"]} #{device["state"]} #{runtime}"')
          echo "Using $SIMULATOR_ID on $SIMULATOR_RUNTIME"
          if [ "$SIMULATOR_STATE" != "Booted" ]; then
            xcrun simctl boot "$SIMULATOR_ID"
          fi
          xcrun simctl bootstatus "$SIMULATOR_ID" -b
      - name: Run iOS full harness on simulator
        run: cargo run -p waterkit-test -- ios .