From 351923d17e3b5f2a2a3ed848c171d7d2b077a55a Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sat, 17 Feb 2024 14:58:56 -0700 Subject: [PATCH] Run round-trip adventure codec tests with JSON, NBT, and Java ops. Use JavaOps for conversions. (#10031) --- ...patch => 0464-add-number-format-api.patch} | 0 patches/server/0004-Test-changes.patch | 244 +++++++++++++++++- patches/server/0010-Adventure.patch | 127 +++++---- ...oleAppender-for-console-improvements.patch | 4 +- ...ktraces-in-log-messages-crash-report.patch | 4 +- ...patch => 1047-add-number-format-api.patch} | 0 6 files changed, 326 insertions(+), 53 deletions(-) rename patches/api/{0463-add-number-format-api.patch => 0464-add-number-format-api.patch} (100%) rename patches/server/{1046-add-number-format-api.patch => 1047-add-number-format-api.patch} (100%) diff --git a/patches/api/0463-add-number-format-api.patch b/patches/api/0464-add-number-format-api.patch similarity index 100% rename from patches/api/0463-add-number-format-api.patch rename to patches/api/0464-add-number-format-api.patch diff --git a/patches/server/0004-Test-changes.patch b/patches/server/0004-Test-changes.patch index 863fa7bf7..63baf3d13 100644 --- a/patches/server/0004-Test-changes.patch +++ b/patches/server/0004-Test-changes.patch @@ -5,10 +5,18 @@ Subject: [PATCH] Test changes diff --git a/build.gradle.kts b/build.gradle.kts -index e7fa464573909d4c3d649ebb5f40ef54055e09a8..2df1cae62cff433a7f3f55f561f70719bb6a745b 100644 +index e7fa464573909d4c3d649ebb5f40ef54055e09a8..8d2aa99b4bd0d1c46c66274907a1f11d605a75da 100644 --- a/build.gradle.kts +++ b/build.gradle.kts -@@ -57,6 +57,12 @@ tasks.compileJava { +@@ -22,6 +22,7 @@ dependencies { + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") + testImplementation("org.hamcrest:hamcrest:2.2") + testImplementation("org.mockito:mockito-core:5.5.0") ++ testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest + } + + val craftbukkitPackageVersion = "1_20_R3" // Paper +@@ -57,6 +58,12 @@ tasks.compileJava { options.setIncremental(false) } @@ -97,6 +105,238 @@ index 0000000000000000000000000000000000000000..6eb95a5e2534974c0e52e2b78b04e7c2 + 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, AnnotationConsumer { ++ private MethodParameterSource source; ++ ++ MethodParameterProvider() { ++ } ++ ++ @Override ++ public void accept(final MethodParameterSource source) { ++ this.source = source; ++ } ++ ++ @Override ++ public Stream provideArguments(ExtensionContext context, Parameter parameter) { ++ return this.provideArguments(context, this.source); ++ } ++ ++ // Below is mostly copied from MethodArgumentsProvider ++ ++ private static final Predicate isFactoryMethod = // ++ method -> isConvertibleToStream(method.getReturnType()) && !isTestMethod(method); ++ ++ protected Stream 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 isCandidate = candidate -> factoryMethodName.equals(candidate.getName()) ++ && !testMethod.equals(candidate); ++ List candidates = ReflectionUtils.findMethods(clazz, isCandidate); ++ ++ List 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/support/DummyServer.java b/src/test/java/org/bukkit/support/DummyServer.java index b19d4f7d1fcb604b448a5084f6bfe56d47ab12b3..f3017525b0c2397fdc7ce0778add2e7b38e9e2ba 100644 --- a/src/test/java/org/bukkit/support/DummyServer.java diff --git a/patches/server/0010-Adventure.patch b/patches/server/0010-Adventure.patch index 2ba9bdac0..4bf3f3be9 100644 --- a/patches/server/0010-Adventure.patch +++ b/patches/server/0010-Adventure.patch @@ -1247,7 +1247,7 @@ index 0000000000000000000000000000000000000000..2fd6c3e65354071af71c7d8ebb97b559 +} diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java new file mode 100644 -index 0000000000000000000000000000000000000000..2e757cd9b01ac7eba1e4723a6e21dcea9d062483 +index 0000000000000000000000000000000000000000..032d23ecda574ed1a3c740c16d13055f399bd6c4 --- /dev/null +++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java @@ -0,0 +1,421 @@ @@ -1674,19 +1674,18 @@ index 0000000000000000000000000000000000000000..2e757cd9b01ac7eba1e4723a6e21dcea +} diff --git a/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java b/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java new file mode 100644 -index 0000000000000000000000000000000000000000..49809d4fa42d1054119cf38babba5a6161b5ee97 +index 0000000000000000000000000000000000000000..0a7764797b0f1f346fb35f09377d5639bc382bff --- /dev/null +++ b/src/main/java/io/papermc/paper/adventure/WrapperAwareSerializer.java -@@ -0,0 +1,40 @@ +@@ -0,0 +1,39 @@ +package io.papermc.paper.adventure; + +import com.mojang.datafixers.util.Pair; +import java.util.function.Function; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.ComponentSerializer; -+import net.minecraft.nbt.NbtOps; -+import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.ComponentSerialization; ++import net.minecraft.util.JavaOps; + +final class WrapperAwareSerializer implements ComponentSerializer { + @Override @@ -1694,26 +1693,26 @@ index 0000000000000000000000000000000000000000..49809d4fa42d1054119cf38babba5a61 + if (input instanceof AdventureComponent) { + return ((AdventureComponent) input).adventure; + } -+ final Tag tag = ComponentSerialization.CODEC.encodeStart(NbtOps.INSTANCE, input) ++ final Object obj = ComponentSerialization.CODEC.encodeStart(JavaOps.INSTANCE, input) + .get().map(Function.identity(), partial -> { + throw new RuntimeException("Failed to encode Minecraft Component: " + input + "; " + partial.message()); + }); -+ final Pair converted = AdventureCodecs.COMPONENT_CODEC.decode(NbtOps.INSTANCE, tag) ++ final Pair converted = AdventureCodecs.COMPONENT_CODEC.decode(JavaOps.INSTANCE, obj) + .get().map(Function.identity(), partial -> { -+ throw new RuntimeException("Failed to decode to adventure Component: " + tag + "; " + partial.message()); ++ throw new RuntimeException("Failed to decode to adventure Component: " + obj + "; " + partial.message()); + }); + return converted.getFirst(); + } + + @Override + public net.minecraft.network.chat.Component serialize(final Component component) { -+ final Tag tag = AdventureCodecs.COMPONENT_CODEC.encodeStart(NbtOps.INSTANCE, component) ++ final Object obj = AdventureCodecs.COMPONENT_CODEC.encodeStart(JavaOps.INSTANCE, component) + .get().map(Function.identity(), partial -> { + throw new RuntimeException("Failed to encode adventure Component: " + component + "; " + partial.message()); + }); -+ final Pair converted = ComponentSerialization.CODEC.decode(NbtOps.INSTANCE, tag) ++ final Pair converted = ComponentSerialization.CODEC.decode(JavaOps.INSTANCE, obj) + .get().map(Function.identity(), partial -> { -+ throw new RuntimeException("Failed to decode to Minecraft Component: " + tag + "; " + partial.message()); ++ throw new RuntimeException("Failed to decode to Minecraft Component: " + obj + "; " + partial.message()); + }); + return converted.getFirst(); + } @@ -5631,16 +5630,23 @@ index 0000000000000000000000000000000000000000..3aedd0bbc97edacc1ebf71264b310e55 +} diff --git a/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java b/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java new file mode 100644 -index 0000000000000000000000000000000000000000..b97078552a86885bf77a2832d292f4062116c743 +index 0000000000000000000000000000000000000000..3ced2357b5604aac59a2aa57ded58898c7b78ed4 --- /dev/null +++ b/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java -@@ -0,0 +1,369 @@ +@@ -0,0 +1,403 @@ +package io.papermc.paper.adventure; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; ++import com.mojang.serialization.DynamicOps; ++import com.mojang.serialization.JsonOps; ++import io.papermc.paper.util.MethodParameterSource; +import java.io.IOException; ++import java.lang.annotation.ElementType; ++import java.lang.annotation.Retention; ++import java.lang.annotation.RetentionPolicy; ++import java.lang.annotation.Target; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; @@ -5663,6 +5669,7 @@ index 0000000000000000000000000000000000000000..b97078552a86885bf77a2832d292f406 +import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.resources.ResourceLocation; ++import net.minecraft.util.JavaOps; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import org.apache.commons.lang3.RandomStringUtils; @@ -5671,6 +5678,7 @@ index 0000000000000000000000000000000000000000..b97078552a86885bf77a2832d292f406 +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; ++import org.junitpioneer.jupiter.cartesian.CartesianTest; + +import static io.papermc.paper.adventure.AdventureCodecs.CLICK_EVENT_CODEC; +import static io.papermc.paper.adventure.AdventureCodecs.COMPONENT_CODEC; @@ -5704,6 +5712,8 @@ index 0000000000000000000000000000000000000000..b97078552a86885bf77a2832d292f406 + +class AdventureCodecsTest extends AbstractTestingBase { + ++ static final String PARAMETERIZED_NAME = "[{index}] {displayName}: {arguments}"; ++ + @Test + void testTextColor() { + final TextColor color = color(0x1d38df); @@ -5731,7 +5741,7 @@ index 0000000000000000000000000000000000000000..b97078552a86885bf77a2832d292f406 + assertEquals(key.asString(), location.toString()); + } + -+ @ParameterizedTest ++ @ParameterizedTest(name = PARAMETERIZED_NAME) + @EnumSource(value = ClickEvent.Action.class, mode = EnumSource.Mode.EXCLUDE, names = {"OPEN_FILE"}) + void testClickEvent(final ClickEvent.Action action) { + final ClickEvent event = ClickEvent.clickEvent(action, RandomStringUtils.randomAlphanumeric(20)); @@ -5794,33 +5804,47 @@ index 0000000000000000000000000000000000000000..b97078552a86885bf77a2832d292f406 + assertEquals(requireNonNull(style.color()).value(), requireNonNull(nms.getColor()).getValue()); + } + -+ @ParameterizedTest -+ @MethodSource({"testStyles"}) -+ void testDirectRoundTripStyle(final Style style) { -+ testDirectRoundTrip(STYLE_MAP_CODEC.codec(), style); ++ @CartesianTest(name = PARAMETERIZED_NAME) ++ void testDirectRoundTripStyle( ++ @MethodParameterSource("dynamicOps") final DynamicOps dynamicOps, ++ @MethodParameterSource("testStyles") final Style style ++ ) { ++ testDirectRoundTrip(dynamicOps, STYLE_MAP_CODEC.codec(), style); + } + -+ @ParameterizedTest -+ @MethodSource({"testStyles"}) -+ void testMinecraftRoundTripStyle(final Style style) { -+ testMinecraftRoundTrip(STYLE_MAP_CODEC.codec(), net.minecraft.network.chat.Style.Serializer.CODEC, style); ++ @CartesianTest(name = PARAMETERIZED_NAME) ++ void testMinecraftRoundTripStyle( ++ @MethodParameterSource("dynamicOps") final DynamicOps dynamicOps, ++ @MethodParameterSource("testStyles") final Style style ++ ) { ++ testMinecraftRoundTrip(dynamicOps, STYLE_MAP_CODEC.codec(), net.minecraft.network.chat.Style.Serializer.CODEC, style); + } + -+ @ParameterizedTest -+ @MethodSource({"testTexts", "testTranslatables", "testKeybinds", "testScores", -+ "testSelectors", "testBlockNbts", "testEntityNbts", "testStorageNbts"}) -+ void testDirectRoundTripComponent(final Component component) { -+ testDirectRoundTrip(COMPONENT_CODEC, component); ++ @CartesianTest(name = PARAMETERIZED_NAME) ++ void testDirectRoundTripComponent( ++ @MethodParameterSource("dynamicOps") final DynamicOps dynamicOps, ++ @TestComponents final Component component ++ ) { ++ testDirectRoundTrip(dynamicOps, COMPONENT_CODEC, component); + } + -+ @ParameterizedTest -+ @MethodSource({"testTexts", "testTranslatables", "testKeybinds", "testScores", -+ "testSelectors", "testBlockNbts", "testEntityNbts", "testStorageNbts"}) -+ void testMinecraftRoundTripComponent(final Component component) { -+ testMinecraftRoundTrip(COMPONENT_CODEC, ComponentSerialization.CODEC, component); ++ @CartesianTest(name = PARAMETERIZED_NAME) ++ void testMinecraftRoundTripComponent( ++ @MethodParameterSource("dynamicOps") final DynamicOps dynamicOps, ++ @TestComponents final Component component ++ ) { ++ testMinecraftRoundTrip(dynamicOps, COMPONENT_CODEC, ComponentSerialization.CODEC, component); + } + -+ @ParameterizedTest ++ static List> dynamicOps() { ++ return List.of( ++ NbtOps.INSTANCE, ++ JavaOps.INSTANCE, ++ JsonOps.INSTANCE ++ ); ++ } ++ ++ @ParameterizedTest(name = PARAMETERIZED_NAME) + @MethodSource({"invalidData"}) + void invalidThrows(final Tag input) { + assertThrows(RuntimeException.class, () -> { @@ -5831,33 +5855,33 @@ index 0000000000000000000000000000000000000000..b97078552a86885bf77a2832d292f406 + }); + } + -+ static void testDirectRoundTrip(final Codec codec, final A adventure) { -+ final Tag encoded = require( -+ codec.encodeStart(NbtOps.INSTANCE, adventure), ++ static void testDirectRoundTrip(final DynamicOps ops, final Codec codec, final A adventure) { ++ final O encoded = require( ++ codec.encodeStart(ops, adventure), + msg -> "Failed to encode " + adventure + ": " + msg + ); -+ final Pair roundTripResult = require( -+ codec.decode(NbtOps.INSTANCE, encoded), ++ final Pair roundTripResult = require( ++ codec.decode(ops, encoded), + msg -> "Failed to decode " + encoded + ": " + msg + ); + assertEquals(adventure, roundTripResult.getFirst()); + } + -+ static void testMinecraftRoundTrip(final Codec adventureCodec, final Codec minecraftCodec, final A adventure) { -+ final Tag encoded = require( -+ adventureCodec.encodeStart(NbtOps.INSTANCE, adventure), ++ static void testMinecraftRoundTrip(final DynamicOps ops, final Codec adventureCodec, final Codec minecraftCodec, final A adventure) { ++ final O encoded = require( ++ adventureCodec.encodeStart(ops, adventure), + msg -> "Failed to encode " + adventure + ": " + msg + ); + final M minecraftResult = require( -+ minecraftCodec.decode(NbtOps.INSTANCE, encoded), ++ minecraftCodec.decode(ops, encoded), + msg -> "Failed to decode to Minecraft: " + encoded + "; " + msg + ).getFirst(); -+ final Tag minecraftReEncoded = require( -+ minecraftCodec.encodeStart(NbtOps.INSTANCE, minecraftResult), ++ final O minecraftReEncoded = require( ++ minecraftCodec.encodeStart(ops, minecraftResult), + msg -> "Failed to re-encode Minecraft: " + minecraftResult + "; " + msg + ); -+ final Pair roundTripResult = require( -+ adventureCodec.decode(NbtOps.INSTANCE, minecraftReEncoded), ++ final Pair roundTripResult = require( ++ adventureCodec.decode(ops, minecraftReEncoded), + msg -> "Failed to decode " + minecraftReEncoded + ": " + msg + ); + assertEquals(adventure, roundTripResult.getFirst()); @@ -5902,6 +5926,15 @@ index 0000000000000000000000000000000000000000..b97078552a86885bf77a2832d292f406 + ); + } + ++ @Retention(RetentionPolicy.RUNTIME) ++ @Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) ++ @MethodParameterSource({ ++ "testTexts", "testTranslatables", "testKeybinds", "testScores", ++ "testSelectors", "testBlockNbts", "testEntityNbts", "testStorageNbts" ++ }) ++ @interface TestComponents { ++ } ++ + static List testTexts() { + return List.of( + Component.empty(), @@ -5938,7 +5971,7 @@ index 0000000000000000000000000000000000000000..b97078552a86885bf77a2832d292f406 + .key("thisIsA") + .fallback("This is a test.") + .build(), -+ translatable(key, numeric(5), text("HEY")), // boolean doesn't work in vanilla, can't test here ++ translatable(key, numeric(Integer.MAX_VALUE), text("HEY")), // boolean doesn't work in vanilla, can't test here + translatable( + key, + text().content(name) diff --git a/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch b/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch index 125139cc3..6eb8e387d 100644 --- a/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch +++ b/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch @@ -25,7 +25,7 @@ Other changes: Co-Authored-By: Emilia Kond diff --git a/build.gradle.kts b/build.gradle.kts -index 65e9d5918d46b123fb4f8122344a7d3863aec758..7a3c96318f95fcd6cf6fd94415958382d1193ec6 100644 +index e865c5ce514770f4fde9146b6e7138e88932c33b..d8f909f4f108343e3973e965af617596c91e3d0e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,9 +6,30 @@ plugins { @@ -60,7 +60,7 @@ index 65e9d5918d46b123fb4f8122344a7d3863aec758..7a3c96318f95fcd6cf6fd94415958382 implementation("org.apache.logging.log4j:log4j-iostreams:2.19.0") // Paper - remove exclusion implementation("org.ow2.asm:asm-commons:9.5") implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files -@@ -78,7 +99,7 @@ relocation { +@@ -79,7 +100,7 @@ relocation { } tasks.shadowJar { diff --git a/patches/server/0366-Deobfuscate-stacktraces-in-log-messages-crash-report.patch b/patches/server/0366-Deobfuscate-stacktraces-in-log-messages-crash-report.patch index 8a44558d3..0ceb6d01a 100644 --- a/patches/server/0366-Deobfuscate-stacktraces-in-log-messages-crash-report.patch +++ b/patches/server/0366-Deobfuscate-stacktraces-in-log-messages-crash-report.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Deobfuscate stacktraces in log messages, crash reports, and diff --git a/build.gradle.kts b/build.gradle.kts -index eaaf9a9779f57ee048245899750bf7a1599b716f..450f7c03bdcc109938ba9b66328bdbb2c96c03c9 100644 +index 1a0d26d4604b10fd85797a774a2d159cbd237d7e..76ae7fd188adc1c48d4a58253824e0ff75887339 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { @@ -17,7 +17,7 @@ index eaaf9a9779f57ee048245899750bf7a1599b716f..450f7c03bdcc109938ba9b66328bdbb2 runtimeOnly("org.xerial:sqlite-jdbc:3.42.0.1") runtimeOnly("com.mysql:mysql-connector-j:8.2.0") runtimeOnly("com.lmax:disruptor:3.4.4") // Paper -@@ -124,6 +125,18 @@ tasks.check { +@@ -125,6 +126,18 @@ tasks.check { } // Paper end diff --git a/patches/server/1046-add-number-format-api.patch b/patches/server/1047-add-number-format-api.patch similarity index 100% rename from patches/server/1046-add-number-format-api.patch rename to patches/server/1047-add-number-format-api.patch