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

- convert to mockito for mocking of types
- Allow use of TYPE_USE annotations
- Ignore package-private methods for nullability annotations
- Add excludes for classes which don't pass

Co-authored-by: Riley Park <rileysebastianpark@gmail.com>
Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>

diff --git a/build.gradle.kts b/build.gradle.kts
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -0,0 +0,0 @@ dependencies {
     compileOnlyApi(checkerQual)
     testCompileOnly(checkerQual)
     // Paper end
+    testImplementation("org.mockito:mockito-core:4.9.0") // Paper - add mockito
 
     testImplementation("org.apache.commons:commons-lang3:3.12.0")
     testImplementation("junit:junit:4.13.2")
diff --git a/src/test/java/io/papermc/paper/testing/EmptyRegistry.java b/src/test/java/io/papermc/paper/testing/EmptyRegistry.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/test/java/io/papermc/paper/testing/EmptyRegistry.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.testing;
+
+import java.util.Collections;
+import java.util.Iterator;
+import org.bukkit.Keyed;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Registry;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public record EmptyRegistry() implements Registry<Keyed> {
+
+    @NotNull
+    @Override
+    public Iterator<Keyed> iterator() {
+        return Collections.emptyIterator();
+    }
+
+    @Override
+    public @Nullable Keyed get(@NotNull final NamespacedKey key) {
+        return null;
+    }
+}
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..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/test/java/io/papermc/paper/testing/EmptyTag.java
@@ -0,0 +0,0 @@
+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/io/papermc/paper/testing/TestServer.java b/src/test/java/io/papermc/paper/testing/TestServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/test/java/io/papermc/paper/testing/TestServer.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.testing;
+
+import java.util.logging.Logger;
+import org.bukkit.Bukkit;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Server;
+import org.bukkit.command.SimpleCommandMap;
+import org.bukkit.plugin.PluginManager;
+import org.bukkit.plugin.SimplePluginManager;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public final class TestServer {
+
+    @SuppressWarnings("removal")
+    public static void setup() {
+        //noinspection ConstantValue
+        if (Bukkit.getServer() != null) {
+            return;
+        }
+
+        final Server dummyServer = mock(Server.class);
+
+        final Logger logger = Logger.getLogger(TestServer.class.getCanonicalName());
+        when(dummyServer.getLogger()).thenReturn(logger);
+        when(dummyServer.getName()).thenReturn(TestServer.class.getSimpleName());
+        when(dummyServer.getVersion()).thenReturn("Version_" + TestServer.class.getPackage().getImplementationVersion());
+        when(dummyServer.getBukkitVersion()).thenReturn("BukkitVersion_" + TestServer.class.getPackage().getImplementationVersion());
+
+
+        final Thread currentThread = Thread.currentThread();
+        when(dummyServer.isPrimaryThread()).thenAnswer(ignored -> Thread.currentThread().equals(currentThread));
+
+        when(dummyServer.getTag(anyString(), any(NamespacedKey.class), any())).thenAnswer(ignored -> new EmptyTag());
+
+        final PluginManager pluginManager = new SimplePluginManager(dummyServer, new SimpleCommandMap(dummyServer));
+        when(dummyServer.getPluginManager()).thenReturn(pluginManager);
+
+        when(dummyServer.getRegistry(any())).thenAnswer(ignored -> new EmptyRegistry());
+        when(dummyServer.getScoreboardCriteria(anyString())).thenReturn(null);
+
+        Bukkit.setServer(dummyServer);
+    }
+
+}
diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/test/java/org/bukkit/AnnotationTest.java
+++ b/src/test/java/org/bukkit/AnnotationTest.java
@@ -0,0 +0,0 @@ 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 = {
@@ -0,0 +0,0 @@ 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
@@ -0,0 +0,0 @@ public class AnnotationTest {
                 }
 
                 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;
+                                }
+                            }
+                        }
+                        // Paper end - Allow use of TYPE_USE annotations
                         ParameterNode paramNode = parameters == null ? null : parameters.get(i);
                         String paramName = paramNode == null ? null : paramNode.name;
 
@@ -0,0 +0,0 @@ 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");
         }
 
-        Assert.fail("There " + errors.size() + " are missing annotation(s)");
+        Assert.fail(builder.toString());
     }
 
     private static void collectClasses(@NotNull File from, @NotNull Map<String, ClassNode> to) throws IOException {
@@ -0,0 +0,0 @@ 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)) {
@@ -0,0 +0,0 @@ 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;
         }
 
