tqcq 060158eb2e
Gitea Actions Compatibility Suite / c7 (push) Failing after 0s
Gitea Actions Compatibility Suite / c9 (push) Failing after 0s
Gitea Actions Compatibility Suite / probe (push) Successful in 0s
Gitea Actions Compatibility Suite / c1 (push) Failing after 0s
Gitea Actions Compatibility Suite / c2 (push) Failing after 0s
Gitea Actions Compatibility Suite / c3 (push) Failing after 0s
Gitea Actions Compatibility Suite / c4 (push) Failing after 0s
Gitea Actions Compatibility Suite / c5 (push) Failing after 0s
Gitea Actions Compatibility Suite / c6 (push) Failing after 0s
Gitea Actions Compatibility Suite / c8 (push) Failing after 0s
Gitea Actions Compatibility Suite / c10 (push) Failing after 0s
Gitea Actions Compatibility Suite / aggregate (push) Failing after 0s
feat: Gitea Actions compatibility test suite
A self-validating test suite that proves Gitea's implementations of
GitHub-style actions behave like GitHub's. Push to any Gitea instance
with a registered runner — on push or manual dispatch, it runs 10
independent test workflows covering checkout, artifacts, caching,
language setup, expression contexts, workflow commands, services,
composite/reusable workflows, control flow, and known divergences.

Components:
- lib/assert.sh — self-asserting shell helper library (assert_eq,
  assert_contains, assert_exists, assert_not_exists, assert_match,
  sha256_of) with verified negative fail path
- 10 reusable workflows (on: workflow_call):
  01-checkout, 02-artifacts, 03-cache, 04-setup-runtime,
  05-contexts, 06-workflow-commands, 07-services,
  08-composite-reusable, 09-control-flow, 10-gitea-divergences
