Skip to content

Sync addons-source@maintenance/gramps60 with upstream (2026-07-10)#60

Open
eduralph wants to merge 10 commits into
maintenance/gramps60from
sync/upstream-maintenance-gramps60-auto
Open

Sync addons-source@maintenance/gramps60 with upstream (2026-07-10)#60
eduralph wants to merge 10 commits into
maintenance/gramps60from
sync/upstream-maintenance-gramps60-auto

Conversation

@eduralph

Copy link
Copy Markdown
Owner

Automated nightly sync from gramps-project/addons-source@maintenance/gramps60. Generated by .github/workflows/upstream-sync.yml on the testbed.

javadr and others added 9 commits July 9, 2026 14:13
feat: sort items by month and day
…py runner

Adds a repo-root test suite for addons, runnable via `make.py <series> test`:

- tests/gramps_test_env.py — GrampsTestCase / GrampsDbTestCase base classes
  that boot the real Gramps plugin manager and registry once per process.
- tests/test_plugin_registration.py — registers every addon through Gramps'
  PluginRegister, validates id/name/version and the target Gramps series, loads
  each addon in a crash-isolated subprocess, and smoke-tests import/export
  entry points.
- tests/test_plugin_load_gate.py — regression test that the load check gates
  (self.fail) on a genuine load failure rather than only logging it.
- tests/test_addon_dependency_loading.py — exercises Gramps' depends_on
  mechanism over the tree: every declared dependency resolves to a registered
  plugin, the dependency graph is acyclic, and each declared dependency is
  load-bearing — proven by an isolated subprocess import so sys.modules caching
  can't mask a missing/undeclared dependency. Verified against Gramps 6.0 and 6.1.
- make.py — new `test` command that runs the suite via unittest discovery,
  gated on GRAMPSPATH like the other commands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-ups from repeated adversarial review of the suite:

- Load test: display/GTK/environment failures are classified consistently
  (anchored signatures, unit-tested) and advisory by default; a genuine hard
  failure — or an import hang, detected via a REGISTRY_READY marker rather than
  a slow registry scan — always gates. One slow addon load can no longer abort
  the whole test.
- Strict mode (GRAMPS_ADDON_TEST_STRICT=1) turns the smoke run into a full
  gate: dependency skips, environment failures and unverified addon
  dependencies become hard failures; only slow registry-scan timeouts stay
  advisory. Covered by synthetic regression tests that don't boot the registry.
- Import/export smoke tests fail (not silently skip) when a dependency-satisfied
  plugin fails to load.
- Unlisted-addon manifest: source-based and per-registration (parses each
  .gpr.py with ast), so an accidental include_in_listing=False — even on one
  plugin inside a mixed directory like TMGimporter — fails the test.
- Neutralise blocking modal dialogs an addon may pop at import (e.g. lxml
  without python3-lxml) so a load can't hang or scatter UI.
- Silence expected locale / PyGI noise so results are readable.
- Failure messages lead with the real failures; make.py's test docstring and
  the load-test docstring document default (smoke) vs strict (full gate) modes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…heck

Gramps accepts a comma-separated list of acceptable versions in a plugin's
requires_gi: Requirements.check_gi() in gramps/gen/utils/requirements.py splits
on "," and takes the first version that resolves. _check_dependencies() passed
the raw string straight to gi.require_version(), which raises ValueError — so
the two addons in the tree that use the form, GraphView (GooCanvas "2.0,3.0")
and PhotoTaggingGramplet (GExiv2 "0.10,0.12,0.14,0.16"), were reported as
having an unmet dependency on hosts that in fact satisfy it. Advisory in the
default smoke run; a false hard failure under GRAMPS_ADDON_TEST_STRICT=1.
Neither .gpr.py is wrong. Reported by Gary Griffin on PR 980.

Delegate to Gramps' own Requirements rather than re-implement the checks, so
the test agrees with what Gramps does at runtime. That also drops two smaller
deviations: requires_mod was import_module()'d where Gramps uses find_spec (no
import side effects), and requires_exe hand-rolled a PATH scan where Gramps'
search_for also tries name + ".exe" for Windows. The gi.repository import probe
is kept, now run after check_gi() has pinned the matching version, so a typelib
that resolves but fails to import stays a dependency skip instead of a hard
failure blamed on the addon.

