mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-10-19 13:53:01 +02:00
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
296 lines
12 KiB
YAML
296 lines
12 KiB
YAML
name: PR Compile Check
|
|
|
|
on:
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
|
|
jobs:
|
|
compile:
|
|
name: Compile (Java 21)
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Set up Java 21
|
|
uses: actions/setup-java@v6.0.1
|
|
with:
|
|
distribution: temurin
|
|
java-version: "21"
|
|
|
|
- name: Ensure gradlew is executable
|
|
run: chmod +x gradlew
|
|
|
|
# classes + testClasses rather than compileJava + compileTestJava. The old pair did
|
|
# cover Kotlin -- checked with a deliberately broken Kotlin test file, and also in
|
|
# --dry-run: the Kotlin plugin makes compileTestJava depend on compileTestKotlin,
|
|
# because Java has to see the Kotlin output. But that is the plugin's wiring and not
|
|
# this job's statement of intent, and it covered no resources. These two aggregates
|
|
# say "compile everything in all three modules" and do not depend on knowing that.
|
|
#
|
|
# They also run the translation generator, which app-desktop's compileKotlin depends
|
|
# on. That is why a pull request contributing a language needs no workflow of its own:
|
|
# an orphan key, a dropped placeholder or a duplicate fails here, with the generator's
|
|
# own message naming the file.
|
|
- name: Compile
|
|
run: ./gradlew -S classes testClasses
|
|
|
|
# Report only: both tools run with ignoreFailures, so a finding cannot fail this step
|
|
# -- a crash of the tool itself still does, which is wanted. The findings used to exist
|
|
# only in the console log; since #110 they are files, and the table and the artifact
|
|
# below are what makes them readable from a pull request.
|
|
- name: Static analysis (report only)
|
|
run: ./gradlew -S pmdMain spotbugsMain
|
|
|
|
# Counted from the report XML, never from Gradle's output or exit code. A missing
|
|
# report prints "no report" and does not fail the step: that case is already visible
|
|
# in the step above.
|
|
- name: Summarise the static analysis
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
|
|
count() {
|
|
local file="$1" element="$2"
|
|
if [ -f "$file" ]; then
|
|
# grep exits 1 on zero matches, which pipefail would turn into a failed step.
|
|
{ grep -o "<$element " "$file" || true; } | wc -l | tr -d ' '
|
|
else
|
|
echo "no report"
|
|
fi
|
|
}
|
|
|
|
{
|
|
echo "### Static analysis"
|
|
echo ""
|
|
echo "| module | PMD violations | SpotBugs bugs |"
|
|
echo "|---|---|---|"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
for module in core app-desktop i18n-generator; do
|
|
pmd=$(count "$module/build/reports/pmd/main.xml" violation)
|
|
spotbugs=$(count "$module/build/reports/spotbugs/main.xml" BugInstance)
|
|
echo "$module: pmd=$pmd spotbugs=$spotbugs"
|
|
echo "| \`$module\` | $pmd | $spotbugs |" >> "$GITHUB_STEP_SUMMARY"
|
|
done
|
|
|
|
{
|
|
echo ""
|
|
echo "Report only: nothing fails on a finding. The XML and HTML reports are the"
|
|
echo "static-analysis artifact of this run. i18n-generator is Kotlin only, so PMD"
|
|
echo "has no Java source there and writes no report."
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Keep the static analysis reports
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: static-analysis
|
|
path: |
|
|
*/build/reports/pmd/
|
|
*/build/reports/spotbugs/
|
|
if-no-files-found: ignore
|
|
retention-days: 14
|
|
|
|
# What a translation PR is actually about, put where its author will look. The counts
|
|
# come from the files rather than from Gradle's output, so an up-to-date task cannot
|
|
# make the table disappear.
|
|
- name: Summarise the interface texts
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
|
|
base="app-desktop/src/main/i18n/strings_en.properties"
|
|
if [ ! -f "$base" ]; then
|
|
echo "no base translation file at $base" >&2
|
|
exit 1
|
|
fi
|
|
|
|
count_keys() { grep -cE '^[^#!][^=]*=' "$1" || true; }
|
|
|
|
total=$(count_keys "$base")
|
|
|
|
{
|
|
echo "### Interface texts"
|
|
echo ""
|
|
echo "| language | translated | of | missing |"
|
|
echo "|---|---|---|---|"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
for file in app-desktop/src/main/i18n/strings_*.properties; do
|
|
lang=$(basename "$file" .properties); lang=${lang#strings_}
|
|
have=$(count_keys "$file")
|
|
missing=$(( total - have ))
|
|
[ "$missing" -lt 0 ] && missing=0
|
|
echo "| \`$lang\` | $have | $total | $missing |" >> "$GITHUB_STEP_SUMMARY"
|
|
done
|
|
|
|
{
|
|
echo ""
|
|
echo "A language may be incomplete: a key it leaves out falls back to English,"
|
|
echo "which is what lets a translation be contributed a piece at a time. German is"
|
|
echo "held complete by a test, because it ships."
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
worksheets=(app-desktop/build/generated/i18n/todo/*.todo.properties)
|
|
if [ ${#worksheets[@]} -gt 0 ]; then
|
|
{
|
|
echo ""
|
|
echo "The build wrote a worksheet of what is still missing for:"
|
|
echo ""
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
for w in "${worksheets[@]}"; do
|
|
echo "- \`$(basename "$w")\`" >> "$GITHUB_STEP_SUMMARY"
|
|
done
|
|
fi
|
|
|
|
test:
|
|
name: Test (Java 21, headless)
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Set up Java 21
|
|
uses: actions/setup-java@v6.0.1
|
|
with:
|
|
distribution: temurin
|
|
java-version: "21"
|
|
|
|
- name: Ensure gradlew is executable
|
|
run: chmod +x gradlew
|
|
|
|
# Skiko's native library links against libGL, so it has to be loadable even though
|
|
# nothing here renders through it: the drawing tests use Skia's software backend and
|
|
# runComposeUiTest never opens a window. Without this the whole Compose half of the
|
|
# suite dies with "libskiko-linux-x64.so: libGL.so.1: cannot open shared object
|
|
# file" -- which reads like a missing display and is not one. Found by running this
|
|
# job under act, where the container has no libGL; GitHub's own runner image happens
|
|
# to ship it, so the step is what makes the job reproducible off GitHub.
|
|
- name: Install the OpenGL client library skiko links against
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends libgl1
|
|
|
|
# No Xvfb, deliberately. Everything that pins behaviour runs without a display:
|
|
# runComposeUiTest reaches the semantics tree headless (measured before this job was
|
|
# written), and the ImageComposeScene drawing tests render in software. The handful
|
|
# of tests that open a real window guard themselves with assumeFalse(isHeadless) and
|
|
# skip here; the summary below prints the skip count, so that is visible rather than
|
|
# assumed.
|
|
#
|
|
# Software GL under Xvfb was considered and rejected: a missing Skia GL context is
|
|
# the hardest failure in this codebase to read, and importing that class of flake
|
|
# into CI would cost more than the few window tests are worth here.
|
|
|
|
# All three modules. The translation generator is a Gradle module and not buildSrc
|
|
# precisely so that its 42 tests run here: buildSrc tests do not run on `./gradlew
|
|
# build` -- measured, not assumed. The summary below globs */build/test-results, so
|
|
# the module was counted the moment it existed.
|
|
- name: Test
|
|
run: ./gradlew -S test
|
|
|
|
# Coverage is a report, not a gate: no threshold, nothing in it can fail on a
|
|
# percentage. Deliberately not `if: always()` -- without a completed test run the
|
|
# report would be partial, and after a failed test the result XML below is what matters.
|
|
# jacocoTestReport writes the per-module XML the summary reads; the aggregate merges
|
|
# core and app-desktop (i18n-generator is build tooling and stays out).
|
|
- name: Coverage report
|
|
run: ./gradlew -S jacocoTestReport jacocoAggregatedReport
|
|
|
|
- name: Summarise the test results
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
files=(*/build/test-results/test/TEST-*.xml)
|
|
if [ ${#files[@]} -eq 0 ]; then
|
|
echo "No test result XML was produced -- the test task did not run." >&2
|
|
exit 1
|
|
fi
|
|
# Counted from the XML and not from Gradle's console output: this project's own
|
|
# rule is never to infer "all tests passed" from an exit code.
|
|
summary=$(grep -h "<testsuite " "${files[@]}" \
|
|
| sed 's/.*tests="\([0-9]*\)".*skipped="\([0-9]*\)".*failures="\([0-9]*\)".*errors="\([0-9]*\)".*/\1 \2 \3 \4/' \
|
|
| awk '{t+=$1; s+=$2; f+=$3; e+=$4} END {print t" "s" "f" "e}')
|
|
read -r total skipped failures errors <<< "$summary"
|
|
echo "tests=$total skipped=$skipped failures=$failures errors=$errors"
|
|
{
|
|
echo "### Tests"
|
|
echo ""
|
|
echo "| total | skipped | failures | errors |"
|
|
echo "|---|---|---|---|"
|
|
echo "| $total | $skipped | $failures | $errors |"
|
|
echo ""
|
|
echo "Skipped are the tests that need a display, plus the deliberately"
|
|
echo "disabled reference-data generator. This job runs headless on purpose."
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
if [ "$failures" != "0" ] || [ "$errors" != "0" ]; then
|
|
echo "The result XML reports failures or errors." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Summarise the coverage
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
# The last LINE/BRANCH counter in a JaCoCo XML is the report-level total; the
|
|
# per-package and per-class counters come before it.
|
|
percent() {
|
|
local file="$1" type="$2"
|
|
{ grep -o "<counter type=\"$type\"[^>]*/>" "$file" || true; } | tail -1 \
|
|
| sed 's/.*missed="\([0-9]*\)".*covered="\([0-9]*\)".*/\1 \2/' \
|
|
| awk 'NF == 2 { t=$1+$2; if (t==0) print "n/a"; else printf "%.1f%% (%d/%d)", 100*$2/t, $2, t }'
|
|
}
|
|
row() {
|
|
local label="$1" file="$2"
|
|
if [ -f "$file" ]; then
|
|
echo "| $label | $(percent "$file" LINE) | $(percent "$file" BRANCH) |"
|
|
else
|
|
echo "| $label | no report | no report |"
|
|
fi
|
|
}
|
|
{
|
|
echo ""
|
|
echo "### Coverage"
|
|
echo ""
|
|
echo "| module | lines | branches |"
|
|
echo "|---|---|---|"
|
|
row "core" core/build/reports/jacoco/test/jacocoTestReport.xml
|
|
row "app-desktop" app-desktop/build/reports/jacoco/test/jacocoTestReport.xml
|
|
row "core + app-desktop" build/reports/jacoco/aggregated/jacoco.xml
|
|
echo ""
|
|
echo "Report only: nothing fails on a percentage. The aggregated HTML report is the"
|
|
echo "coverage-report artifact of this run. i18n-generator is build tooling and is"
|
|
echo "not counted."
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Keep the coverage report
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: coverage-report
|
|
path: build/reports/jacoco/aggregated/html/
|
|
if-no-files-found: ignore
|
|
retention-days: 14
|
|
|
|
- name: Keep the test reports on a failure
|
|
if: failure()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: test-results
|
|
path: |
|
|
*/build/test-results/test/
|
|
*/build/reports/tests/test/
|
|
if-no-files-found: ignore
|
|
retention-days: 7
|