addon-unit: dotted-path invocation + scheduled runs #4
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Addon Unit Tests | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| schedule: | |
| # Nightly drift check at 06:30 UTC — 2.5h after upstream-sync (04:00) | |
| # and 30min after docker-build (06:00), keeping the existing | |
| # staggered-by-30min cadence. Surfaces addon-side import/ABI | |
| # regressions against fresh upstream, which is exactly the class of | |
| # bug the testbed catches while addons-source's own CI stays green. | |
| - cron: '30 6 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| addons: | |
| description: 'Space-separated addon names (empty = all with tests/)' | |
| default: '' | |
| gramps_repo: | |
| description: 'gramps repo (owner/name); empty = same-owner fork' | |
| default: '' | |
| gramps_ref: | |
| description: 'gramps branch/tag/sha' | |
| default: 'maintenance/gramps60' | |
| addons_source_repo: | |
| description: 'addons-source repo (owner/name); empty = same-owner fork' | |
| default: '' | |
| addons_ref: | |
| description: 'addons-source branch/tag/sha' | |
| default: 'maintenance/gramps60' | |
| # See interface-tests.yml for rationale — dorny/test-reporter needs | |
| # checks: write + pull-requests: write, and the repo's default token | |
| # permissions are restrictive. | |
| permissions: | |
| contents: read | |
| checks: write | |
| pull-requests: write | |
| jobs: | |
| addon-unit-tests: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| # Advisory gate. Addon tests run upstream addon code, so failures | |
| # here usually reflect addon/upstream bugs rather than testbed | |
| # issues. continue-on-error keeps the workflow conclusion green | |
| # while the per-addon check-run surfaces the failing addon — same | |
| # shape as unit-tests.yml. | |
| continue-on-error: true | |
| steps: | |
| - name: Checkout testbed | |
| uses: actions/checkout@v4 | |
| with: | |
| path: gramps-testbed | |
| # Repo resolution (in order): workflow input → repository variable | |
| # → same-owner fork. Matches unit-tests.yml / interface-tests.yml. | |
| - name: Checkout gramps fork | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ github.event.inputs.gramps_repo || vars.GRAMPS_REPO || format('{0}/gramps', github.repository_owner) }} | |
| ref: ${{ github.event.inputs.gramps_ref || vars.GRAMPS_REF || 'maintenance/gramps60' }} | |
| path: gramps | |
| - name: Checkout addons-source fork | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ github.event.inputs.addons_source_repo || vars.ADDONS_SOURCE_REPO || format('{0}/addons-source', github.repository_owner) }} | |
| ref: ${{ github.event.inputs.addons_ref || vars.ADDONS_SOURCE_REF || 'maintenance/gramps60' }} | |
| path: addons-source | |
| - name: Install system dependencies | |
| # No xvfb/dbus/at-spi — addon unit tests do not launch the GUI. | |
| # Same apt list as unit-tests.yml: gramps.gen still imports gi on | |
| # load, so GTK introspection bindings must be present. | |
| # | |
| # python3-venv / python3-pip are installed here so the next step | |
| # can build a venv that inherits python3-gi from the system | |
| # interpreter. | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| python3 python3-venv python3-pip \ | |
| python3-gi python3-gi-cairo python3-cairo python3-icu \ | |
| gir1.2-gtk-3.0 gir1.2-osmgpsmap-1.0 gir1.2-goocanvas-2.0 gir1.2-gexiv2-0.10 \ | |
| libgirepository1.0-dev \ | |
| gettext intltool \ | |
| librsvg2-common | |
| - name: Set up Python venv (inherits python3-gi from apt) | |
| # See unit-tests.yml for rationale — apt-installed PyGObject is | |
| # only visible to the system Python, not to setup-python@v5's | |
| # toolcache interpreter, so we use a system-site-packages venv. | |
| run: | | |
| python3 -m venv --system-site-packages .venv | |
| echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH" | |
| .venv/bin/python -m pip install --upgrade pip | |
| - name: Restore pip cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: pip-${{ runner.os }}-${{ hashFiles('gramps-testbed/requirements-test.txt') }} | |
| restore-keys: | | |
| pip-${{ runner.os }}- | |
| - name: Install Python dependencies | |
| run: | | |
| pip install -r gramps-testbed/requirements-test.txt | |
| # [testing] extras pulls in jsonschema/mock/lxml from gramps setup.py, | |
| # which some addon tests also import transitively. | |
| pip install -e 'gramps[testing]' | |
| - name: Install addon runtime deps (derived from requires_mod) | |
| # Auto-derive the union of requires_mod across every .gpr.py in | |
| # the checked-out addons-source/. Mirrors Gramps' Addon Manager | |
| # install path (gramps/gui/plug/_windows.py __on_install_clicked | |
| # → req.install → gen/utils/requirements.py). Keeps the .gpr.py | |
| # files as the single source of truth for addon deps — no | |
| # parallel list to drift from upstream. Best-effort: a package | |
| # that needs exotic system deps (e.g. pygraphviz → graphviz-dev) | |
| # may fail here; the affected addon's tests will skip or fail in | |
| # isolation without blocking the rest. | |
| shell: bash | |
| run: | | |
| addon_mods=$(python3 - <<'PY' | |
| import ast, glob, re | |
| pat = re.compile(r"requires_mod\s*=\s*(\[[^\]]*\])") | |
| mods = set() | |
| for f in glob.glob("addons-source/*/*.gpr.py"): | |
| try: | |
| text = open(f, encoding="utf-8").read() | |
| except OSError: | |
| continue | |
| for m in pat.finditer(text): | |
| try: | |
| mods.update(ast.literal_eval(m.group(1))) | |
| except (ValueError, SyntaxError): | |
| pass | |
| print(" ".join(sorted(mods))) | |
| PY | |
| ) | |
| if [ -n "$addon_mods" ]; then | |
| echo "→ addon deps: $addon_mods" | |
| # Install one at a time so a single failing build | |
| # (pygraphviz without graphviz-dev, psycopg2 without | |
| # libpq-dev, etc.) does not abort the batch. The affected | |
| # addon's tests will skip or fail in isolation. | |
| for mod in $addon_mods; do | |
| pip install "$mod" || \ | |
| echo "× $mod failed to install (continuing)" | |
| done | |
| else | |
| echo "no requires_mod declarations found" | |
| fi | |
| - name: Compile Gramps translations | |
| # Same rationale as unit-tests.yml: keeps gramps.gen imports quiet | |
| # during test collection. | |
| working-directory: gramps | |
| run: | | |
| for po in po/*.po; do | |
| lang=$(basename "$po" .po) | |
| dest="build/mo/$lang/LC_MESSAGES" | |
| mkdir -p "$dest" | |
| msgfmt "$po" -o "$dest/gramps.mo" | |
| done | |
| - name: Run addon unit tests | |
| # Mirrors scripts/ubuntu/run-addon-unit.sh. Each test module is | |
| # loaded by its dotted path (<Addon>.tests.<module>) from | |
| # addons-source/, matching the upstream invocation in | |
| # addons-source/.github/workflows/ci.yml. Loading via dotted | |
| # path — not discover-from-tests/ — puts the addon on | |
| # sys.modules as a namespace package before the test body runs, | |
| # which is the exact arrangement that exposes package-shadowing | |
| # traps like bug 0012691. Discover inside tests/ hides them. | |
| env: | |
| GRAMPS_RESOURCES: ${{ github.workspace }}/gramps | |
| ADDONS: ${{ github.event.inputs.addons }} | |
| run: | | |
| mkdir -p "$GITHUB_WORKSPACE/gramps-testbed/test-results" | |
| if [ -n "$ADDONS" ]; then | |
| addons=( $ADDONS ) | |
| else | |
| addons=() | |
| for d in "$GITHUB_WORKSPACE"/addons-source/*/tests; do | |
| [ -d "$d" ] || continue | |
| parent_name=$(basename "$(dirname "$d")") | |
| [ "$parent_name" = "addons-source" ] && continue | |
| compgen -G "$d/test_*.py" >/dev/null || continue | |
| addons+=( "$parent_name" ) | |
| done | |
| fi | |
| if [ ${#addons[@]} -eq 0 ]; then | |
| echo "no addons with tests/test_*.py were found" >&2 | |
| exit 1 | |
| fi | |
| echo "→ addon unit tests: ${addons[*]}" | |
| fail=0 | |
| for addon in "${addons[@]}"; do | |
| test_dir="$GITHUB_WORKSPACE/addons-source/$addon/tests" | |
| if [ ! -d "$test_dir" ]; then | |
| echo "× $addon: addons-source/$addon/tests/ not found" >&2 | |
| fail=1 | |
| continue | |
| fi | |
| echo | |
| echo "=== $addon ===" | |
| out_dir="$GITHUB_WORKSPACE/gramps-testbed/test-results/$addon" | |
| mkdir -p "$out_dir" | |
| modules=() | |
| for f in "$GITHUB_WORKSPACE"/addons-source/"$addon"/tests/test_*.py; do | |
| [ -f "$f" ] || continue | |
| rel="${f#$GITHUB_WORKSPACE/addons-source/}" | |
| mod="${rel%.py}" | |
| mod="${mod//\//.}" | |
| modules+=( "$mod" ) | |
| done | |
| if [ ${#modules[@]} -eq 0 ]; then | |
| echo "× $addon: no test_*.py in tests/" >&2 | |
| fail=1 | |
| continue | |
| fi | |
| ( | |
| cd "$GITHUB_WORKSPACE/addons-source" | |
| python -m xmlrunner "${modules[@]}" \ | |
| -o "$out_dir" \ | |
| -v | |
| ) || fail=1 | |
| done | |
| exit $fail | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: addon-unit-test-results | |
| path: gramps-testbed/test-results/ | |
| - name: Publish test report | |
| if: always() | |
| uses: dorny/test-reporter@v1 | |
| with: | |
| # See interface-tests.yml for the working-directory rationale — | |
| # checkouts use `path:` subdirs so the workflow root isn't a | |
| # git repo; anchor on the testbed checkout. | |
| working-directory: gramps-testbed | |
| name: Addon Unit Tests | |
| path: test-results/**/*.xml | |
| reporter: java-junit |