Skip to content

[java] New rule: AvoidAssert#3488

Closed
nathanspectacular wants to merge 10 commits into
pmd:masterfrom
nathanspectacular:AvoidAssertRule
Closed

[java] New rule: AvoidAssert#3488
nathanspectacular wants to merge 10 commits into
pmd:masterfrom
nathanspectacular:AvoidAssertRule

Conversation

@nathanspectacular

Copy link
Copy Markdown

Describe the PR

Adds a rule called AvoidAssert.

For test code, use the JUnit or TestNG Assert class since it cannot be disabled like the assert keyword.

For production code, it requires 3 test cases to cover all scenarios:

  1. The assert defaults to disabled. In this case, the assert's condition is not executed.
  2. If asserts are enabled, the assert's condition can evaluate to true and the condition may introduce side-effects that must be tested.
  3. If asserts are enabled, the assert's condition can evaluate to false and the exception handling must be tested.

An if statement is a better choice since it cannot be disabled and hence reduces the need for 1 test case.

Ready?

  • Added unit tests for fixed bug/feature
  • Passing all unit tests
  • Complete build ./mvnw clean verify passes (checked automatically by github actions)
  • Added (in-code) documentation (if needed)

Note: This is my first pull request for PMD and Github. This rule is very simple so that I can learn the process. Please let me know what else I need to do. If you feel I need to enhance the rule, please let me know.

@adangel adangel changed the title Avoid assert rule [java] New rule: AvoidAssert Aug 27, 2021
@adangel adangel added the a:new-rule Proposal to add a new built-in rule label Aug 27, 2021

@adangel adangel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR!

I'm not sure, whether the rule is useful in it's current state. I agree with your first point, that for unit tests, the assert statement should not be used, since it can be disabled.

But for production code, this is different: I think, that assert statements are used to check pre-conditions / post-conditions. And in production, the asserts are often not enabled (since it makes the program run slower). But for running unit tests, these are typically enabled (at least maven-surefire-plugin does it that way). So you would notice, when your pre/post-conditions fail during unit tests.

The good point about assert statements is, that they indeed can be disabled. But then again, they must not have any side-effect. I think, you can just use an arbitrary expression that evaluates to a boolean as an assert expression, so you could potentially write something like

assert i++ == 0

I currently see two ways, to improve the rule:

  1. detect, whether the compilation unit is a test case --> then any assert statement should be banned
  2. if it's not a test case, then check, whether the condition has any side-effects (like calling a method, assigning a variable). In that case, the assert should be flagged.

Otherwise, we wouldn't allow any valid usages of the assert statement at all...

This release ships with 1 new Java rule.

