Skip to content

Commit

Permalink
Add code convention tests using ArchUnit
Browse files Browse the repository at this point in the history
  • Loading branch information
stijn-dejongh committed Sep 23, 2024
1 parent c08855c commit b2764a5
Show file tree
Hide file tree
Showing 20 changed files with 306 additions and 74 deletions.
21 changes: 21 additions & 0 deletions commons-kernel/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>be.sddevelopment.commons</groupId>
<artifactId>code-utils</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<artifactId>commons-kernel</artifactId>

<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

</project>
Original file line number Diff line number Diff line change
@@ -1,43 +1,39 @@
/*-
* #%L
* code-utils
* commons-kernel
* %%
* Copyright (C) 2020 - 2021 SD Development
* Copyright (C) 2020 - 2024 SD Development
* %%
* Licensed under the EUPL, Version 1.1 or – as soon they will be
* approved by the European Commission - subsequent versions of the
* EUPL (the "Licence");
*
*
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
*
* http://ec.europa.eu/idabc/eupl5
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
* #L%
*/
package be.sddevelopment.commons.access;

import static be.sddevelopment.commons.access.AccessProtectionUtils.utilityClassConstructor;

import lombok.SneakyThrows;
package be.sddevelopment.commons.annotations;

/**
* <class_description>
* <p><b>notes</b>:
* <p>ON : Feb 27, 2021
* <p>
* Simple annotation to indicate the class is meant to contain only utilities.
* </p>
*
* @author <a href="https://github.com/stijn-dejongh" target="_blank">Stijn Dejongh</a>
* @version 1.0.0
* @created 25.03.21, Thursday
* @since 1.0.0
*/
public class UtilityClass {

@SneakyThrows
private UtilityClass() {
utilityClassConstructor();
}
@Utility
public @interface Constants {

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,13 @@
* #L%
*/

package be.sddevelopment.commons.access;
package be.sddevelopment.commons.annotations;

/**
* <p>
* Simple annotation to indicate the class is meant to be a utility class.
* </p>
*
* <h6>Example usage</h6>
* <pre>
* <code>
* // No example available yet
* </code>
* </pre>
*
* <h6>References</h6>
*
* @author <a href="https://github.com/stijn-dejongh" target="_blank">Stijn Dejongh</a>
* @version 1.0.0
* @created 25.03.21, Thursday
Expand Down
20 changes: 14 additions & 6 deletions commons-testing/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>be.sddevelopment</groupId>
<groupId>be.sddevelopment.commons</groupId>
<artifactId>code-utils</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
Expand All @@ -19,6 +19,10 @@

<dependencies>
<!-- BEGIN UTIL SCOPE -->
<dependency>
<groupId>be.sddevelopment.commons</groupId>
<artifactId>commons-kernel</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand All @@ -38,18 +42,22 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5-api</artifactId>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5-engine</artifactId>
<version>${archunit.version}</version>
</dependency>

<!-- BEGIN TEST SCOPE -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*-
* #%L
* commons-testing
* %%
* Copyright (C) 2020 - 2024 SD Development
* %%
* Licensed under the EUPL, Version 1.1 or – as soon they will be
* approved by the European Commission - subsequent versions of the
* EUPL (the "Licence");
*
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl5
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
* #L%
*/

package be.sddevelopment.commons.testing.conventions;

import static com.tngtech.archunit.lang.SimpleConditionEvent.satisfied;
import static com.tngtech.archunit.lang.SimpleConditionEvent.violated;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS;
import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS;
import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_USE_FIELD_INJECTION;
import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING;
import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_USE_JODATIME;
import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible;

import be.sddevelopment.commons.annotations.Constants;
import be.sddevelopment.commons.annotations.Utility;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaConstructor;
import com.tngtech.archunit.core.domain.JavaModifier;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.syntax.elements.GivenClassesConjunction;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import org.junit.jupiter.api.DisplayName;


@DisplayName("Complies with code conventions")
public interface CodeConventions {

@ArchTest
ArchRule CLASSES_DO_NOT_ACCESS_STANDARD_STREAMS = NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS;

@ArchTest
ArchRule NO_CLASSES_USE_FIELD_INJECTION = NO_CLASSES_SHOULD_USE_FIELD_INJECTION;

@ArchTest
ArchRule NO_GENERIC_EXCEPTION = NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS;

@ArchTest
ArchRule NO_JAVA_UTIL_LOGGING = NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING;

@ArchTest
ArchRule NO_JODATIME = NO_CLASSES_SHOULD_USE_JODATIME;

@ArchTest
ArchRule UTILITY_CLASSES_CAN_NOT_BE_INSTANTIATED = utilityClasses()
.should(notBeInstantiatable())
.because(
"Utility classes should not be instantiated")
.allowEmptyShould(true);

@ArchTest
ArchRule UTILITY_CLASSES_ARE_FINAL = utilityClasses()
.should()
.haveModifier(JavaModifier.FINAL)
.because("Utility classes should be final")
.allowEmptyShould(true);

private static GivenClassesConjunction utilityClasses() {
return classes().that().areAnnotatedWith(Utility.class).and().areNotAnnotations();
}

private static GivenClassesConjunction constantClasses() {
return classes().that().areAnnotatedWith(Constants.class).and().areNotAnnotations();
}

static ArchCondition<? super JavaClass> notBeInstantiatable() {
return new ArchCondition<>("not be instantiatable") {
@Override
public void check(JavaClass item, ConditionEvents events) {
if (isInstantiatable(item)) {
events.add(violated(item, "Class " + item.getName() + " is instantiatable"));
} else {
events.add(satisfied(item, "Class " + item.getName() + " is not instantiatable"));
}
}
};
}

static boolean isInstantiatable(JavaClass classToCheck) {

try {
if (classToCheck
.getConstructors()
.stream()
.map(JavaConstructor::getParameters)
.anyMatch(List::isEmpty)) {
var constructor = classToCheck.getConstructor().reflect();
makeAccessible(constructor);
constructor.newInstance();
return true;
} else {
return false;
}
} catch (UnsupportedOperationException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
return false;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* #L%
*/

package be.sddevelopment.commons.testing;
package be.sddevelopment.commons.testing.naming;

import java.lang.reflect.Method;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*-
* #%L
* commons
* %%
* Copyright (C) 2020 - 2024 SD Development
* %%
* Licensed under the EUPL, Version 1.1 or – as soon they will be
* approved by the European Commission - subsequent versions of the
* EUPL (the "Licence");
*
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl5
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
* #L%
*/

package be.sddevelopment.commons.testing;

import be.sddevelopment.commons.testing.conventions.CodeConventions;
import com.tngtech.archunit.junit.AnalyzeClasses;

@AnalyzeClasses(packages = "be.sddevelopment.commons.testing")
public class ConventionsAdherenceTests implements CodeConventions {

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static be.sddevelopment.commons.testing.ReflectionAssertionUtils.assertPrivateMember;
import static be.sddevelopment.commons.testing.ReflectionAssertionUtils.assertPrivateMemberReflectionProtection;

import be.sddevelopment.commons.testing.naming.ReplaceUnderscoredCamelCasing;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

import be.sddevelopment.commons.testing.naming.ReplaceUnderscoredCamelCasing;
import java.util.Optional;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
Expand Down
10 changes: 7 additions & 3 deletions commons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>be.sddevelopment</groupId>
<groupId>be.sddevelopment.commons</groupId>
<artifactId>code-utils</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
Expand All @@ -29,6 +29,10 @@
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>be.sddevelopment.commons</groupId>
<artifactId>commons-kernel</artifactId>
</dependency>

<!-- BEGIN TEST SCOPE -->
<dependency>
Expand All @@ -55,9 +59,9 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>be.sddevelopment</groupId>
<groupId>be.sddevelopment.commons</groupId>
<artifactId>commons-testing</artifactId>
<version>${parent.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import static be.sddevelopment.commons.access.AccessProtectionUtils.AccessProtectionConstants.UTILITY_CLASS;
import static java.lang.String.format;

import be.sddevelopment.commons.annotations.Utility;

/**
* <p>Shortcodes for class access restrictions and common error codes</p>
*
Expand Down
Loading

0 comments on commit b2764a5

Please sign in to comment.