Skip to content

Commit 351923d

Browse files
authored
Run round-trip adventure codec tests with JSON, NBT, and Java ops. Use JavaOps for conversions. (PaperMC#10031)
1 parent d95341e commit 351923d

6 files changed

+326
-53
lines changed

patches/server/0004-Test-changes.patch

Lines changed: 242 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@ Subject: [PATCH] Test changes
55

66

77
diff --git a/build.gradle.kts b/build.gradle.kts
8-
index e7fa464573909d4c3d649ebb5f40ef54055e09a8..2df1cae62cff433a7f3f55f561f70719bb6a745b 100644
8+
index e7fa464573909d4c3d649ebb5f40ef54055e09a8..8d2aa99b4bd0d1c46c66274907a1f11d605a75da 100644
99
--- a/build.gradle.kts
1010
+++ b/build.gradle.kts
11-
@@ -57,6 +57,12 @@ tasks.compileJava {
11+
@@ -22,6 +22,7 @@ dependencies {
12+
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
13+
testImplementation("org.hamcrest:hamcrest:2.2")
14+
testImplementation("org.mockito:mockito-core:5.5.0")
15+
+ testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest
16+
}
17+
18+
val craftbukkitPackageVersion = "1_20_R3" // Paper
19+
@@ -57,6 +58,12 @@ tasks.compileJava {
1220
options.setIncremental(false)
1321
}
1422

@@ -97,6 +105,238 @@ index 0000000000000000000000000000000000000000..6eb95a5e2534974c0e52e2b78b04e7c2
97105
+ return Collections.emptySet();
98106
+ }
99107
+}
108+
diff --git a/src/test/java/io/papermc/paper/util/MethodParameterProvider.java b/src/test/java/io/papermc/paper/util/MethodParameterProvider.java
109+
new file mode 100644
110+
index 0000000000000000000000000000000000000000..3f58ef36df34cd15fcb72189eeff057654adf0c6
111+
--- /dev/null
112+
+++ b/src/test/java/io/papermc/paper/util/MethodParameterProvider.java
113+
@@ -0,0 +1,206 @@
114+
+/*
115+
+ * 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
116+
+ *
117+
+ * All rights reserved. This program and the accompanying materials are
118+
+ * made available under the terms of the Eclipse Public License v2.0 which
119+
+ * accompanies this distribution and is available at
120+
+ *
121+
+ * https://www.eclipse.org/legal/epl-v20.html
122+
+ */
123+
+
124+
+package io.papermc.paper.util;
125+
+
126+
+import java.lang.reflect.Method;
127+
+import java.lang.reflect.Parameter;
128+
+import java.util.List;
129+
+import java.util.function.Predicate;
130+
+import java.util.stream.Stream;
131+
+import org.junit.jupiter.api.Test;
132+
+import org.junit.jupiter.api.TestFactory;
133+
+import org.junit.jupiter.api.TestTemplate;
134+
+import org.junit.jupiter.api.extension.ExtensionContext;
135+
+import org.junit.jupiter.params.support.AnnotationConsumer;
136+
+import org.junit.platform.commons.JUnitException;
137+
+import org.junit.platform.commons.PreconditionViolationException;
138+
+import org.junit.platform.commons.util.ClassLoaderUtils;
139+
+import org.junit.platform.commons.util.CollectionUtils;
140+
+import org.junit.platform.commons.util.Preconditions;
141+
+import org.junit.platform.commons.util.ReflectionUtils;
142+
+import org.junit.platform.commons.util.StringUtils;
143+
+import org.junitpioneer.jupiter.cartesian.CartesianParameterArgumentsProvider;
144+
+
145+
+import static java.lang.String.format;
146+
+import static java.util.Arrays.stream;
147+
+import static java.util.stream.Collectors.toList;
148+
+import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated;
149+
+import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream;
150+
+
151+
+public class MethodParameterProvider implements CartesianParameterArgumentsProvider<Object>, AnnotationConsumer<MethodParameterSource> {
152+
+ private MethodParameterSource source;
153+
+
154+
+ MethodParameterProvider() {
155+
+ }
156+
+
157+
+ @Override
158+
+ public void accept(final MethodParameterSource source) {
159+
+ this.source = source;
160+
+ }
161+
+
162+
+ @Override
163+
+ public Stream<Object> provideArguments(ExtensionContext context, Parameter parameter) {
164+
+ return this.provideArguments(context, this.source);
165+
+ }
166+
+
167+
+ // Below is mostly copied from MethodArgumentsProvider
168+
+
169+
+ private static final Predicate<Method> isFactoryMethod = //
170+
+ method -> isConvertibleToStream(method.getReturnType()) && !isTestMethod(method);
171+
+
172+
+ protected Stream<Object> provideArguments(ExtensionContext context, MethodParameterSource methodSource) {
173+
+ Class<?> testClass = context.getRequiredTestClass();
174+
+ Method testMethod = context.getRequiredTestMethod();
175+
+ Object testInstance = context.getTestInstance().orElse(null);
176+
+ String[] methodNames = methodSource.value();
177+
+ // @formatter:off
178+
+ return stream(methodNames)
179+
+ .map(factoryMethodName -> findFactoryMethod(testClass, testMethod, factoryMethodName))
180+
+ .map(factoryMethod -> validateFactoryMethod(factoryMethod, testInstance))
181+
+ .map(factoryMethod -> context.getExecutableInvoker().invoke(factoryMethod, testInstance))
182+
+ .flatMap(CollectionUtils::toStream);
183+
+ // @formatter:on
184+
+ }
185+
+
186+
+ private static Method findFactoryMethod(Class<?> testClass, Method testMethod, String factoryMethodName) {
187+
+ String originalFactoryMethodName = factoryMethodName;
188+
+
189+
+ // If the user did not provide a factory method name, find a "default" local
190+
+ // factory method with the same name as the parameterized test method.
191+
+ if (StringUtils.isBlank(factoryMethodName)) {
192+
+ factoryMethodName = testMethod.getName();
193+
+ return findFactoryMethodBySimpleName(testClass, testMethod, factoryMethodName);
194+
+ }
195+
+
196+
+ // Convert local factory method name to fully-qualified method name.
197+
+ if (!looksLikeAFullyQualifiedMethodName(factoryMethodName)) {
198+
+ factoryMethodName = testClass.getName() + "#" + factoryMethodName;
199+
+ }
200+
+
201+
+ // Find factory method using fully-qualified name.
202+
+ Method factoryMethod = findFactoryMethodByFullyQualifiedName(testClass, testMethod, factoryMethodName);
203+
+
204+
+ // Ensure factory method has a valid return type and is not a test method.
205+
+ Preconditions.condition(isFactoryMethod.test(factoryMethod), () -> format(
206+
+ "Could not find valid factory method [%s] for test class [%s] but found the following invalid candidate: %s",
207+
+ originalFactoryMethodName, testClass.getName(), factoryMethod));
208+
+
209+
+ return factoryMethod;
210+
+ }
211+
+
212+
+ private static boolean looksLikeAFullyQualifiedMethodName(String factoryMethodName) {
213+
+ if (factoryMethodName.contains("#")) {
214+
+ return true;
215+
+ }
216+
+ int indexOfFirstDot = factoryMethodName.indexOf('.');
217+
+ if (indexOfFirstDot == -1) {
218+
+ return false;
219+
+ }
220+
+ int indexOfLastOpeningParenthesis = factoryMethodName.lastIndexOf('(');
221+
+ if (indexOfLastOpeningParenthesis > 0) {
222+
+ // Exclude simple/local method names with parameters
223+
+ return indexOfFirstDot < indexOfLastOpeningParenthesis;
224+
+ }
225+
+ // If we get this far, we conclude the supplied factory method name "looks"
226+
+ // like it was intended to be a fully-qualified method name, even if the
227+
+ // syntax is invalid. We do this in order to provide better diagnostics for
228+
+ // the user when a fully-qualified method name is in fact invalid.
229+
+ return true;
230+
+ }
231+
+
232+
+ // package-private for testing
233+
+ static Method findFactoryMethodByFullyQualifiedName(
234+
+ Class<?> testClass, Method testMethod,
235+
+ String fullyQualifiedMethodName
236+
+ ) {
237+
+ String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName);
238+
+ String className = methodParts[0];
239+
+ String methodName = methodParts[1];
240+
+ String methodParameters = methodParts[2];
241+
+ ClassLoader classLoader = ClassLoaderUtils.getClassLoader(testClass);
242+
+ Class<?> clazz = loadRequiredClass(className, classLoader);
243+
+
244+
+ // Attempt to find an exact match first.
245+
+ Method factoryMethod = ReflectionUtils.findMethod(clazz, methodName, methodParameters).orElse(null);
246+
+ if (factoryMethod != null) {
247+
+ return factoryMethod;
248+
+ }
249+
+
250+
+ boolean explicitParameterListSpecified = //
251+
+ StringUtils.isNotBlank(methodParameters) || fullyQualifiedMethodName.endsWith("()");
252+
+
253+
+ // If we didn't find an exact match but an explicit parameter list was specified,
254+
+ // that's a user configuration error.
255+
+ Preconditions.condition(!explicitParameterListSpecified,
256+
+ () -> format("Could not find factory method [%s(%s)] in class [%s]", methodName, methodParameters,
257+
+ className));
258+
+
259+
+ // Otherwise, fall back to the same lenient search semantics that are used
260+
+ // to locate a "default" local factory method.
261+
+ return findFactoryMethodBySimpleName(clazz, testMethod, methodName);
262+
+ }
263+
+
264+
+ /**
265+
+ * Find the factory method by searching for all methods in the given {@code clazz}
266+
+ * with the desired {@code factoryMethodName} which have return types that can be
267+
+ * converted to a {@link Stream}, ignoring the {@code testMethod} itself as well
268+
+ * as any {@code @Test}, {@code @TestTemplate}, or {@code @TestFactory} methods
269+
+ * with the same name.
270+
+ *
271+
+ * @return the single factory method matching the search criteria
272+
+ * @throws PreconditionViolationException if the factory method was not found or
273+
+ * multiple competing factory methods with the same name were found
274+
+ */
275+
+ private static Method findFactoryMethodBySimpleName(Class<?> clazz, Method testMethod, String factoryMethodName) {
276+
+ Predicate<Method> isCandidate = candidate -> factoryMethodName.equals(candidate.getName())
277+
+ && !testMethod.equals(candidate);
278+
+ List<Method> candidates = ReflectionUtils.findMethods(clazz, isCandidate);
279+
+
280+
+ List<Method> factoryMethods = candidates.stream().filter(isFactoryMethod).collect(toList());
281+
+
282+
+ Preconditions.condition(factoryMethods.size() > 0, () -> {
283+
+ // If we didn't find the factory method using the isFactoryMethod Predicate, perhaps
284+
+ // the specified factory method has an invalid return type or is a test method.
285+
+ // In that case, we report the invalid candidates that were found.
286+
+ if (candidates.size() > 0) {
287+
+ return format(
288+
+ "Could not find valid factory method [%s] in class [%s] but found the following invalid candidates: %s",
289+
+ factoryMethodName, clazz.getName(), candidates);
290+
+ }
291+
+ // Otherwise, report that we didn't find anything.
292+
+ return format("Could not find factory method [%s] in class [%s]", factoryMethodName, clazz.getName());
293+
+ });
294+
+ Preconditions.condition(factoryMethods.size() == 1,
295+
+ () -> format("%d factory methods named [%s] were found in class [%s]: %s", factoryMethods.size(),
296+
+ factoryMethodName, clazz.getName(), factoryMethods));
297+
+ return factoryMethods.get(0);
298+
+ }
299+
+
300+
+ private static boolean isTestMethod(Method candidate) {
301+
+ return isAnnotated(candidate, Test.class) || isAnnotated(candidate, TestTemplate.class)
302+
+ || isAnnotated(candidate, TestFactory.class);
303+
+ }
304+
+
305+
+ private static Class<?> loadRequiredClass(String className, ClassLoader classLoader) {
306+
+ return ReflectionUtils.tryToLoadClass(className, classLoader).getOrThrow(
307+
+ cause -> new JUnitException(format("Could not load class [%s]", className), cause));
308+
+ }
309+
+
310+
+ private static Method validateFactoryMethod(Method factoryMethod, Object testInstance) {
311+
+ Preconditions.condition(
312+
+ factoryMethod.getDeclaringClass().isInstance(testInstance) || ReflectionUtils.isStatic(factoryMethod),
313+
+ () -> format("Method '%s' must be static: local factory methods must be static "
314+
+ + "unless the PER_CLASS @TestInstance lifecycle mode is used; "
315+
+ + "external factory methods must always be static.",
316+
+ factoryMethod.toGenericString()));
317+
+ return factoryMethod;
318+
+ }
319+
+}
320+
diff --git a/src/test/java/io/papermc/paper/util/MethodParameterSource.java b/src/test/java/io/papermc/paper/util/MethodParameterSource.java
321+
new file mode 100644
322+
index 0000000000000000000000000000000000000000..6cbf11c898439834cffb99ef84e5df1494356809
323+
--- /dev/null
324+
+++ b/src/test/java/io/papermc/paper/util/MethodParameterSource.java
325+
@@ -0,0 +1,14 @@
326+
+package io.papermc.paper.util;
327+
+
328+
+import java.lang.annotation.ElementType;
329+
+import java.lang.annotation.Retention;
330+
+import java.lang.annotation.RetentionPolicy;
331+
+import java.lang.annotation.Target;
332+
+import org.junitpioneer.jupiter.cartesian.CartesianArgumentsSource;
333+
+
334+
+@Retention(RetentionPolicy.RUNTIME)
335+
+@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
336+
+@CartesianArgumentsSource(MethodParameterProvider.class)
337+
+public @interface MethodParameterSource {
338+
+ String[] value() default {};
339+
+}
100340
diff --git a/src/test/java/org/bukkit/support/DummyServer.java b/src/test/java/org/bukkit/support/DummyServer.java
101341
index b19d4f7d1fcb604b448a5084f6bfe56d47ab12b3..f3017525b0c2397fdc7ce0778add2e7b38e9e2ba 100644
102342
--- a/src/test/java/org/bukkit/support/DummyServer.java

0 commit comments

Comments
 (0)