- 00-suite-runner.yml — sole entry (on: push + workflow_dispatch):
  probe gate (Gitea >= 1.22.0) -> c1-c10 parallel callers
  (c5/c8 forward secrets: inherit for act_runner #125) ->
  aggregate (if: always, skipped-as-failure)
- Fixtures: LFS 5MB binary, git tag v1-test, 4 language lockfiles
  (npm/go/pip/maven), sparse-cone layout
- Makefile: make lint (actionlint static gate)
- README: prerequisites, per-test expectations, correction table,
  manual eyeball checklist for UI-only behaviors
2026-07-07 16:08:49 +00:00

gitea-actions-compat-test

A self-asserting compatibility suite for Gitea Actions. Push this repo to a Gitea instance with a registered runner, and 00-suite-runner.yml runs ten independent test workflows that prove Gitea's implementation of GitHub-style actions behaves like GitHub's. Where Gitea diverges from upstream docs, the relevant check fails loudly with a clear message instead of passing silently.

This README is the human-facing guide. It tells you what to set up before you push, what each test verifies, where the docs disagree with the source code, and which behaviors cannot be checked from inside a job (those live in the manual eyeball checklist at the bottom).


HARD prerequisites

These are non-negotiable. The probe job in 00-suite-runner.yml fails fast and turns the whole run red if any of them is missing.

Runner

  • A runner registered with the label used by runs-on. The default label is ubuntu-24.04. If your instance uses a different label, set RUNS_ON_LABEL (or edit runs-on: in every workflow) to match, or the jobs will sit in waiting forever.

Gitea version

  • Gitea >= 1.22.0. This is the minimum to PARSE/accept these workflows. It is NOT a guarantee that every component passes. Several checks (notably the upload-artifact@v4 regression and reusable-workflow secret passthrough) may still fail on 1.22.x and only turn green on later point releases. See the per-test table below for which components need a newer version.

    To be explicit about the two thresholds:

    Threshold Meaning
    1.22.0 Minimum to PARSE/accept the workflow YAML. Below this, the runner rejects the suite outright.
    higher, version-dependent Minimum to PASS all components. Each component that needs more lists its version note in the per-test table.

Storage and caches

  • Storage backend enabled. The artifact and cache actions need object storage (local, MinIO, S3-compatible). Without it, uploads and downloads error out.
  • Cache server enabled. actions/cache@v3 relies on the Gitea cache server. Confirm it is reachable before expecting C3 to pass.
  • LFS enabled. test.bin is an LFS object. The checkout LFS test (C1) and the fixtures depend on git lfs working on both the server and the runner.

Action mirroring

  • Action mirroring allowed. ACTIONS_URL and DEFAULT_ACTIONS_URL must NOT be set to a non-mirror strategy. The workflows reference actions by the owner/repo@ref shorthand (for example actions/checkout@v4), which Gitea resolves by mirroring the upstream action on first use. If mirroring is off or pointed at a custom non-mirror source, those uses: refs 404 and the run fails at fetch time. The mirror fetch is itself a behavior under test.

Secrets

  • A repo secret TEST_SECRET=placeholder. Define it in the repo settings. The reusable workflows that read it (05-contexts and 08-composite-reusable) receive it through explicit forwarding (secrets: inherit) in the caller jobs, because Gitea's act_runner does not auto-forward caller secrets into reusable workflows by default (see act_runner #125 in the correction table). The placeholder value placeholder is what the assertion checks for, so do not put anything else there unless you also update the assertion.

Warm-up: run workflow_dispatch once

After first setup, trigger the suite once via the Run workflow button (workflow_dispatch) before drawing any conclusions from a push run. Action mirroring is lazy. The first fetch of each actions/* ref can take a moment to populate, and a cold run may show spurious fetch failures that vanish on the second attempt. One warm-up dispatch lets the mirror settle.


Per-test expectations

The suite is split into ten reusable workflows (on: workflow_call), each invoked by a caller job (c1 through c10) in 00-suite-runner.yml. Each component reports its own pass/fail, and aggregate fails the whole run if any component is not success (a skipped or cancelled component counts as failure).

# Workflow What it verifies Expected result Min-version notes
C1 01-checkout actions/checkout@v4 variants: default checkout, fetch-depth, lfs, ref to a tag, path, sparse-checkout cone, and persist-credentials (both true and false, with a non-vacuous assertion that the embedded credential token differs between the two). Pass on Gitea >= 1.22 with LFS enabled. The LFS sub-case is conditional on git lfs being present. 1.22.0 to parse. LFS sub-case needs LFS enabled on both server and runner.
C2 02-artifacts Artifacts v3 and v4 round-trip, cross-job checksum comparison (producer writes, consumer verifies sha256). v3 passes. v4 MAY fail on Gitea 1.22 (the GHES regression error). Passes on fixed versions. v4 needs a point release after the #31256 fix. v3 works on 1.22.
C3 03-cache Cache save/restore: a seed job writes a file and caches it (first run is a miss that saves), a restore job asserts an exact-key hit, then a miss-with-restore-keys fallback that restores the file but reports cache-hit == 'false'. Pass if the cache server is configured and reachable. Probe gates this; no extra version requirement beyond 1.22.
C4 04-setup-runtime setup-node, setup-python, setup-go, setup-java (Temurin, Maven) in a matrix, each asserting the installed version and exercising the action's built-in cache: option against the committed lockfiles. Pass if the lockfiles from T2 are present (package-lock.json, go.sum, requirements.txt, pom.xml). Java is Maven, not Gradle. 1.22.0. Runtime downloads depend on upstream availability.
C5 05-contexts github.sha / github.ref / github.run_id, a job env: var round-tripping into a step, the auto-injected GITHUB_TOKEN, the forwarded TEST_SECRET (asserted == 'placeholder'), needs outputs consumed by a dependent job, and if: conditionals with success() / failure() / always(). Pass. TEST_SECRET must be forwarded by the caller; the assertion distinguishes "empty because caller did not forward" from "empty because the repo secret is unset". cancelled() is manual-only (see eyeball checklist). 1.22.0 to parse. Secret forwarding depends on act_runner #125 (see correction table).
C6 06-workflow-commands GITHUB_OUTPUT (multi-line value, consumed via step output), GITHUB_PATH (added dir becomes callable), GITHUB_ENV (set in one step, read in the next), and ::warning:: / ::error:: / ::notice:: annotations plus $GITHUB_STEP_SUMMARY. Pass. The annotation assertion is emit-only: it checks the command was accepted and the process did not crash. Annotation RENDERING in the UI is a manual checklist item. 1.22.0. Rendering is UI-dependent (manual).
C7 07-services postgres:16 and redis:7 services with healthchecks, connectivity asserted from the job after healthcheck passes, plus a container: node:20 job that checks out the repo and runs an assertion inside the container. Pass if Docker is available on the runner. May be flaky on resource-constrained runners. The service healthcheck is the source of truth for "started" vs "harness cannot reach it". 1.22.0. Docker on the runner is required.
C8 08-composite-reusable A local composite action (lib/composite-greet) with input/output round-trip, and a reusable workflow that declares inputs and secrets on its workflow_call trigger and asserts ${{ inputs.who }} and the forwarded ${{ secrets.TEST_SECRET }} are populated. This is an intentional divergence probe for act_runner #125. May fail on buggy versions where reusable-workflow secrets are not forwarded. Version-dependent. See the note below.
C9 09-control-flow timeout-minutes (a job that sleeps past the limit, asserted dead from a dependent if: always() job as needs.slow.result != 'success'), continue-on-error (a failing step tolerated, dependent job sees needs.flaky.result == 'success'), and concurrency as parse-and-accept only. Pass. The timeout result string (failure vs cancelled) is version-dependent and asserted as != 'success' to avoid the ambiguity. Concurrency cancellation is manual-only. 1.22.0. Cancellation is server-side (manual).
C10 10-gitea-divergences environment: non-blocking proof (a job with environment: test-env runs to completion without blocking, asserted from a dependent job as needs.env.result == 'success'), the upload-artifact@v4 regression re-confirmed, and problem matchers emitted (emit-only). environment: is unsupported in act and treated as a no-op, so the job runs through. The v4 upload re-surfaces the #31256 regression on affected versions. Matcher rendering is manual-only. 1.22.0 to parse. v4 needs the fix from #31256.

Correction table: where docs and source disagree

GitHub's public docs describe the canonical runtime. Gitea's act implementation sometimes diverges. The table below records the known divergences with source citations and the covering test. Keep this bounded: at most 10 rows.

topic doc-claim source-truth citation covering-test
timeout-minutes Docs say it cancels a runaway job. Source enforces it. job_executor.go around L370-380 applies the timeout and kills the job. act source (job_executor.go L370-380) C9
success / failure / cancelled / always Docs describe four status-check functions. All four are implemented. interpreter.go around L621-640 defines them. act source (interpreter.go L621-640) C5
environment: Docs say it gates deployments behind protection rules. Genuinely unsupported. There is no environment field on the Job struct in act, so the key is parsed but treated as a no-op. act source (Job struct) C10
upload-artifact@v4 Docs frame v4 as a drop-in replacement for v3. Not on Gitea 1.22. v4 triggers a GHES-style regression error. Reported fixed but may recur on certain configurations (see Gitea issue #36024 for a Nov 2025 recurrence on Docker-based runners). Gitea issue #31256 C2, C10
Reusable-workflow secret inheritance Docs say secrets auto-forward to called workflows. They do not by default. You must pass secrets: inherit (or list them explicitly), otherwise the called workflow sees empty secrets. act_runner #125 C5, C8
continue-on-error Docs say the job reports success to dependents despite a tolerated step failure. Source honors this. workflow.go SetContinueOnError around L216-227 sets the result. act source (workflow.go L216-227) C9
Problem matchers Docs say registered matchers render annotations in the UI. Registration is accepted, but rendering depends on the Gitea UI and cannot be observed from inside the job. act source (matcher registration path) C6, C10
concurrency: cancel-in-progress Docs say an in-flight run is cancelled when a newer run starts. Enforcement is server-side and not observable from within the run. The suite parses and accepts the key only. Gitea server-side enforcement C9

reusable-workflow secret passthrough fix version

act_runner #125 is now CLOSED, fixed via act PR #41 ("Parse secret inputs in reusable workflows"). This suite still tests the behavior empirically in C8 (and the forwarding path in C5) rather than assuming a version, because the fix landed across several act and Gitea releases and older instances may still be affected. If C8 fails on your instance, the most likely cause is that you are running a version where the fix has not landed yet. Check the act_runner #125 thread for the current status rather than gating on a specific Gitea version number.


Manual eyeball checklist (UI-only behaviors)

A workflow cannot observe its own rendered UI. These behaviors are emitted where possible and left for a human to confirm in the Gitea web interface after a run.

  • Annotation rendering. Open the run for 06-workflow-commands. Confirm the ::warning::, ::error::, and ::notice:: messages appear as rendered annotations on the relevant steps (not just as raw text in the log).
  • Step summary rendering. In the same run, confirm the content written to $GITHUB_STEP_SUMMARY appears in the run's summary view.
  • Run cancellation. Trigger a run, then cancel it from the UI while it is executing. Confirm the run transitions to a cancelled state and that the aggregate job reflects it as a failure (not green).
  • Deployment environment gate. Note that environment: is treated as a no-op by act (see the correction table). There is no protection-rule gate to enforce, so this item is about confirming that absence rather than testing a gate. If you need real environment gating, that is a known gap.

Repository layout

.github/
  actionlint.yaml            narrowly-scoped lint config
  problem-matcher.json       problem matcher fixture
  workflows/
    00-suite-runner.yml      entry: probe -> c1..c10 -> aggregate
    01-checkout.yml          C1
    02-artifacts.yml         C2
    03-cache.yml             C3
    04-setup-runtime.yml     C4
    05-contexts.yml          C5
    06-workflow-commands.yml C6
    07-services.yml          C7
    08-composite-reusable.yml C8
    09-control-flow.yml      C9
    10-gitea-divergences.yml C10
lib/
  assert.sh                  self-asserting shell helpers
  composite-greet/action.yml local composite action (used by C8)
fixtures/                    sha256 of the LFS object, etc.
sparse-cone/                 layout for sparse-checkout tests
Makefile                     `make lint` runs actionlint

Numeric filename prefixes do NOT imply execution order. Ordering comes only from needs:. The prefixes exist to give humans a stable reading order.


Running the suite

  1. Push the repo to your Gitea instance.
  2. Confirm every HARD prerequisite above (runner label, version, storage, cache, LFS, mirroring, TEST_SECRET).
  3. Trigger one warm-up workflow_dispatch.
  4. Read the per-test results. aggregate is green only when the probe and every c1..c10 are success.
  5. Walk the manual eyeball checklist for the UI-only behaviors.
S
Description
No description provided
Readme 6.5 MiB
Languages
Shell 91.8%
Makefile 8.2%