From d0ee71a0ef7364201b742c4ed7283f852e316fe0 Mon Sep 17 00:00:00 2001
From: Manfred Hanke
Date: Mon, 3 Oct 2022 13:28:31 +0200
Subject: [PATCH] add general coding rule ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE
Signed-off-by: Manfred Hanke
---
.../archunit/library/GeneralCodingRules.java | 18 ++++++++++
.../library/GeneralCodingRulesTest.java | 35 +++++++++++++++++++
.../assertion/ArchRuleCheckAssertion.java | 18 ++++------
3 files changed, 60 insertions(+), 11 deletions(-)
diff --git a/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java b/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java
index 3296f2f1aa..f0a6725cad 100644
--- a/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java
+++ b/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java
@@ -480,4 +480,22 @@ public void check(JavaClass implementationClass, ConditionEvents events) {
}
};
}
+
+ /**
+ * A rule that checks that all {@link AssertionError AssertionErrors} (e.g. from the {@code assert} keyword) have a detail message.
+ *
+ * Example:
+ *
{@code
+ * assert x > 0; // violation
+ * throw new AssertionError(); // violation
+ *
+ * assert x > 0 : "x is not positive"; // no violation
+ * throw new AssertionError("x is not positive"); // no violation
+ * }
+
+ */
+ @PublicAPI(usage = ACCESS)
+ public static final ArchRule ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE =
+ noClasses().should().callConstructor(AssertionError.class /* without detailMessage */)
+ .because("assertions should have a detail message");
}
diff --git a/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java b/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java
index f274c1d01b..352cffe787 100644
--- a/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java
+++ b/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java
@@ -12,8 +12,10 @@
import com.tngtech.archunit.library.testclasses.packages.incorrect.wrongsubdir.defaultsuffix.subdir.ImplementationClassWithWrongTestClassPackageTest;
import org.junit.Test;
+import static com.tngtech.archunit.library.GeneralCodingRules.ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE;
import static com.tngtech.archunit.library.GeneralCodingRules.testClassesShouldResideInTheSamePackageAsImplementation;
import static com.tngtech.archunit.testutil.Assertions.assertThatRule;
+import static java.util.regex.Pattern.quote;
public class GeneralCodingRulesTest {
@@ -80,4 +82,37 @@ public void should_not_pass_when_none_of_multiple_matching_test_classes_resides_
);
}
+ @Test
+ public void ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE_should_fail_on_assert_without_detail_message() {
+ class InvalidAssertions {
+ void f(int x) {
+ assert x > 0;
+ }
+
+ void f() {
+ throw new AssertionError();
+ }
+ }
+ assertThatRule(ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE)
+ .checking(new ClassFileImporter().importClasses(InvalidAssertions.class))
+ .hasViolations(2)
+ .hasViolationMatching(quote("Method <" + InvalidAssertions.class.getName() + ".f(int)> calls constructor ()> in ") + ".*")
+ .hasViolationMatching(quote("Method <" + InvalidAssertions.class.getName() + ".f()> calls constructor ()> in ") + ".*");
+ }
+
+ @Test
+ public void ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE_should_accept_assert_with_detail_message() {
+ class ValidAssertions {
+ void f(int x) {
+ assert x > 0 : "argument should be postive";
+ }
+
+ void f() {
+ throw new AssertionError("f() should not be called");
+ }
+ }
+ assertThatRule(ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE)
+ .checking(new ClassFileImporter().importClasses(ValidAssertions.class))
+ .hasNoViolation();
+ }
}
diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ArchRuleCheckAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ArchRuleCheckAssertion.java
index 7a65183649..e54def8e37 100644
--- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ArchRuleCheckAssertion.java
+++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ArchRuleCheckAssertion.java
@@ -30,20 +30,16 @@ private Optional checkRule(ArchRule rule, JavaClasses classes) {
}
public ArchRuleCheckAssertion hasViolationMatching(String regex) {
- for (String detail : evaluationResult.getFailureReport().getDetails()) {
- if (detail.matches(regex)) {
- return this;
- }
- }
- throw new AssertionError(String.format("No violation matches '%s'", regex));
+ assertThat(evaluationResult.getFailureReport().getDetails())
+ .as("violation details (should match '%s')", regex)
+ .anyMatch(detail -> detail.matches(regex));
+ return this;
}
public ArchRuleCheckAssertion hasNoViolationMatching(String regex) {
- for (String detail : evaluationResult.getFailureReport().getDetails()) {
- if (detail.matches(regex)) {
- throw new AssertionError(String.format("Violation matches '%s'", regex));
- }
- }
+ assertThat(evaluationResult.getFailureReport().getDetails())
+ .as("violation details (should not match '%s')", regex)
+ .noneMatch(detail -> detail.matches(regex));
return this;
}