aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJason Penilla <[email protected]>2024-02-17 14:58:56 -0700
committerGitHub <[email protected]>2024-02-17 16:58:56 -0500
commit351923d17e3b5f2a2a3ed848c171d7d2b077a55a (patch)
tree3ee3777f828b206c63f70fd46c919f45dbc63de4
parentd95341e44a8ba027af80547a44300617f314e548 (diff)
downloadPaper-351923d17e3b5f2a2a3ed848c171d7d2b077a55a.tar.gz
Paper-351923d17e3b5f2a2a3ed848c171d7d2b077a55a.zip
Run round-trip adventure codec tests with JSON, NBT, and Java ops. Use JavaOps for conversions. (#10031)
-rw-r--r--patches/api/0464-add-number-format-api.patch (renamed from patches/api/0463-add-number-format-api.patch)0
-rw-r--r--patches/server/0004-Test-changes.patch244
-rw-r--r--patches/server/0010-Adventure.patch127
-rw-r--r--patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch4
-rw-r--r--patches/server/0366-Deobfuscate-stacktraces-in-log-messages-crash-report.patch4
-rw-r--r--patches/server/1047-add-number-format-api.patch (renamed from patches/server/1046-add-number-format-api.patch)0
6 files changed, 326 insertions, 53 deletions
diff --git a/patches/api/0463-add-number-format-api.patch b/patches/api/0464-add-number-format-api.patch
index 6d32302ad0..6d32302ad0 100644
--- a/patches/api/0463-add-number-format-api.patch
+++ b/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 863fa7bf7e..63baf3d138 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<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/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 2ba9bdac09..4bf3f3be99 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<Component, Component, net.minecraft.network.chat.Component> {
+ @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<Component, Tag> converted = AdventureCodecs.COMPONENT_CODEC.decode(NbtOps.INSTANCE, tag)
++ final Pair<Component, Object> 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<net.minecraft.network.chat.Component, Tag> converted = ComponentSerialization.CODEC.decode(NbtOps.INSTANCE, tag)
++ final Pair<net.minecraft.network.chat.Component, Object> 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<?>> 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 <A> void testDirectRoundTrip(final Codec<A> codec, final A adventure) {
-+ final Tag encoded = require(
-+ codec.encodeStart(NbtOps.INSTANCE, adventure),
++ static <A, O> void testDirectRoundTrip(final DynamicOps<O> ops, final Codec<A> codec, final A adventure) {
++ final O encoded = require(
++ codec.encodeStart(ops, adventure),
+ msg -> "Failed to encode " + adventure + ": " + msg
+ );
-+ final Pair<A, Tag> roundTripResult = require(
-+ codec.decode(NbtOps.INSTANCE, encoded),
++ final Pair<A, O> roundTripResult = require(
++ codec.decode(ops, encoded),
+ msg -> "Failed to decode " + encoded + ": " + msg
+ );
+ assertEquals(adventure, roundTripResult.getFirst());
+ }
+
-+ static <A, M> void testMinecraftRoundTrip(final Codec<A> adventureCodec, final Codec<M> minecraftCodec, final A adventure) {
-+ final Tag encoded = require(
-+ adventureCodec.encodeStart(NbtOps.INSTANCE, adventure),
++ static <A, M, O> void testMinecraftRoundTrip(final DynamicOps<O> ops, final Codec<A> adventureCodec, final Codec<M> 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<A, Tag> roundTripResult = require(
-+ adventureCodec.decode(NbtOps.INSTANCE, minecraftReEncoded),
++ final Pair<A, O> 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<Component> 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 125139cc38..6eb8e387d2 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 <[email protected]>
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 8a44558d31..0ceb6d01a5 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
index d412b5f929..d412b5f929 100644
--- a/patches/server/1046-add-number-format-api.patch
+++ b/patches/server/1047-add-number-format-api.patch