1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-13 03:43:08 +00:00

Fix crash when joining a dedicated server

We can't use FriendlyByte.readCollection to read to a
pre-allocated/array-backed NonNullList, as that doesn't implement
List.add. Instead, we just need to do a normal loop.

We add a couple of tests to round-trip our recipe specs. Unfortunately
we can't test the recipes themselves as our own registries aren't set
up, so this'll have to do for now.
This commit is contained in:
Jonathan Coates
2023-10-08 15:21:33 +01:00
parent e7ab05d064
commit 905d4cb091
14 changed files with 297 additions and 25 deletions

View File

@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.recipe;
import dan200.computercraft.test.shared.MinecraftArbitraries;
import it.unimi.dsi.fastutil.ints.IntIntImmutablePair;
import net.jqwik.api.Arbitraries;
import net.jqwik.api.Arbitrary;
import net.jqwik.api.Combinators;
import net.minecraft.core.NonNullList;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.Ingredient;
/**
* {@link Arbitrary} implementations for recipes.
*/
public final class RecipeArbitraries {
public static Arbitrary<RecipeProperties> recipeProperties() {
return Combinators.combine(
Arbitraries.strings().ofMinLength(1).withChars("abcdefghijklmnopqrstuvwxyz_"),
Arbitraries.of(CraftingBookCategory.values())
).as(RecipeProperties::new);
}
public static Arbitrary<ShapelessRecipeSpec> shapelessRecipeSpec() {
return Combinators.combine(
recipeProperties(),
MinecraftArbitraries.ingredient().array(Ingredient[].class).ofMinSize(1).map(x -> NonNullList.of(Ingredient.EMPTY, x)),
MinecraftArbitraries.nonEmptyItemStack()
).as(ShapelessRecipeSpec::new);
}
public static Arbitrary<ShapedTemplate> shapedTemplate() {
return Combinators.combine(Arbitraries.integers().between(1, 3), Arbitraries.integers().between(1, 3))
.as(IntIntImmutablePair::new)
.flatMap(x -> MinecraftArbitraries.ingredient().array(Ingredient[].class).ofSize(x.leftInt() * x.rightInt())
.map(i -> new ShapedTemplate(x.leftInt(), x.rightInt(), NonNullList.of(Ingredient.EMPTY, i)))
);
}
public static Arbitrary<ShapedRecipeSpec> shapedRecipeSpec() {
return Combinators.combine(
recipeProperties(),
shapedTemplate(),
MinecraftArbitraries.nonEmptyItemStack()
).as(ShapedRecipeSpec::new);
}
}

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.recipe;
import dan200.computercraft.test.core.StructuralEquality;
import dan200.computercraft.test.shared.MinecraftEqualities;
/**
* {@link StructuralEquality} implementations for recipes.
*/
public final class RecipeEqualities {
private RecipeEqualities() {
}
public static final StructuralEquality<ShapelessRecipeSpec> shapelessRecipeSpec = StructuralEquality.all(
StructuralEquality.at("properties", ShapelessRecipeSpec::properties),
StructuralEquality.at("ingredients", ShapelessRecipeSpec::ingredients, MinecraftEqualities.ingredient.list()),
StructuralEquality.at("result", ShapelessRecipeSpec::result, MinecraftEqualities.itemStack)
);
public static final StructuralEquality<ShapedTemplate> shapedTemplate = StructuralEquality.all(
StructuralEquality.at("width", ShapedTemplate::width),
StructuralEquality.at("height", ShapedTemplate::height),
StructuralEquality.at("ingredients", ShapedTemplate::ingredients, MinecraftEqualities.ingredient.list())
);
public static final StructuralEquality<ShapedRecipeSpec> shapedRecipeSpec = StructuralEquality.all(
StructuralEquality.at("properties", ShapedRecipeSpec::properties),
StructuralEquality.at("ingredients", ShapedRecipeSpec::template, shapedTemplate),
StructuralEquality.at("result", ShapedRecipeSpec::result, MinecraftEqualities.itemStack)
);
}

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.recipe;
import dan200.computercraft.test.shared.NetworkSupport;
import dan200.computercraft.test.shared.WithMinecraft;
import net.jqwik.api.Arbitrary;
import net.jqwik.api.ForAll;
import net.jqwik.api.Property;
import net.jqwik.api.Provide;
import static org.hamcrest.MatcherAssert.assertThat;
@WithMinecraft
public class ShapedRecipeSpecTest {
static {
WithMinecraft.Setup.bootstrap(); // @Property doesn't run test lifecycle methods.
}
@Property
public void testRoundTrip(@ForAll("recipe") ShapedRecipeSpec spec) {
var converted = NetworkSupport.roundTrip(spec, ShapedRecipeSpec::toNetwork, ShapedRecipeSpec::fromNetwork);
assertThat("Recipes are equal", converted, RecipeEqualities.shapedRecipeSpec.asMatcher(ShapedRecipeSpec.class, spec));
}
@Provide
Arbitrary<ShapedRecipeSpec> recipe() {
return RecipeArbitraries.shapedRecipeSpec();
}
}

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.recipe;
import dan200.computercraft.test.shared.NetworkSupport;
import dan200.computercraft.test.shared.WithMinecraft;
import net.jqwik.api.Arbitrary;
import net.jqwik.api.ForAll;
import net.jqwik.api.Property;
import net.jqwik.api.Provide;
import static org.hamcrest.MatcherAssert.assertThat;
@WithMinecraft
public class ShapelessRecipeSpecTest {
static {
WithMinecraft.Setup.bootstrap(); // @Property doesn't run test lifecycle methods.
}
@Property
public void testRoundTrip(@ForAll("recipe") ShapelessRecipeSpec spec) {
var converted = NetworkSupport.roundTrip(spec, ShapelessRecipeSpec::toNetwork, ShapelessRecipeSpec::fromNetwork);
assertThat("Recipes are equal", converted, RecipeEqualities.shapelessRecipeSpec.asMatcher(ShapelessRecipeSpec.class, spec));
}
@Provide
Arbitrary<ShapelessRecipeSpec> recipe() {
return RecipeArbitraries.shapelessRecipeSpec();
}
}

