Skip to content

UN-2202 [FEAT] Add co-owner management for shared resources #7672

UN-2202 [FEAT] Add co-owner management for shared resources

UN-2202 [FEAT] Add co-owner management for shared resources #7672

Workflow file for this run

name: Run tests (unit + integration + e2e)
# Multi-job, one report. unit/integration run as parallel matrix legs on
# testcontainers; e2e builds images and boots the full platform via compose on
# its own runner (images don't travel across jobs). The report job needs all
# three, merges every tier's junit into ONE critical-path evaluation, and posts
# a single sticky PR comment — so e2e coverage (e.g. auth-login) shows up next
# to unit/integration. Tiers never run in one rig invocation: the rig picks one
# infra mode per call, so mixing compose e2e with testcontainers integration
# would point the backend at the compose DB hostname, unreachable from pytest.
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
workflow_dispatch:
jobs:
# Path filtering at job level, not trigger paths-ignore: an untriggered
# workflow reports no check run, so a required check waits forever; a job
# skipped via `if:` reports conclusion `skipped`, which passes. Gates only
# unit/integration — e2e runs regardless (docker/** changes affect it).
changes:
runs-on: ubuntu-latest
outputs:
relevant: ${{ steps.filter.outputs.relevant }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for changes outside ignored paths
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: filter
with:
# `every`: a file counts as relevant only if it matches ALL the
# negated globs, i.e. lies outside every ignored dir.
predicate-quantifier: every
filters: |
relevant:
- "!docker/**"
- "!frontend/**"
- "!docs/**"
# Tiers share no state (separate runners, disjoint groups), so they run as
# parallel matrix legs; cross-tier reconciliation happens in `report`.
test:
needs: changes
if: needs.changes.outputs.relevant == 'true' && github.event.pull_request.draft == false
runs-on: ubuntu-latest
strategy:
# One tier failing must not cancel the other; report needs both results.
fail-fast: false
matrix:
tier: [unit, integration]
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0 # rig --changed-only needs git history
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
with:
version: "0.6.14"
python-version: 3.12.9
- name: Cache tox environments
uses: actions/cache@v5
with:
path: .tox/
# Tier in the key: legs build different tox envs, and parallel saves
# to one key race (first save wins).
key: ${{ runner.os }}-tox-uv-${{ matrix.tier }}-${{ hashFiles('**/pyproject.toml', '**/tox.ini') }}
restore-keys: |
${{ runner.os }}-tox-uv-${{ matrix.tier }}-
${{ runner.os }}-tox-uv-
- name: Install tox with UV
run: uv tool install tox --with tox-uv
- name: Validate test manifests
# Cheap pre-flight: catches manifest schema errors before tier runs.
run: tox -e rig -- validate
- name: Run ${{ matrix.tier }} tier
# Gap check on PRs too: main is gap-free and PRs test the merge ref, so
# an in-scope gap here is PR-introduced. No baseline restore or
# --update-baseline — the report job is the sole regression authority
# (a per-tier eval would flag other tiers' paths as missing).
run: tox -e ${{ matrix.tier }} -- --fail-on-critical-gap
- name: Suffix coverage data for cross-tier merge
# Every tier emits a bare reports/.coverage that would clobber on
# download; suffixed names match the `.coverage.*` glob combine unions.
if: always()
run: |
if [ -f reports/.coverage ]; then
mv reports/.coverage "reports/.coverage.tier-${{ matrix.tier }}"
fi
- name: Upload tier reports artifact
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: reports-${{ matrix.tier }}
path: reports/
if-no-files-found: ignore
retention-days: 14
# Own runner: builds images then runs e2e against the full compose platform.
# Not gated by `changes` — docker/** edits don't count as "relevant" yet must
# be e2e-tested.
e2e:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 45
env:
# One tag end to end: docker-compose.build.yaml tags images with VERSION,
# the base compose boots ${VERSION}, and the test overlay pins its
# services via UNSTRACT_TEST_VERSION. Keep them identical.
VERSION: ${{ github.sha }}
UNSTRACT_TEST_VERSION: ${{ github.sha }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- name: Login to Docker Hub
# Authenticated pulls avoid the anonymous rate limit shared by all
# runners. Forks have no access to secrets.
if: github.event.pull_request == null || github.event.pull_request.head.repo.fork == false
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
with:
version: "0.6.14"
python-version: 3.12.9
- name: Cache tox environments
uses: actions/cache@v5
with:
path: .tox/
key: ${{ runner.os }}-tox-uv-e2e-${{ hashFiles('**/pyproject.toml', '**/tox.ini') }}
restore-keys: |
${{ runner.os }}-tox-uv-e2e-
${{ runner.os }}-tox-uv-
- name: Install tox with UV
run: uv tool install tox --with tox-uv
- name: Validate test manifests
# Cheap pre-flight before the multi-minute image build.
run: tox -e rig -- validate
- name: Set up env files
run: ./run-platform.sh -e
- name: Build service images
run: docker compose -f docker/docker-compose.build.yaml build
- name: Run e2e tier (rig boots the platform via compose)
env:
UNSTRACT_E2E_RUNTIME: compose
# Gap check only; report owns baseline/regression across all tiers.
run: tox -e e2e -- --fail-on-critical-gap
- name: Suffix coverage data for cross-tier merge
if: always()
run: |
if [ -f reports/.coverage ]; then
mv reports/.coverage "reports/.coverage.tier-e2e"
fi
- name: Capture docker compose logs on failure
if: failure()
run: |
mkdir -p reports
docker compose -p unstract-test \
-f docker/docker-compose.yaml \
-f tests/compose/docker-compose.test.yaml \
logs --no-color > reports/docker-compose-logs.txt || true
- name: Upload e2e reports artifact
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: reports-e2e
path: reports/
if-no-files-found: ignore
retention-days: 14
report:
needs: [changes, test, e2e]
# `always()` so a red tier still posts a report. e2e always runs on
# non-draft, so gate only on draft; the fail step below distinguishes a
# legitimate skip (irrelevant paths) from a real tier failure.
if: always() && github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
with:
version: "0.6.14"
python-version: 3.12.9
- name: Cache tox environments
uses: actions/cache@v5
with:
path: .tox/
key: ${{ runner.os }}-tox-uv-rig-${{ hashFiles('**/pyproject.toml', '**/tox.ini') }}
restore-keys: |
${{ runner.os }}-tox-uv-rig-
${{ runner.os }}-tox-uv-
- name: Install tox with UV
run: uv tool install tox --with tox-uv
- name: Restore main-branch test baseline (for regression detection)
# One unified baseline across all tiers; this job is its sole
# reader+writer. Restore-only here, save below on green main.
uses: actions/cache/restore@v5
with:
path: reports/previous-summary.json
key: unstract-test-baseline-main-${{ github.run_id }}
restore-keys: |
unstract-test-baseline-main-
- name: Download tier reports
uses: actions/download-artifact@v4
with:
pattern: reports-*
path: reports/
merge-multiple: true
- name: Combine tiers into one report
# Merges every tier's junit into a single critical-path evaluation + PR
# comment. --update-baseline is main-only and green-only — a PR must not
# write main's baseline, nor a red build a partial one.
run: |
flags=""
if [ "${{ github.ref }}" = "refs/heads/main" ] \
&& [ "${{ needs.test.result }}" = "success" ] \
&& [ "${{ needs.e2e.result }}" = "success" ]; then
flags="--update-baseline"
fi
tox -e rig -- report combine $flags
- name: Save updated baseline
# actions/cache saves only on a miss; the run id keys each main build a
# fresh entry, and restore-keys' prefix pulls the latest next run.
if: github.ref == 'refs/heads/main'
uses: actions/cache/save@v5
with:
path: reports/previous-summary.json
key: unstract-test-baseline-main-${{ github.run_id }}
- name: Render combined test report to PR
uses: marocchino/sticky-pull-request-comment@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3.0.2
if: always() && hashFiles('reports/combined-test-report.md') != '' && github.event.pull_request.head.repo.fork == false
with:
header: test-results
recreate: true
path: reports/combined-test-report.md
- name: Output combined report to job summary
if: always() && hashFiles('reports/summary.md') != ''
shell: bash
run: cat reports/summary.md >> $GITHUB_STEP_SUMMARY
- name: Upload combined reports artifact
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: test-reports
path: reports/
if-no-files-found: ignore
retention-days: 14
- name: Fail if any tier failed
# Single branch-protection check. A skipped `test` means irrelevant
# paths (path-filter skip == pass); e2e always runs, so it must be
# green. Runs after the comment so a red tier still reports.
if: always()
run: |
test_result="${{ needs.test.result }}"
e2e_result="${{ needs.e2e.result }}"
[ "$test_result" = "success" ] || [ "$test_result" = "skipped" ] || exit 1
[ "$e2e_result" = "success" ] || exit 1