Skip to content
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

Add test_without_fail_case lint to check if a test actually has a way to fail or not #13579

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

kayagokalp
Copy link

@kayagokalp kayagokalp commented Oct 21, 2024

related to #12484.

changelog: Adds a test_without_fail_case lint which checks if a test can fail or not. If failure is not possible, the test is linted. This is done to prevent any silently failing tests that actually was not asserting anything, improving test suite quality.

@rustbot
Copy link
Collaborator

rustbot commented Oct 21, 2024

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @blyxyas (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 (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Oct 21, 2024
@kayagokalp kayagokalp changed the title Kayagokalp/test without panic Add test_without_fail_case lint to check if a test actually has a way to fail or not Oct 21, 2024
README.md Show resolved Hide resolved
@kayagokalp kayagokalp marked this pull request as ready for review October 22, 2024 00:01
@kayagokalp kayagokalp marked this pull request as draft October 22, 2024 00:02
@kayagokalp kayagokalp marked this pull request as ready for review October 22, 2024 00:16
@kayagokalp
Copy link
Author

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Oct 24, 2024
@kayagokalp
Copy link
Author

I am adding configuration capabilities to this PR and will also add more tests.

Copy link
Member

@blyxyas blyxyas left a comment

Choose a reason for hiding this comment

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

Good starting point, but I'm worried for performance and memory usage. I'm trying to think of a way to check if a function links to the panic handler DefId.

Comment on lines 846 to 859
Whether to consider indexing as a fallible operation while assesing if a test can fail.
Indexing is fallible, and thus the a test that is doing that can fail but it is likely
that tests that fail this way were not intended.

If set true, the lint will consider indexing into a slice a failable case
and won't lint tests that has some sort of indexing. This analysis still done
in a interprocedural manner. Meaning that any indexing opeartion done inside of
a function that the test calls will still result the test getting marked fallible.

By default this is set to `false`. That is because from a usability perspective,
indexing an array is not the intended way to fail a test. So setting this `true`
reduces false positives but makes the analysis more focused on possible byproducts
of a test. That is the set of operations to get the point we assert something rather
than the existance of asserting that thing.
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be excellent to have a way to, instead of adding a third configuration option, be able to use std::ops::Index::index or similar in the other two configuration options.

Anyway, this is way too complex.

Suggested change
Whether to consider indexing as a fallible operation while assesing if a test can fail.
Indexing is fallible, and thus the a test that is doing that can fail but it is likely
that tests that fail this way were not intended.
If set true, the lint will consider indexing into a slice a failable case
and won't lint tests that has some sort of indexing. This analysis still done
in a interprocedural manner. Meaning that any indexing opeartion done inside of
a function that the test calls will still result the test getting marked fallible.
By default this is set to `false`. That is because from a usability perspective,
indexing an array is not the intended way to fail a test. So setting this `true`
reduces false positives but makes the analysis more focused on possible byproducts
of a test. That is the set of operations to get the point we assert something rather
than the existance of asserting that thing.
Whether to consider indexing (`a[b]`) as a fallible operation while checking if a test can fail.
Indexing is fallible, and thus it can panic, but this panic is likely not intended to be tested.

Copy link
Author

Choose a reason for hiding this comment

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

I think it would be excellent to have a way to, instead of adding a third configuration option, be able to use std::ops::Index::index or similar in the other two configuration options.

I agree, I'll think about it

book/src/lint_configuration.md Outdated Show resolved Hide resolved
clippy_lints/src/test_without_fail_case.rs Outdated Show resolved Hide resolved
Comment on lines +61 to +63
indexing_fallible: conf.test_without_fail_case_include_indexing_as_fallible,
fallible_paths: conf.test_without_fail_case_fallible_paths.iter().cloned().collect(),
non_fallible_paths: conf.test_without_fail_case_non_fallible_paths.iter().cloned().collect(),
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason that these are FxHashSet?

Copy link
Author

Choose a reason for hiding this comment

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

Mostly out of habit of using FxHashSet in the compiler for performance reasons. But given that this is only the configuration I'll change it into HashSet.

Copy link
Member

Choose a reason for hiding this comment

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

I meant: Is there a reason is it not a simple Vec? We prefer FxHashSet to HashSet, but I just see no use for this to be a hashed collection. (Unless there's a very performance improvement I'm not aware of)

Copy link
Author

Choose a reason for hiding this comment

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

I guess it is a small lookup table, as the list coming from configuration file likely to be limited, can't see a reason why it couldn't be a Vec for sure. Can change it.

type NestedFilter = nested_filter::OnlyBodies;

fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.fail_found {
Copy link
Member

Choose a reason for hiding this comment

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

Could you use ControlFlow<()> instead of self.fail_found?

The Visitor trait accepts it :)


impl<'tcx> Visitor<'tcx> for SearchFailIntraFunction<'_, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
type Result = ControlFlow<()>;

clippy_lints/src/test_without_fail_case.rs Outdated Show resolved Hide resolved
@kayagokalp
Copy link
Author

Good starting point, but I'm worried for performance and memory usage. I'm trying to think of a way to check if a function links to the panic handler DefId.

If there is way to see if a function links to a panic handler without visiting them explicitly that would be great! Otherwise to access the performance characteristics it might be wise to take a look at set of benchmarks. I am curious to see how many interleaved levels of functions calls are average/likely to be upper limit.

Comment on lines +6219 to +6221
[`test-without-fail-case-fallible-paths`]: https://doc.rust-lang.org/clippy/lint_configuration.html#test-without-fail-case-fallible-paths
[`test-without-fail-case-include-indexing-as-fallible`]: https://doc.rust-lang.org/clippy/lint_configuration.html#test-without-fail-case-include-indexing-as-fallible
[`test-without-fail-case-non-fallible-paths`]: https://doc.rust-lang.org/clippy/lint_configuration.html#test-without-fail-case-non-fallible-paths
Copy link
Member

Choose a reason for hiding this comment

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

I find 3 configuration values for this lint a bit much. Also those are a mouth full.

I think there should only one configuration option at most: A list of items/functions that should prevent triggering the lint.

Comment on lines +84 to +86
fn implicit_panic() {
panic!("this is an implicit panic");
}
Copy link
Member

Choose a reason for hiding this comment

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

I would expect that most of those functions, that move the assertion or panic out of the #[test] function is defined inside the same module as the tested code. Pruning the functions this lint checks with this heuristic, whether they panic, might prevent big performance impacts this lint might have.

fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
// Only interested in functions that are annotated with `#[test]`.
if let ItemKind::Fn(_, _, body_id) = item.kind
&& is_in_test_function(cx.tcx, item.hir_id())
Copy link
Member

Choose a reason for hiding this comment

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

This lint only runs on tests. This also means, that the lint can only detect something, if Clippy is run with cargo clippy --all-targets or cargo clippy --tests. This should be mentioned in the documentation of the lint.

I'm not sure, if lintcheck is run with --all-targets. It at least didn't trigger once for all the crates we test on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants