Skip to content

test: make unasserted lifecycle deprecations fail the test suite (opt-in) - #2782

Merged
krlmlr merged 8 commits into
mainfrom
claude/lifecycle-warnings-baseline
Jul 27, 2026
Merged

test: make unasserted lifecycle deprecations fail the test suite (opt-in)#2782
krlmlr merged 8 commits into
mainfrom
claude/lifecycle-warnings-baseline

Conversation

@krlmlr

@krlmlr krlmlr commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Establishes the lifecycle baseline requested for the ellipsis migration,
at error strength, as an opt-in test-suite mode:
IGRAPH_LIFECYCLE_ERRORS=true turns it on,
and one job in the full rcc matrix runs the suite that way.

What actually governs verbosity in tests

Investigating the flip from warnings to errors surfaced the real mechanism:
testthat's 3rd edition already forces lifecycle_verbosity = "warning"
inside every test_that() block
(local_test_context()local_reproducible_output(), hard-coded).
Two consequences:

  • soft deprecations — including indirect calls that
    deprecate_soft() keeps silent for users — have always signalled
    as warnings inside this test suite;
  • a setup-file options(lifecycle_verbosity = ...) can never escalate
    them: the per-test forcing overrides it again at the start of each test.
    (globalCallingHandlers() is no alternative either:
    R refuses to install global handlers from testthat's setup-file context.)

What this PR does

tests/testthat/setup-lifecycle.R follows the repo precedent for
nonstandard test behaviors — the IGRAPH_CHECK_RNG_STATE seed-leak check
in tests/testthat/setup.R (#2713, #2714):
gated behind IGRAPH_LIFECYCLE_ERRORS=true,
the setup file shadows test_that() for all test files
and forwards each block to testthat::test_that()
as a literal language object,
prepended with rlang::local_options(lifecycle_verbosity = "error").
The bump runs inside the test, after testthat has forced "warning",
so any deprecation no expectation asserts
including indirect soft deprecations —
fails its test outright.
With the variable unset, the suite behaves exactly like vanilla
testthat edition 3.

The escape hatches stay intact,
and the baseline test in test-migration-fixture.R pins the regime
in both modes (warning when off, error when on):

  • lifecycle::expect_deprecated() pins the verbosity back to "warning"
    internally — asserted deprecations keep working;
  • snapshot tests record the warning text under
    rlang::local_options(lifecycle_verbosity = "warning");
  • a test can opt out with
    rlang::local_options(lifecycle_verbosity = "quiet").

Nine existing snapshot tests exercise deprecated interfaces on purpose
(eigen_centrality(scale =), as.directed(), as.undirected(),
as_adjacency_matrix(attr =), as_biadjacency_matrix(attr =),
get.edge.ids(), positional mode in radius()/eccentricity(),
subgraph.edges()) and carry that verbosity pin —
correct under both regimes, their recorded snapshots are unchanged.

The full-matrix entry

The new .github/versions-matrix.R — the generic hook that
rcc-full already forwards into the custom before-install action
(env: ${{ matrix.env }}) — contributes one entry on R-release,
so the job shows up as e.g. rcc: ubuntu-26.04 (4.5) with lifecycle errors:

data.frame(
  os = "ubuntu-26.04",
  r = r_versions[[2]],
  env = "IGRAPH_LIFECYCLE_ERRORS=true",
  desc = "with lifecycle errors"
)

custom/before-install/action.yml gains the matching generic env input
and writes it to $GITHUB_ENV
(the same shape duckdb-r uses for its vendored-build and
engine-poisoning entries),
so the variable reaches the R session that runs the tests.
The smoke job and every other matrix entry stay on vanilla behavior.

Baseline result

Both modes are clean — the full suite, locally:

  • gate off: FAIL 0 · WARN 0 · SKIP 6 · PASS 8215
  • gate on: FAIL 0 · WARN 0 · SKIP 6 · PASS 8215

(the skips are the known sandbox network failure in test-foreign.R
and locally unavailable Suggests).
Package-internal code passes keyword-only arguments by name everywhere;
beyond the nine verbosity pins, the enforcement needed no fixes.

Note for review

The override shadows test_that() only for code testthat sources after
the setup files — i.e. every test file;
a fully qualified testthat::test_that() call would bypass it
(none exist in the suite).
Like the RNG-state override, it forwards the block as a literal language
object (no rlang::inject()),
so srcrefs (failure locations), skips, and snapshot lookup behave
normally — verified empirically.
Enable at most one of IGRAPH_LIFECYCLE_ERRORS and
IGRAPH_CHECK_RNG_STATE:
both shadow test_that(),
and the RNG override (sourced later) would win;
the baseline test fails loudly if both are set.

Generated by Claude Code

claude and others added 3 commits July 26, 2026 19:23
Set lifecycle_verbosity = "warning" in a testthat setup file,
so every deprecation -- including soft deprecations on *indirect*
calls, which stay silent by default -- surfaces as a warning
during tests.
The suite is already clean under the forced verbosity:
package-internal callers pass keyword-only arguments by name,
and the migrated call sites in the tests were converted
together with the signatures.
A baseline test asserts both the forced option
and that the recovery path of a migrated signature
does warn when reached indirectly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016M32izVHZPfxAqemAe4BrX
testthat's 3rd edition forces lifecycle_verbosity = "warning" inside
every test_that() block (local_reproducible_output()), so an option set
in a setup file cannot escalate deprecations to errors -- it is
overridden again at the start of each test.
Extend the exact hook testthat uses instead:
after the per-test context is set up, bump the verbosity to "error".
Deprecations asserted via expect_deprecated(), expect_snapshot(), or a
local verbosity option keep working;
anything unasserted -- including indirect soft deprecations that stay
silent for users -- now fails its test.

Full suite: FAIL 0, WARN 0, PASS 8166
(only the known sandbox network skip).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016M32izVHZPfxAqemAe4BrX
@krlmlr krlmlr changed the title test: force lifecycle warnings in the test suite test: make unasserted lifecycle deprecations fail the test suite Jul 26, 2026
krlmlr and others added 4 commits July 26, 2026 21:34
Nine snapshot tests exercise deprecated interfaces on purpose and
record the deprecation warning text.
With unasserted deprecations promoted to errors,
they must use the documented escape hatch:
rlang::local_options(lifecycle_verbosity = "warning").
The recorded snapshots are unchanged.

Full suite: FAIL 0, WARN 0, PASS 8215.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016M32izVHZPfxAqemAe4BrX
Following the IGRAPH_CHECK_RNG_STATE precedent in setup.R (#2713),
setup-lifecycle.R now shadows test_that() to bump the per-test verbosity
only when IGRAPH_LIFECYCLE_ERRORS=true,
and a new full-matrix entry in .github/versions-matrix.R
(exported via the before-install env hook) runs the check in that mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016M32izVHZPfxAqemAe4BrX
@krlmlr krlmlr changed the title test: make unasserted lifecycle deprecations fail the test suite test: make unasserted lifecycle deprecations fail the test suite (opt-in) Jul 27, 2026
@krlmlr
krlmlr merged commit f04e959 into main Jul 27, 2026
6 checks passed
@krlmlr
krlmlr deleted the claude/lifecycle-warnings-baseline branch July 27, 2026 17:44
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.

2 participants