Skip to content

Commit

Permalink
Merge pull request #10399 from Eulbobo/feat/equals_hashcode_verification
Browse files Browse the repository at this point in the history
feat(archUnit): adding equals/hashcode contract check
  • Loading branch information
murdos authored Jul 26, 2024
2 parents 32763d0 + facb0fa commit b321582
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) {
.add(SOURCE.template("archunit.properties"), to("src/test/resources/archunit.properties"))
.add(SOURCE.template("AnnotationArchTest.java"), testDestination.append("AnnotationArchTest.java"))
.add(SOURCE.template("HexagonalArchTest.java"), testDestination.append("HexagonalArchTest.java"))
.add(SOURCE.template("EqualsHashcodeArchTest.java"), testDestination.append("EqualsHashcodeArchTest.java"))
.and()
.javaDependencies()
.addDependency(archUnitDependency())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package {{packageName}};

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.lang.ArchCondition;
import org.junit.jupiter.api.Test;

@UnitTest
class EqualsHashcodeArchTest {
private static final String ROOT_PACKAGE = "{{packageName}}..";
private static final JavaClasses classes = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages(ROOT_PACKAGE);
@Test
void shouldHaveValidEqualsHashcodeContract() {
classes().that().resideInAnyPackage(ROOT_PACKAGE).and().areNotInterfaces().should(implementBothOrNone()).check(classes);
}

private ArchCondition.ConditionByPredicate<JavaClass> implementBothOrNone() {
return ArchCondition.from(
new DescribedPredicate<>("Class should implement none or both method equals and hashcode") {
@Override
public boolean test(JavaClass clazz) {
return hasEquals(clazz) == hasHashCode(clazz);
}

private boolean hasHashCode(JavaClass clazz) {
return clazz.getMethods().stream().anyMatch(this::isMethodHashCode);
}

private boolean isMethodHashCode(JavaMethod method) {
return method.getName().equals("hashCode") && method.getParameters().isEmpty();
}

private boolean hasEquals(JavaClass clazz) {
return clazz.getMethods().stream().anyMatch(this::isMethodEquals);
}

private boolean isMethodEquals(JavaMethod method) {
return method.getName().equals("equals") && hasOneObjectParameter(method);
}

private boolean hasOneObjectParameter(JavaMethod method) {
return method.getParameters().size() == 1 && method.getParameters().getFirst().getRawType().isAssignableFrom(Object.class);
}
}
);
}
}
4 changes: 3 additions & 1 deletion src/test/features/server/javatool/arch-unit.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ Feature: Arch Unit
When I apply "java-archunit" module to default project with maven file
| packageName | tech.jhipster.chips |
Then I should have files in "src/test/java/tech/jhipster/chips"
| HexagonalArchTest.java |
| HexagonalArchTest.java |
| AnnotationArchTest.java |
| EqualsHashcodeArchTest.java |
58 changes: 58 additions & 0 deletions src/test/java/tech/jhipster/lite/EqualsHashcodeArchTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package tech.jhipster.lite;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.lang.ArchCondition;
import org.junit.jupiter.api.Test;

@UnitTest
class EqualsHashcodeArchTest {

private static final String ROOT_PACKAGE = "tech.jhipster.lite..";

private static final JavaClasses classes = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages(ROOT_PACKAGE);

@Test
void shouldHaveValidEqualsHashcodeContract() {
classes().that().resideInAnyPackage(ROOT_PACKAGE).and().areNotInterfaces().should(implementBothOrNone()).check(classes);
}

private ArchCondition.ConditionByPredicate<JavaClass> implementBothOrNone() {
return ArchCondition.from(
new DescribedPredicate<>("Class should implement none or both method equals and hashcode") {
@Override
public boolean test(JavaClass clazz) {
return hasEquals(clazz) == hasHashCode(clazz);
}

private boolean hasHashCode(JavaClass clazz) {
return clazz.getMethods().stream().anyMatch(this::isMethodHashCode);
}

private boolean isMethodHashCode(JavaMethod method) {
return method.getName().equals("hashCode") && method.getParameters().isEmpty();
}

private boolean hasEquals(JavaClass clazz) {
return clazz.getMethods().stream().anyMatch(this::isMethodEquals);
}

private boolean isMethodEquals(JavaMethod method) {
return method.getName().equals("equals") && hasOneObjectParameter(method);
}

private boolean hasOneObjectParameter(JavaMethod method) {
return method.getParameters().size() == 1 && method.getParameters().getFirst().getRawType().isAssignableFrom(Object.class);
}
}
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ void shouldBuildModule() {
JHipsterModule module = factory.buildModule(properties);

assertThatModuleWithFiles(module, pomFile(), testLogbackFile())
.hasFiles("src/test/resources/archunit.properties", "src/test/java/tech/jhipster/jhlitest/AnnotationArchTest.java")
.hasFiles(
"src/test/resources/archunit.properties",
"src/test/java/tech/jhipster/jhlitest/AnnotationArchTest.java",
"src/test/java/tech/jhipster/jhlitest/HexagonalArchTest.java",
"src/test/java/tech/jhipster/jhlitest/EqualsHashcodeArchTest.java"
)
.hasFile("pom.xml")
.containing("<artifactId>archunit-junit5-api</artifactId>")
.and()
Expand Down

0 comments on commit b321582

Please sign in to comment.