diff --git a/.gitea/scripts/bats-coverage.sh b/.gitea/scripts/bats-coverage.sh
index 43c76d9..ce34c87 100755
--- a/.gitea/scripts/bats-coverage.sh
+++ b/.gitea/scripts/bats-coverage.sh
@@ -1,37 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
-WORKSPACE_VOLUME="${1:-}"
-REPORT_DIR="${2:-}"
+REPORT_DIR="${1:-}"
+COVERAGE_DIR="${REPORT_DIR}/coverage"
-[ -n "$WORKSPACE_VOLUME" ] || { echo "ERROR: workspace volume name required" >&2; exit 1; }
[ -n "$REPORT_DIR" ] || { echo "ERROR: report directory required" >&2; exit 1; }
-HAS_COVERAGE=false
-COVERAGE_SRC=""
-if docker run --rm -v "$WORKSPACE_VOLUME":/data alpine sh -c '[ -d /data/coverage ] && ls -A /data/coverage | grep -q .' 2>/dev/null; then
- COVERAGE_SRC="/data/coverage"
+if [ -d coverage ]; then
+ mkdir -p "$COVERAGE_DIR"
+ cp -a coverage/. "$COVERAGE_DIR/"
fi
-if [ -n "$COVERAGE_SRC" ]; then
- mkdir -p "$REPORT_DIR/coverage"
- docker run --rm -v "$WORKSPACE_VOLUME":/data alpine tar c -C "$COVERAGE_SRC" . | tar x -C "$REPORT_DIR/coverage"
- HAS_COVERAGE=true
+if [ -d "$COVERAGE_DIR" ] && [ ! -f "$COVERAGE_DIR/index.html" ]; then
+ SHA8="${GITHUB_SHA:0:8}"
+ {
+ echo '
'
+ echo "Coverage report ${SHA8}"
+ echo ''
+ echo "Coverage report ${SHA8}
"
+ while IFS= read -r -d '' f; do
+ base=$(basename "$f")
+ name="${base%.*}"
+ name="${name//-/ }"
+ echo "- ${name^}
"
+ done < <(find "$COVERAGE_DIR" -maxdepth 1 -type f \( -name '*.html' -o -name '*.txt' \) ! -name index.html -print0 2>/dev/null || true)
+ echo '
'
+ } > "$COVERAGE_DIR/index.html"
fi
-
-cat > "$REPORT_DIR/index.html" << EOF
-
-Bats report ${GITHUB_SHA:0:8}
-
-Bats report ${GITHUB_SHA:0:8}
-' >> "$REPORT_DIR/index.html"
diff --git a/.gitea/workflows/example-bats-tests.yml b/.gitea/workflows/example-bats-tests.yml
index 9557334..b3f8443 100644
--- a/.gitea/workflows/example-bats-tests.yml
+++ b/.gitea/workflows/example-bats-tests.yml
@@ -6,8 +6,9 @@ on:
required: true
type: string
bats-image:
- required: true
+ required: false
type: string
+ default: gitea.app.keskikuja.site/niko/ci-bats:latest
secrets:
GITEA_TOKEN:
required: true
@@ -23,6 +24,8 @@ env:
jobs:
bats:
runs-on: ubuntu-latest
+ container:
+ image: ${{ inputs.bats-image }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
@@ -31,34 +34,18 @@ jobs:
path: .ci
- name: Run bats tests
- id: bats-tests
- shell: bash
run: |
- docker volume create bats-workspace
- tar c . | docker run --rm -i -v bats-workspace:/data alpine tar x -C /data
- mkdir -p "reports/${GITHUB_SHA:0:8}/bats"
- set +e
- docker run --rm \
- -v bats-workspace:/data \
- --entrypoint bash ${{ inputs.bats-image }} \
- -c 'cd /data && bashcov -- bats tests/' \
- > "reports/${GITHUB_SHA:0:8}/bats/results.txt" 2>&1
- BATS_EXIT=$?
- bash .ci/.gitea/scripts/bats-coverage.sh bats-workspace "reports/${GITHUB_SHA:0:8}/bats"
- docker volume rm bats-workspace > /dev/null 2>&1
- bash .ci/.gitea/scripts/bats-report.sh "reports/${GITHUB_SHA:0:8}/bats"
- echo "BATS_EXIT=${BATS_EXIT}" >> "${GITHUB_ENV}"
- exit ${BATS_EXIT}
+ mkdir -p reports/bats
+ bashcov -- bats tests/ > reports/bats/results.txt 2>&1
- - name: Publish bats reports
+ - name: Post-process coverage
if: always()
- run: bash .ci/scripts/publish-git-pages.sh bats
+ run: bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
- - name: Report status
+ - name: Post-process test report
if: always()
- run: |
- if [ "${BATS_EXIT}" = "0" ]; then
- bash .ci/scripts/report-status.sh success "Link to Bats reports" unit-tests bats
- else
- bash .ci/scripts/report-status.sh failure "Link to Bats reports" unit-tests bats
- fi
+ run: bash .ci/.gitea/scripts/bats-report.sh reports/bats
+
+ - name: Report
+ if: always()
+ run: bash .ci/scripts/ci-report.sh "Bats test report" unit-tests bats
diff --git a/.gitea/workflows/example-build-ci-cucumber.yml b/.gitea/workflows/example-build-ci-cucumber.yml
index d45c482..5821f2f 100644
--- a/.gitea/workflows/example-build-ci-cucumber.yml
+++ b/.gitea/workflows/example-build-ci-cucumber.yml
@@ -25,14 +25,14 @@ on:
jobs:
load-config:
- uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
+ uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: ${{ inputs.config_path }}
build-push:
needs: [load-config]
- uses: niko/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@v1
+ uses: niko/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
diff --git a/.gitea/workflows/example-cucumber-tests.yml b/.gitea/workflows/example-cucumber-tests.yml
index 88fb68b..d604044 100644
--- a/.gitea/workflows/example-cucumber-tests.yml
+++ b/.gitea/workflows/example-cucumber-tests.yml
@@ -6,8 +6,9 @@ on:
required: true
type: string
cucumber-node-image:
- required: true
+ required: false
type: string
+ default: gitea.app.keskikuja.site/niko/ci-cucumber:latest
secrets:
GITEA_TOKEN:
required: true
@@ -33,36 +34,14 @@ jobs:
path: .ci
- name: Run cucumber tests
- id: cucumber-tests
shell: bash
run: |
- mkdir -p "reports/${GITHUB_SHA:0:8}/cucumber"
- set +e
+ mkdir -p reports/cucumber
npx cucumber-js \
- --format json:"reports/${GITHUB_SHA:0:8}/cucumber/report.json" \
- --format html:"reports/${GITHUB_SHA:0:8}/cucumber/index.html" 2>&1
- CUCUMBER_EXIT=$?
- echo "CUCUMBER_EXIT=${CUCUMBER_EXIT}" >> "${GITHUB_ENV}"
- exit ${CUCUMBER_EXIT}
+ --format json:reports/cucumber/report.json \
+ --format html:reports/cucumber/report.html 2>&1
- - name: Publish cucumber reports
- if: always()
- run: bash .ci/scripts/publish-git-pages.sh cucumber
-
- - name: Report status
+ - name: Report
if: always()
shell: bash
- run: |
- if [ "${CUCUMBER_EXIT}" = "0" ]; then
- if [ -f "reports/${GITHUB_SHA:0:8}/cucumber/index.html" ]; then
- bash .ci/scripts/report-status.sh success "Link to Cucumber reports" acc-tests cucumber
- else
- bash .ci/scripts/report-status.sh success "Link to Cucumber reports" acc-tests
- fi
- else
- if [ -f "reports/${GITHUB_SHA:0:8}/cucumber/index.html" ]; then
- bash .ci/scripts/report-status.sh failure "Link to Cucumber reports" acc-tests cucumber
- else
- bash .ci/scripts/report-status.sh failure "Link to Cucumber reports" acc-tests
- fi
- fi
+ run: bash .ci/scripts/ci-report.sh "Cucumber test report" acc-tests cucumber
diff --git a/.gitea/workflows/example-feature.yml b/.gitea/workflows/example-feature.yml
index 47d0fd5..42d79b2 100644
--- a/.gitea/workflows/example-feature.yml
+++ b/.gitea/workflows/example-feature.yml
@@ -20,7 +20,6 @@ jobs:
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
- bats-image: gitea.app.keskikuja.site/niko/ci-bats:latest
cucumber:
name: Cucumber tests
@@ -29,7 +28,6 @@ jobs:
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
- cucumber-node-image: gitea.app.keskikuja.site/niko/ci-cucumber:latest
report-summary:
name: Report Summary
diff --git a/.gitea/workflows/example-main.yml b/.gitea/workflows/example-main.yml
index 5cc8c4c..a734123 100644
--- a/.gitea/workflows/example-main.yml
+++ b/.gitea/workflows/example-main.yml
@@ -29,7 +29,6 @@ jobs:
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
- bats-image: gitea.app.keskikuja.site/niko/ci-bats:latest
cucumber:
name: Cucumber tests
@@ -39,7 +38,6 @@ jobs:
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
- cucumber-node-image: gitea.app.keskikuja.site/niko/ci-cucumber:latest
build-push:
name: Build & Push Docker
diff --git a/Dockerfile.ci-bats b/Dockerfile.ci-bats
index 1ca8e17..6c54ae0 100644
--- a/Dockerfile.ci-bats
+++ b/Dockerfile.ci-bats
@@ -1,3 +1,3 @@
-FROM bats/bats:latest
+FROM bats/bats:1.11.0
RUN apk add --no-cache lsof python3 jq curl ruby && \
gem install bashcov -v 3.3.0
diff --git a/README.md b/README.md
index e02fb62..a94da3a 100644
--- a/README.md
+++ b/README.md
@@ -123,8 +123,8 @@ Hae token Giteasta:
```bash
GITEA_URL="https://"
-GITEA_ACTIONS_TOKEN=""
GITEA_ACTIONS_NAMESPACE="gitea-actions"
+GITEA_ACTIONS_TOKEN=""
```
### 3. Tee secret vain init install yhteydessä
@@ -158,6 +158,7 @@ helm upgrade --install act-runner gitea/actions \
--set giteaRootURL="$GITEA_URL" \
--set existingSecret=act-runner-token \
--set existingSecretKey=token \
+ --set statefulset.replicas=3 \
--set statefulset.runner.tag=1.0.8 \
--set statefulset.dind.tag=29.5.2-dind \
--set-string 'statefulset.runner.config=log:
diff --git a/scripts/ci-report.sh b/scripts/ci-report.sh
new file mode 100644
index 0000000..9cc1dd7
--- /dev/null
+++ b/scripts/ci-report.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+DESCRIPTION="${1:-}"
+CONTEXT="${2:-}"
+SUITE="${3:-}"
+
+[ -n "$DESCRIPTION" ] || { echo "ERROR: description argument required" >&2; exit 1; }
+[ -n "$CONTEXT" ] || { echo "ERROR: context argument required" >&2; exit 1; }
+[ -n "$SUITE" ] || { echo "ERROR: suite argument required" >&2; exit 1; }
+
+REPORT_DIR="reports/${SUITE}"
+
+if [ ! -d "$REPORT_DIR" ]; then
+ echo "ERROR: $REPORT_DIR not found" >&2
+ bash .ci/scripts/report-status.sh failure "$DESCRIPTION" "$CONTEXT"
+ exit 1
+fi
+
+FILES=()
+while IFS= read -r -d '' f; do
+ FILES+=("$(basename "$f")")
+done < <(find "$REPORT_DIR" -maxdepth 1 -type f ! -name index.html -print0 2>/dev/null || true)
+
+SUBDIRS=()
+while IFS= read -r -d '' d; do
+ name="${d#$REPORT_DIR/}"
+ [ -f "$d/index.html" ] && SUBDIRS+=("$name")
+done < <(find "$REPORT_DIR" -maxdepth 1 -type d ! -name . -print0 2>/dev/null || true)
+
+TOTAL=$(( ${#FILES[@]} + ${#SUBDIRS[@]} ))
+
+if [ "$TOTAL" -eq 0 ]; then
+ echo "ERROR: no reportable items in $REPORT_DIR" >&2
+ bash .ci/scripts/report-status.sh failure "$DESCRIPTION" "$CONTEXT"
+ exit 1
+fi
+
+SHA8="${GITHUB_SHA:0:8}"
+
+humanize() {
+ local name="$1"
+ name="${name%.*}"
+ name="${name//-/ }"
+ name="${name//_/ }"
+ echo "${name^}"
+}
+
+generate_index() {
+ local html
+ html=''
+ html+="$DESCRIPTION"
+ html+=''
+ html+="$DESCRIPTION
"
+ for f in "${FILES[@]}"; do
+ html+="- $(humanize "$f")
"
+ done
+ for d in "${SUBDIRS[@]}"; do
+ html+="- ${d^}
"
+ done
+ html+='
'
+ printf '%s' "$html" > "$REPORT_DIR/index.html"
+}
+
+STAGED="reports/${SHA8}/${SUITE}"
+mkdir -p "$STAGED"
+
+if [ "$TOTAL" -eq 1 ]; then
+ cp -a "$REPORT_DIR/." "$STAGED/"
+ bash .ci/scripts/publish-git-pages.sh "$SUITE"
+
+ if [ ${#FILES[@]} -eq 1 ]; then
+ ENTRY="${FILES[0]}"
+ else
+ ENTRY="${SUBDIRS[0]}/index.html"
+ fi
+ URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}/${ENTRY}"
+ bash .ci/scripts/report-status.sh success "$DESCRIPTION" "$CONTEXT" "" "$URL"
+else
+ generate_index
+ cp -a "$REPORT_DIR/." "$STAGED/"
+ bash .ci/scripts/publish-git-pages.sh "$SUITE"
+ bash .ci/scripts/report-status.sh success "$DESCRIPTION" "$CONTEXT" "$SUITE"
+fi
+
+rm -rf "$STAGED"
diff --git a/scripts/publish-git-pages.sh b/scripts/publish-git-pages.sh
index 81228a7..acee12c 100755
--- a/scripts/publish-git-pages.sh
+++ b/scripts/publish-git-pages.sh
@@ -33,7 +33,42 @@ else
fi
mkdir -p "$TARGET"
cp -a "$REPORT_DIR/." "$TARGET/"
-cat > "$WORK/${OWNER}/${REPO}/reports/${SHA8}/.meta" </dev/null || true)
+ while IFS= read -r -d '' d; do
+ name=$(basename "$d")
+ [ -f "$d/index.html" ] && items+=("$name")
+ done < <(find "$TARGET" -maxdepth 1 -type d ! -name . -print0 2>/dev/null || true)
+
+ if [ ${#items[@]} -gt 1 ]; then
+ {
+ echo ''
+ echo "Test report ${SHA8}"
+ echo ''
+ echo "Test report ${SHA8}
"
+ for item in "${items[@]}"; do
+ label="${item%.*}"
+ label="${label//-/ }"
+ label="${label//_/ }"
+ if [ -f "$TARGET/$item" ]; then
+ echo "- ${label^}
"
+ else
+ echo "- ${label^}
"
+ fi
+ done
+ echo '
'
+ } > "$TARGET/index.html"
+ fi
+fi
+
+cat > "$TARGET/.meta" <> GITHUB_ENV` -käärettä ei tarvita.
```yaml
- name: Run tests
shell: bash
run: |
> results.txt 2>&1
- EXIT=$?
- echo "EXIT=${EXIT}" >> "${GITHUB_ENV}"
- exit ${EXIT}
```
**Miksi ei pipeä (`| tee`):**
@@ -101,8 +99,29 @@ Jokainen testi kaappaa komentonsa exit-koodin eksplisiittisesti:
> results.txt 2>&1
```
-Ilman `EXIT=$?` + `exit ${EXIT}` komento voi feilata mutta job menee läpi vihreänä — `container:`-modessa
-shellin käyttäytyminen vaihtelee.
+`set -e` ei pelasta pipe-tilanteessa — `|` syö exit-koodin kuten ennenkin. Redirectillä exit-koodi
+välittyy luonnollisesti.
+
+**Yksi asia per step:** Älä koskaan niputa useaa post-process-komentoa samaan `run:`-blockiin.
+`bash -e` pysäyttää koko stepin jos yksi komento epäonnistuu — seuraavat jäävät ajamatta.
+Käytä erillisiä steppejä `if: always()`:lla, jotta jokainen vaihe ajetaan itsenäisesti:
+
+```yaml
+# VÄÄRIN — jos coverage epäonnistuu, report jää generoimatta
+- name: Post-process reports
+ run: |
+ bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
+ bash .ci/.gitea/scripts/bats-report.sh reports/bats
+
+# OIKEIN — erilliset stepit if: always()
+- name: Post-process coverage
+ if: always()
+ run: bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
+
+- name: Post-process test report
+ if: always()
+ run: bash .ci/.gitea/scripts/bats-report.sh reports/bats
+```
## 4. Konttipolitiikka
@@ -114,44 +133,33 @@ shellin käyttäytyminen vaihtelee.
`latest` on näille paras käytäntö, ei kompromissi
3. **Ei koskaan `curl`-latauksia CI-ajon sisällä** — työkalujen asennus CI-stepeissä hidastaa,
epäluotettavaa, ja vaikeuttaa toistettavuutta
+4. **Konttikuva hallitaan workflow'ssa, ei kutsujassa** — jos workflow vaatii tietyn
+ konttikuvan, se määritellään oletuksena (`default:`) workflow'n inputissa.
+ Kutsujan ei tarvitse tietää eikä välittää image-nimeä ellei halua ylikirjoittaa.
CI-kontin build-workflow'n template: [skills/ci-container-build/SKILL.md](../ci-container-build/SKILL.md) — sisältää
valmiin `ci-container-build-.yml`-pohjan jossa `workflow_dispatch`-tuki manuaaliajoon.
### 4.1 CI-kontin ajaminen jobissa
-CI-kontin voi ajaa joko `container:`-direktiivillä (kaikki stepit kontissa)
-tai `docker run --rm`:llä stepin sisällä (checkout natiivisti). Molemmat tavat
-toimivat.
+Ainoa sallittu tapa on `container:`-direktiivi. `docker run` komennolla kontin
+käynnistäminen stepin sisällä on anti-pattern.
+
+**Miksi:** `docker run` erilliskonttina aiheuttaa:
+- Tiedostojen jako vaatii erillisen volyyminhallinnan (`docker volume create`)
+- Coverage-data jää volyymiin, ei filesystemille → post-process-skriptit eivät löydä sitä
+- Ylimääräisiä siirtoja (`tar`, `docker cp`), jotka voivat epäonnistua hiljaa
+- Vaikeampi debugata (data on kontissa, ei CWD:ssä)
+
+`container:`-direktiivillä kaikki ajetaan samassa kontissa — tiedostot ovat suoraan
+filesystemillä, post-process-skriptit näkevät ne ilman erillistä siirtoa.
```yaml
-# Tapa A: container:-direktiivi
jobs:
:
runs-on: ubuntu-latest
container:
image: ${{ inputs. }}
- steps:
- - uses: actions/checkout@v4
- - uses: actions/checkout@v4
- with:
- repository: /gitea-ci-library
- path: .ci
- - name: Run
- shell: bash
- run: |
- mkdir -p "reports/${GITHUB_SHA:0:8}/"
- > "reports/${GITHUB_SHA:0:8}//results.txt" 2>&1
- EXIT=$?
- echo "EXIT=${EXIT}" >> "${GITHUB_ENV}"
- exit ${EXIT}
-```
-
-```yaml
-# Tapa B: docker run --rm stepin sisällä (kuten example-bats-tests.yml)
-jobs:
- :
- runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
@@ -162,87 +170,179 @@ jobs:
- name: Run
shell: bash
run: |
- docker volume create ws-
- tar c . | docker run --rm -i -v ws-:/data alpine tar x -C /data
- mkdir -p "reports/${GITHUB_SHA:0:8}/"
- set +e
- docker run --rm \
- -v ws-:/data \
- --entrypoint bash ${{ inputs. }} \
- -c 'cd /data && ' \
- > "reports/${GITHUB_SHA:0:8}//results.txt" 2>&1
- EXIT=$?
- docker volume rm ws- > /dev/null 2>&1
- echo "EXIT=${EXIT}" >> "${GITHUB_ENV}"
- exit ${EXIT}
+ mkdir -p "reports/"
+ > "reports//results.txt" 2>&1
- - name: Publish reports
- if: always()
- run: bash .ci/scripts/publish-git-pages.sh
-
- - name: Report status
+ - name: Post-process reports
if: always()
run: |
- if [ "${EXIT}" = "0" ]; then
- bash .ci/scripts/report-status.sh success ""
- else
- bash .ci/scripts/report-status.sh failure ""
- fi
+
+
+ - name: Report
+ if: always()
+ run: bash .ci/scripts/ci-report.sh ""
```
-**Malli:** `example-bats-tests.yml` (tapa B).
+Jos testi tuottaa raportteja suoraan ilman jälkikäsittelyä, Post-process-steppiä ei tarvita.
+Jos jälkikäsittely on tarpeen (coverage-siirto, HTML-generointi raa'asta outputista),
+se tehdään omassa stepissä `if: always()` — katso tarkemmin [Raporttitasot](#5-raporttitasot).
+
+**Mallit:**
+- `example-cucumber-tests.yml` — ei post-processia
+- `example-bats-tests.yml` — post-process coverage + report
## 5. Raporttitasot
-Testi tuottaa raportin `reports/${GITHUB_SHA:0:8}//`-hakemistoon. `publish-git-pages.sh` julkaisee sen,
-`report-status.sh` linkittää commit-statusin siihen. Molemmat `if: always()`.
+Testi tuottaa raportin `reports//`-hakemistoon. Yksi `ci-report.sh`-kutsu hoitaa sekä
+julkaisun että commit-statuksen — erillistä Publish + Report Status -kaksivaiheisuutta ei tarvita.
-### Taso 1: Pelkkä teksti
+### Taso 1: Ei jälkikäsittelyä
-Kun testi tuottaa vain stdout/stderr — tallennetaan `results.txt`:
+Kun testi tuottaa raportit suoraan (kuten `pytest --html` tai `cucumber-js --format html`):
```yaml
- name: Run tests
shell: bash
run: |
- mkdir -p "reports/${GITHUB_SHA:0:8}/"
- > "reports/${GITHUB_SHA:0:8}//results.txt" 2>&1
- EXIT=$?
- echo "EXIT=${EXIT}" >> "${GITHUB_ENV}"
- exit ${EXIT}
+ mkdir -p "reports/"
+
-- name: Publish reports
+- name: Report
if: always()
- shell: bash
- run: bash .ci/scripts/publish-git-pages.sh
+ run: bash .ci/scripts/ci-report.sh ""
+```
-- name: Report status
- if: always()
+### Taso 2: Jälkikäsittely tarvitaan
+
+Kun testi tuottaa raakadataa (stdout, coverage-tiedostot) joka pitää muuntaa tai siirtää
+`reports//`-hakemistoon, käytetään Post-process-steppejä. **Jokainen operaatio
+omassa stepissään** — älä koskaan niputa useaa post-process-komentoa samaan `run:`-blockiin:
+
+```yaml
+- name: Run tests
shell: bash
run: |
- if [ "${EXIT}" = "0" ]; then
- bash .ci/scripts/report-status.sh success ""
- else
- bash .ci/scripts/report-status.sh failure ""
- fi
+ mkdir -p "reports/"
+ > "reports//results.txt" 2>&1
+
+- name: Post-process coverage
+ if: always()
+ run: /coverage/-hakemistoon>
+
+- name: Post-process test report
+ if: always()
+ run:
+
+- name: Report
+ if: always()
+ run: bash .ci/scripts/ci-report.sh ""
```
-### Taso 2: HTML-raportti
+**Huomio subdir-sisällöstä:** Jos testi tuottaa dataa alihakemistoon (esim.
+coverage `./coverage/`-kansioon), se pitää erikseen SIIRTÄÄ
+`reports///`-hakemistoon ennen `ci-report.sh`:n ajoa.
+Ilman siirtoa sisältö ei näy index-sivulla, vaikka työkalu tuottaisi sen oikein.
+Subdir vaatii lisäksi `index.html`:n, jotta `ci-report.sh` löytää sen.
-Kun testi tuottaa strukturoitua dataa (JUnit XML, coverage, tms.) — generoidaan HTML ja `index.html`:
+**Miksi:** Gitea Actions käyttää `bash -e`-oletusta. Jos yksi post-process-komento
+epäonnistuu (esim. `set -euo pipefail`-skripti), koko stepi pysähtyy eivätkä seuraavat
+komennot käynnisty — raportti jää julkaisematta. Erilliset stepit `if: always()` takaavat
+että jokainen post-process-vaihe ajetaan itsenäisesti.
+
+### Monta raportoitavaa tiedostoa
+
+Kun `reports//`-hakemistossa on useita tiedostoja tai alihakemistoja,
+`ci-report.sh` generoi automaattisesti `reports//index.html` jos hakemistossa
+on enemmän kuin yksi raportoitava item.
```
-reports///
-├── index.html ← generoitu: linkit alla oleviin
-├── results.txt ← testin stdout
-├── junit.xml ← testin JUnit XML -output
-└── junit.html ← generoitu HTML (xsltproc, tms.)
+reports//
+├── results.txt ← testin stdout (skannataan FILES)
+├── test-report.html ← generoitu HTML (skannataan FILES)
+└── / ← alihakemisto (skannataan SUBDIRS)
+ └── index.html ← VAIN jos tämä on olemassa
```
-`index.html` linkittää kaikkiin raporttitiedostoihin. Selain avaa sen ja navigoi sieltä
-yksittäisiin raportteihin.
+**Subdir-sääntö:** Alihakemisto näkyy indexissä VAIN jos se sisältää `index.html`:n.
+Pelkkä tyhjä subdir ilman `index.html`:ää ei näy — tämä on yleisin syy miksi
+jokin raportin osa (kuten coverage) puuttuu indexistä.
-## 6. Nimeäminen
+## 6. Raportin julkaisukelpoisuus
+
+`ci-report.sh` päättää onko raportti julkaisukelpoinen skannaamalla
+`reports//`-hakemistoa.
+
+### Mitä skannataan
+
+| Mitä | Sääntö |
+|---|---|
+| **Tiedostot (FILES)** | Kaikki `reports//`-juuressa olevat tiedostot paitsi `index.html` |
+| **Alihakemistot (SUBDIRS)** | Vain ne, joissa on `index.html` |
+
+### Julkaisukelpoisuus
+
+| Tila | Seuraus |
+|---|---|
+| `FILES + SUBDIRS = 0` | **Failure** — `ci-report.sh` palauttaa virheen, raporttia ei julkaista |
+| `FILES + SUBDIRS = 1` | Suora linkki itemiin — ei generoi index-sivua |
+| `FILES + SUBDIRS > 1` | Generoi `reports//index.html`-sivun, linkit kaikkiin itemeihin |
+
+### Esimerkki: coverage-näkymä
+
+```
+reports//coverage/index.html ← on olemassa
+```
+
+Coverage-dataa ei siirretä automaattisesti. Testin tai post-process-stepin pitää
+siirtää coverage `reports//coverage/`-hakemistoon ja varmistaa että
+`index.html` on mukana. Sama periaate pätee mihin tahansa subdir-sisältöön.
+
+## 7. Debug-ohje: raportti ei näy
+
+### 1. Aja lokaalisti samalla komennolla kuin CI
+
+Näet mitä tiedostoja syntyy, mihin ne tulevat ja mikä on työkalun exit-koodi.
+
+```bash
+# Esimerkki
+mkdir -p reports/bats
+bashcov -- bats tests/ > reports/bats/results.txt 2>&1
+echo "exit: $?"
+ls -la reports/bats/
+```
+
+### 2. Lisää `echo "DEBUG: ..." >&2` ennen ja jälkeen kriittisen operaation
+
+Debuggaus stderriin (`>&2`) näkyy CI-logissa eikä häiritse skriptin normaalia outputia.
+
+```bash
+echo "DEBUG: coverage exists? $([ -d coverage ] && echo YES || echo NO)" >&2
+echo "DEBUG: target/index.html exists? $([ -f reports/suite/coverage/index.html ] && echo YES || echo NO)" >&2
+```
+
+### 3. Tarkista kutsuparametrit
+
+Yleisin virhe: skripti odottaa `$1` = X, mutta kutsuja antaa `$1` = Y ja `$2` = X.
+Skripti lukee väärän parametrin ja etsii dataa väärästä paikasta.
+
+### 4. Tarkista tiedostopolut
+
+1. Onko lähdetiedosto olemassa ennen kopiointia?
+2. Onko kohde olemassa kopioinnin jälkeen?
+3. Onko `index.html` subdirissä (vaaditaan `ci-report.sh`:lle)?
+
+Jos vastaus johonkin on "ei" — tiedät mikä pitää korjata.
+
+### 5. Poista debug-echot kun ongelma on korjattu
+
+Debug-rivit eivät kuulu tuotantoon.
+
+### 6. Älä kokeile — debuggaa
+
+Kokeilu = arvaus. Debuggaus = lisää echo, aja, lue logi, eristä ongelma.
+Vasta sitten korjaa. Tämä on nopeampi tie oikeaan ratkaisuun.
+
+## 8. Nimeäminen
Tiedostonimet `.gitea/workflows/`-kansiossa noudattavat yhtenäistä rakennetta, jotta
tiedostot löytyvät nopeasti ja niiden rooli on selvillä:
@@ -261,7 +361,7 @@ Single repossa `` jätetään pois — tiedostot ovat suoraan `ci-f
Monorepossa prefiksi pitää komponentin tiedostot yhdessä: `ls .*` löytää kaikki
kerralla.
-## 7. Artifact-kuri
+## 9. Artifact-kuri
Gitea Actionsin `upload-artifact` jättää pysyvän tiedoston. Artifakteja ei käytetä
workflow_call:ien väliseen datan siirtoon ellei se ole teknisesti välttämätöntä.
@@ -457,8 +557,9 @@ Gitean Settings → Branches → Add Rule:
| Skripti | Käyttötarkoitus |
|---|---|
-| `report-status.sh` | POSTaa commit-statuksen linkillä |
-| `publish-git-pages.sh` | Julkaisee raporttihakemiston git-pagesiin |
+| `ci-report.sh` | Yhdistetty raportointi: julkaisee git-pagesiin ja asettaa commit-statuksen. Korvaa erilliset `publish-git-pages.sh` + `report-status.sh` -kutsut. Käyttö: `bash .ci/scripts/ci-report.sh "" ` |
+| `report-status.sh` | POSTaa commit-statuksen linkillä (kutsutaan `ci-report.sh`:n sisältä) |
+| `publish-git-pages.sh` | Julkaisee raporttihakemiston git-pagesiin (kutsutaan `ci-report.sh`:n sisältä) |
| `ci-validate.sh` | Validoi `.conf`-tiedoston (kutsutaan `config-provider.yml`:stä) |
---
@@ -499,7 +600,7 @@ Ei pipeä (`|`) komennon perässä — se syö exit-koodin. Käytä redirectiä
### Commit-status vain raporttilinkille (ADR 0007)
-`report-status.sh`-skriptiä käytetään VAIN kun on raportti linkitettäväksi.
+`ci-report.sh`-skriptiä käytetään VAIN kun on raportti linkitettäväksi.
Tool-jobit (build, deploy) luottavat Gitean natiiviin job-statukseen.
### Providerin checkout ei kuulu consumerille