-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Lint to prevent redundant test_
prefix in test names.
#12861
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @xFrednet (or someone else) some time within the next two weeks. Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (
|
There are merge commits (commits with multiple parents) in your changes. We have a no merge policy so these commits will need to be removed for this pull request to be merged. You can start a rebase with the following commands:
The following commits are merge commits: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like there are conflicting opinions in the related issue, about whether this should only lint the functions that are directly within a cfg(test)
modules. So it might be a good idea to add a configuration allowing users to turn it on or off, something like warn-test-prefix-in-test-module-only
, but don't count me on naming stuffs, I'm so bad at it lol 😄
&& is_in_test_function(cx.tcx, body.id().hir_id) | ||
&& is_in_cfg_test(cx.tcx, body.id().hir_id) | ||
{ | ||
span_lint_and_help( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nicer if this lint could be auto-fixed, so, maybe use span_lint_and_sugg
instead?
But then it'll make things a bit more complicated when such test function is called in another test function, such as:
#[cfg(test)]
mod tests {
#[test]
fn test_a() {
// ...
}
#[test]
fn test_b() {
// ...
}
#[test]
fn test_a_and_b() {
test_a();
test_b();
}
}
therefore, you'll need to check for the function calls within the test function bodies, then adjust the applicability to Applicability::MaybeIncorrect
or something better... suggest changing all those names as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm interested in learning more about how to get this done, I'm pretty new to the clippy tooling as a contributor, and I was wondering how to plumb up fixing this automatically. Perhaps we could pair on this if you have some time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was in a family trip few days ago so I didn't see this message, sorry~
...and I was wondering how to plumb up fixing this automatically...
Well, what I meant to say was instead of using span_lint_and_help
, try using span_lint_and_sugg
. Therefore you can offer a suggestion with the lint, in this case, a new function name without the test_
prefix, as well as an applicablity tell it's machine applicable, then people can run clippy with --fix
to automatically apply the suggestion.
Something like...:
if let FnKind::ItemFn(ident, ..) = fn_kind &&
let Some(non_prefixed) = ident.name.as_str()
{
span_lint_and_sugg(
cx,
REDUNDANT_TEST_PREFIX,
span,
"this test is prefixed redundantly with `test`",
"consider removing the redundant `test` prefix from the test name",
non_prefixed,
Applicability::MachineApplicable
);
}
But then it'll make things a bit more complicated when such test function is called in another test function
As for this, it might just me being too paranoid 😅, as I haven't seen any real world example doing that afaik, so you can ignore it for now.
But if you are interested, here's a little hint:
Expand Me!
You can check each `expr` if it `is_in_test_function`, then if it is `Call`ing a function that have `test_` prefixed name, you can have the following info stored in a map:- the function's
DefId
(key) Span
s where it was called- a suggested name without prefix
- a bool indicates whether this function is a test function or not
for example
FxHashMap<DefId, (Vec<Span>, String, bool)>
Then you can check_fn
as usual but don't call span_lint*
yet, just updating the bool
value (or insert new entry to) the map you had and continue.
Finally, having a check_crate_post
function, you can go over that map you have, picking only the ones marked as test function and call emit lint, if the Vec<Span>
is not empty, you can then use multipart_suggestion.
I think this is one way to do it? idk, I'm writing this at almost 2am so most of them might not make sense lol 😅 .
fn test_redundant_name() { | ||
// | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add some test cases where the test functions are not in #[cfg(test)]
, because it seems like this should only lint when the test is_in_cfg_test
, but there are changes against it... well, got me a bit confused 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems like this should only lint when the test is_in_cfg_test
Not quite, because test can also be defined outside of a a `#[cfg(test)] annotation (and typical "test" module). IIRC, there's at least one example of that in the diff from this PR.
I'm not sure if there are conflicting opinions about this being constrained to modules or if that comment just thought it was more about not repeating the module name in the test name. Personally, I can't think of any good reason even if the test fn isn't in a test mod. It's clear it's a test since we can see the macro annotation directly above it. Same with looking at the stdout from running |
Sometimes you can't remove the (To be clear, this is an argument for at the very least making linting outside of test modules configurable. In test modules this seems like a non issue) |
/// ### Example | ||
/// ```no_run | ||
/// #[test] | ||
/// fn my_test_case() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// fn my_test_case() { | |
/// fn test_my_test_case() { |
I assume you're meaning to show a 'bad' example here that should trigger the lint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right, I need to update this.
@y21 True, but the intention of this is to avoid a common Java-ism where people prefix I agree, though, that it's hard to think of a name for happy path tests, since it's either So based on what I've seen:
That being said, people can still write a test like |
That being said, I wonder why |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation mostly looks good to me. I left some comments, mostly regarding the tests. The comments from @J-ZhengLi are also excellent and should be addressed.
You're welcome to ask if you need any clarification or help :)
/// ``` | ||
#[clippy::version = "1.79.0"] | ||
pub REDUNDANT_TEST_PREFIX, | ||
pedantic, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this shouldn't be in pedantic
but in restriction
instead. The usage of the test_
prefix doesn't seem like an antipattern to me. There are good arguments against it, but the number of changes required in existing changes shows that the usage is still quite common.
I'll include this decision in the group discussion before we merge this PR :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are arguments to be made for several categories: style
, pedantic
, restriction
, etc.
The usage of test_
seems to be pedantic
to me, because it's clearly unnecessary in almost every case, including some of the ones mentioned in the threads in this PR. For example, test names that have test_
+ method_name
, should just be super::method_name
IMO, following the same like of reasoning for as everywhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One argument I always consider is the number of lint triggers that happen in current code. Clippy can be a very pedantic, but the number of lint emissions has to be reasonable. If users get 100+ warnings of this lint with the next Clippy release it's way more likely that users get annoyed and simply allow the lint and that would help nobody.
This is not meant as a hard no, but just an explanation of my suggestion. I'm not sure where to place it and would leave this up to a group decision.
@@ -0,0 +1,11 @@ | |||
#![warn(clippy::redundant_test_prefix)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add more tests? Also negative examples, where the test_
prefix should be kept. For example, in normal functions and methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The normal functions and methods example makes sense, but can you (or anyone else here) think of other examples?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think those would be enough. One specific example I can think of would be:
mod no_attr {
pub mod tests {
pub fn test_nothing() {}
}
}
This looks very close to unit tests, but is missing the attributes. This should not be linted.
use super::*; | ||
|
||
#[test] | ||
fn test_redundant_name() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our UI test support error annotations. Could you add them?
These should work:
fn test_redundant_name() { | |
fn test_redundant_name() { | |
//~^ ERROR: this test is prefixed redundantly with `test` | |
//~| HELP: consider removing the redundant `test` prefix from the test name | |
//~| NOTE: `-D clippy::redundant-test-prefix` implied by `-D warnings` | |
//~| HELP: to override `-D warnings` add `#[allow(clippy::redundant_test_prefix)]` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add these when I'm on later tonight.
@rustbot author |
☔ The latest upstream changes (presumably #12849) made this pull request unmergeable. Please resolve the merge conflicts. |
Hey this is triage, I'm closing this due to inactivity. If you want to continue this implementation, you're welcome to create a new PR. Thank you for the time, you already put into this! Interested parties are welcome to pick this implementation up as well :) @rustbot label +S-inactive-closed -S-waiting-on-author -S-waiting-on-review |
The followup PR: #13710 |
This PR has started as an effort to proceed from the feedback in #12861. - Checks test functions (functions marked with `#[test]` annotation) for redundant "test_" prefix. - Auto-fix is supported (and handles collisions gracefully, see below). - If removing "test_" prefix from, say, `test_foo()` results in a name collision (either because function `foo()` is already defined within the current scope, or because the `foo()` call exists within function -- thus creating an unwanted recursion), lint suggests function rename, warning the user that a simple trimming of `test_` prefix will result in a name collision. - If removing "test_" prefix results in invalid identifier (consider `test_const`, `test_`, `test_42`), then again no auto-fix is suggested, user is asked to rename function, with a note that a simple prefix trimming will result in an invalid function name. (`Applicability::HasPlaceholders` is used and user is suggested to: drop `test_` prefix + add `_works` suffix, i.e. `test_foo` becomes `foo_works` -- but again, user has to apply those changes manually). - If trimmed version of the function name is a valid identifier, doesn't result in name collision or unwanted recursion, then user is able to run auto-fix. fixes #8931 changelog: new lint: [`redundant_test_prefix`]
Adds redundant_test_prefix lint, which checks test names for the
test_
prefix, and warns if found. Closes #8931changelog: New Lint: [
redundant_test_prefix
] #12861