* java-errorprone
* [`AvoidAssert`](https://pmd.github.io/pmd-6.38.0-SNAPSHOT/pmd_rules_java_errorprone.html#avoidassert) reports usages of assert. An if statement is a better choice since it cannot be disabled and hence reduces the need for 1 test case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We use here a own liquid tag to link to rules: {% rule java/errorprone/AvoidAssert %}
See also https://pmd.github.io/latest/pmd_devdocs_writing_documentation.html#custom-liquid-tags

@ghost

ghost commented Aug 27, 2021

Copy link
Copy Markdown
1 Message
📖 This changeset changes 0 violations,
introduces 1884 new violations, 0 new errors and 0 new configuration errors,
removes 0 violations, 0 errors and 3 configuration errors.
Full report

Generated by 🚫 Danger

@nathanspectacular

Copy link
Copy Markdown
Author

I did not check off "Complete build ./mvnw clean verify passes" since it appears to be automated. However, I did do this. In the future, shall I check this off?

@nathanspectacular

nathanspectacular commented Aug 27, 2021

Copy link
Copy Markdown
Author

I figured I might need to add some properties.

The first property I propose is annotations. annotations will have JUnit's and TestNG's @Test annotations written as fully qualified names. If any method in the class has these annotations, then the rule will flag all asserts in the class.

The second property I propose is paths. paths is a : delimited set of path fragments (e.g. /test/). Any class that has one of these path fragments in the file path will not be allowed to have asserts in the class. The default for this property will be /test/. The end user can add additional paths as desired. For example, I could put /src/ in my projects and disallow asserts in all code including production code.

The third property I propose is strictCondition. strictCondition is a boolean that defaults to true and enforces simple boolean expressions with no assignments and no method calls. When set to false, the rule will allow for anything in the condition. A future project could relax this and allow for calls to methods that are side-effect free.

What do you think?

@oowekyala

oowekyala commented Aug 29, 2021

Copy link
Copy Markdown
Member

The good point about assert statements is, that they indeed can be disabled. But then again, they must not have any side-effect.

This is exactly my sentiment. I think we should have a rule "SideEffectInAssert" to flag side effects, but pure asserts are absolutely fine and we shouldn't track them down with a generic AvoidAssert.

My suggestion would be to have a rule SideEffectInAssert, and another rule to flag asserts just in tests (maybe named "UseTestFrameworkAssertion") instead of AvoidAssert.

For test code, use the JUnit or TestNG Assert class since it cannot be disabled like the assert keyword.

I think this justification is pretty thin, since I would say every test runner out there uses -ea (or should do so). One more tangible benefit to using those classes is they provide better error messages.

The first property I propose is annotations. annotations will have JUnit's and TestNG's @test annotations written as fully qualified names. If any method in the class has these annotations, then the rule will flag all asserts in the class.

I think there's no need to have a property for this. We could just use a list of annotations that belong to those test frameworks, in line with some other rules about testing.

The second property I propose is paths. paths is a : delimited set of path fragments (e.g. /test/). Any class that has one of these path fragments in the file path will not be allowed to have asserts in the class. The default for this property will be /test/. The end user can add additional paths as desired. For example, I could put /src/ in my projects and disallow asserts in all code including production code.

I think this can be achieved by configuring rulesets. I don't like the idea of adding ad-hoc file filters to rules

@adangel

adangel commented Sep 3, 2021

Copy link
Copy Markdown
Member

I did not check off "Complete build ./mvnw clean verify passes" since it appears to be automated. However, I did do this. In the future, shall I check this off?

This is a checklist for you and us: If you tick this, you say "it worked on my machine, I tested it...".

Since you are a first-time contributor, you pull request must first be approved by one of us, before it is executed on github actions, see https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks for background info.

@adangel

adangel commented Sep 3, 2021

Copy link
Copy Markdown
Member

introduces 1884 new violations,

That is a lot of new violations! Is there something I need to do with this? Is this expected?

Since this is a new rule, new violations are expected.
But the report gives you the opportunity to look through the found violations and think about, whether they are real violations or false positives.

@adangel

adangel commented Sep 24, 2021

Copy link
Copy Markdown
Member

For test code, use the JUnit or TestNG Assert class since it cannot be disabled like the assert keyword.
I think this justification is pretty thin, since I would say every test runner out there uses -ea (or should do so).

There is indeed a test runner, that doesn't enable assertions by default (for whatever reason...): https://www.eclipse.org/tycho/sitedocs/tycho-surefire-plugin/test-mojo.html#enableAssertions

The usual maven surefire plugin however, does this by default: https://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#enableAssertions

One more tangible benefit to using those classes is they provide better error messages.

Yes, that's the reason, why you should use a more sophisticated Assert class to report a failing test.

In summary, I think, this rule should be named something like "AssertStatementInTests" and the rule should only report assert statements in test classes. The category here would be best practices.
An example for this would be GenericConversionServiceTests.java#L422

And maybe, we should have a separate rule, that finds assert statement in both production code and test code, that have side effects (e.g. "AssertStatementWithSideeffect"). This would belong to the category error prone.

@oowekyala oowekyala left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@nathanspectacular I split off #3777 and #3776 to track new rules about assertions. You're welcome to rework this PR to eg implement #3777 and submit it again. I'll be happy to review it and help.

@oowekyala oowekyala closed this Feb 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a:new-rule Proposal to add a new built-in rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants