aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0004-Test-changes.patch
blob: 97ddef94044a2ef968a02f7cecc760151fe6faef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Mon, 13 Feb 2023 14:14:56 -0800
Subject: [PATCH] Test changes

Co-authored-by: yannnicklamprecht <yannicklamprecht@live.de>

diff --git a/build.gradle.kts b/build.gradle.kts
index f276414e9e81abf8f1f80991ebd5ab43472e07b1..7a0f2391a464eeebc5e57856300bc000b8d35e52 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -22,6 +22,7 @@ dependencies {
     testImplementation("org.hamcrest:hamcrest:2.2")
     testImplementation("org.mockito:mockito-core:5.11.0")
     testImplementation("org.ow2.asm:asm-tree:9.7")
+    testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest
 }
 
 paperweight {
@@ -55,6 +56,12 @@ tasks.jar {
     }
 }
 
+// Paper start - compile tests with -parameters for better junit parameterized test names
+tasks.compileTestJava {
+    options.compilerArgs.add("-parameters")
+}
+// Paper end
+
 publishing {
     publications.create<MavenPublication>("maven") {
     }
diff --git a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e1c14886064cde56be7fcd8f22a6ecb2d222a762
--- /dev/null
+++ b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
@@ -0,0 +1,33 @@
+package io.papermc.paper.registry;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+import net.minecraft.core.Registry;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.resources.ResourceLocation;
+import org.bukkit.support.AbstractTestingBase;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class RegistryKeyTest extends AbstractTestingBase {
+
+    @BeforeAll
+    static void before() throws ClassNotFoundException {
+        Class.forName(RegistryKey.class.getName()); // load all keys so they are found for the test
+    }
+
+    static Stream<RegistryKey<?>> data() {
+        return RegistryKeyImpl.REGISTRY_KEYS.stream();
+    }
+
+    @ParameterizedTest
+    @MethodSource("data")
+    void testApiRegistryKeysExist(final RegistryKey<?> key) {
+        final Optional<Registry<Object>> registry = AbstractTestingBase.REGISTRY_CUSTOM.registry(ResourceKey.createRegistryKey(ResourceLocation.parse(key.key().asString())));
+        assertTrue(registry.isPresent(), "Missing vanilla registry for " + key.key().asString());
+
+    }
+}
diff --git a/src/test/java/io/papermc/paper/util/EmptyTag.java b/src/test/java/io/papermc/paper/util/EmptyTag.java
new file mode 100644
index 0000000000000000000000000000000000000000..6eb95a5e2534974c0e52e2b78b04e7c2b2f28525
--- /dev/null
+++ b/src/test/java/io/papermc/paper/util/EmptyTag.java
@@ -0,0 +1,31 @@
+package io.papermc.paper.util;
+
+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/util/MethodParameterProvider.java b/src/test/java/io/papermc/paper/util/MethodParameterProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..3f58ef36df34cd15fcb72189eeff057654adf0c6
--- /dev/null
+++ b/src/test/java/io/papermc/paper/util/MethodParameterProvider.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2015-2023 the original author or authors of https://github.com/junit-team/junit5/blob/6593317c15fb556febbde11914fa7afe00abf8cd/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package io.papermc.paper.util;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.support.AnnotationConsumer;
+import org.junit.platform.commons.JUnitException;
+import org.junit.platform.commons.PreconditionViolationException;
+import org.junit.platform.commons.util.ClassLoaderUtils;
+import org.junit.platform.commons.util.CollectionUtils;
+import org.junit.platform.commons.util.Preconditions;
+import org.junit.platform.commons.util.ReflectionUtils;
+import org.junit.platform.commons.util.StringUtils;
+import org.junitpioneer.jupiter.cartesian.CartesianParameterArgumentsProvider;
+
+import static java.lang.String.format;
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.toList;
+import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated;
+import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream;
+
+public class MethodParameterProvider implements CartesianParameterArgumentsProvider<Object>, AnnotationConsumer<MethodParameterSource> {
+    private MethodParameterSource source;
+
+    MethodParameterProvider() {
+    }
+
+    @Override
+    public void accept(final MethodParameterSource source) {
+        this.source = source;
+    }
+
+    @Override
+    public Stream<Object> provideArguments(ExtensionContext context, Parameter parameter) {
+        return this.provideArguments(context, this.source);
+    }
+
+    // Below is mostly copied from MethodArgumentsProvider
+
+    private static final Predicate<Method> isFactoryMethod = //
+        method -> isConvertibleToStream(method.getReturnType()) && !isTestMethod(method);
+
+    protected Stream<Object> provideArguments(ExtensionContext context, MethodParameterSource methodSource) {
+        Class<?> testClass = context.getRequiredTestClass();
+        Method testMethod = context.getRequiredTestMethod();
+        Object testInstance = context.getTestInstance().orElse(null);
+        String[] methodNames = methodSource.value();
+        // @formatter:off
+        return stream(methodNames)
+            .map(factoryMethodName -> findFactoryMethod(testClass, testMethod, factoryMethodName))
+            .map(factoryMethod -> validateFactoryMethod(factoryMethod, testInstance))
+            .map(factoryMethod -> context.getExecutableInvoker().invoke(factoryMethod, testInstance))
+            .flatMap(CollectionUtils::toStream);
+        // @formatter:on
+    }
+
+    private static Method findFactoryMethod(Class<?> testClass, Method testMethod, String factoryMethodName) {
+        String originalFactoryMethodName = factoryMethodName;
+
+        // If the user did not provide a factory method name, find a "default" local
+        // factory method with the same name as the parameterized test method.
+        if (StringUtils.isBlank(factoryMethodName)) {
+            factoryMethodName = testMethod.getName();
+            return findFactoryMethodBySimpleName(testClass, testMethod, factoryMethodName);
+        }
+
+        // Convert local factory method name to fully-qualified method name.
+        if (!looksLikeAFullyQualifiedMethodName(factoryMethodName)) {
+            factoryMethodName = testClass.getName() + "#" + factoryMethodName;
+        }
+
+        // Find factory method using fully-qualified name.
+        Method factoryMethod = findFactoryMethodByFullyQualifiedName(testClass, testMethod, factoryMethodName);
+
+        // Ensure factory method has a valid return type and is not a test method.
+        Preconditions.condition(isFactoryMethod.test(factoryMethod), () -> format(
+            "Could not find valid factory method [%s] for test class [%s] but found the following invalid candidate: %s",
+            originalFactoryMethodName, testClass.getName(), factoryMethod));
+
+        return factoryMethod;
+    }
+
+    private static boolean looksLikeAFullyQualifiedMethodName(String factoryMethodName) {
+        if (factoryMethodName.contains("#")) {
+            return true;
+        }
+        int indexOfFirstDot = factoryMethodName.indexOf('.');
+        if (indexOfFirstDot == -1) {
+            return false;
+        }
+        int indexOfLastOpeningParenthesis = factoryMethodName.lastIndexOf('(');
+        if (indexOfLastOpeningParenthesis > 0) {
+            // Exclude simple/local method names with parameters
+            return indexOfFirstDot < indexOfLastOpeningParenthesis;
+        }
+        // If we get this far, we conclude the supplied factory method name "looks"
+        // like it was intended to be a fully-qualified method name, even if the
+        // syntax is invalid. We do this in order to provide better diagnostics for
+        // the user when a fully-qualified method name is in fact invalid.
+        return true;
+    }
+
+    // package-private for testing
+    static Method findFactoryMethodByFullyQualifiedName(
+        Class<?> testClass, Method testMethod,
+        String fullyQualifiedMethodName
+    ) {
+        String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName);
+        String className = methodParts[0];
+        String methodName = methodParts[1];
+        String methodParameters = methodParts[2];
+        ClassLoader classLoader = ClassLoaderUtils.getClassLoader(testClass);
+        Class<?> clazz = loadRequiredClass(className, classLoader);
+
+        // Attempt to find an exact match first.
+        Method factoryMethod = ReflectionUtils.findMethod(clazz, methodName, methodParameters).orElse(null);
+        if (factoryMethod != null) {
+            return factoryMethod;
+        }
+
+        boolean explicitParameterListSpecified = //
+            StringUtils.isNotBlank(methodParameters) || fullyQualifiedMethodName.endsWith("()");
+
+        // If we didn't find an exact match but an explicit parameter list was specified,
+        // that's a user configuration error.
+        Preconditions.condition(!explicitParameterListSpecified,
+            () -> format("Could not find factory method [%s(%s)] in class [%s]", methodName, methodParameters,
+                className));
+
+        // Otherwise, fall back to the same lenient search semantics that are used
+        // to locate a "default" local factory method.
+        return findFactoryMethodBySimpleName(clazz, testMethod, methodName);
+    }
+
+    /**
+     * Find the factory method by searching for all methods in the given {@code clazz}
+     * with the desired {@code factoryMethodName} which have return types that can be
+     * converted to a {@link Stream}, ignoring the {@code testMethod} itself as well
+     * as any {@code @Test}, {@code @TestTemplate}, or {@code @TestFactory} methods
+     * with the same name.
+     *
+     * @return the single factory method matching the search criteria
+     * @throws PreconditionViolationException if the factory method was not found or
+     *                                        multiple competing factory methods with the same name were found
+     */
+    private static Method findFactoryMethodBySimpleName(Class<?> clazz, Method testMethod, String factoryMethodName) {
+        Predicate<Method> isCandidate = candidate -> factoryMethodName.equals(candidate.getName())
+            && !testMethod.equals(candidate);
+        List<Method> candidates = ReflectionUtils.findMethods(clazz, isCandidate);
+
+        List<Method> factoryMethods = candidates.stream().filter(isFactoryMethod).collect(toList());
+
+        Preconditions.condition(factoryMethods.size() > 0, () -> {
+            // If we didn't find the factory method using the isFactoryMethod Predicate, perhaps
+            // the specified factory method has an invalid return type or is a test method.
+            // In that case, we report the invalid candidates that were found.
+            if (candidates.size() > 0) {
+                return format(
+                    "Could not find valid factory method [%s] in class [%s] but found the following invalid candidates: %s",
+                    factoryMethodName, clazz.getName(), candidates);
+            }
+            // Otherwise, report that we didn't find anything.
+            return format("Could not find factory method [%s] in class [%s]", factoryMethodName, clazz.getName());
+        });
+        Preconditions.condition(factoryMethods.size() == 1,
+            () -> format("%d factory methods named [%s] were found in class [%s]: %s", factoryMethods.size(),
+                factoryMethodName, clazz.getName(), factoryMethods));
+        return factoryMethods.get(0);
+    }
+
+    private static boolean isTestMethod(Method candidate) {
+        return isAnnotated(candidate, Test.class) || isAnnotated(candidate, TestTemplate.class)
+            || isAnnotated(candidate, TestFactory.class);
+    }
+
+    private static Class<?> loadRequiredClass(String className, ClassLoader classLoader) {
+        return ReflectionUtils.tryToLoadClass(className, classLoader).getOrThrow(
+            cause -> new JUnitException(format("Could not load class [%s]", className), cause));
+    }
+
+    private static Method validateFactoryMethod(Method factoryMethod, Object testInstance) {
+        Preconditions.condition(
+            factoryMethod.getDeclaringClass().isInstance(testInstance) || ReflectionUtils.isStatic(factoryMethod),
+            () -> format("Method '%s' must be static: local factory methods must be static "
+                    + "unless the PER_CLASS @TestInstance lifecycle mode is used; "
+                    + "external factory methods must always be static.",
+                factoryMethod.toGenericString()));
+        return factoryMethod;
+    }
+}
diff --git a/src/test/java/io/papermc/paper/util/MethodParameterSource.java b/src/test/java/io/papermc/paper/util/MethodParameterSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cbf11c898439834cffb99ef84e5df1494356809
--- /dev/null
+++ b/src/test/java/io/papermc/paper/util/MethodParameterSource.java
@@ -0,0 +1,14 @@
+package io.papermc.paper.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junitpioneer.jupiter.cartesian.CartesianArgumentsSource;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
+@CartesianArgumentsSource(MethodParameterProvider.class)
+public @interface MethodParameterSource {
+    String[] value() default {};
+}
diff --git a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java
index ebcb65cb74acdb9d1bcf2b4b3551a2dc6d809bc9..7d9dbed7281099b78d7f898885b37cdcfe8b099f 100644
--- a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java
+++ b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java
@@ -24,7 +24,7 @@ public class RegistryConstantsTest extends AbstractTestingBase {
     @Test
     public void testDamageType() {
         this.testExcessConstants(DamageType.class, Registry.DAMAGE_TYPE);
-        // this.testMissingConstants(DamageType.class, Registries.DAMAGE_TYPE); // WIND_CHARGE not registered
+        this.testMissingConstants(DamageType.class, Registries.DAMAGE_TYPE); // Paper - re-enable this one
     }
 
     @Test
diff --git a/src/test/java/org/bukkit/support/AbstractTestingBase.java b/src/test/java/org/bukkit/support/AbstractTestingBase.java
index 544307fe34cbcfa286a7d7b30900ebea127d189e..5bda0bc976920f224586f2c0e083b771c676e307 100644
--- a/src/test/java/org/bukkit/support/AbstractTestingBase.java
+++ b/src/test/java/org/bukkit/support/AbstractTestingBase.java
@@ -53,6 +53,11 @@ public abstract class AbstractTestingBase {
         MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, resourceRepository.getAvailablePacks().stream().map(Pack::open).toList());
         // add tags and loot tables for unit tests
         LayeredRegistryAccess<RegistryLayer> layers = RegistryLayer.createRegistryAccess();
+        // Paper start - load registry here to ensure bukkit object registry are correctly delayed if needed
+        try {
+            Class.forName("org.bukkit.Registry");
+        } catch (ClassNotFoundException ignored) {}
+        // Paper end - load registry here to ensure bukkit object registry are correctly delayed if needed
         layers = WorldLoader.loadAndReplaceLayer(resourceManager, layers, RegistryLayer.WORLDGEN, RegistryDataLoader.WORLDGEN_REGISTRIES);
         REGISTRY_CUSTOM = layers.compositeAccess().freeze();
         // Register vanilla pack
diff --git a/src/test/java/org/bukkit/support/DummyServer.java b/src/test/java/org/bukkit/support/DummyServer.java
index 1acdf5bc439c073c1777c2c4f5743ae082f4a621..183d30e3d3d413b05c762f374a964498d6ffdec4 100644
--- a/src/test/java/org/bukkit/support/DummyServer.java
+++ b/src/test/java/org/bukkit/support/DummyServer.java
@@ -62,7 +62,7 @@ public final class DummyServer {
             when(instance.getTag(any(), any(), any())).then(mock -> {
                 String registry = mock.getArgument(0);
                 Class<?> clazz = mock.getArgument(2);
-                MinecraftKey key = CraftNamespacedKey.toMinecraft(mock.getArgument(1));
+                net.minecraft.resources.ResourceLocation key = CraftNamespacedKey.toMinecraft(mock.getArgument(1)); // Paper - address remapping issues
 
                 switch (registry) {
                     case org.bukkit.Tag.REGISTRY_BLOCKS -> {
@@ -81,24 +81,32 @@ public final class DummyServer {
                     }
                     case org.bukkit.Tag.REGISTRY_FLUIDS -> {
                         Preconditions.checkArgument(clazz == org.bukkit.Fluid.class, "Fluid namespace must have fluid type");
-                        TagKey<FluidType> fluidTagKey = TagKey.create(Registries.FLUID, key);
+                        TagKey<net.minecraft.world.level.material.Fluid> fluidTagKey = TagKey.create(Registries.FLUID, key); // Paper - address remapping issues
                         if (BuiltInRegistries.FLUID.getTag(fluidTagKey).isPresent()) {
                             return new CraftFluidTag(BuiltInRegistries.FLUID, fluidTagKey);
                         }
                     }
                     case org.bukkit.Tag.REGISTRY_ENTITY_TYPES -> {
                         Preconditions.checkArgument(clazz == org.bukkit.entity.EntityType.class, "Entity type namespace must have entity type");
-                        TagKey<EntityTypes<?>> entityTagKey = TagKey.create(Registries.ENTITY_TYPE, key);
+                        TagKey<net.minecraft.world.entity.EntityType<?>> entityTagKey = TagKey.create(Registries.ENTITY_TYPE, key); // Paper - address remapping issues
                         if (BuiltInRegistries.ENTITY_TYPE.getTag(entityTagKey).isPresent()) {
                             return new CraftEntityTag(BuiltInRegistries.ENTITY_TYPE, entityTagKey);
                         }
                     }
-                    default -> throw new IllegalArgumentException();
+                    default -> new io.papermc.paper.util.EmptyTag(); // Paper - testing additions
                 }
 
                 return null;
             });
 
+            // Paper start - testing additions
+            final Thread currentThread = Thread.currentThread();
+            when(instance.isPrimaryThread()).thenAnswer(ignored -> Thread.currentThread().equals(currentThread));
+
+            final org.bukkit.plugin.PluginManager pluginManager = new org.bukkit.plugin.SimplePluginManager(instance, new org.bukkit.command.SimpleCommandMap(instance));
+            when(instance.getPluginManager()).thenReturn(pluginManager);
+            // paper end - testing additions
+
             Bukkit.setServer(instance);
         } catch (Throwable t) {
             throw new Error(t);