TestDependencyCheck covers the comma form (red before this change: reports a
spurious gi:Gtk-3.0,4.0), the single-version form, an absent namespace, and an
absent module/executable. It boots no registry and needs no display.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
neutralize_gui_dialogs() replaced gramps.gui.dialog's dialogs with silent
no-ops, so an addon that constructs one at import time — e.g. lxmlGramplet on a
host without python3-lxml — loaded clean and the suite said nothing. That kept
the load from hanging on a modal (Gramps' ErrorDialog.__init__ calls .run()),
but it also hid the addon bug the stub exists to survive: before the stub the
same addon was reported as "hung during import", after it, as nothing at all.
Raised by Gary Griffin on PR 980 ("Is this now hidden?"). It was.

Each stub now records its construction, on gramps_test_env.dialogs_raised and as
a DIALOG_MARKER line on stdout. The isolated load already captures the
subprocess' stdout, so test_load_all_addon_modules scans it and reports a dialog
at import as a hard failure in both modes, naming the addon and the dialog
class. The marker is matched only at the start of a line, so a traceback quoting
it cannot forge a hit.

The load-failure header now reads "failed to load cleanly ... real defects"
rather than "failed to load ... real load failures": an addon caught this way
did load, and the message must not claim otherwise.

Verified against the real addon: with lxml unimportable, lxml/lxmlGramplet.py
raises ErrorDialog at import (dialogs_raised == ['ErrorDialog'], hard failure);
the same probe against PR 981's lxmlGramplet raises none. The gate itself is
covered synthetically by test_dialog_raised_at_import_gates, which is red
without the stdout scan even though the load exits 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lxmlGramplet raised ErrorDialog(...).run() at module *import* time when gzip or
lxml was unavailable. That is a blocking modal shown before any GUI action: it
stalls plugin loading until dismissed, and fires (or aborts) when Gramps is
imported without a display — e.g. under the CLI or a test harness, where a
missing python3-lxml made the module hang or scatter dialogs.

The gramplet already degrades gracefully — it falls back to xml.etree when lxml
is absent — so the missing dependency is not fatal. Set the availability flags
silently at import (log instead of a dialog), and show the notice in init(),
when the gramplet is actually opened and the GUI is running.

No .gpr.py change: the addon version is incremented automatically at publish
time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4034278c94

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +85 to 89
if len(self.gui.data) == 3:
self.sort_mode = self.gui.data[0]
self.ignore_tag = self.gui.data[1]
self.only_tag = self.gui.data[2]
else:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve existing Birthday tag filters on load

When a user already has this gramplet saved from the previous version, self.gui.data still contains the two values that used to be written ([ignore_tag, only_tag]). This branch treats any length other than 3 as a fresh configuration and clears both tag filters, so upgrading silently loses saved Birthday filter settings until the user reselects them; please handle len(self.gui.data) == 2 as legacy data and default only the new sort option.

Useful? React with 👍 / 👎.

capture_output=True,
text=True,
timeout=_LOAD_TIMEOUT,
env={**os.environ, "PYTHONPATH": ADDONS_ROOT},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass GRAMPSPATH through isolated load subprocesses

With the documented GRAMPSPATH=/path/to/gramps python3 make.py gramps60 test flow and no globally installed gramps package, the parent test process can import Gramps because make.py inserts GRAMPSPATH into sys.path, but this child process replaces PYTHONPATH with only ADDONS_ROOT and imports gramps before adding any checkout path. That makes every addon load look like a hard failure with No module named 'gramps' in a valid source-checkout test environment; include the existing PYTHONPATH/GRAMPSPATH or a resolved Gramps root in the subprocess environment/path.

Useful? React with 👍 / 👎.

@eduralph eduralph force-pushed the sync/upstream-maintenance-gramps60-auto branch 3 times, most recently from a9129d9 to bbf54c5 Compare July 13, 2026 06:43
@eduralph eduralph force-pushed the sync/upstream-maintenance-gramps60-auto branch from bbf54c5 to 8064e7a Compare July 14, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants