name: "E2E: Rust Linux interactive" on: workflow_dispatch: inputs: ref: description: "Optional full 40-character commit SHA; defaults to dispatch SHA" required: false default: "" lane: description: "Diagnostic lane; canonical dispatches use all" required: false type: choice default: all options: - all - shared - native - capture cell_filter: description: "Optional shared-cell substring for targeted diagnostics" required: false default: "" permissions: contents: read actions: read jobs: source: name: "Resolve exact source" runs-on: ubuntu-latest outputs: sha: ${{ steps.resolve.outputs.sha }} steps: - id: resolve name: Validate source SHA shell: bash env: REQUESTED_SHA: ${{ inputs.ref }} DISPATCH_SHA: ${{ github.sha }} run: | sha="${REQUESTED_SHA:-$DISPATCH_SHA}" if [[ ! "$sha" =~ ^[0-9a-fA-F]{40}$ ]]; then echo "ref must be a full 40-character commit SHA" >&2 exit 2 fi echo "sha=${sha,,}" >> "$GITHUB_OUTPUT" shared: if: inputs.lane == 'all' || inputs.lane == 'shared' name: "Linux / shared Electron + Tauri" needs: source runs-on: ubuntu-latest timeout-minutes: 60 env: CUA_E2E_SOURCE_SHA: ${{ needs.source.outputs.sha }} CUA_E2E_CELL_FILTER: ${{ inputs.cell_filter }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ needs.source.outputs.sha }} - uses: dtolnay/rust-toolchain@stable - name: Install GUI and Rust build dependencies run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ xvfb dbus-x11 at-spi2-core openbox python3-gi gir1.2-gtk-3.0 \ libgtk-3-dev clang pkg-config libdbus-1-dev libpipewire-0.3-dev \ libspa-0.2-dev libei-dev libx11-dev libxi-dev libxtst-dev libxext-dev \ libwebkit2gtk-4.1-dev libssl-dev libxdo-dev \ libayatana-appindicator3-dev librsvg2-dev ffmpeg - name: Run shared Rust behavior matrix env: CUA_E2E_INTERNAL_LANE: shared run: | xvfb-run -a --server-args="-screen 0 1920x1080x24" \ dbus-run-session -- bash -lc \ "openbox >/tmp/cua-openbox.log 2>&1 & sleep 2; scripts/ci/linux/run-rust-e2e.sh" - name: Upload shared results if: always() uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 with: name: rust-linux-shared path: artifacts/cua-driver/linux if-no-files-found: ignore compression-level: 0 retention-days: 14 native: if: inputs.lane == 'all' || inputs.lane == 'native' name: "Linux / GTK3 native harness" needs: source runs-on: ubuntu-latest timeout-minutes: 60 env: CUA_E2E_SOURCE_SHA: ${{ needs.source.outputs.sha }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ needs.source.outputs.sha }} - uses: dtolnay/rust-toolchain@stable - name: Install GUI and Rust build dependencies run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ xvfb dbus-x11 at-spi2-core openbox python3-gi gir1.2-gtk-3.0 \ libgtk-3-dev clang pkg-config libdbus-1-dev libpipewire-0.3-dev \ libspa-0.2-dev libei-dev libx11-dev libxi-dev libxtst-dev libxext-dev \ libwebkit2gtk-4.1-dev libssl-dev libxdo-dev \ libayatana-appindicator3-dev librsvg2-dev ffmpeg - name: Run GTK3 native Rust harness env: CUA_E2E_INTERNAL_LANE: native run: | xvfb-run -a --server-args="-screen 0 1920x1080x24" \ dbus-run-session -- bash -lc \ "openbox >/tmp/cua-openbox.log 2>&1 & sleep 2; scripts/ci/linux/run-rust-e2e.sh" - name: Upload native results if: always() uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 with: name: rust-linux-native path: artifacts/cua-driver/linux if-no-files-found: ignore compression-level: 0 retention-days: 14 capture: if: inputs.lane == 'all' || inputs.lane == 'capture' name: "Linux / capture and desktop scope" needs: source runs-on: ubuntu-latest timeout-minutes: 60 env: CUA_E2E_SOURCE_SHA: ${{ needs.source.outputs.sha }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ needs.source.outputs.sha }} - uses: dtolnay/rust-toolchain@stable - name: Install GUI and Rust build dependencies run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ xvfb dbus-x11 at-spi2-core openbox python3-gi gir1.2-gtk-3.0 \ libgtk-3-dev clang pkg-config libdbus-1-dev libpipewire-0.3-dev \ libspa-0.2-dev libei-dev libx11-dev libxi-dev libxtst-dev libxext-dev \ libwebkit2gtk-4.1-dev libssl-dev libxdo-dev \ libayatana-appindicator3-dev librsvg2-dev ffmpeg - name: Run capture and desktop-scope contracts env: CUA_E2E_INTERNAL_LANE: capture run: | xvfb-run -a --server-args="-screen 0 1920x1080x24" \ dbus-run-session -- bash -lc \ "openbox >/tmp/cua-openbox.log 2>&1 & sleep 2; scripts/ci/linux/run-rust-e2e.sh" - name: Upload capture results if: always() uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 with: name: rust-linux-capture path: artifacts/cua-driver/linux if-no-files-found: ignore compression-level: 0 retention-days: 14 summary: if: always() needs: [source, shared, native, capture] name: "Linux / matrix summary" runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ needs.source.outputs.sha }} - name: Download lane results uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 with: path: artifacts - name: Publish matrix summary shell: bash env: GH_TOKEN: ${{ github.token }} run: | summary_path=matrix-summary.md { echo "# CUA Rust Linux E2E matrix" echo echo "The lane jobs above are independent; a failure in one lane does not hide the others." echo } > "$summary_path" artifacts_json=$(gh api \ "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/artifacts?per_page=100") found=0 while IFS= read -r summary; do found=1 artifact=$(basename "$(dirname "$summary")") echo "## $artifact" >> "$summary_path" artifact_id=$(jq -r --arg name "$artifact" \ '.artifacts[] | select(.name == $name) | .id' <<< "$artifacts_json" | head -n 1) if [[ -n "$artifact_id" && "$artifact_id" != "null" ]]; then artifact_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/artifacts/$artifact_id" scripts/ci/link-e2e-evidence.sh "$summary" "$artifact_url" >> "$summary_path" else cat "$summary" >> "$summary_path" fi echo >> "$summary_path" done < <(find artifacts -type f -name summary.md -print | sort) if [[ "$found" == 0 ]]; then echo "No lane summary artifact was produced." >> "$summary_path" fi { echo echo "## Trajectory videos" echo echo "Full-desktop MP4s are retained for 14 days inside each lane artifact." echo echo "| Lane | Videos | Artifact |" echo "| --- | ---: | --- |" } >> "$summary_path" while IFS='|' read -r lane artifact; do recording_dir="artifacts/$artifact/recordings" if [[ -d "$recording_dir" ]]; then video_count=$(find "$recording_dir" -type f -name recording.mp4 | wc -l | tr -d ' ') else video_count=0 fi artifact_id=$(jq -r --arg name "$artifact" \ '.artifacts[] | select(.name == $name) | .id' <<< "$artifacts_json" | head -n 1) if [[ -n "$artifact_id" && "$artifact_id" != "null" ]]; then artifact_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/artifacts/$artifact_id" artifact_link="[Download $artifact]($artifact_url)" else artifact_link="Not produced in this dispatch" fi echo "| $lane | $video_count | $artifact_link |" >> "$summary_path" done <<'EOF' Shared Electron + Tauri|rust-linux-shared GTK3 native|rust-linux-native Capture + desktop scope|rust-linux-capture EOF { echo echo "Each evidence link opens its owning lane artifact; the row text is the exact \`recordings/-pid-/recording.mp4\` path, with an adjacent \`trajectory.json\`." } >> "$summary_path" cat "$summary_path" >> "$GITHUB_STEP_SUMMARY" - name: Upload matrix summary if: always() uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 with: name: rust-linux-matrix-summary path: matrix-summary.md if-no-files-found: error