Repository navigation
ci(deps): bump astral-sh/setup-uv from 10.0.1 to 10.1.0 #2512
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tests | |
| # Zmiany czysto dokumentacyjne (docs/, mkdocs.yml, .readthedocs.yaml) | |
| # NIE uruchamiaja calej suity testow — buduje je wtedy tylko workflow | |
| # `Docs` (ma komplementarny `paths:`). Dzieki temu commit z sama | |
| # dokumentacja deployuje MkDocs na Pages, ale nie odpala ~10-min testow. | |
| # UWAGA: `[skip ci]` w komunikacie commita ubilby ROWNIEZ `Docs` — | |
| # dlatego selekcja jest po sciezkach, nie po komunikacie. Commit | |
| # mieszany (dokumentacja + kod) i tak odpali testy, bo nie wszystkie | |
| # zmienione pliki sa ignorowane. | |
| on: | |
| push: | |
| branches: | |
| - dev | |
| - master | |
| paths-ignore: | |
| - docs/** | |
| - mkdocs.yml | |
| - .readthedocs.yaml | |
| pull_request: | |
| branches: | |
| - dev | |
| paths-ignore: | |
| - docs/** | |
| - mkdocs.yml | |
| - .readthedocs.yaml | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| # Default minimum permissions; per-job overrides below where extra | |
| # scopes are needed (notably `packages:` for the GHCR push/pull flow). | |
| permissions: | |
| contents: read | |
| jobs: | |
| lint: | |
| # Lint only Python files changed in this push/PR by running the | |
| # pre-commit ruff hooks THEMSELVES (see .pre-commit-config.yaml) | |
| # against the changed range. The ruff version + args live in one | |
| # place, so local pre-commit and CI can never disagree. The repo's | |
| # convention is changed-files-only; pre-commit's --from-ref/--to-ref | |
| # reproduces that scope and historic drift is left alone. | |
| name: Lint changed files | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| # No apt build deps / project sync here: pre-commit builds an | |
| # isolated ruff env from a prebuilt wheel (no native compilation), | |
| # so the heavy toolchain the test jobs need is unnecessary for | |
| # linting. uv is only a fast launcher for pre-commit (via uvx). | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 | |
| with: | |
| # Patrz komentarz przy jobie `lock-check` — bez `version` setup-uv | |
| # bierze "latest", co czyni CI niedeterministycznym. | |
| version: "0.11.29" | |
| enable-cache: true | |
| - name: Determine diff base | |
| id: base | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_REF: ${{ github.base_ref }} | |
| BEFORE_SHA: ${{ github.event.before }} | |
| run: | | |
| if [ "$EVENT_NAME" = "pull_request" ]; then | |
| git fetch --no-tags --depth=100 origin "$BASE_REF" | |
| echo "ref=origin/$BASE_REF" >> "$GITHUB_OUTPUT" | |
| elif [ -n "$BEFORE_SHA" ] && \ | |
| [ "$BEFORE_SHA" != "0000000000000000000000000000000000000000" ]; then | |
| echo "ref=$BEFORE_SHA" >> "$GITHUB_OUTPUT" | |
| else | |
| # First push to a new branch: diff against dev | |
| git fetch --no-tags --depth=100 origin dev | |
| echo "ref=origin/dev" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Assert ruff pins agree (pyproject vs .pre-commit-config) | |
| # Krok POPRZEDZA lint, bo ustala CZYM lintujemy. Ruff ma dwa piny: | |
| # `ruff==X` w extrze [dev] (pyproject.toml) i `rev: vX` przy | |
| # astral-sh/ruff-pre-commit (.pre-commit-config.yaml). Krok niżej | |
| # używa tego drugiego, `uv run ruff` u dewelopera — pierwszego. | |
| # To dwa różne binaria, więc rozjazd pinów przywraca dokładnie ten | |
| # tryb awarii, który krok niżej deklaruje jako niemożliwy ("one | |
| # ruff, one config"): patrz komentarz przy rev w pre-commit config | |
| # o układzie I001 przepuszczonym lokalnie i odrzuconym przez CI. | |
| # | |
| # Osobny krok, a nie hook w tamtym wywołaniu, bo krok niżej odpala | |
| # hooki PO NAZWIE (`pre-commit run ruff`), więc lokalny hook | |
| # `ruff-piny-zsynchronizowane` nigdy by się w CI nie uruchomił. | |
| # Skrypt jest stdlib-only → goły `python3`, bez `uv sync`. | |
| run: python3 bin/check-ruff-pin-sync.py | |
| - name: Ruff + ruff-format (via pre-commit — single source of truth) | |
| # Runs the SAME `ruff` and `ruff-format` hooks declared in | |
| # .pre-commit-config.yaml (pinned rev) over the changed range, so | |
| # CI and a local `pre-commit run` execute the byte-identical ruff | |
| # binary + args (--fix --exit-non-zero-on-fix). "passes locally, | |
| # fails CI" becomes impossible — there is one ruff, one config. | |
| # Bumping the ruff `rev` in .pre-commit-config.yaml updates CI | |
| # automatically; no edit here is ever needed for a ruff upgrade. | |
| # | |
| # pre-commit derives the file scope ITSELF from --from-ref/ | |
| # --to-ref; we deliberately do NOT hand it a file list. A | |
| # hand-rolled `git diff | tr '\n' ' '` list carries a trailing | |
| # space, and `pre-commit run --files "<path> "` matches no real | |
| # file → "(no files to check) Skipped" → exit 0 → the gate | |
| # silently passes broken code. --from-ref/--to-ref sidesteps that | |
| # entirely. --show-diff-on-failure prints the exact reformat diff | |
| # so the fix is obvious straight from the CI log. | |
| # | |
| # `uvx` runs pre-commit as an isolated tool, so no `uv sync` / apt | |
| # build toolchain is needed merely to lint (pre-commit builds ruff | |
| # from a prebuilt wheel — no native code). pre-commit is left | |
| # UNPINNED on purpose: it is just the runner, and only the ruff | |
| # `rev` in .pre-commit-config.yaml affects results — so there is | |
| # no third version to babysit alongside the ruff rev + pin. | |
| env: | |
| DIFF_BASE: ${{ steps.base.outputs.ref }} | |
| run: | | |
| uvx pre-commit run ruff \ | |
| --from-ref "$DIFF_BASE" --to-ref HEAD --show-diff-on-failure | |
| uvx pre-commit run ruff-format \ | |
| --from-ref "$DIFF_BASE" --to-ref HEAD --show-diff-on-failure | |
| lockfile: | |
| # Assert uv.lock is resolved against the CURRENT pyproject.toml. The | |
| # test image installs deps with `uv sync --frozen`, which applies | |
| # uv.lock AS-IS without checking it against pyproject.toml — so a dep | |
| # edited without re-running `uv lock` would leave dev CI green while | |
| # testing stale deps, and the release pipeline's own `uv lock` step | |
| # would then silently re-resolve into deps dev never tested. This | |
| # fast standalone gate makes lock consistency a tested invariant on | |
| # dev, turning uv.lock into a trustworthy artifact. `uv lock --check` | |
| # is read-only: it never writes the lockfile, it only fails on drift. | |
| # Mirrors the `lint` job: same pinned uv setup, no project sync. | |
| name: uv.lock in sync | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 | |
| with: | |
| # PIN OBOWIAZKOWY. Bez `version` setup-uv loguje "Could not | |
| # determine uv version from uv.toml or pyproject.toml. Falling back | |
| # to latest" i bierze najnowsze uv z releases.astral.sh. `uv lock | |
| # --check` zaczyna wtedy zalezec od DATY przebiegu, nie od tresci | |
| # repo: ten sam, bit-w-bit identyczny uv.lock przechodzil na uv | |
| # 0.11.29 i padal na 0.11.31 ("lockfile needs to be updated"), | |
| # wywracajac gate na branchach, ktore deps w ogole nie ruszaly. | |
| # Wersje podnosimy SWIADOMIE, razem z przegenerowanym uv.lock. | |
| version: "0.11.29" | |
| enable-cache: true | |
| - name: Assert uv.lock matches pyproject.toml | |
| run: uv lock --check | |
| baseline-check: | |
| # Guard repo-state-only: czy baseline-sql/baseline.sql nadaza za migracjami | |
| # (manage.py baseline_check). Wydzielone z prepare-image i puszczone | |
| # ROWNOLEGLE — wczesniej te ~37s (apt + uv sync + check) blokowaly start | |
| # builda obrazu, a wiec i wszystkich shardow, na KAZDYM runie. Drift jest | |
| # rzadki: sporadyczny "zmarnowany" build przy realnym drifcie jest tanszy | |
| # niz 37s na kazdym runie. Drift = ten job czerwony = caly run czerwony. | |
| name: Check baseline freshness | |
| if: | | |
| !(github.ref == 'refs/heads/dev' && contains(github.event.head_commit.message, 'Merge tag')) | |
| && !(github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'Release v')) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install system build deps (for uv sync) | |
| run: | | |
| sudo apt-get update | |
| xargs -a docker/bpp_base/build.txt sudo apt-get install -y | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 | |
| with: | |
| # Patrz komentarz przy jobie `lock-check`. Tu dochodzi drugi powod: | |
| # `uv sync --frozen` czyta uv.lock, wiec plywajaca wersja uv czyni | |
| # srodowisko testowe niereprodukowalnym miedzy przebiegami. | |
| version: "0.11.29" | |
| enable-cache: false | |
| - name: Install project deps | |
| run: uv sync --frozen --no-install-project | |
| - name: Check baseline freshness | |
| env: | |
| DJANGO_BPP_SKIP_DOTENV: "1" | |
| run: uv run python src/manage.py baseline_check --max-delta 50 | |
| prepare-image: | |
| # Build the test-runner image once and push it to GHCR with two | |
| # tags: `:sha-<commit>` (consumed by downstream test jobs) and | |
| # `:cache` (registry-mode buildx cache, auto-merged across builds). | |
| # All test jobs `needs:` this step so the image is built once per | |
| # workflow run instead of N times per matrix runner. | |
| name: Build test-runner image | |
| if: | | |
| !(github.ref == 'refs/heads/dev' && contains(github.event.head_commit.message, 'Merge tag')) | |
| && !(github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'Release v')) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| # GHCR push needs `packages: write`. GITHUB_TOKEN inherits this | |
| # scope only when the job declares it explicitly. | |
| packages: write | |
| outputs: | |
| image: ${{ steps.tag.outputs.image }} | |
| env: | |
| PYTHON_VERSION: "3.12" | |
| DEBIAN_VERSION: trixie | |
| NODEJS_VERSION: "20" | |
| # Lowercase repo owner, since GHCR rejects mixed-case namespaces. | |
| # GitHub Actions ${{ github.repository_owner }} preserves case. | |
| IMAGE_REPO: ghcr.io/iplweb/bpp-test-runner | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| # baseline_check przeniesiony do osobnego joba `baseline-check`, ktory | |
| # biegnie ROWNOLEGLE z tym buildem (wczesniej jego ~37s apt+uv sync+check | |
| # blokowaly start builda obrazu — a wiec i wszystkich shardow). Baseline | |
| # drift jest rzadki, wiec sporadyczny "zmarnowany" build przy realnym | |
| # drifcie jest tanszy niz 37s na kazdym runie. | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 | |
| with: | |
| driver: docker-container | |
| - name: Log in to GHCR | |
| uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Compute image tag | |
| id: tag | |
| env: | |
| SHA: ${{ github.sha }} | |
| run: | | |
| echo "image=${IMAGE_REPO}:sha-${SHA}" >> "$GITHUB_OUTPUT" | |
| - name: Build & push test-runner image | |
| uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 | |
| with: | |
| context: . | |
| file: docker/bpp_base/Dockerfile | |
| target: test-runner | |
| # Zstd zmniejsza transfer obrazu pullowanego rownolegle przez 12 | |
| # shardow. `outputs: type=registry` jest odpowiednikiem push=true. | |
| outputs: type=registry,compression=zstd,compression-level=3,force-compression=true | |
| # Two tags: immutable sha-pinned (consumed by tests jobs) and | |
| # the registry-cache helper used to seed `cache-from` next time. | |
| tags: | | |
| ${{ steps.tag.outputs.image }} | |
| build-args: | | |
| PYTHON_VERSION=${{ env.PYTHON_VERSION }} | |
| DEBIAN_VERSION=${{ env.DEBIAN_VERSION }} | |
| NODEJS_VERSION=${{ env.NODEJS_VERSION }} | |
| # Zmienia się co commit -> busta warstwy COPY src/ + grunt w | |
| # test-runnerze (patrz ARG GIT_SHA w Dockerfile), żeby cache-from | |
| # nie przywrócił nieaktualnego drzewa źródeł. | |
| GIT_SHA=${{ github.sha }} | |
| # Registry-mode cache. mode=max stores all intermediate layers, | |
| # so subsequent builds where uv.lock/Dockerfile/build.txt are | |
| # unchanged finish in seconds. The first run after rolling out | |
| # this workflow has no cache and pays the full ~3 min build — | |
| # warm cache afterwards. | |
| cache-from: type=registry,ref=${{ env.IMAGE_REPO }}:cache | |
| cache-to: type=registry,ref=${{ env.IMAGE_REPO }}:cache,mode=max | |
| tests: | |
| # Single sharded job covering the full suite (playwright + | |
| # non-playwright + serial) under `pytest -n auto`. Earlier the | |
| # workflow split this into three specialised jobs (parallel / | |
| # serial / playwright); shard-then-mix is faster end-to-end because | |
| # the per-shard fixed cost (image pull, PG init + baseline load, | |
| # Redis startup) is paid once instead of three times. | |
| # | |
| # Splitting is duration-based (pytest-split, `--splitting-algorithm | |
| # least_duration`): each group gets tests whose ESTIMATED total | |
| # runtime is roughly equal, read from the committed `.test_durations` | |
| # file (mounted read-only via docker-compose.test.ci.yml). This | |
| # replaced pytest-shard's hash-based split, which balanced test COUNT | |
| # not TIME and so left whichever shard happened to collect the slow | |
| # Playwright tests ~2x slower than the rest — and the gate waits on | |
| # the slowest shard. If `.test_durations` is missing/stale, | |
| # pytest-split degrades gracefully to an even split (tests absent | |
| # from the file are weighted at the average), so CI never breaks on | |
| # a stale file — it just balances less well until `make tests` | |
| # regenerates it. The matrix stays 0-indexed (shard) to keep the job | |
| # names — and the branch-protection required checks — unchanged; | |
| # pytest-split groups are 1-indexed, so the run step maps group=shard+1. | |
| # | |
| # @pytest.mark.serial tests are deliberately mixed in here. They | |
| # don't actually depend on running outside xdist — the marker is | |
| # used by callers that want to opt out of parallelism, but the | |
| # tests themselves pass under -n auto. If that ever changes, gate | |
| # serial tests with @pytest.mark.xdist_group(name="serial") so | |
| # `--dist=loadgroup` keeps them on a single worker. | |
| # | |
| # DB setup: docker-compose.test.yml mounts baseline.sql into | |
| # /docker-entrypoint-initdb.d/ and sets DJANGO_BPP_TEST_TEMPLATE=bpp, | |
| # so xdist worker DBs are created via `CREATE DATABASE … WITH | |
| # TEMPLATE bpp` (instant in-server clone) instead of replaying | |
| # `psql -f baseline.sql` per worker. | |
| name: Tests (sharded) | |
| needs: prepare-image | |
| if: | | |
| !(github.ref == 'refs/heads/dev' && contains(github.event.head_commit.message, 'Merge tag')) | |
| && !(github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'Release v')) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 35 | |
| permissions: | |
| contents: read | |
| packages: read | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 12 | |
| matrix: | |
| python-version: ["3.12"] | |
| # 0-indexed; MUSI byc spojne z NUM_SHARDS nizej. Duration-split | |
| # (pytest-split, .test_durations) balansuje dowolne N po czasie, wiec | |
| # bump jest tani: 8 -> 12 tnie czas testow najwolniejszego sharda | |
| # (~220s -> ~150s). Powyzej ~12 zysk topnieje, bo czas sharda spada | |
| # ponizej czasu prepare-image (to staje sie podloga krytycznej sciezki). | |
| shard: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] | |
| env: | |
| TEST_RUNNER_IMAGE: ${{ needs.prepare-image.outputs.image }} | |
| COMPOSE_FILES: -f docker-compose.test.yml -f docker-compose.test.ci.yml | |
| NUM_SHARDS: "12" | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Prepare ci-output dir | |
| run: mkdir -p ci-output | |
| - name: Pull test-runner image | |
| # Retry na transient network/registry error (timeout GHCR/Docker Hub | |
| # to najczestszy szum CI — nie regresja kodu). 3 proby, 10s backoff; | |
| # dopiero trwala porazka wywala krok. | |
| run: | | |
| n=0 | |
| until docker compose $COMPOSE_FILES pull test-runner; do | |
| n=$((n + 1)) | |
| if [ "$n" -ge 3 ]; then | |
| echo "docker pull test-runner nie powiodl sie po $n probach" >&2 | |
| exit 1 | |
| fi | |
| echo "pull test-runner padl (proba $n), ponawiam za 10s..." >&2 | |
| sleep 10 | |
| done | |
| - name: Pull service images (db, redis) | |
| # Osobny, retry-owany pull obrazow z Docker Huba (iplweb/bpp_dbserver, | |
| # redis) PRZED `up -d`. Wczesniej `up -d` pobieral je implicite bez | |
| # retry — timeout Docker Huba na iplweb/bpp_dbserver wywalal caly shard | |
| # (poz. 3.3 audytu). Teraz `up -d` widzi obrazy juz lokalnie. | |
| run: | | |
| n=0 | |
| until docker compose $COMPOSE_FILES pull db redis; do | |
| n=$((n + 1)) | |
| if [ "$n" -ge 3 ]; then | |
| echo "docker pull db/redis nie powiodl sie po $n probach" >&2 | |
| exit 1 | |
| fi | |
| echo "pull db/redis padl (proba $n), ponawiam za 10s..." >&2 | |
| sleep 10 | |
| done | |
| - name: Start services | |
| run: docker compose $COMPOSE_FILES up -d db redis | |
| - name: Wait for services | |
| timeout-minutes: 2 | |
| run: | | |
| until docker compose $COMPOSE_FILES exec db \ | |
| pg_isready -U bpp; do | |
| echo "Waiting for PostgreSQL..." | |
| sleep 2 | |
| done | |
| until docker compose $COMPOSE_FILES exec redis \ | |
| redis-cli ping; do | |
| echo "Waiting for Redis..." | |
| sleep 2 | |
| done | |
| - name: Tests (shard ${{ matrix.shard }}/${{ env.NUM_SHARDS }}) | |
| timeout-minutes: 25 | |
| env: | |
| SHARD_ID: ${{ matrix.shard }} | |
| run: | | |
| # pytest-split groups are 1-indexed; the matrix shard is | |
| # 0-indexed (kept that way so the job names / required checks | |
| # don't change). Map group = shard + 1. | |
| GROUP=$((SHARD_ID + 1)) | |
| docker compose $COMPOSE_FILES run --rm \ | |
| test-runner uv run pytest \ | |
| -n auto \ | |
| --splits "$NUM_SHARDS" --group "$GROUP" \ | |
| --splitting-algorithm least_duration \ | |
| --durations-path .test_durations \ | |
| --timeout 300 | |
| - name: Cleanup | |
| if: always() | |
| run: docker compose $COMPOSE_FILES down -v | |
| js-tests: | |
| # Jednostkowe testy czystej logiki JS (vitest): budowanie URL-i | |
| # eksploratora powiazan, geometria ukladow grafu, normalizacja | |
| # rozmiarow, formatowanie/lokalizacja DjangoQL. NIE wymaga obrazu | |
| # testowego ani DB — czysty node, ~1 min. notifications.js zyje i | |
| # jest testowany w pakiecie django-channels-broadcast (jego CI). | |
| name: JS unit tests (vitest) | |
| if: | | |
| !(github.ref == 'refs/heads/dev' && contains(github.event.head_commit.message, 'Merge tag')) | |
| && !(github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'Release v')) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Node | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "20" | |
| cache: yarn | |
| - name: Install JS dependencies | |
| run: yarn install --frozen-lockfile | |
| - name: Run vitest suite | |
| run: npx vitest run |