mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-03 19:14:56 -05:00
fix(ci): use native workflow path filters (#13037)
* ci: select affected checks and share add-on builds Keep validation check identities present while omitting expensive steps when explicit dependency rules prove they are unaffected. Unknown inputs and diff failures retain broad validation. Select add-on package legs on PRs and master without weakening catalog checks or changing release publishing. Manual package runs validate only, including their reusable Sandbox guest image dependency. Share bounded, same-run production fixtures across the five add-on test groups. Verify checkout identity, tools, paths, modes and checksums before using them. Keep instrumented coverage builds separate. Assisted-by: nib:gpt-6.1-sol Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(ci): validate documentation test contracts Documentation is executable input to Tables, recovery, diagnostics, gallery and other add-on tests. Select all domains for docs changes instead of treating the whole directory as inert. Cover known readers, unfamiliar paths and deleted guides in classifier regression fixtures. Assisted-by: nib:gpt-6.1-sol Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(ci): gate affected jobs before allocation Classify each workflow once and gate whole expensive jobs. Limit add-on matrices to affected packages and host groups instead of allocating all runners before deciding whether their steps should run. Select documentation readers explicitly and derive host consumers from local Go imports. Keep unknown source inputs conservative. Preserve coverage, production fixtures, publish locks and manual full validation. Remove the unrequested weekly schedules. Reject classifier failures and unexpected dependency skips in stable aggregate checks. Document required-check migration without claiming access to protected-branch settings. Test actual YAML job and matrix eligibility against representative paths and the original triggers. Assisted-by: nib:gpt-6.1-sol Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(ci): validate executable and fixture seams Include executed fixture binaries and their production imports in host selection. Lexical Go imports alone miss the mock backend started by the UI test server and add-on suites. Rebase restored artifact timestamps only after identity and checksum validation. Preserve the existing stale-source checks across a later consumer checkout and reject subsequent source edits. Validate package plan outputs before allowing dependency skips. Missing or malformed plans must not turn failed publication preparation green. Cover all three review findings with before-fix reproductions and actual workflow eligibility regressions. Assisted-by: nib:gpt-6.1-sol Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(ci): use native workflow path filters Replace calculated dependencies with visible PR and master path lists. Run complete area suites without classifier runners or result plumbing. Restore original packaging validation and publication dependencies. Keep one production fixture build for the five original add-on groups. Verify artifact identity and freshness without changing suite inventories. Document allowlist maintenance and required-check migration limits. Assisted-by: nib:gpt-6.1-sol Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
1 parent
74e293ef38
commit
8b4f3c35e8
14 files changed
+699
-111
No files matched your search
@@ -62,3 +62,12 @@ The Office text-input tests inspect actual PDF glyphs with Poppler’s
|
||||
`pdftotext`. Install `poppler-utils` on Ubuntu/Debian or `poppler` with
|
||||
Homebrew before running `go test ./addons/office/...` or the main Go suites.
|
||||
Missing PDF inspection tools are test failures, not skipped checks.
|
||||
|
||||
### Lightweight CI workflow checks
|
||||
|
||||
Run `make test-ci-scripts`, `python3 scripts/ci/test-native-paths.py` (PyYAML),
|
||||
`python3 scripts/ci/test-fixtures.py`, and
|
||||
`sh scripts/ci/test-prepare-build.sh` without building the product. Run
|
||||
`sh scripts/addons/prerequisites_test.sh` for the original readiness regressions.
|
||||
See [ci-caching.md](ci-caching.md#native-validation-paths) for path maintenance,
|
||||
required-check migration and same-run production fixture constraints.
|
||||
@@ -390,3 +390,58 @@ When changing `image_build.yml`, `backend_build.yml`, any of the `backend/Docker
|
||||
5. **Keep `provenance: false` on push-by-digest steps** — multi-registry digest divergence is the Bug We Already Fixed; reintroducing provenance attestation re-breaks the merge.
|
||||
6. **`install-base-deps.sh` is the single source of truth for base contents.** Both `Dockerfile.base-grpc-builder` (CI) and the variant Dockerfiles' `builder-fromsource` (local) bind-mount and execute it. If you add a package to one path, add it to the script — don't fork the logic into a Dockerfile RUN.
|
||||
7. **After adding a `base-images.yml` matrix variant, run the workflow on your branch before merging consumer changes** that depend on the new tag — otherwise the consumer's CI fails "image not found."
|
||||
|
||||
## Native validation paths
|
||||
|
||||
The core, build, backend E2E, distributed, UI E2E, add-on test and add-on
|
||||
package workflows use native workflow-level `paths` on PRs and master pushes.
|
||||
Their YAML lists the consuming areas and shared inputs (`go.mod`, `go.sum`,
|
||||
`Makefile`, scripts, local actions and each workflow). Integration workflows
|
||||
include broad `core/**` and `pkg/**` areas because their fixture binaries import
|
||||
them. Matching changes run the original complete suites and matrices; there is
|
||||
no classifier job, dependency graph or focused documentation runner. The
|
||||
existing backend image selector and matrices are unchanged.
|
||||
|
||||
Ordinary docs and website edits skip these workflows. Executable documentation
|
||||
contracts remain explicit inputs:
|
||||
|
||||
- `features/addons.md`: packaging, Sandbox tools, add-on endpoints, recovery
|
||||
and Tables administration (core tests, add-on tests and packaging).
|
||||
- `features/tables.md`: Tables tools and administration (core and add-on tests).
|
||||
- `features/image-generation.md`: gallery entries (core tests).
|
||||
- `features/diagnostics.md`, `reference/cli-reference.md` and
|
||||
`getting-started/troubleshooting.md`: diagnostics (core tests).
|
||||
|
||||
These paths are under `docs/content/`. Gallery edits still run core gallery
|
||||
invariants. Host CSS does not trigger package publishing; the package workflow
|
||||
retains its original add-on inputs plus shared build and documentation inputs.
|
||||
Package pushes run on master, not every feature branch. Tags remain exempt
|
||||
from GitHub path filtering, and release events retain the original publishing
|
||||
jobs and failure dependencies. PR cancellation does not cancel master runs or
|
||||
replace the per-channel publishing locks.
|
||||
|
||||
**Maintenance rule:** when adding or moving source, generated inputs, fixtures,
|
||||
build configuration or documentation readers, update both event path lists of
|
||||
each consuming workflow in the same change. These are closed allowlists:
|
||||
unknown future directories do not automatically trigger validation. Do not add
|
||||
a catchall to hide that maintenance requirement. The core workflow has a
|
||||
manual full-test entry; distributed tests retain their existing manual and
|
||||
nightly entries. No new scheduled runs are added.
|
||||
|
||||
GitHub considers at most 300 changed files for path filtering; a relevant file
|
||||
outside that list can be missed. Diffs over 1,000 commits, or diff timeouts,
|
||||
run workflows regardless of paths. PR and push diff semantics also differ.
|
||||
The small static path tests cover representative patterns, not GitHub's diff
|
||||
service or expression engine.
|
||||
|
||||
**Required checks:** a path-skipped workflow can leave its required check
|
||||
pending. Before merging, maintainers must review branch protection/rulesets
|
||||
for these workflow checks and the removed classifier/result checks. Preserve
|
||||
the original suite check names and do not require a path-filtered workflow on
|
||||
every PR. Repository settings are not verified by these tests.
|
||||
|
||||
Add-on tests build production fixtures once for the original five groups.
|
||||
Same-run artifacts verify SHA, configuration, tool versions, paths, checksums
|
||||
and executable modes before rebasing generated timestamps to the consumer
|
||||
checkout. Subsequent source edits still fail freshness guards. This does not
|
||||
share or replace the instrumented UI coverage build or alter case inventories.
|
||||
@@ -47,27 +47,10 @@ name: 'add-on packages'
|
||||
# local-check build.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
tags: ['v*']
|
||||
paths:
|
||||
- 'addons/**'
|
||||
- 'pkg/addons/**'
|
||||
- 'scripts/addons/**'
|
||||
- 'gallery/addons/**'
|
||||
- 'tests/addons/packaging/**'
|
||||
- 'core/services/addons/**'
|
||||
- 'core/http/react-ui/package.json'
|
||||
- 'core/http/react-ui/package-lock.json'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- '.github/workflows/addons-package.yml'
|
||||
- '.github/workflows/sandbox-images.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'addons/**'
|
||||
- 'pkg/addons/**'
|
||||
- 'scripts/addons/**'
|
||||
- 'gallery/addons/**'
|
||||
- 'tests/addons/packaging/**'
|
||||
- 'core/services/addons/**'
|
||||
@@ -77,9 +60,40 @@ on:
|
||||
- 'go.sum'
|
||||
- '.github/workflows/addons-package.yml'
|
||||
- '.github/workflows/sandbox-images.yml'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'docs/content/features/addons.md'
|
||||
- '.github/actions/**'
|
||||
push:
|
||||
branches: [master]
|
||||
tags: ['v*']
|
||||
paths:
|
||||
- 'addons/**'
|
||||
- 'pkg/addons/**'
|
||||
- 'gallery/addons/**'
|
||||
- 'tests/addons/packaging/**'
|
||||
- 'core/services/addons/**'
|
||||
- 'core/http/react-ui/package.json'
|
||||
- 'core/http/react-ui/package-lock.json'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- '.github/workflows/addons-package.yml'
|
||||
- '.github/workflows/sandbox-images.yml'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'docs/content/features/addons.md'
|
||||
- '.github/actions/**'
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
concurrency:
|
||||
group: ci-addons-package-${{ github.event.pull_request.number || github.sha }}-${{ github.repository }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
||||
@@ -15,23 +15,50 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'addons/**'
|
||||
- 'tests/addons/**'
|
||||
- 'tests/e2e-ui/**'
|
||||
- 'tests/e2e/mock-backend/**'
|
||||
- 'pkg/addons/**'
|
||||
- 'core/services/addons/**'
|
||||
- 'core/http/endpoints/addons/**'
|
||||
- 'core/http/auth/addons*.go'
|
||||
- 'core/application/**'
|
||||
- 'core/library/**'
|
||||
- 'core/http/react-ui/**'
|
||||
- 'scripts/addons/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- '.github/workflows/addons-tests.yml'
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'tests/**'
|
||||
- 'configuration/**'
|
||||
- 'swagger/**'
|
||||
- 'prompt-templates/**'
|
||||
- 'gallery/addons/**'
|
||||
- 'docs/content/features/addons.md'
|
||||
- 'docs/content/features/tables.md'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'addons/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- '.github/workflows/addons-tests.yml'
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'tests/**'
|
||||
- 'configuration/**'
|
||||
- 'swagger/**'
|
||||
- 'prompt-templates/**'
|
||||
- 'gallery/addons/**'
|
||||
- 'docs/content/features/addons.md'
|
||||
- 'docs/content/features/tables.md'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
|
||||
concurrency:
|
||||
group: ci-addons-tests-${{ github.event.pull_request.number || github.sha }}-${{ github.repository }}
|
||||
@@ -41,7 +68,54 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: prepare shared production fixtures
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
# prepare.sh accepts Go 1.26.x only and runs with GOTOOLCHAIN=local.
|
||||
go-version: '1.26.x'
|
||||
cache: false
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
# prepare.sh accepts Node 22.12 or later within Node 22.
|
||||
node-version: '22'
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libopus-dev unzip curl
|
||||
- name: Install protoc
|
||||
# prepare.sh requires exactly libprotoc 26.1.
|
||||
run: |
|
||||
curl -L -s https://github.com/protocolbuffers/protobuf/releases/download/v26.1/protoc-26.1-linux-x86_64.zip -o protoc.zip
|
||||
sudo unzip -j -d /usr/local/bin protoc.zip bin/protoc
|
||||
rm protoc.zip
|
||||
protoc --version
|
||||
- name: Prepare the build toolchain
|
||||
# The producer installs build inputs only; consumers install Chromium.
|
||||
run: sh scripts/addons/prepare.sh --prepare-build
|
||||
- name: Build the UI, the mock backend and the UI test server
|
||||
run: COVERAGE=false COVERAGE_V8=false sh scripts/addons/prepare.sh --integration
|
||||
- name: Archive production fixtures
|
||||
run: |
|
||||
python3 scripts/ci/fixtures.py pack
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: addon-production-fixtures
|
||||
path: ${{ runner.temp }}/addon-fixtures.tgz
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
suites:
|
||||
needs: prepare
|
||||
name: ${{ matrix.group }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
@@ -98,8 +172,14 @@ jobs:
|
||||
run: sh scripts/addons/prepare.sh --prepare
|
||||
- name: Install the browser's system libraries
|
||||
run: sudo env "PATH=$PATH" core/http/react-ui/node_modules/.bin/playwright install-deps chromium
|
||||
- name: Build the UI, the mock backend and the UI test server
|
||||
run: sh scripts/addons/prepare.sh --integration
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: addon-production-fixtures
|
||||
path: ${{ runner.temp }}
|
||||
- name: Restore and check production fixtures
|
||||
run: |
|
||||
python3 scripts/ci/fixtures.py unpack
|
||||
sh scripts/addons/prepare.sh --check
|
||||
- name: Run the suites
|
||||
env:
|
||||
SUITES: ${{ matrix.suites }}
|
||||
|
||||
@@ -1,33 +1,48 @@
|
||||
name: Build test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
# GoReleaser and the darwin launcher take no gallery, docs or markdown
|
||||
# input, so a diff confined to these paths cannot change either binary.
|
||||
# The darwin job matters here: macOS is the scarcest runner class.
|
||||
#
|
||||
# backend/{cpp,go,python}/**: GoReleaser builds ./cmd/local-ai and the
|
||||
# launcher builds ./cmd/launcher; neither compiles a backend. The
|
||||
# before-hooks still matter, but their input is backend/backend.proto
|
||||
# (protogen-go) and go.mod/go.sum (go mod tidy), none of which live under
|
||||
# these prefixes, so a change to any of those still triggers a full run.
|
||||
# See .agents/ci-caching.md.
|
||||
paths-ignore:
|
||||
- 'gallery/**'
|
||||
- 'docs/**'
|
||||
- 'examples/**'
|
||||
- '**/*.md'
|
||||
- 'backend/cpp/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/python/**'
|
||||
paths:
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'addons/**'
|
||||
- 'configuration/**'
|
||||
- 'swagger/**'
|
||||
- 'prompt-templates/**'
|
||||
- '.goreleaser*'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/workflows/build-test.yaml'
|
||||
- 'Entitlements.plist'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'addons/**'
|
||||
- 'configuration/**'
|
||||
- 'swagger/**'
|
||||
- 'prompt-templates/**'
|
||||
- '.goreleaser*'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/workflows/build-test.yaml'
|
||||
- 'Entitlements.plist'
|
||||
|
||||
# Supersede an in-flight run when a PR gets a new push. Keyed on the PR number
|
||||
# so every push to the same PR shares a group; on a master push the key falls
|
||||
# back to github.sha (unique per commit) and cancel-in-progress is false, so
|
||||
# master runs never cancel each other -- each commit is built on its own.
|
||||
concurrency:
|
||||
group: ci-build-test-${{ github.event.pull_request.number || github.sha }}-${{ github.repository }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
@@ -74,7 +89,7 @@ jobs:
|
||||
name: launcher-macos
|
||||
path: dist/
|
||||
retention-days: 30
|
||||
|
||||
|
||||
launcher-build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -98,4 +113,4 @@ jobs:
|
||||
with:
|
||||
name: launcher-linux
|
||||
path: local-ai-launcher-linux.tar.xz
|
||||
retention-days: 30
|
||||
retention-days: 30
|
||||
@@ -3,11 +3,68 @@ name: 'tests'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'addons/**'
|
||||
- 'tests/**'
|
||||
- 'configuration/**'
|
||||
- 'swagger/**'
|
||||
- 'prompt-templates/**'
|
||||
- 'backend/cpp/**'
|
||||
- 'gallery/**'
|
||||
- 'docs/content/features/addons.md'
|
||||
- 'docs/content/features/tables.md'
|
||||
- 'docs/content/features/image-generation.md'
|
||||
- 'docs/content/features/diagnostics.md'
|
||||
- 'docs/content/reference/cli-reference.md'
|
||||
- 'docs/content/getting-started/troubleshooting.md'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/workflows/test.yml'
|
||||
- 'backend/index.yaml'
|
||||
- 'coverage-baseline.txt'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
branches: [master]
|
||||
tags: ['*']
|
||||
paths:
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'addons/**'
|
||||
- 'tests/**'
|
||||
- 'configuration/**'
|
||||
- 'swagger/**'
|
||||
- 'prompt-templates/**'
|
||||
- 'backend/cpp/**'
|
||||
- 'gallery/**'
|
||||
- 'docs/content/features/addons.md'
|
||||
- 'docs/content/features/tables.md'
|
||||
- 'docs/content/features/image-generation.md'
|
||||
- 'docs/content/features/diagnostics.md'
|
||||
- 'docs/content/reference/cli-reference.md'
|
||||
- 'docs/content/getting-started/troubleshooting.md'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/workflows/test.yml'
|
||||
- 'backend/index.yaml'
|
||||
- 'coverage-baseline.txt'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ci-tests-${{ github.event.pull_request.number || github.sha }}-${{ github.repository }}
|
||||
|
||||
@@ -10,28 +10,40 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'tests/e2e/distributed/**'
|
||||
- 'core/services/nodes/**'
|
||||
- 'core/services/messaging/**'
|
||||
- 'core/services/testutil/**'
|
||||
- 'core/services/studio/**'
|
||||
- 'core/services/collections/**'
|
||||
- 'core/services/syncstate/**'
|
||||
- 'core/services/advisorylock/**'
|
||||
- 'core/services/galleryop/**'
|
||||
- 'core/services/modeladmin/**'
|
||||
- 'core/services/failover/**'
|
||||
- 'core/services/storage/**'
|
||||
- 'core/application/distributed*.go'
|
||||
- 'core/cli/**'
|
||||
- 'pkg/natsauth/**'
|
||||
- 'scripts/nats-auth-setup.sh'
|
||||
- 'Makefile'
|
||||
- '.github/workflows/tests-e2e-distributed.yml'
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'configuration/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'tests/e2e/distributed/**'
|
||||
- 'Makefile'
|
||||
- '.github/workflows/tests-e2e-distributed.yml'
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'configuration/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
schedule:
|
||||
# Nightly, so a regression that the path filter missed shows within a day.
|
||||
# Pre-existing nightly coverage of the distributed suites.
|
||||
- cron: '17 3 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
@@ -3,19 +3,47 @@ name: 'E2E Backend Tests'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
# The e2e suite drives backends over gRPC directly and reads none of these
|
||||
# paths, so a diff confined to them cannot move it.
|
||||
# See .agents/ci-caching.md.
|
||||
paths-ignore:
|
||||
- 'gallery/**'
|
||||
- 'docs/**'
|
||||
- 'examples/**'
|
||||
- '**/*.md'
|
||||
paths:
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'addons/**'
|
||||
- 'tests/**'
|
||||
- 'configuration/**'
|
||||
- 'swagger/**'
|
||||
- 'prompt-templates/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/workflows/tests-e2e.yml'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
branches: [master]
|
||||
tags: ['*']
|
||||
paths:
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'addons/**'
|
||||
- 'tests/**'
|
||||
- 'configuration/**'
|
||||
- 'swagger/**'
|
||||
- 'prompt-templates/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/workflows/tests-e2e.yml'
|
||||
|
||||
concurrency:
|
||||
group: ci-tests-e2e-backend-${{ github.event.pull_request.number || github.sha }}-${{ github.repository }}
|
||||
|
||||
@@ -3,25 +3,46 @@ name: 'UI E2E Tests'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
# The suite builds the add-on UI bundles from addons/<id>/ui (global-setup)
|
||||
# and drives the real chats service, the add-on host and the studio
|
||||
# services through the test server, so a change to any of them can break a
|
||||
# spec without touching core/http.
|
||||
paths:
|
||||
- 'core/http/**'
|
||||
- 'core/application/**'
|
||||
- 'core/services/studio/**'
|
||||
- 'core/services/addons/**'
|
||||
- 'pkg/addons/**'
|
||||
- 'addons/**'
|
||||
- 'tests/e2e-ui/**'
|
||||
- 'tests/e2e/mock-backend/**'
|
||||
- 'tests/addons/fixture/**'
|
||||
- 'scripts/ensure-playwright-browser.sh'
|
||||
- '.github/workflows/tests-ui-e2e.yml'
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'tests/**'
|
||||
- 'configuration/**'
|
||||
- 'swagger/**'
|
||||
- 'prompt-templates/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'addons/**'
|
||||
- '.github/workflows/tests-ui-e2e.yml'
|
||||
- 'core/**'
|
||||
- 'pkg/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'backend/go/**'
|
||||
- 'backend/*.proto'
|
||||
- 'backend/*/*.proto'
|
||||
- 'tests/**'
|
||||
- 'configuration/**'
|
||||
- 'swagger/**'
|
||||
- 'prompt-templates/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'Makefile'
|
||||
- 'scripts/**'
|
||||
- '.github/actions/**'
|
||||
|
||||
concurrency:
|
||||
group: ci-tests-ui-e2e-${{ github.event.pull_request.number || github.sha }}-${{ github.repository }}
|
||||
|
||||
@@ -9,7 +9,7 @@ export GOCACHE="$root/.cache/addons/go-build" GOBIN="$root/.cache/addons/bin"
|
||||
export npm_config_cache="$root/.cache/addons/npm" PLAYWRIGHT_BROWSERS_PATH="$root/.cache/addons/browsers"
|
||||
export PATH="$GOBIN:$PATH"
|
||||
mode=${1:-}
|
||||
case "$mode" in --check|--prepare|--integration) ;; *) echo 'usage: prepare.sh --check|--prepare|--integration' >&2; exit 2;; esac
|
||||
case "$mode" in --check|--prepare|--prepare-build|--integration) ;; *) echo 'usage: prepare.sh --check|--prepare|--prepare-build|--integration' >&2; exit 2;; esac
|
||||
[ "$#" -eq 1 ] || exit 2
|
||||
fail() { echo "$*" >&2; exit 1; }
|
||||
go version 2>/dev/null | grep -Eq '^go version go1\.26\.[0-9]+ ' || fail 'Go 1.26.x required (GOTOOLCHAIN=local)'
|
||||
@@ -28,7 +28,7 @@ space() {
|
||||
available=$(df -Pk "$root" | awk 'END {print $4}')
|
||||
[ "$available" -ge 20971520 ] || fail 'stop: at least 20 GiB free required before setup/build'
|
||||
}
|
||||
if [ "$mode" = --prepare ]; then
|
||||
if [ "$mode" = --prepare ] || [ "$mode" = --prepare-build ]; then
|
||||
space
|
||||
mkdir -p "$GOBIN" "$root/.cache/addons/tmp"
|
||||
export TMPDIR="$root/.cache/addons/tmp"
|
||||
@@ -42,7 +42,11 @@ if [ "$mode" = --prepare ]; then
|
||||
(cd core/http/react-ui && npm ci --no-audit --no-fund)
|
||||
# Explicit setup only: the lockfile-pinned local CLI selects the browser
|
||||
# revision. Never use npx or install host OS packages here.
|
||||
if [ -z "${PLAYWRIGHT_CHROMIUM_PATH:-}" ]; then
|
||||
# A producer that only compiles fixtures needs no browser. Test consumers
|
||||
# still use --prepare and the unchanged browser readiness checks.
|
||||
if [ "$mode" = --prepare-build ]; then
|
||||
:
|
||||
elif [ -z "${PLAYWRIGHT_CHROMIUM_PATH:-}" ]; then
|
||||
space
|
||||
core/http/react-ui/node_modules/.bin/playwright install chromium
|
||||
else
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Same-run production fixtures only. Tar retains executable bits and paths."""
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import time
|
||||
|
||||
PATHS = ['core/http/react-ui/dist', 'pkg/grpc/proto', 'pkg/motion/proto',
|
||||
'tests/e2e/mock-backend/mock-backend', 'tests/e2e-ui/ui-test-server']
|
||||
BINARIES = PATHS[-2:]
|
||||
|
||||
def metadata(root):
|
||||
files = {}
|
||||
for name in PATHS:
|
||||
path = root / name
|
||||
assert path.exists(), f'missing fixture: {name}'
|
||||
for p in sorted(path.rglob('*')) if path.is_dir() else [path]:
|
||||
assert not p.is_symlink(), f'fixture symlink: {p}'
|
||||
if p.is_file():
|
||||
# tar's data filter removes group/other write bits.
|
||||
files[str(p.relative_to(root))] = [hashlib.sha256(p.read_bytes()).hexdigest(), p.stat().st_mode & 0o755]
|
||||
for name in BINARIES:
|
||||
assert os.access(root / name, os.X_OK), f'not executable: {name}'
|
||||
for name in ['core/http/react-ui/dist/index.html', 'pkg/grpc/proto/backend.pb.go',
|
||||
'pkg/grpc/proto/backend_grpc.pb.go', 'pkg/motion/proto/motion.pb.go']:
|
||||
assert (root / name).stat().st_size > 0, f'missing generated input: {name}'
|
||||
return files
|
||||
|
||||
def transfer(mode, root, archive, sha, tools=None):
|
||||
identity = {'sha': sha, 'config': 'ubuntu-24.04-amd64-go1.26-auth-production-v1', 'tools': tools or {}}
|
||||
if mode == 'pack':
|
||||
manifest = dict(identity, files=metadata(root))
|
||||
(root / 'addon-fixtures.json').write_text(json.dumps(manifest, sort_keys=True))
|
||||
with tarfile.open(archive, 'w:gz') as tar:
|
||||
for name in PATHS + ['addon-fixtures.json']:
|
||||
tar.add(root / name, arcname=name)
|
||||
assert archive.stat().st_size <= 512 * 1024 * 1024, 'fixture artifact exceeds 512 MiB'
|
||||
elif mode == 'unpack':
|
||||
with tarfile.open(archive) as tar:
|
||||
for member in tar.getmembers():
|
||||
name = member.name
|
||||
assert any(name == p or name.startswith(p + '/') for p in PATHS + ['addon-fixtures.json']), f'unexpected path: {name}'
|
||||
assert '..' not in Path(name).parts and not Path(name).is_absolute()
|
||||
assert member.isfile() or member.isdir(), f'unsupported member: {name}'
|
||||
tar.extractall(root, filter='data')
|
||||
manifest = json.loads((root / 'addon-fixtures.json').read_text())
|
||||
for key, value in identity.items():
|
||||
assert manifest[key] == value, f'fixture {key} mismatch'
|
||||
assert metadata(root) == manifest['files'], 'fixture checksum/mode mismatch'
|
||||
# The consumer checks out the same SHA later than the producer. Only
|
||||
# after identity and content verification, rebase generated mtimes onto
|
||||
# that checkout. Keep the server newer than its embedded dist, and leave
|
||||
# all source files untouched so a subsequent edit still fails -nt guards.
|
||||
stamp = time.time_ns()
|
||||
for name in manifest['files']:
|
||||
if name not in BINARIES: os.utime(root / name, ns=(stamp, stamp))
|
||||
for offset, name in enumerate(BINARIES, 1):
|
||||
os.utime(root / name, ns=(stamp + offset, stamp + offset))
|
||||
else:
|
||||
raise ValueError(mode)
|
||||
|
||||
if __name__ == '__main__':
|
||||
root = Path.cwd()
|
||||
sha = subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip()
|
||||
tools = {name: subprocess.check_output(command, text=True).strip() for name, command in
|
||||
{'go': ['go', 'version'], 'node': ['node', '--version'], 'protoc': ['protoc', '--version']}.items()}
|
||||
transfer(sys.argv[1], root, Path(os.environ['RUNNER_TEMP']) / 'addon-fixtures.tgz', sha, tools)
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Same-run fixture integrity and source freshness, without product builds."""
|
||||
import importlib.util
|
||||
import io
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
sys.dont_write_bytecode = True
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
spec = importlib.util.spec_from_file_location('fixtures', ROOT / 'scripts/ci/fixtures.py')
|
||||
fixtures = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(fixtures)
|
||||
|
||||
class Fixtures(unittest.TestCase):
|
||||
def make(self, root):
|
||||
for name in ['core/http/react-ui/dist/index.html', 'pkg/grpc/proto/backend.pb.go',
|
||||
'pkg/grpc/proto/backend_grpc.pb.go', 'pkg/motion/proto/motion.pb.go'] + fixtures.BINARIES:
|
||||
path = root / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text('fixture')
|
||||
if name in fixtures.BINARIES:
|
||||
path.chmod(0o755)
|
||||
|
||||
def test_roundtrip_modes_paths_sha_and_checksums(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); src = root/'src'; dst = root/'dst'
|
||||
src.mkdir(); dst.mkdir(); self.make(src)
|
||||
archive = root/'fixtures.tgz'
|
||||
fixtures.transfer('pack', src, archive, 'abc', {'go': '1.26'})
|
||||
fixtures.transfer('unpack', dst, archive, 'abc', {'go': '1.26'})
|
||||
self.assertEqual(fixtures.metadata(src), fixtures.metadata(dst))
|
||||
with self.assertRaisesRegex(AssertionError, 'sha mismatch'):
|
||||
fixtures.transfer('unpack', dst, archive, 'def', {'go': '1.26'})
|
||||
with self.assertRaisesRegex(AssertionError, 'tools mismatch'):
|
||||
fixtures.transfer('unpack', dst, archive, 'abc', {'go': '1.27'})
|
||||
manifest = src/'addon-fixtures.json'
|
||||
manifest.write_text(manifest.read_text().replace('production-v1', 'production-v2'))
|
||||
with tarfile.open(root/'config.tgz', 'w:gz') as tar:
|
||||
for name in fixtures.PATHS + ['addon-fixtures.json']:
|
||||
tar.add(src/name, arcname=name)
|
||||
with self.assertRaisesRegex(AssertionError, 'config mismatch'):
|
||||
fixtures.transfer('unpack', dst, root/'config.tgz', 'abc', {'go': '1.26'})
|
||||
with tarfile.open(archive, 'w:gz') as tar:
|
||||
payload = b'escape'; member = tarfile.TarInfo('../escape'); member.size = len(payload)
|
||||
tar.addfile(member, io.BytesIO(payload))
|
||||
with self.assertRaises(AssertionError):
|
||||
fixtures.transfer('unpack', dst, archive, 'abc', {'go': '1.26'})
|
||||
|
||||
def test_restore_rebases_freshness_only_after_identity_validation(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); src = root/'src'; dst = root/'dst'
|
||||
src.mkdir(); dst.mkdir(); self.make(src)
|
||||
old = time.time_ns()-10_000_000_000
|
||||
for name in fixtures.metadata(src): os.utime(src/name, ns=(old, old))
|
||||
archive = root/'fixtures.tgz'; fixtures.transfer('pack', src, archive, 'abc')
|
||||
# A separate checkout occurs after the producer built its files.
|
||||
sources = ['core/http/react-ui/src/components/addons/RecoveryPanel.jsx',
|
||||
'core/http/react-ui/src/components/addons/recoveryState.js',
|
||||
'core/http/react-ui/src/utils/addonsApi.js',
|
||||
'core/services/addons/recovery/backup.go',
|
||||
'core/http/endpoints/addons/recovery.go', 'tests/e2e/mock-backend/main.go']
|
||||
for name in sources:
|
||||
target = dst/name; target.parent.mkdir(parents=True, exist_ok=True); target.write_text('source')
|
||||
fixtures.transfer('unpack', dst, archive, 'abc')
|
||||
# Execute the exact freshness assertions from recovery.sh 33-39.
|
||||
text = (ROOT/'scripts/addons/recovery.sh').read_text()
|
||||
start = text.index('[ "$root/tests/e2e-ui/ui-test-server" -nt "$ui/dist/index.html" ]')
|
||||
end = text.index('\n\n', start)
|
||||
guard = text[start:end]
|
||||
env = dict(os.environ, root=str(dst), ui=str(dst/'core/http/react-ui'))
|
||||
self.assertEqual(subprocess.run(['sh', '-c', guard], env=env, capture_output=True).returncode, 0)
|
||||
source = dst/sources[3]
|
||||
later = (dst/'tests/e2e-ui/ui-test-server').stat().st_mtime_ns+1_000_000
|
||||
source.write_text('new edit'); os.utime(source, ns=(later, later))
|
||||
self.assertNotEqual(subprocess.run(['sh', '-c', guard], env=env, capture_output=True).returncode, 0)
|
||||
# Failed identity checks must not mark a stale binary fresh.
|
||||
with self.assertRaisesRegex(AssertionError, 'sha mismatch'):
|
||||
fixtures.transfer('unpack', dst, archive, 'different')
|
||||
self.assertLess((dst/'tests/e2e-ui/ui-test-server').stat().st_mtime_ns, source.stat().st_mtime_ns)
|
||||
|
||||
def test_missing_executable_and_tampered_payload(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); self.make(root)
|
||||
binary = root/fixtures.BINARIES[0]; binary.chmod(0o644)
|
||||
with self.assertRaisesRegex(AssertionError, 'not executable'):
|
||||
fixtures.transfer('pack', root, root/'fixtures.tgz', 'abc')
|
||||
binary.chmod(0o755)
|
||||
fixtures.transfer('pack', root, root/'fixtures.tgz', 'abc')
|
||||
binary.write_text('tampered')
|
||||
# Pack files with the old manifest, emulating damaged artifact contents.
|
||||
with tarfile.open(root/'bad.tgz', 'w:gz') as tar:
|
||||
for name in fixtures.PATHS + ['addon-fixtures.json']:
|
||||
tar.add(root/name, arcname=name)
|
||||
with self.assertRaisesRegex(AssertionError, 'checksum/mode mismatch'):
|
||||
fixtures.transfer('unpack', root, root/'bad.tgz', 'abc')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Static native-filter examples, not a GitHub expression evaluator. Needs PyYAML."""
|
||||
import fnmatch
|
||||
import subprocess
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
NAMES = ['test.yml', 'build-test.yaml', 'tests-ui-e2e.yml', 'tests-e2e.yml',
|
||||
'tests-e2e-distributed.yml', 'addons-tests.yml', 'addons-package.yml']
|
||||
|
||||
def load(name, base=False):
|
||||
path = '.github/workflows/' + name
|
||||
text = subprocess.check_output(['git', 'show', '16acc8926:' + path], cwd=ROOT) if base else (ROOT/path).read_text()
|
||||
return yaml.load(text, Loader=yaml.BaseLoader)
|
||||
|
||||
def matches(name, path, event):
|
||||
return any(fnmatch.fnmatchcase(path, pattern) for pattern in load(name)['on'][event]['paths'])
|
||||
|
||||
class NativePaths(unittest.TestCase):
|
||||
def test_shared_inputs_and_original_jobs(self):
|
||||
for path in (ROOT/'.github/workflows').iterdir():
|
||||
if path.suffix in ['.yml', '.yaml']:
|
||||
yaml.load(path.read_text(), Loader=yaml.BaseLoader)
|
||||
for name in NAMES:
|
||||
workflow = load(name); base = load(name, True)
|
||||
self.assertEqual(workflow['on']['push']['paths'], workflow['on']['pull_request']['paths'])
|
||||
self.assertEqual(workflow['on'].get('schedule'), base['on'].get('schedule'))
|
||||
self.assertEqual(set(workflow['jobs']), set(base['jobs']) | ({'prepare'} if name == 'addons-tests.yml' else set()))
|
||||
for event in ['pull_request', 'push']:
|
||||
for path in ['go.mod', 'go.sum', 'Makefile', 'scripts/build.sh', '.github/workflows/' + name]:
|
||||
self.assertTrue(matches(name, path, event), (name, path, event))
|
||||
self.assertFalse(matches(name, 'docs/content/news.md', event), name)
|
||||
self.assertFalse(matches(name, 'website/content/blog/news.md', event), name)
|
||||
if name != 'addons-tests.yml':
|
||||
self.assertEqual(workflow['jobs'], base['jobs'])
|
||||
self.assertNotIn('classify', str(workflow))
|
||||
self.assertNotIn('job-result', str(workflow))
|
||||
self.assertEqual(workflow['concurrency']['cancel-in-progress'], "${{ github.event_name == 'pull_request' }}")
|
||||
|
||||
def test_representative_consumers(self):
|
||||
cases = {
|
||||
'core/services/chats/store.go': NAMES[:-1],
|
||||
'pkg/model/loader.go': NAMES[:-1],
|
||||
'tests/e2e/mock-backend/main.go': ['test.yml', 'tests-e2e.yml', 'tests-ui-e2e.yml', 'addons-tests.yml'],
|
||||
'backend/go/cloud-proxy/main.go': ['test.yml', 'tests-e2e.yml', 'tests-ui-e2e.yml', 'addons-tests.yml'],
|
||||
'gallery/models/example.yaml': ['test.yml'],
|
||||
'backend/index.yaml': ['test.yml'],
|
||||
'coverage-baseline.txt': ['test.yml'],
|
||||
'Entitlements.plist': ['build-test.yaml'],
|
||||
'docs/content/features/image-generation.md': ['test.yml'],
|
||||
'docs/content/features/addons.md': ['test.yml', 'addons-tests.yml', 'addons-package.yml'],
|
||||
'docs/content/features/tables.md': ['test.yml', 'addons-tests.yml'],
|
||||
'docs/content/features/diagnostics.md': ['test.yml'],
|
||||
'docs/content/reference/cli-reference.md': ['test.yml'],
|
||||
'docs/content/getting-started/troubleshooting.md': ['test.yml'],
|
||||
}
|
||||
for path, names in cases.items():
|
||||
for name in names:
|
||||
for event in ['pull_request', 'push']:
|
||||
self.assertTrue(matches(name, path, event), (path, name, event))
|
||||
for event in ['pull_request', 'push']:
|
||||
self.assertFalse(matches('addons-package.yml', 'core/http/react-ui/src/index.css', event))
|
||||
self.assertFalse(matches('test.yml', 'future-area/unknown.input', event))
|
||||
|
||||
def test_suite_and_publish_preservation(self):
|
||||
old = load('addons-tests.yml', True)['jobs']['suites']
|
||||
new = load('addons-tests.yml')['jobs']['suites']
|
||||
self.assertEqual(new['strategy'], old['strategy'])
|
||||
self.assertEqual(new['needs'], 'prepare')
|
||||
self.assertNotIn('if', new)
|
||||
for path in ['scripts/addons/suites.json', 'pkg/addons/packaging/policy.go', 'pkg/addons/packaging/packaging_test.go']:
|
||||
self.assertEqual((ROOT/path).read_bytes(), subprocess.check_output(['git', 'show', '16acc8926:' + path], cwd=ROOT))
|
||||
package = load('addons-package.yml'); base = load('addons-package.yml', True)
|
||||
self.assertEqual(package['jobs'], base['jobs'])
|
||||
self.assertEqual(package['on']['push']['branches'], ['master'])
|
||||
self.assertEqual(package['on']['push']['tags'], ['v*'])
|
||||
self.assertEqual(package['on']['release'], base['on']['release'])
|
||||
self.assertNotIn('workflow_dispatch', package['on'])
|
||||
self.assertEqual(load('sandbox-images.yml'), load('sandbox-images.yml', True))
|
||||
for name in NAMES:
|
||||
self.assertEqual(load(name)['on']['push'].get('tags'), load(name, True)['on']['push'].get('tags'))
|
||||
for name in ['lint.yml', 'tests-ui-unit.yml', 'yaml-check.yml']:
|
||||
self.assertEqual(load(name), load(name, True))
|
||||
self.assertFalse((ROOT/'scripts/lib/ci-select.mjs').exists())
|
||||
self.assertFalse((ROOT/'scripts/ci/select.mjs').exists())
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: MIT
|
||||
set -eu
|
||||
root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
|
||||
mkdir -p "$tmp/scripts/addons" "$tmp/core/http/react-ui/node_modules/.bin" "$tmp/bin"
|
||||
cp "$root/scripts/addons/prepare.sh" "$tmp/scripts/addons/"
|
||||
cat > "$tmp/bin/go" <<'TOOL'
|
||||
#!/bin/sh
|
||||
case "$1" in version) echo 'go version go1.26.1 linux/amd64';; env) echo 1;; install|mod) exit 0;; *) exit 1;; esac
|
||||
TOOL
|
||||
printf '#!/bin/sh\necho v22.14.0\n' > "$tmp/bin/node"
|
||||
printf '#!/bin/sh\necho "libprotoc 26.1"\n' > "$tmp/bin/protoc"
|
||||
printf '#!/bin/sh\necho "fake 1 1 999999999"\n' > "$tmp/bin/df"
|
||||
printf '#!/bin/sh\nexit 0\n' > "$tmp/bin/npm"
|
||||
printf '#!/bin/sh\nexit 99\n' > "$tmp/core/http/react-ui/node_modules/.bin/playwright"
|
||||
chmod +x "$tmp/bin/"* "$tmp/core/http/react-ui/node_modules/.bin/playwright"
|
||||
unset PLAYWRIGHT_CHROMIUM_PATH
|
||||
PATH="$tmp/bin:$PATH" sh "$tmp/scripts/addons/prepare.sh" --prepare-build
|
||||
# Full consumer preparation must reach the browser and propagate its failure.
|
||||
if PATH="$tmp/bin:$PATH" sh "$tmp/scripts/addons/prepare.sh" --prepare; then
|
||||
echo 'consumer preparation unexpectedly omitted the browser' >&2
|
||||
exit 1
|
||||
fi
|
||||
Reference in new issue
Block a user