From 05380f23fcb57d45500a6d4b8a42bd9092e45c66 Mon Sep 17 00:00:00 2001 From: Bill Easton Date: Wed, 28 Jan 2026 14:00:54 -0600 Subject: [PATCH] Move tests to a reusable action and enable nightly checks (#3017) --- .github/actions/run-pytest/action.yml | 44 ++++++++ .github/actions/setup-uv/action.yml | 33 ++++++ .github/workflows/run-static.yml | 13 +-- .github/workflows/run-tests.yml | 67 ++++-------- .github/workflows/run-upgrade-checks.yml | 124 +++++++++++++++++++++++ 5 files changed, 224 insertions(+), 57 deletions(-) create mode 100644 .github/actions/run-pytest/action.yml create mode 100644 .github/actions/setup-uv/action.yml create mode 100644 .github/workflows/run-upgrade-checks.yml diff --git a/.github/actions/run-pytest/action.yml b/.github/actions/run-pytest/action.yml new file mode 100644 index 000000000..ff429a4cc --- /dev/null +++ b/.github/actions/run-pytest/action.yml @@ -0,0 +1,44 @@ +name: "Run Pytest" +description: "Run pytest with appropriate flags for the test type and platform" + +inputs: + test-type: + description: "Type of tests to run: unit, integration, or client_process" + required: false + default: "unit" + +runs: + using: "composite" + steps: + - name: Run pytest + shell: bash + run: | + if [ "${{ inputs.test-type }}" == "integration" ]; then + MARKER="integration" + TIMEOUT="30" + MAX_PROCS="2" + EXTRA_FLAGS="" + elif [ "${{ inputs.test-type }}" == "client_process" ]; then + MARKER="client_process" + TIMEOUT="5" + MAX_PROCS="0" + EXTRA_FLAGS="-x" + else + MARKER="not integration and not client_process" + TIMEOUT="5" + MAX_PROCS="4" + EXTRA_FLAGS="" + fi + + PARALLEL_FLAGS="" + if [ "$MAX_PROCS" != "0" ] && [ "${{ runner.os }}" != "Windows" ]; then + PARALLEL_FLAGS="--numprocesses auto --maxprocesses $MAX_PROCS --dist worksteal" + fi + + uv run --no-sync pytest \ + --inline-snapshot=disable \ + --timeout=$TIMEOUT \ + -m "$MARKER" \ + $PARALLEL_FLAGS \ + $EXTRA_FLAGS \ + tests diff --git a/.github/actions/setup-uv/action.yml b/.github/actions/setup-uv/action.yml new file mode 100644 index 000000000..0becaffad --- /dev/null +++ b/.github/actions/setup-uv/action.yml @@ -0,0 +1,33 @@ +name: "Setup UV Environment" +description: "Install uv and dependencies (requires checkout first)" + +inputs: + python-version: + description: "Python version to use" + required: false + default: "3.10" + resolution: + description: "Dependency resolution: locked, upgrade, or lowest-direct" + required: false + default: "locked" + +runs: + using: "composite" + steps: + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + python-version: ${{ inputs.python-version }} + + - name: Install dependencies + shell: bash + run: | + if [ "${{ inputs.resolution }}" == "locked" ]; then + uv sync --locked + elif [ "${{ inputs.resolution }}" == "upgrade" ]; then + uv sync --upgrade + else + uv sync --resolution ${{ inputs.resolution }} + fi diff --git a/.github/workflows/run-static.yml b/.github/workflows/run-static.yml index d479930a3..5a15b23d4 100644 --- a/.github/workflows/run-static.yml +++ b/.github/workflows/run-static.yml @@ -1,8 +1,6 @@ name: Run static analysis env: - # enable colored output - # https://github.com/pytest-dev/pytest/issues/7443 PY_COLORS: 1 on: @@ -26,19 +24,14 @@ permissions: jobs: static_analysis: timeout-minutes: 2 - runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - cache-dependency-glob: "uv.lock" - - name: Install dependencies - run: uv sync --upgrade + - uses: ./.github/actions/setup-uv + with: + resolution: locked - name: Run prek uses: j178/prek-action@v1 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 1cbb9cbd1..9cfb0cfa0 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,7 +1,6 @@ name: Tests env: - # enable colored output PY_COLORS: 1 on: @@ -24,7 +23,7 @@ permissions: jobs: run_tests: - name: "Run tests: Python ${{ matrix.python-version }} on ${{ matrix.os }}" + name: "Tests: Python ${{ matrix.python-version }} on ${{ matrix.os }}" runs-on: ${{ matrix.os }} strategy: matrix: @@ -39,76 +38,50 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Install uv - uses: astral-sh/setup-uv@v7 + - uses: ./.github/actions/setup-uv with: - enable-cache: true - cache-dependency-glob: "uv.lock" python-version: ${{ matrix.python-version }} + resolution: locked - - name: Install FastMCP - # run with upgrade to always test against the latest compatible versions - run: uv sync --upgrade + - uses: ./.github/actions/run-pytest - - name: Run tests (excluding integration and client_process) - run: | - if [ "${{ matrix.os }}" == "windows-latest" ]; then - uv run pytest --inline-snapshot=disable tests -m "not integration and not client_process" - else - uv run pytest --inline-snapshot=disable tests -m "not integration and not client_process" --numprocesses auto --maxprocesses 4 --dist worksteal - fi - shell: bash - - - name: Run client process tests separately - run: uv run pytest --inline-snapshot=disable tests -m "client_process" -x + - uses: ./.github/actions/run-pytest + with: + test-type: client_process run_tests_lowest_direct: - name: "Run tests with lowest-direct dependencies" + name: "Tests with lowest-direct dependencies" runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v6 - - name: Install uv - uses: astral-sh/setup-uv@v7 + - uses: ./.github/actions/setup-uv with: - enable-cache: true - cache-dependency-glob: "uv.lock" - python-version: "3.10" + resolution: lowest-direct - - name: Install FastMCP with lowest-direct resolution - # run with lowest-direct to test against the minimum allowed dependency versions - run: uv sync --resolution lowest-direct + - uses: ./.github/actions/run-pytest - - name: Run tests (excluding integration and client_process) - run: uv run --resolution lowest-direct pytest --inline-snapshot=disable tests -m "not integration and not client_process" --numprocesses auto --maxprocesses 4 --dist worksteal - - - name: Run client process tests separately - run: uv run --resolution lowest-direct pytest --inline-snapshot=disable tests -m "client_process" -x + - uses: ./.github/actions/run-pytest + with: + test-type: client_process run_integration_tests: - name: "Run integration tests" + name: "Integration tests" runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v6 - - name: Install uv - uses: astral-sh/setup-uv@v7 + - uses: ./.github/actions/setup-uv with: - enable-cache: true - cache-dependency-glob: "uv.lock" - python-version: "3.10" + resolution: locked - - name: Install FastMCP - # run with upgrade to always test against the latest compatible versions - run: uv sync --upgrade - - - name: Run integration tests - # use longer per-test timeout for remote API calls (default is 5s) - run: uv run pytest tests -m "integration" --timeout=30 --numprocesses auto --maxprocesses 2 --dist worksteal + - uses: ./.github/actions/run-pytest + with: + test-type: integration env: FASTMCP_GITHUB_TOKEN: ${{ secrets.FASTMCP_GITHUB_TOKEN }} FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID }} diff --git a/.github/workflows/run-upgrade-checks.yml b/.github/workflows/run-upgrade-checks.yml new file mode 100644 index 000000000..5cd0558ff --- /dev/null +++ b/.github/workflows/run-upgrade-checks.yml @@ -0,0 +1,124 @@ +name: Upgrade checks + +env: + PY_COLORS: 1 + +on: + push: + branches: ["main"] + paths: + - "src/**" + - "tests/**" + - "uv.lock" + - "pyproject.toml" + - ".github/workflows/**" + + schedule: + # Run daily at 2 AM UTC + - cron: "0 2 * * *" + + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + static_analysis: + name: Static analysis + timeout-minutes: 2 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-uv + with: + resolution: upgrade + + - name: Run prek + uses: j178/prek-action@v1 + env: + SKIP: no-commit-to-branch + + run_tests: + name: "Tests: Python ${{ matrix.python-version }} on ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + python-version: ["3.10"] + include: + - os: ubuntu-latest + python-version: "3.13" + fail-fast: false + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-uv + with: + python-version: ${{ matrix.python-version }} + resolution: upgrade + + - uses: ./.github/actions/run-pytest + + - uses: ./.github/actions/run-pytest + with: + test-type: client_process + + run_integration_tests: + name: "Integration tests" + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v6 + + - uses: ./.github/actions/setup-uv + with: + resolution: upgrade + + - uses: ./.github/actions/run-pytest + with: + test-type: integration + env: + FASTMCP_GITHUB_TOKEN: ${{ secrets.FASTMCP_GITHUB_TOKEN }} + FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID }} + FASTMCP_TEST_AUTH_GITHUB_CLIENT_SECRET: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_SECRET }} + + notify: + name: Notify on failure + needs: [static_analysis, run_tests, run_integration_tests] + if: failure() && github.event.pull_request == null + runs-on: ubuntu-latest + + steps: + - name: Create or update failure issue + uses: jayqi/failed-build-issue-action@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + label: "build-failure" + title-template: "Upgrade checks failing on main branch" + body-template: | + ## Upgrade Checks Failure on Main Branch + + The upgrade checks workflow has failed on the main branch. + + **Workflow Run**: [#{{runNumber}}]({{serverUrl}}/{{repo}}/actions/runs/{{runId}}) + **Commit**: {{sha}} + **Branch**: {{ref}} + **Event**: {{eventName}} + + ### What to do + + This likely means that upgraded dependencies have introduced new errors. Please review the failed jobs and determine if the code needs to be updated or if dependency constraints need to be adjusted. + + - [ ] Review the failure in the workflow run + - [ ] Identify root cause (dependency changes, new linter/type rules, etc.) + - [ ] Implement fix or adjust dependency constraints + - [ ] Verify fix resolves the issue + + --- + *This issue was automatically created by a GitHub Action.*