View File

@@ -8,16 +8,13 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleToolDurability;
import dan200.computercraft.test.core.StructuralEquality;
import dan200.computercraft.test.shared.MinecraftArbitraries;
import dan200.computercraft.test.shared.MinecraftEqualities;
import dan200.computercraft.test.shared.NetworkSupport;
import dan200.computercraft.test.shared.WithMinecraft;
import io.netty.buffer.Unpooled;
import net.jqwik.api.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.item.ItemStack;
import org.hamcrest.Description;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
@WithMinecraft
class TurtleToolSerialiserTest {
@@ -32,11 +29,9 @@ class TurtleToolSerialiserTest {
*/
@Property
public void testRoundTrip(@ForAll("tool") TurtleTool tool) {
var buffer = new FriendlyByteBuf(Unpooled.directBuffer());
TurtleToolSerialiser.INSTANCE.toNetwork(buffer, tool);
var converted = TurtleToolSerialiser.INSTANCE.fromNetwork(tool.getUpgradeID(), buffer);
assertEquals(buffer.readableBytes(), 0, "Whole packet was read");
var converted = NetworkSupport.roundTripSerialiser(
tool.getUpgradeID(), tool, TurtleToolSerialiser.INSTANCE::toNetwork, TurtleToolSerialiser.INSTANCE::fromNetwork
);
if (!equality.equals(tool, converted)) {
System.out.println("Break");
@@ -58,22 +53,10 @@ class TurtleToolSerialiserTest {
).as(TurtleTool::new);
}
private static final StructuralEquality<ItemStack> stackEquality = new StructuralEquality<>() {
@Override
public boolean equals(ItemStack left, ItemStack right) {
return ItemStack.isSameItemSameTags(left, right) && left.getCount() == right.getCount();
}
@Override
public void describe(Description description, ItemStack object) {
description.appendValue(object).appendValue(object.getTag());
}
};
private static final StructuralEquality<TurtleTool> equality = StructuralEquality.all(
StructuralEquality.at("id", ITurtleUpgrade::getUpgradeID),
StructuralEquality.at("craftingItem", ITurtleUpgrade::getCraftingItem, stackEquality),
StructuralEquality.at("tool", x -> x.item, stackEquality),
StructuralEquality.at("craftingItem", ITurtleUpgrade::getCraftingItem, MinecraftEqualities.itemStack),
StructuralEquality.at("tool", x -> x.item, MinecraftEqualities.itemStack),
StructuralEquality.at("damageMulitiplier", x -> x.damageMulitiplier),
StructuralEquality.at("allowEnchantments", x -> x.allowEnchantments),
StructuralEquality.at("consumeDurability", x -> x.consumeDurability),