@@ -5,10 +5,18 @@ Subject: [PATCH] Test changes
5
5
6
6
7
7
diff --git a/build.gradle.kts b/build.gradle.kts
8
- index e7fa464573909d4c3d649ebb5f40ef54055e09a8..2df1cae62cff433a7f3f55f561f70719bb6a745b 100644
8
+ index e7fa464573909d4c3d649ebb5f40ef54055e09a8..8d2aa99b4bd0d1c46c66274907a1f11d605a75da 100644
9
9
--- a/build.gradle.kts
10
10
+++ 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 {
12
20
options.setIncremental(false)
13
21
}
14
22
@@ -97,6 +105,238 @@ index 0000000000000000000000000000000000000000..6eb95a5e2534974c0e52e2b78b04e7c2
97
105
+ return Collections.emptySet();
98
106
+ }
99
107
+ }
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
+ + }
100
340
diff --git a/src/test/java/org/bukkit/support/DummyServer.java b/src/test/java/org/bukkit/support/DummyServer.java
101
341
index b19d4f7d1fcb604b448a5084f6bfe56d47ab12b3..f3017525b0c2397fdc7ce0778add2e7b38e9e2ba 100644
102
342
--- a/src/test/java/org/bukkit/support/DummyServer.java
0 commit comments