Skip to content

Add generic allow-in-tests configuration - #17520

Draft
dcsommer wants to merge 1 commit into
rust-lang:masterfrom
dcsommer:test-config
Draft

Add generic allow-in-tests configuration#17520
dcsommer wants to merge 1 commit into
rust-lang:masterfrom
dcsommer:test-config

Conversation

@dcsommer

@dcsommer dcsommer commented Aug 7, 2026

Copy link
Copy Markdown

View all comments

Fixes #12581

NOTE: some of the code for this PR originated from an LLM (Claude Opus 5), so I am disclosing this per the
disclosure guidelines. I (the human) reviewed all code and wrote the human-facing parts (PR description, documentation, error messages) myself, to comply with the LLM policy which also applies to rust-clippy repo.

Problem

Clippy has nine per-lint options controlling whether a lint applies to test code:

option lint(s) affected
allow-dbg-in-tests dbg_macro
allow-expect-in-tests expect_used
allow-indexing-slicing-in-tests indexing_slicing
allow-large-stack-frames-in-tests large_stack_frames
allow-panic-in-tests panic
allow-print-in-tests print_stderr, print_stdout
allow-unwrap-in-tests unwrap_used
allow-useless-vec-in-tests useless_vec
check-incompatible-msrv-in-tests incompatible_msrv

They all express the same idea: should this lint be applied to test code?. Each is implemented from scratch inside the lint it controls.

What each individual allow-*-in-tests costs to add

