name: folio # One spec, three platforms. Dispatch-only: each job boots a device or a # browser, builds the folio app for that platform, and runs # examples/folio/sanderling/spec.ts against it. # # web and ios are expect-the-bug jobs: folio double-submits a transaction on a # double tap, so the run is supposed to end with exit 2. Exit 0 means the fuzzer # stopped finding a bug that is still there; exit 1 means the harness broke. The # two are worth telling apart, which is why --exit-on-violation exits 2 and not # 1. # # android is a health gate. It convicts in four runs out of five, which is real # evidence but not a gate: the fifth would report a regression it had not found. # The budget is set so the conviction it usually gets is reported as a bonus. on: workflow_dispatch: inputs: platforms: description: which legs to run type: choice options: [all, android, ios, web] default: all seed: description: seed override (0 = each job's calibrated seed) default: "0" duration: description: wall-clock budget per run default: 20m max-steps: description: step budget override (0 = each job's calibrated budget) default: "0" permissions: contents: read jobs: android: timeout-minutes: 90 if: ${{ inputs.platforms == 'all' || inputs.platforms == 'android' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Set up Go uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true - name: Set up the JDKs uses: actions/setup-java@v5 with: distribution: temurin # The metro gradle plugin folio builds with needs a 21 runtime; the # sidecar toolchain pins 17. Both are installed so gradle can pick. java-version: | 17 21 - name: Set up Android SDK uses: android-actions/setup-android@v4 - name: Set up bun uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.13" - name: Cache Gradle uses: actions/cache@v6 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: folio-gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | folio-gradle-${{ runner.os }}- # Without this the emulator falls back to software rendering and every # step costs several seconds. - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - name: Build the folio APK run: ./gradlew :app:androidApp:assembleDebug working-directory: examples/folio - name: Build sanderling run: make sanderling-android - name: Fuzz folio on an emulator uses: reactivecircus/android-emulator-runner@v2 with: api-level: 34 target: google_apis arch: x86_64 emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim disable-animations: true script: .github/scripts/folio-run.sh android env: SEED: ${{ inputs.seed != '0' && inputs.seed || '9' }} MAX_STEPS: ${{ inputs.max-steps != '0' && inputs.max-steps || '200' }} DURATION: ${{ inputs.duration }} - name: Upload the run if: always() uses: actions/upload-artifact@v7 with: name: folio-android path: runs/ retention-days: 14 ios: timeout-minutes: 90 if: ${{ inputs.platforms == 'all' || inputs.platforms == 'ios' }} runs-on: macos-15 steps: - uses: actions/checkout@v7 - name: Set up Go uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true - name: Set up bun uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.13" # idb-companion is not in homebrew-core, only in facebook/homebrew-fb, so # it has to be named by its full tap path. xcodegen and just are core. - name: Install idb-companion, xcodegen and just run: brew install facebook/fb/idb-companion xcodegen just - name: Set up the JDKs uses: actions/setup-java@v5 with: distribution: temurin # The metro gradle plugin folio builds with needs a 21 runtime; the # sidecar toolchain pins 17. Both are installed so gradle can pick. java-version: | 17 21 # The iOS app builds its Kotlin framework through the folio gradle # project, which configures :app:androidApp and so needs an Android SDK # even on this leg. - name: Set up Android SDK uses: android-actions/setup-android@v4 # Both asset tarballs are built by the prepare scripts, and the runner # bundle is an xcodebuild of companion/Sources. Keyed on the scripts and # the versions the Makefile embeds, so a later run reuses them. - name: Cache the companion and runner bundles uses: actions/cache@v6 with: path: | internal/driver/ioscompanion/companionassets/assets internal/driver/ioscompanion/runnerassets/assets key: ios-assets-${{ runner.os }}-${{ hashFiles('internal/driver/ioscompanion/companionassets/prepare.sh', 'companion/prepare.sh', 'companion/project.yml', 'companion/Sources/**') }} - name: Build sanderling run: make sanderling-ios - name: Boot a simulator run: | xcrun simctl boot "$IOS_DEVICE" || true xcrun simctl bootstatus "$IOS_DEVICE" -b env: IOS_DEVICE: iPhone 16 Pro - name: Build and install folio run: just ios working-directory: examples/folio env: IOS_DEVICE: iPhone 16 Pro # `just ios` leaves the app running, and the run's first act is to clear # its state. Stopping it here means the run always opens the same way. - name: Stop the app before the run run: xcrun simctl terminate booted app.folio || true - name: Fuzz folio on the simulator run: .github/scripts/folio-run.sh ios env: SEED: ${{ inputs.seed != '0' && inputs.seed || '7' }} MAX_STEPS: ${{ inputs.max-steps != '0' && inputs.max-steps || '240' }} DURATION: ${{ inputs.duration }} IOS_DEVICE: iPhone 16 Pro - name: Upload the run if: always() uses: actions/upload-artifact@v7 with: name: folio-ios path: runs/ retention-days: 14 web: timeout-minutes: 60 if: ${{ inputs.platforms == 'all' || inputs.platforms == 'web' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Set up Go uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true - name: Set up the JDKs uses: actions/setup-java@v5 with: distribution: temurin # The metro gradle plugin folio builds with needs a 21 runtime; the # sidecar toolchain pins 17. Both are installed so gradle can pick. java-version: | 17 21 - name: Set up bun uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.13" - name: Cache Gradle uses: actions/cache@v6 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: folio-gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | folio-gradle-${{ runner.os }}- - name: Set up Chrome uses: browser-actions/setup-chrome@v2 with: chrome-version: stable - name: Allow Chrome under unprivileged user namespaces run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Verify headless Chrome starts run: | chrome --version chrome --headless --no-sandbox --disable-gpu --disable-dev-shm-usage \ --dump-dom 'data:text/html,ok' - name: Build the folio wasmJs app run: ./gradlew :app:webApp:wasmJsBrowserDevelopmentExecutableDistribution working-directory: examples/folio - name: Build sanderling run: make sanderling-web - name: Fuzz folio in the browser run: .github/scripts/folio-run.sh web env: SEED: ${{ inputs.seed != '0' && inputs.seed || '3' }} MAX_STEPS: ${{ inputs.max-steps != '0' && inputs.max-steps || '240' }} DURATION: ${{ inputs.duration }} - name: Upload the run if: always() uses: actions/upload-artifact@v7 with: name: folio-web path: runs/ retention-days: 14