test: make unasserted lifecycle deprecations fail the test suite (opt-in) - #2782
Merged
Conversation
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
This was referenced Jul 26, 2026
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
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
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Establishes the lifecycle baseline requested for the ellipsis migration,
at error strength, as an opt-in test-suite mode:
IGRAPH_LIFECYCLE_ERRORS=trueturns 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:
deprecate_soft()keeps silent for users — have always signalledas warnings inside this test suite;
options(lifecycle_verbosity = ...)can never escalatethem: 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.Rfollows the repo precedent fornonstandard test behaviors — the
IGRAPH_CHECK_RNG_STATEseed-leak checkin
tests/testthat/setup.R(#2713, #2714):gated behind
IGRAPH_LIFECYCLE_ERRORS=true,the setup file shadows
test_that()for all test filesand 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.Rpins the regimein both modes (warning when off, error when on):
lifecycle::expect_deprecated()pins the verbosity back to"warning"internally — asserted deprecations keep working;
rlang::local_options(lifecycle_verbosity = "warning");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(), positionalmodeinradius()/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 thatrcc-fullalready 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:custom/before-install/action.ymlgains the matching genericenvinputand 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:
(the skips are the known sandbox network failure in
test-foreign.Rand 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 afterthe 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_ERRORSandIGRAPH_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