Skip to content

Commit 4eaf858

Browse files
committed
Add ArchUnit check for @NullMarked on all packages
1 parent 7cff2ff commit 4eaf858

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ val archUnit by testing.suites.registering(JvmTestSuite::class) {
120120
implementation(libs.apiguardian) {
121121
because("we validate that public classes are annotated")
122122
}
123+
implementation(libs.jspecify) {
124+
because("we validate that packages are annotated")
125+
}
126+
implementation(libs.assertj)
123127
runtimeOnly.bundle(libs.bundles.log4j)
124128
val modularProjects: List<Project> by rootProject
125129
modularProjects.forEach {

platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES;
1616
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.TOP_LEVEL_CLASSES;
1717
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
18-
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAnyPackage;
1918
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName;
2019
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameEndingWith;
2120
import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC;
@@ -27,6 +26,7 @@
2726
import static com.tngtech.archunit.lang.conditions.ArchPredicates.have;
2827
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
2928
import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;
29+
import static org.assertj.core.api.Assertions.assertThat;
3030
import static org.junit.jupiter.api.Assertions.assertTrue;
3131

3232
import java.lang.annotation.Annotation;
@@ -35,17 +35,21 @@
3535
import java.lang.annotation.Target;
3636
import java.util.Arrays;
3737
import java.util.function.BiPredicate;
38+
import java.util.stream.Stream;
3839

3940
import com.tngtech.archunit.base.DescribedPredicate;
4041
import com.tngtech.archunit.core.domain.JavaClass;
4142
import com.tngtech.archunit.core.domain.JavaClasses;
43+
import com.tngtech.archunit.core.domain.JavaPackage;
44+
import com.tngtech.archunit.core.domain.PackageMatcher;
4245
import com.tngtech.archunit.junit.AnalyzeClasses;
4346
import com.tngtech.archunit.junit.ArchTest;
4447
import com.tngtech.archunit.lang.ArchCondition;
4548
import com.tngtech.archunit.lang.ArchRule;
4649
import com.tngtech.archunit.library.GeneralCodingRules;
4750

4851
import org.apiguardian.api.API;
52+
import org.jspecify.annotations.NullMarked;
4953

5054
@AnalyzeClasses(packages = { "org.junit.platform", "org.junit.jupiter", "org.junit.vintage" })
5155
class ArchUnitTests {
@@ -64,7 +68,7 @@ class ArchUnitTests {
6468
.and(not(describe("are Kotlin SAM type implementations", simpleName("")))) //
6569
.and(not(describe("are Kotlin-generated classes that contain only top-level functions",
6670
simpleNameEndingWith("Kt")))) //
67-
.and(not(describe("are shadowed", resideInAnyPackage("..shadow..")))) //
71+
.and(not(describe("are shadowed", resideInAPackage("..shadow..")))) //
6872
.should().beAnnotatedWith(API.class);
6973

7074
@SuppressWarnings("unused")
@@ -76,6 +80,32 @@ class ArchUnitTests {
7680
.should(haveContainerAnnotationWithSameRetentionPolicy()) //
7781
.andShould(haveContainerAnnotationWithSameTargetTypes());
7882

83+
@ArchTest
84+
void packagesShouldBeNullMarked(JavaClasses classes) {
85+
var exclusions = Stream.of( //
86+
"..shadow..", //
87+
"org.junit.jupiter.api..", //
88+
"org.junit.jupiter.engine..", //
89+
"org.junit.jupiter.migrationsupport..", //
90+
"org.junit.jupiter.params..", //
91+
"org.junit.platform.launcher.." //
92+
).map(PackageMatcher::of).toList();
93+
94+
var subpackages = Stream.of("org.junit.platform", "org.junit.jupiter", "org.junit.vintage") //
95+
.map(classes::getPackage) //
96+
.flatMap(rootPackage -> rootPackage.getSubpackagesInTree().stream()) //
97+
.filter(pkg -> exclusions.stream().noneMatch(it -> it.matches(pkg.getName()))) //
98+
.filter(pkg -> !pkg.getClasses().isEmpty()) //
99+
.toList();
100+
assertThat(subpackages).isNotEmpty();
101+
102+
var violations = subpackages.stream() //
103+
.filter(pkg -> !pkg.isAnnotatedWith(NullMarked.class)) //
104+
.map(JavaPackage::getName) //
105+
.sorted();
106+
assertThat(violations).describedAs("The following packages are missing the @NullMarked annotation").isEmpty();
107+
}
108+
79109
@ArchTest
80110
void allAreIn(JavaClasses classes) {
81111
// about 928 classes found in all jars

0 commit comments

Comments
 (0)