Skip to content

Commit

Permalink
WIP gametest
Browse files Browse the repository at this point in the history
  • Loading branch information
altrisi committed Jun 25, 2024
1 parent 2cdb697 commit 7803097
Show file tree
Hide file tree
Showing 10 changed files with 639 additions and 6 deletions.
40 changes: 34 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ repositories {
base {
archivesName = project.archives_base_name
}

version = project.minecraft_version+'-'+project.mod_version
group = project.maven_group

loom {
}

dependencies {
//to change the versions see the gradle.properties file
Expand All @@ -25,11 +24,40 @@ dependencies {
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "carpet:fabric-carpet:${project.minecraft_version}-${project.carpet_core_version}"

// Fabric API. This is technically optional, but you probably want it anyway.
// modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
// for gametests
modRuntimeOnly fabricApi.module("fabric-resource-loader-v0", project.fabric_api_version)
modRuntimeOnly fabricApi.module("fabric-api-base", project.fabric_api_version)
modRuntimeOnly fabricApi.module("fabric-gametest-api-v1", project.fabric_api_version)
}

sourceSets {
gametest {
java {
compileClasspath += main.output
compileClasspath += main.compileClasspath
runtimeClasspath += main.output
runtimeClasspath += main.runtimeClasspath
}
}
}

// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
// You may need to force-disable transitiveness on them.
loom {
runs {
gametest {
inherit server
name "Game Test"
vmArg "-Dfabric-api.gametest"
//vmArg "-Dmixin.debug.countInjections=true"
vmArg "-Dfabric-api.gametest.report-file=${project.buildDir}/junit.xml"
source sourceSets.gametest
runDir "build/gametest"
ideConfigGenerated = false
}
}
runConfigs.configureEach {
// we want to have access to be able to create gametests on regular debug
source sourceSets.gametest
}
}

processResources {
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ org.gradle.jvmargs=-Xmx1G
loader_version=0.15.11
# check available versions on maven (https://masa.dy.fi/maven/carpet/fabric-carpet/) for the given minecraft version you are using
carpet_core_version=1.4.147+v240613
# for gametests
fabric_api_version=0.100.3+1.21

# Mod Properties
mod_version = 1.4.147
Expand Down
10 changes: 10 additions & 0 deletions src/gametest/java/carpetextra/TestUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package carpetextra;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestUtils {

public static final Logger LOGGER = LoggerFactory.getLogger("CarpetExtraTest");

}
80 changes: 80 additions & 0 deletions src/gametest/java/carpetextra/testbootstrap/TestBootstraper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package carpetextra.testbootstrap;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;

import carpet.CarpetServer;
import carpetextra.TestUtils;
import net.minecraft.test.TestFunctions;

public class TestBootstraper {
static final String TEST_BASE_PACKAGE = "carpetextra.tests";
static {
registerExtensionForCommands();
registerAllClasses();
}

static Stream<ClassInfo> allTestClasses() {
try {
return ClassPath.from(TestBootstraper.class.getClassLoader())
.getAllClasses()
.stream()
.filter(clazz -> clazz.getPackageName().startsWith(TEST_BASE_PACKAGE) && clazz.isTopLevel());
} catch (IOException e) {
throw new UncheckedIOException("Exception while listing Carpet Extra gametests", e);
}
}

private static void registerAllClasses() {
AtomicInteger count = new AtomicInteger();
allTestClasses()
.map(cls -> {
try {
// cls.load() doesn't initialize it, making it not return annotations to the framework
return Class.forName(cls.getName());
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Couldn't initialize test class we know is there", e);
}
})
.peek(__ -> count.setPlain(count.getPlain() + 1)) // technically shouldn't be used for this, but good enough
.forEach(TestBootstraper::register);
TestUtils.LOGGER.info("Registered " + count + " gametest classes for Carpet Extra");
}

static void register(Class<?> test) {
// HACK: add our modid to gametest api first, or it'll crash
@SuppressWarnings("unchecked")
class HackHolder {
static final HashMap<Class<?>, String> IDS;
static {
HashMap<Class<?>, String> ids;
try {
Field f = Class.forName("net.fabricmc.fabric.impl.gametest.FabricGameTestModInitializer").getDeclaredField("GAME_TEST_IDS");
f.setAccessible(true);
ids = (HashMap<Class<?>, String>)f.get(null);
} catch (ReflectiveOperationException e) {
TestUtils.LOGGER.warn("Hack to register gametest into fabric gametest failed, expect a crash!");
ids = null;
}
IDS = ids;
}
}

if (HackHolder.IDS != null) {
HackHolder.IDS.put(test, "carpet-extra");
}

TestFunctions.register(test);
}

static void registerExtensionForCommands() {
CarpetServer.manageExtension(new TestsClassesCommandExtension());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package carpetextra.testbootstrap;

import static carpetextra.testbootstrap.TestBootstraper.TEST_BASE_PACKAGE;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static com.mojang.brigadier.arguments.StringArgumentType.word;
import static net.minecraft.command.CommandSource.suggestMatching;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.stream.Stream;

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;

import carpet.CarpetExtension;
import carpet.utils.Messenger;
import carpetextra.TestUtils;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.data.DataWriter;
import net.minecraft.data.dev.NbtProvider;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.test.TestFunctions;
import net.minecraft.util.WorldSavePath;

// to add a command to register test classes at runtime
public class TestsClassesCommandExtension implements CarpetExtension {
@Override
public void registerCommands(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess commandBuildContext) {
registerTestClassCommand(dispatcher);
}

public static void registerTestClassCommand(CommandDispatcher<ServerCommandSource> dispatcher) {
LiteralArgumentBuilder<ServerCommandSource> command = literal("testhelper")
.then(literal("class")
.then(argument("class", word())
.suggests((c, b) -> suggestMatching(TestBootstraper.allTestClasses()
.<String>mapMulti((clazz, stream) -> {
if (!TestFunctions.testClassExists(clazz.getSimpleName()))
stream.accept(clazz.getName());
})
.map(name -> name.substring(TEST_BASE_PACKAGE.length() + 1))
, b))
.executes(c -> {
String className = getString(c, "class");
try {
Class<?> testClass = Class.forName(TEST_BASE_PACKAGE + '.' + className);
if (TestFunctions.testClassExists(testClass.getSimpleName())) {
c.getSource().sendError(Messenger.c("r Reloading classes is not supported"));
return 0;
}
TestBootstraper.register(testClass);
c.getSource().sendMessage(Messenger.c(" Added test class " + className));
return 1;
} catch (ClassNotFoundException e) {
c.getSource().sendError(Messenger.c("r Failed to load class " + className));
return 0;
}
}))
)
.then(literal("convertnbt")
.then(argument("structure", word())
.suggests((c, b) -> suggestMatching(listStructures(c), b))
.executes(c -> {
String fileName = getString(c, "structure");
Path from = structuresPath(c).resolve(fileName + ".nbt");
Path to = Path.of("../src/gametest/resources/data/carpet-extra/structures");
NbtProvider.convertNbtToSnbt(DataWriter.UNCACHED, from, fileName, to);
c.getSource().sendMessage(Messenger.c(" Converted and moved structure " + fileName));
return 0;
})
)
);
dispatcher.register(command);
}

private static Path structuresPath(CommandContext<ServerCommandSource> ctx) {
return ctx.getSource().getServer().getSavePath(WorldSavePath.GENERATED).resolve("minecraft/structures");
}

private static Collection<String> listStructures(CommandContext<ServerCommandSource> ctx) {
try (Stream<Path> paths = Files.list(structuresPath(ctx))) {
return paths
.map(p -> p.getFileName().toString())
.filter(p -> p.endsWith(".nbt"))
.map(p -> p.substring(0, p.length() - 4))
.toList();
} catch (IOException e) {
TestUtils.LOGGER.info("Failed to list structures", e);
// do nothing?
return Stream.<String>empty().toList();
}
}

// private static void reloadTestClass(Class<?> cls) {
// String fabricBatchId = cls.getSimpleName().toLowerCase();
// TestFunctions.getTestFunctions().removeIf(fn -> fn.templatePath().startsWith(fabricBatchId));
// // we'd need to remove before and after batch handlers too
// TestFunctions.register(cls);
// }

@Override
public String version() {
return "testcommand-adder";
}
}
Loading

0 comments on commit 7803097

Please sign in to comment.