@@ -0,0 +0,0 @@ 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/TestServer.java b/src/test/java/org/bukkit/TestServer.java
deleted file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- a/src/test/java/org/bukkit/TestServer.java
+++ /dev/null
@@ -0,0 +0,0 @@
-package org.bukkit;
-
-import com.google.common.collect.ImmutableMap;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.logging.Logger;
-import org.bukkit.command.SimpleCommandMap;
-import org.bukkit.plugin.PluginManager;
-import org.bukkit.plugin.SimplePluginManager;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-public final class TestServer implements InvocationHandler {
-    private static interface MethodHandler {
-        Object handle(TestServer server, Object[] args);
-    }
-
-    private static final Map<Method, MethodHandler> methods;
-
-    static {
-        try {
-            ImmutableMap.Builder<Method, MethodHandler> methodMap = ImmutableMap.builder();
-            methodMap.put(
-                    Server.class.getMethod("isPrimaryThread"),
-                    new MethodHandler() {
-                        @Override
-                        public Object handle(TestServer server, Object[] args) {
-                            return Thread.currentThread().equals(server.creatingThread);
-                        }
-                    }
-                );
-            methodMap.put(
-                    Server.class.getMethod("getPluginManager"),
-                    new MethodHandler() {
-                        @Override
-                        public Object handle(TestServer server, Object[] args) {
-                            return server.pluginManager;
-                        }
-                    }
-                );
-            methodMap.put(
-                    Server.class.getMethod("getLogger"),
-                    new MethodHandler() {
-                        final Logger logger = Logger.getLogger(TestServer.class.getCanonicalName());
-                        @Override
-                        public Object handle(TestServer server, Object[] args) {
-                            return logger;
-                        }
-                    }
-                );
-            methodMap.put(
-                    Server.class.getMethod("getName"),
-                    new MethodHandler() {
-                        @Override
-                        public Object handle(TestServer server, Object[] args) {
-                            return TestServer.class.getSimpleName();
-                        }
-                    }
-                );
-            methodMap.put(
-                    Server.class.getMethod("getVersion"),
-                    new MethodHandler() {
-                        @Override
-                        public Object handle(TestServer server, Object[] args) {
-                            return "Version_" + TestServer.class.getPackage().getImplementationVersion();
-                        }
-                    }
-                );
-            methodMap.put(
-                    Server.class.getMethod("getBukkitVersion"),
-                    new MethodHandler() {
-                        @Override
-                        public Object handle(TestServer server, Object[] args) {
-                            return "BukkitVersion_" + TestServer.class.getPackage().getImplementationVersion();
-                        }
-                    }
-                );
-            methodMap.put(
-                    Server.class.getMethod("getRegistry", Class.class),
-                    new MethodHandler() {
-                        @Override
-                        public Object handle(TestServer server, Object[] args) {
-                            return new Registry() {
-                                @NotNull
-                                @Override
-                                public Iterator iterator() {
-                                    return null;
-                                }
-
-                                @Nullable
-                                @Override
-                                public Keyed get(@NotNull NamespacedKey key) {
-                                    return null;
-                                }
-                            };
-                        }
-                    }
-                );
-            methodMap.put(
-                    Server.class.getMethod("getScoreboardCriteria", String.class),
-                    new MethodHandler() {
-                        @Override
-                        public Object handle(TestServer server, Object[] args) {
-                            // Does not need to return anything. Exists solely to test CriteriaTest which has static init fields
-                            return null;
-                        }
-                    }
-                );
-            methods = methodMap.build();
-
-            TestServer server = new TestServer();
-            Server instance = Proxy.getProxyClass(Server.class.getClassLoader(), Server.class).asSubclass(Server.class).getConstructor(InvocationHandler.class).newInstance(server);
-            Bukkit.setServer(instance);
-            server.pluginManager = new SimplePluginManager(instance, new SimpleCommandMap(instance));
-        } catch (Throwable t) {
-            throw new Error(t);
-        }
-    }
-
-    private Thread creatingThread = Thread.currentThread();
-    private PluginManager pluginManager;
-    private TestServer() {};
-
-    public static Server getInstance() {
-        return Bukkit.getServer();
-    }
-
-    @Override
-    public Object invoke(Object proxy, Method method, Object[] args) {
-        MethodHandler handler = methods.get(method);
-        if (handler != null) {
-            return handler.handle(this, args);
-        }
-        throw new UnsupportedOperationException(String.valueOf(method));
-    }
-}
diff --git a/src/test/java/org/bukkit/TestWorld.java b/src/test/java/org/bukkit/TestWorld.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/test/java/org/bukkit/TestWorld.java
+++ b/src/test/java/org/bukkit/TestWorld.java
@@ -0,0 +0,0 @@ public final class TestWorld implements InvocationHandler {
 
     static {
         try {
-            TestServer.getInstance();
+            io.papermc.paper.testing.TestServer.setup(); // Paper
 
             ImmutableMap.Builder<Method, MethodHandler> methodMap = ImmutableMap.builder();
             methodMap.put(
diff --git a/src/test/java/org/bukkit/event/SyntheticEventTest.java b/src/test/java/org/bukkit/event/SyntheticEventTest.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/test/java/org/bukkit/event/SyntheticEventTest.java
+++ b/src/test/java/org/bukkit/event/SyntheticEventTest.java
@@ -0,0 +0,0 @@
 package org.bukkit.event;
 
-import org.bukkit.TestServer;
 import org.bukkit.plugin.PluginLoader;
 import org.bukkit.plugin.SimplePluginManager;
 import org.bukkit.plugin.TestPlugin;
@@ -0,0 +0,0 @@ public class SyntheticEventTest {
     @SuppressWarnings("deprecation")
     @Test
     public void test() {
-        final JavaPluginLoader loader = new JavaPluginLoader(TestServer.getInstance());
+        io.papermc.paper.testing.TestServer.setup(); // Paper
+        final JavaPluginLoader loader = new JavaPluginLoader(org.bukkit.Bukkit.getServer()); // Paper
         TestPlugin plugin = new TestPlugin(getClass().getName()) {
             @Override
             public PluginLoader getPluginLoader() {
                 return loader;
             }
         };
-        SimplePluginManager pluginManager = new SimplePluginManager(TestServer.getInstance(), null);
+        SimplePluginManager pluginManager = new SimplePluginManager(org.bukkit.Bukkit.getServer(), null); // Paper
 
         TestEvent event = new TestEvent(false);
         Impl impl = new Impl();
diff --git a/src/test/java/org/bukkit/plugin/PluginManagerTest.java b/src/test/java/org/bukkit/plugin/PluginManagerTest.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/test/java/org/bukkit/plugin/PluginManagerTest.java
+++ b/src/test/java/org/bukkit/plugin/PluginManagerTest.java
@@ -0,0 +0,0 @@ package org.bukkit.plugin;
 
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
-import org.bukkit.TestServer;
 import org.bukkit.event.Event;
 import org.bukkit.event.TestEvent;
 import org.bukkit.permissions.Permission;
@@ -0,0 +0,0 @@ public class PluginManagerTest {
         volatile Object value = null;
     }
 
-    private static final PluginManager pm = TestServer.getInstance().getPluginManager();
+    private static final PluginManager pm = org.bukkit.Bukkit.getServer().getPluginManager(); // Paper
 
     private final MutableObject store = new MutableObject();
 
diff --git a/src/test/java/org/bukkit/scoreboard/CriteriaTest.java b/src/test/java/org/bukkit/scoreboard/CriteriaTest.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/test/java/org/bukkit/scoreboard/CriteriaTest.java
+++ b/src/test/java/org/bukkit/scoreboard/CriteriaTest.java
@@ -0,0 +0,0 @@ package org.bukkit.scoreboard;
 
 import org.bukkit.Material;
 import org.bukkit.Statistic;
-import org.bukkit.TestServer;
 import org.bukkit.entity.EntityType;
 import org.junit.Assert;
 import org.junit.Test;
@@ -0,0 +0,0 @@ public class CriteriaTest {
 
     @Test
     public void testStatistic() {
-        TestServer.getInstance();
+        io.papermc.paper.testing.TestServer.setup(); // Paper
 
         Assert.assertThrows(IllegalArgumentException.class, () -> Criteria.statistic(Statistic.AVIATE_ONE_CM, Material.STONE)); // Generic statistic with block
         Assert.assertThrows(IllegalArgumentException.class, () -> Criteria.statistic(Statistic.AVIATE_ONE_CM, EntityType.CREEPER)); // Generic statistic with entity type