Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 167 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,40 +1,199 @@
name: CI

# Triggers are deliberately asymmetric to keep PR feedback fast and cheap:
# - pull_request: run the FAST required gate (lint + unit `test`) on every PR.
# - push to main: re-validate main AND run the heavy post-merge suites
# (full Python matrix + macOS + windows-mock + wheel + slow e2e/browser).
# - schedule: a nightly run of the same heavy suites, so long-lived branches
# and environment drift are caught even without a merge.
# There is intentionally NO bare `push:` -- a bare push trigger fires a SECOND
# identical run alongside `pull_request` on every PR-branch push (double the
# runner spend for zero extra signal).
on:
push:
pull_request:
push:
branches: [main]
schedule:
- cron: "0 7 * * *" # 07:00 UTC nightly full matrix

# Cancel any superseded in-flight run for the same ref (e.g. a force-push or a
# rapid second push to a PR branch) instead of letting both run to completion.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

# GitHub Actions are pinned to full commit SHAs (not mutable tags) as a
# supply-chain control; the trailing comment records the human-readable
# version. Dependabot (.github/dependabot.yml) proposes SHA bumps.

jobs:
# --- Required merge gate: FAST unit suite (ubuntu, single Python) ---------
# This is the ONE required status check (branch protection context: "test").
# It MUST stay a single, non-matrix job named exactly `test` so its reported
# context is exactly `test` -- a matrix would report `test (3.12)` etc. and
# the required `test` check would never report, permanently blocking merges.
# It runs the bulk of the unit suite but EXCLUDES the slow end-to-end
# browser/OCR record->compile->replay suite (tests/e2e), which is exercised
# post-merge by `test-matrix`. Keeps PR feedback quick.
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/setup-python@v5
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml

- name: Install
run: pip install -e .[dev]

# Cache the Playwright browser binaries (chromium) so the lightweight
# browser-backed unit tests don't re-download ~150MB every run. Keyed on
# the resolved playwright version so a dependency bump busts the cache.
- name: Resolve Playwright version
id: pw
run: echo "version=$(python -c 'import importlib.metadata as m; print(m.version(\"playwright\"))')" >> "$GITHUB_OUTPUT"

- name: Cache Playwright browsers
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ steps.pw.outputs.version }}

- name: Install Playwright browser
run: playwright install --with-deps chromium

# --basetemp pins pytest's tmp_path factory inside the workspace so
# run dirs (REPORT.md + step/heal PNGs) survive for artifact upload;
# by default they land in the system temp dir and are unrecoverable
# when a drift/heal test fails in CI.
- name: Test
run: mkdir -p runs && pytest -q --basetemp=runs/ci
# when a drift/heal test fails in CI. --cov reports coverage as a
# visibility number (no hard floor yet). --ignore=tests/e2e drops the
# slow browser/OCR end-to-end suite from the fast required gate.
- name: Test (fast unit suite)
run: |
mkdir -p runs
pytest -q --ignore=tests/e2e --basetemp=runs/ci

- name: Upload run artifacts
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: runs-test
path: |
runs/**/REPORT.md
runs/**/BENCH.md
runs/**/report.json
runs/**/*.png
if-no-files-found: warn

# --- Post-merge / nightly: full Python matrix + macOS, FULL suite --------
# NOT a required check and NOT run on PRs (so merges never block on it). Runs
# the COMPLETE suite including the slow tests/e2e browser/OCR leg across
# Python 3.10-3.13 on Linux plus a macOS leg, on push to main and nightly.
test-matrix:
if: ${{ github.event_name != 'pull_request' }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version: ["3.10", "3.11", "3.12", "3.13"]
include:
# At least one non-Linux leg for the browser/OCR runtime.
- os: macos-latest
python-version: "3.12"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml

- name: Install
run: pip install -e .[dev]

- name: Resolve Playwright version
id: pw
run: echo "version=$(python -c 'import importlib.metadata as m; print(m.version(\"playwright\"))')" >> "$GITHUB_OUTPUT"

- name: Cache Playwright browsers
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ steps.pw.outputs.version }}

- name: Install Playwright browser
run: playwright install --with-deps chromium

- name: Test (full suite incl. e2e)
run: |
mkdir -p runs
pytest -q --basetemp=runs/ci

- name: Upload run artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: runs
name: runs-${{ matrix.os }}-py${{ matrix.python-version }}
path: |
runs/**/REPORT.md
runs/**/BENCH.md
runs/**/report.json
runs/**/*.png
if-no-files-found: warn

# --- Windows: pure-Python / mock backend tests (no live VM, no browser) --
# Post-merge / nightly only (not a required check, not run on PRs).
windows-mock:
if: ${{ github.event_name != 'pull_request' }}
runs-on: windows-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml

- name: Install
run: pip install -e .[dev]

# The WindowsBackend + RDP backend suites mock the WAA/RDP servers with
# stdlib HTTP + fakes (no live VM, no Playwright browser), so they run
# anywhere. This proves the 4-method Backend protocol on Windows.
- name: Windows mock backend tests
run: pytest -q tests/test_windows_backend.py tests/test_rdp_backend.py

# --- Clean-wheel install: build, install in a fresh venv, import ---------
# Post-merge / nightly only (not a required check, not run on PRs).
wheel:
if: ${{ github.event_name != 'pull_request' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml

- name: Build wheel
run: |
pip install build
python -m build --wheel

- name: Install the wheel in a clean venv and import
run: |
python -m venv /tmp/clean
/tmp/clean/bin/pip install dist/*.whl
# Import from a dir without the source tree; assert __version__ equals
# the installed distribution metadata (the drift bug this PR fixes --
# version-agnostic so it keeps holding across releases) and that the
# py.typed marker shipped inside the wheel.
cd /tmp && /tmp/clean/bin/python -c "import os, importlib.metadata as m, openadapt_flow; v = openadapt_flow.__version__; dist = m.version('openadapt-flow'); print('attr', v, 'dist', dist); assert v == dist, (v, dist); p = os.path.join(os.path.dirname(openadapt_flow.__file__), 'py.typed'); assert os.path.exists(p), 'py.typed missing from installed package'; print('py.typed present, import OK')"
Loading