A recent proposed addition (#17516) of a single option (allow-panic-in-result-fn-in-tests) touches 8 files and adds 66 lines, of which only a handful were the actual behavior. None of this code is shared with the other allow-*-in-tests options.

Each allow-* option addition pollutes user-facing documentation in the form of extra search completions, CHANGELOG entries, and option entries.

It is also a burden on maintainers to continue reviewing PRs for each consequent allow-*.

Given the number of these allow-* options, I think we can safely assume this trend will continue, and the costs will continue to be accrued.

Behavioral drift

Because each lint implements the check itself, each has its own method of determining "are we inside test code currently?" Already there is variance: some use expr.hir_id, others local_def_id_to_hir_id(..). Nothing enforces consistency, despite the allow-*-in-tests naming scheme implying some sort of common implementation.

For users

One option name to learn and grep for to solve the problem of "how do I turn off lint X in a test?" instead of a bespoke name per lint would be a much better UX.

Solution

One new option, allow-in-tests = [...], which suppresses the lints specified in tests.

# Example: do not fire dbg_macro and unwrap_used lints inside test code
allow-in-tests = ["dbg_macro", "unwrap_used"]

Rather than threading a flag into each lint, suppression happens once at diagnostic emission. This avoids having to refactor every lint to be aware of allow-in-tests.

In order to support non-late-pass lints, there is a new AST pass to collect spans of test config code. When lints are emitted within those test-config spans, they are eligible for suppression via allow-in-tests.

As one would expect, if you specify a non-existent lint in the list for this option, you get an error.

Deprecation

The existing allow-*-in-tests options keep working but are deprecated in documentation only. Setting a legacy allow option does not warn for now. Each option's docs name the exact replacement line to write. #[replaced_by_allow_in_tests] records the mapping in machine-readable form so the warning can be switched on in a follow-up once allow-in-tests has shipped everywhere.

On naming allow-in-tests

allow-in-tests generalizes the allow-*-in-tests options it supersedes. allow-* is by far the most common prefix in clippy.toml (17 options), and it maps exactly onto #[allow]: the same operation, applied by configuration instead of by attribute. Sticking with historical precedent minimizes mental churn for users.

However, thinking ahead to an "inverse" option, one that enables a lint to apply to test code where it didn't before, its worth considering how this PR's new option and that follow-up option (coming in a later PR) would pair together. Here are the pros/cons for the considered naming schemes.

naming pro con
allow- / check- (chosen scheme) Generalizes existing naming families one-to-one, so follows precedent. allow matches #[allow] semantics; check means "don't skip". The two are not obvious opposites at a glance, so knowing one doesn't let you guess the other.
allow- / disallow- Symmetric and guessable. disallow risks "double negative" confusion as it is read.
disable- / enable- Symmetric and guessable. States the effect in plain terms. Contra existing precedent: it inverts the direction relative to all nine existing options. allow-dbg-in-tests = true means don't lint, so disable-in-tests would be its synonym and enable-in-tests its opposite. Every migrating user has to flip the "sign" of the configuration language (from allow to disable).
skip- / check- Clean opposites that avoid lint-level vocabulary entirely. Contra existing precedent for allow-*.

Prior art

This has been attempted once before, in #15600 and its continuation rust-lang/rust#156396. That approach suppressed lints whenever --test was passed to rustc, which also silenced them in non-test code. @y21 and @flip1995 independently identified the per-node check — what clippy_utils::is_in_test does, and what the existing allow-*-in-tests options do — as the behavior users actually expect, and the PR was closed on that basis. This PR uses that per-node check.

Checklist

  • Added passing UI tests (including committed .stderr files)
  • cargo test passes locally
  • Added documentation
  • Ran cargo dev fmt and cargo bless --test config-metadata

changelog: Added [allow-in-tests] configuration to suppress any lint in test code
changelog: Deprecated the per-lint allow-*-in-tests options in favor of allow-in-tests

Prior requests for this feature

This is not a new idea. It has been asked for repeatedly since 2016.

Related

Follow-up work suggested by these related issues:

  • A way to have lints that default skip over tests actually lint tests too
  • Have similar option(s) to this PR for benches and examples

@dcsommer
dcsommer marked this pull request as ready for review August 7, 2026 20:19
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties S-waiting-on-community-reviews Status: This is awaiting for positive reviews from the community before a maintainer is assigned. labels Aug 7, 2026

@DanielEScherzer DanielEScherzer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this big of a change probably will need an FCP, but some community review

View changes since this review

Comment thread clippy_config/src/conf.rs Outdated
concat!("`", $name_str, "` is deprecated"),
)
.with_help(format!(
"use `allow-in-tests = [{}]` instead, which works for any lint",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

but it doesn't work for "any lint", only for late pass lints

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is fixed now, it works for non late-pass lints too.

Comment thread clippy_config/src/conf.rs Outdated
///
/// This supersedes the per-lint `allow-<lint>-in-tests` options, which are deprecated but
/// still honored: a lint is suppressed in test code if it is listed here or its own
/// `allow-<lint>-in-tests` option is set.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what if the allow-*-in-tests is explicitly set to false, but here the lint is listed - it should be made clear that if either place says the lint should be allowed then it is

Comment thread clippy_lints/src/lib.rs Outdated
/// Resolves the lint names given in the `allow-in-tests` configuration and hands them to
/// `clippy_utils`, which drops their diagnostics when they are emitted from test code.
///
/// Names which don't refer to a Clippy lint are reported and ignored.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what about non-late-pass lints - can they be reported too?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I've added support for non-late-pass lints in the latest version.

Comment thread book/src/lint_configuration.md Outdated
Comment on lines +97 to +101
## `allow-expect-in-tests`
Whether `expect` should be allowed in test functions or `#[cfg(test)]`

Deprecated in favor of [`allow-in-tests`](#allow-in-tests), which works for any
lint. This option still works, but new configurations should use `allow-in-tests`.

@CommanderStorm CommanderStorm Aug 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Community review:
I am not sure that this is a good idea.

I think it is not uncommon to want to allow .expect, but not dbg! in tests.

Therefore this seems like a lot of churn for some users and not an 100% pure win at that.

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@CommanderStorm what in particular is not a good idea? Yes, the premise of this change is that things should be spelled differently, for the benefit of consistency and searchability going forward, as well as immediate extensibility to many more lints. Are you voting that such benefits are not worth the eventual respelling?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think there are people for which this is an regression.
As said:

I think it is not uncommon to want to allow .expect, but not dbg! in tests.

Not everyone wants to opt into an "allow everything, including all the future things we will allow" switch, there are also others who I see wanting exactly this.
Thus, I think it is not a clear win

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

allow-in-tests doesn't allow everything though. It takes a list of specific lints to allow in tests. For the scenario you mentioned, you can get it with this:

allow-in-tests = ["expect_used"]   # `.expect` allowed in tests, `dbg!` still linted

I've reworded these lines in the latest version to be clearer.

@blyxyas

blyxyas commented Aug 10, 2026

Copy link
Copy Markdown
Member

Was AI used in this PR? If so, we require disclosure.

@dcsommer

Copy link
Copy Markdown
Author

Was AI used in this PR? If so, we require disclosure.

Thanks for the flag. Yes, I used AI (Opus 5) and will figure out where to disclose that.

@rustbot

This comment has been minimized.

@blyxyas

blyxyas commented Sep 7, 2026

Copy link
Copy Markdown
Member

Ping @dcsommer 🏓! Are you there?

@dcsommer

dcsommer commented Sep 8, 2026

Copy link
Copy Markdown
Author

this big of a change probably will need an FCP, but some community review

Who determines this? How do I know if I should make an FCP?

@DanielEScherzer

Copy link
Copy Markdown
Contributor

this big of a change probably will need an FCP, but some community review

Who determines this? How do I know if I should make an FCP?

I think only team members can actually start an FCP (e.g. I can't), so this was mostly just a heads up to you that it might take some discussion before this gets merged

@CommanderStorm

Copy link
Copy Markdown
Contributor

this big of a change probably will need an FCP, but some community review

Who determines this

Big user facing changes like new lints or configuration options just do.
See #t-clippy/fcp

We should write this process down btw 😉

How do I know if I should make an FCP

You cannot do an FCP, only people with merge rights can. Once a PR is ready, it gets the lint-nominated label applied.

@rustbot label +needs-fcp +llm-assisted

@rustbot rustbot added llm-assisted For PRs that were partially or completely done with assistance of AI/LLM tools needs-fcp PRs that add, remove, or rename lints and need an FCP labels Sep 8, 2026
@dcsommer
dcsommer marked this pull request as draft September 9, 2026 02:35
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Sep 9, 2026
@dcsommer dcsommer changed the title Add generic allow-in-tests configuration Add generic allow-in-tests (and inverse check-in-tests) configuration Sep 9, 2026
@dcsommer
dcsommer force-pushed the test-config branch 4 times, most recently from 39817f9 to 26c9a14 Compare September 9, 2026 04:06
@dcsommer
dcsommer marked this pull request as ready for review September 9, 2026 04:15
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Sep 9, 2026
@CommanderStorm

CommanderStorm commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

@dcsommer have you had a chance to review our policy? In particular regarding the docs, diagnostics and the PR description?

https://forge.rust-lang.org/policies/llm-usage.html#-banned

@dcsommer

dcsommer commented Sep 9, 2026 via email

Copy link
Copy Markdown
Author

@CommanderStorm

Copy link
Copy Markdown
Contributor

Yes, here is the link of clippy ratifying this

https://doc.rust-lang.org/nightly/clippy/development/llm_usage.html

@Gri-ffin Gri-ffin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Its quite...daunting to review this PR, its trying to do too much, it should probably be split into separate PRs.

View changes since this review

@dcsommer

dcsommer commented Sep 9, 2026 via email

Copy link
Copy Markdown
Author

Clippy has accumulated seven per-lint options for suppressing a lint in
test code, each implemented from scratch inside the lint it controls:

- `allow-dbg-in-tests` (`dbg_macro`)
- `allow-expect-in-tests` (`expect_used`)
- `allow-indexing-slicing-in-tests` (`indexing_slicing`)
- `allow-panic-in-tests` (`panic`)
- `allow-print-in-tests` (`print_stderr`, `print_stdout`)
- `allow-unwrap-in-tests` (`unwrap_used`)
- `allow-useless-vec-in-tests` (`useless_vec`)

`allow-in-tests` replaces them with one option naming lints directly.
Suppression happens once at diagnostic emission in `clippy_utils`, so it
covers every lint rather than needing per-lint plumbing, and it resolves
test-ness against the same node rustc resolves `#[allow]` against.

Every one of those seven lints is emitted from a late pass, because the
existing technique needs a `TyCtxt` and a HIR node; early-pass lints could
never have had such an option. They are covered here as well: they have no
parent chain to walk, so a collector records the spans of `#[cfg(test)]`
items and `#[test]` functions in `check_crate` and emission matches a
lint's span against them. The walk is skipped entirely unless the option
names a lint.

The superseded options keep working and are deprecated in documentation
only, with no warning on use; `#[replaced_by_allow_in_tests]` records the
replacement so the warning can be turned on once this has shipped.

Lint names are resolved against the declared lints at startup, and names
which don't resolve are reported with a span into `clippy.toml`.

changelog: Added [`allow-in-tests`] configuration to suppress any lint in test code
changelog: Deprecated the per-lint `allow-*-in-tests` options in favor of [`allow-in-tests`]
@dcsommer
dcsommer marked this pull request as draft September 10, 2026 20:21
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Sep 10, 2026
@dcsommer dcsommer changed the title Add generic allow-in-tests (and inverse check-in-tests) configuration Add generic allow-in-tests configuration Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llm-assisted For PRs that were partially or completely done with assistance of AI/LLM tools needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-community-reviews Status: This is awaiting for positive reviews from the community before a maintainer is assigned.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lints to disable in tests

6 participants