From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Shane Freeder <theboyetronic@gmail.com> Date: Sun, 17 Mar 2019 23:04:30 +0000 Subject: [PATCH] Test changes - Allow use of TYPE_USE annotations - Ignore package-private methods for nullability annotations - Add excludes for classes which don't pass - Disable stupid BukkitMirrorTest - configure mockito agent to address changes in newer java versions see https://openjdk.org/jeps/451 Co-authored-by: Riley Park <rileysebastianpark@gmail.com> Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com> Co-authored-by: Yannick Lamprecht <yannicklamprecht@live.de> diff --git a/build.gradle.kts b/build.gradle.kts index 75768ac6674accfce35ab8f31994d538f4bca5a6..5b6577b5cc6f383dda6b06dd6469e7339aa2c587 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,6 +11,18 @@ java { val annotationsVersion = "24.1.0" val bungeeCordChatVersion = "1.20-R0.2" +// Paper start - configure mockito agent that is needed in newer java versions +val mockitoAgent = configurations.register("mockitoAgent") +abstract class MockitoAgentProvider : CommandLineArgumentProvider { + @get:CompileClasspath + abstract val fileCollection: ConfigurableFileCollection + + override fun asArguments(): Iterable<String> { + return listOf("-javaagent:" + fileCollection.files.single().absolutePath) + } +} +// Paper end - configure mockito agent that is needed in newer java versions + dependencies { // api dependencies are listed transitively to API consumers api("com.google.guava:guava:33.3.1-jre") @@ -44,6 +56,7 @@ dependencies { testImplementation("org.hamcrest:hamcrest:2.2") testImplementation("org.mockito:mockito-core:5.14.1") testImplementation("org.ow2.asm:asm-tree:9.7.1") + mockitoAgent("org.mockito:mockito-core:5.14.1") { isTransitive = false } // Paper - configure mockito agent that is needed in newer java versions } configure<PublishingExtension> { @@ -105,8 +118,19 @@ tasks.withType<Javadoc> { tasks.test { useJUnitPlatform() + // Paper start - configure mockito agent that is needed in newer java versions + val provider = objects.newInstance<MockitoAgentProvider>() + provider.fileCollection.from(mockitoAgent) + jvmArgumentProviders.add(provider) + // Paper end - configure mockito agent that is needed in newer java versions } +// Paper start - compile tests with -parameters for better junit parameterized test names +tasks.compileTestJava { + options.compilerArgs.add("-parameters") +} +// Paper end + // Paper start val scanJar = tasks.register("scanJarForBadCalls", io.papermc.paperweight.tasks.ScanJarForBadCalls::class) { badAnnotations.add("Lio/papermc/paper/annotation/DoNotUse;") diff --git a/src/test/java/io/papermc/paper/testing/EmptyTag.java b/src/test/java/io/papermc/paper/testing/EmptyTag.java new file mode 100644 index 0000000000000000000000000000000000000000..77154095cfb8b259bdb318e8ff40cb6f559ebc18 --- /dev/null +++ b/src/test/java/io/papermc/paper/testing/EmptyTag.java @@ -0,0 +1,31 @@ +package io.papermc.paper.testing; + +import java.util.Collections; +import java.util.Set; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Tag; +import org.jetbrains.annotations.NotNull; + +public record EmptyTag(NamespacedKey key) implements Tag<Keyed> { + + @SuppressWarnings("deprecation") + public EmptyTag() { + this(NamespacedKey.randomKey()); + } + + @Override + public @NotNull NamespacedKey getKey() { + return this.key; + } + + @Override + public boolean isTagged(@NotNull final Keyed item) { + return false; + } + + @Override + public @NotNull Set<Keyed> getValues() { + return Collections.emptySet(); + } +} diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java index 64e7aef6220097edefdff3b98a771b988365930d..f8b8969ee7a0b6f7b3224ff081e35c14a398c9d0 100644 --- a/src/test/java/org/bukkit/AnnotationTest.java +++ b/src/test/java/org/bukkit/AnnotationTest.java @@ -29,7 +29,13 @@ public class AnnotationTest { "Lorg/jetbrains/annotations/Nullable;", "Lorg/jetbrains/annotations/NotNull;", "Lorg/jetbrains/annotations/Contract;", - "Lorg/bukkit/UndefinedNullability;" + "Lorg/bukkit/UndefinedNullability;", + // Paper start + "Lorg/checkerframework/checker/nullness/qual/MonotonicNonNull;", + "Lorg/checkerframework/checker/nullness/qual/NonNull;", + "Lorg/checkerframework/checker/nullness/qual/Nullable;", + "Lorg/checkerframework/checker/nullness/qual/PolyNull;", + // Paper end }; private static final String[] EXCLUDED_CLASSES = { @@ -40,7 +46,17 @@ public class AnnotationTest { "org/bukkit/util/io/Wrapper", "org/bukkit/plugin/java/PluginClassLoader", // Generic functional interface - "org/bukkit/util/Consumer" + "org/bukkit/util/Consumer", + // Paper start + // Timings history is broken in terms of nullability due to guavas Function defining that the param is NonNull + "co/aikar/timings/TimingHistory$2", + "co/aikar/timings/TimingHistory$2$1", + "co/aikar/timings/TimingHistory$2$1$1", + "co/aikar/timings/TimingHistory$2$1$2", + "co/aikar/timings/TimingHistory$3", + "co/aikar/timings/TimingHistory$4", + "co/aikar/timings/TimingHistoryEntry$1" + // Paper end }; @Test @@ -61,20 +77,60 @@ public class AnnotationTest { continue; } + // Paper start - skip class if it's @NullMarked + if (isClassNullMarked(clazz, foundClasses)) { + continue; + } + // Paper end - skip class if it's @NullMarked + for (MethodNode method : clazz.methods) { if (!isMethodIncluded(clazz, method, foundClasses)) { continue; } if (mustBeAnnotated(Type.getReturnType(method.desc)) && !isWellAnnotated(method.invisibleAnnotations)) { + // Paper start - Allow use of TYPE_USE annotations + boolean warn = true; + if (isWellAnnotated(method.visibleTypeAnnotations)) { + warn = false; + } else if (method.invisibleTypeAnnotations != null) { + dance: for (final org.objectweb.asm.tree.TypeAnnotationNode invisibleTypeAnnotation : method.invisibleTypeAnnotations) { + final org.objectweb.asm.TypeReference ref = new org.objectweb.asm.TypeReference(invisibleTypeAnnotation.typeRef); + if (ref.getSort() == org.objectweb.asm.TypeReference.METHOD_RETURN && java.util.Arrays.asList(ACCEPTED_ANNOTATIONS).contains(invisibleTypeAnnotation.desc)) { + warn = false; + break dance; // cha cha real smooth + } + } + } + if (warn) + // Paper end warn(errors, clazz, method, "return value"); } Type[] paramTypes = Type.getArgumentTypes(method.desc); List<ParameterNode> parameters = method.parameters; + dancing: // Paper for (int i = 0; i < paramTypes.length; i++) { if (mustBeAnnotated(paramTypes[i]) ^ isWellAnnotated(method.invisibleParameterAnnotations == null ? null : method.invisibleParameterAnnotations[i])) { + // Paper start + if (method.invisibleTypeAnnotations != null) { + for (final org.objectweb.asm.tree.TypeAnnotationNode invisibleTypeAnnotation : method.invisibleTypeAnnotations) { + final org.objectweb.asm.TypeReference ref = new org.objectweb.asm.TypeReference(invisibleTypeAnnotation.typeRef); + if (ref.getSort() == org.objectweb.asm.TypeReference.METHOD_FORMAL_PARAMETER && ref.getTypeParameterIndex() == i && java.util.Arrays.asList(ACCEPTED_ANNOTATIONS).contains(invisibleTypeAnnotation.desc)) { + continue dancing; + } + } + } + if (method.visibleTypeAnnotations != null) { + for (final org.objectweb.asm.tree.TypeAnnotationNode invisibleTypeAnnotation : method.visibleTypeAnnotations) { + final org.objectweb.asm.TypeReference ref = new org.objectweb.asm.TypeReference(invisibleTypeAnnotation.typeRef); + if (ref.getSort() == org.objectweb.asm.TypeReference.METHOD_FORMAL_PARAMETER && ref.getTypeParameterIndex() == i && java.util.Arrays.asList(ACCEPTED_ANNOTATIONS).contains(invisibleTypeAnnotation.desc)) { + continue dancing; + } + } + } + // Paper end - Allow use of TYPE_USE annotations ParameterNode paramNode = parameters == null ? null : parameters.get(i); String paramName = paramNode == null ? null : paramNode.name; @@ -91,13 +147,18 @@ public class AnnotationTest { Collections.sort(errors); - System.out.println(errors.size() + " missing annotation(s):"); + StringBuilder builder = new StringBuilder() + .append("There ") + .append(errors.size() != 1 ? "are " : "is ") + .append(errors.size()) + .append(" missing annotation") + .append(errors.size() != 1 ? "s:\n" : ":\n"); + for (String message : errors) { - System.out.print("\t"); - System.out.println(message); + builder.append("\t").append(message).append("\n"); } - fail("There " + errors.size() + " are missing annotation(s)"); + fail(builder.toString()); } private static void collectClasses(@NotNull File from, @NotNull Map<String, ClassNode> to) throws IOException { @@ -125,6 +186,23 @@ public class AnnotationTest { } } + // Paper start - skip class if it's @NullMarked + private static boolean isClassNullMarked(@NotNull ClassNode clazz, @NotNull Map<String, ClassNode> allClasses) { + if (clazz.nestHostClass != null) { + final ClassNode nestHostNode = allClasses.get(clazz.nestHostClass); + if (nestHostNode != null) { + return isClassNullMarked0(nestHostNode); + } + } + + return isClassNullMarked0(clazz); + } + + private static boolean isClassNullMarked0(@NotNull ClassNode clazz) { + return clazz.visibleAnnotations != null && clazz.visibleAnnotations.stream().anyMatch(node -> "Lorg/jspecify/annotations/NullMarked;".equals(node.desc)); + } + // Paper end - skip class if it's @NullMarked + private static boolean isClassIncluded(@NotNull ClassNode clazz, @NotNull Map<String, ClassNode> allClasses) { // Exclude private, synthetic or deprecated classes and annotations, since their members can't be null if ((clazz.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_DEPRECATED | Opcodes.ACC_ANNOTATION)) != 0) { @@ -140,6 +218,11 @@ public class AnnotationTest { // Exceptions are excluded return false; } + // Paper start + if (isInternal(clazz.invisibleAnnotations)) { + return false; + } + // Paper end for (String excludedClass : EXCLUDED_CLASSES) { if (excludedClass.equals(clazz.name)) { @@ -152,7 +235,7 @@ public class AnnotationTest { private static boolean isMethodIncluded(@NotNull ClassNode clazz, @NotNull MethodNode method, @NotNull Map<String, ClassNode> allClasses) { // Exclude private, synthetic and deprecated methods - if ((method.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_DEPRECATED)) != 0) { + if ((method.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_DEPRECATED)) != 0 || (method.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC)) == 0) { // Paper - ignore package-private return false; } @@ -170,11 +253,30 @@ public class AnnotationTest { if ("<init>".equals(method.name) && isAnonymous(clazz)) { return false; } + // Paper start + if (isInternal(method.invisibleAnnotations)) { + return false; + } + // Paper end return true; } + // Paper start + private static boolean isInternal(List<? extends AnnotationNode> annotations) { + if (annotations == null) { + return false; + } + for (AnnotationNode node : annotations) { + if (node.desc.equals("Lorg/jetbrains/annotations/ApiStatus$Internal;")) { + return true; + } + } + + return false; + } + // Paper end - private static boolean isWellAnnotated(@Nullable List<AnnotationNode> annotations) { + private static boolean isWellAnnotated(@Nullable List<? extends AnnotationNode> annotations) { // Paper if (annotations == null) { return false; } diff --git a/src/test/java/org/bukkit/BukkitMirrorTest.java b/src/test/java/org/bukkit/BukkitMirrorTest.java index 89ca06ebecdaadd5dfc7bc74473ca15ad36f6eff..5974ceea58940e1799f3589eac0e39b925a42c3b 100644 --- a/src/test/java/org/bukkit/BukkitMirrorTest.java +++ b/src/test/java/org/bukkit/BukkitMirrorTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +@org.junit.jupiter.api.Disabled // Paper public class BukkitMirrorTest { public static Stream<Arguments> data() { diff --git a/src/test/java/org/bukkit/support/TestServer.java b/src/test/java/org/bukkit/support/TestServer.java index c67e0784c043ed194f6acde32411e156412a9b24..eb1fd4b911c4af76cdd3eac85d5365e7941a4a2e 100644 --- a/src/test/java/org/bukkit/support/TestServer.java +++ b/src/test/java/org/bukkit/support/TestServer.java @@ -83,6 +83,11 @@ public final class TestServer { UnsafeValues unsafeValues = mock(withSettings().stubOnly()); when(instance.getUnsafe()).thenReturn(unsafeValues); + // Paper start - testing changes + when(instance.getTag(anyString(), any(NamespacedKey.class), any())).thenAnswer(ignored -> new io.papermc.paper.testing.EmptyTag()); + when(instance.getScoreboardCriteria(anyString())).thenReturn(null); + // Paper end - testing changes + Bukkit.setServer(instance); }