Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
Expand Down
52 changes: 41 additions & 11 deletions gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/main/java/io/github/misode/packtest/PackTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.minecraft.util.FileUtil;
import net.minecraft.util.Util;
import net.minecraft.commands.CommandBuildContext;
Expand Down Expand Up @@ -56,8 +57,19 @@ public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, buildContext, environment) ->
registerCommands(dispatcher, buildContext)
);
ServerLifecycleEvents.SERVER_STARTED.register(server ->
server.execute(() -> loadTests(server)));
ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, success) -> {
if (success) {
server.execute(() -> loadTests(server));
}
});
}

private static void loadTests(net.minecraft.server.MinecraftServer server) {
new PackTestLibrary(server.registryAccess()).load(server);
}

public static void generateCommandsReport() {
Path path = Paths.get("generated", "reports", "commands.json");
CommandDispatcher<CommandSourceStack> dispatcher = new CommandDispatcher<>();
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/io/github/misode/packtest/PackTestException.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.network.chat.Component;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

public class PackTestException extends GameTestAssertException {
final int line;
final String command;
final @Nullable String description;

public PackTestException(Component message, int tick, int line) {
public PackTestException(Component message, int tick, int line, String command, @Nullable String description) {
super(message, tick);
this.line = line;
this.command = command;
this.description = description;
}

public int getLine() {
Expand All @@ -18,6 +23,9 @@ public int getLine() {

@Override
public @NonNull Component getDescription() {
return Component.literal("On line " + this.line + ": ").append(super.getDescription());
String prefix = this.command.isEmpty()
? "On line " + this.line + ": "
: "\"" + this.command + "\" on line " + this.line + ": ";
return Component.literal(prefix).append(super.getDescription());
}
}
22 changes: 18 additions & 4 deletions src/main/java/io/github/misode/packtest/PackTestExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.github.misode.packtest.commands.assertions.AssertResult;
import io.github.misode.packtest.dummy.Dummy;
import net.minecraft.commands.CommandResultCallback;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.coordinates.Coordinates;
Expand All @@ -15,6 +16,7 @@
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
Expand All @@ -24,13 +26,22 @@
public class PackTestExecutor {
private static final SimpleCommandExceptionType ERROR_NOT_IN_TEST = new SimpleCommandExceptionType(
Component.literal("Command can only be used inside a test"));
private static final CommandSource SILENT_SOURCE = new CommandSource() {
@Override public void sendSystemMessage(@NonNull Component message) {}
@Override public boolean acceptsSuccess() { return true; }
@Override public boolean acceptsFailure() { return true; }
@Override public boolean shouldInformAdmins() { return false; }
@Override public boolean alwaysAccepts() { return true; }
};
private static @Nullable PackTestExecutor current;

private final List<Supplier<Boolean>> awaits = new ArrayList<>();
private final GameTestHelper helper;
private final int timeout;
private final long chatSequence = ChatRecorder.sequence();
private int line = 0;
private PackTestFunction function;
private int sourceLine = 0;
private String commandName = "";
private boolean done = false;

public PackTestExecutor(GameTestHelper helper, int timeout) {
Expand All @@ -46,6 +57,7 @@ public static PackTestExecutor current() throws CommandSyntaxException {
}

public void run(PackTestFunction function) {
this.function = function;
CommandSourceStack source = createCommandSourceStack(function);
Queue<PackTestFunction.Step> steps = new ArrayDeque<>(function.steps());

Expand All @@ -58,7 +70,8 @@ public void run(PackTestFunction function) {
}
while (!steps.isEmpty() && !this.done && this.awaits.isEmpty()) {
PackTestFunction.Step step = steps.poll();
this.line = step.line();
this.sourceLine = step.sourceLine();
this.commandName = step.commandName();
Commands.executeCommandInContext(source, ctx ->
ExecutionContext.queueInitialCommandExecution(ctx, step.command(), step.chain(), source, CommandResultCallback.EMPTY));
}
Expand All @@ -78,7 +91,7 @@ private CommandSourceStack createCommandSourceStack(PackTestFunction function) {
CommandSourceStack source = helper.getLevel().getServer().createCommandSourceStack()
.withLevel(helper.getLevel())
.withPosition(helper.absoluteVec(Vec3.ZERO))
.withSuppressedOutput();
.withSource(SILENT_SOURCE);

Optional<Coordinates> coordinates = function.directives().dummy();
if (coordinates.isPresent()) {
Expand Down Expand Up @@ -144,7 +157,8 @@ private void registerPoll(Supplier<AssertResult> check, boolean negated) {
}

private PackTestException failure(Component message) {
return new PackTestException(message, (int)this.helper.getTick(), this.line);
String description = this.function != null ? this.function.description() : "";
return new PackTestException(message, (int)this.helper.getTick(), this.sourceLine, this.commandName, description);
}

private boolean isLastTick() {
Expand Down
40 changes: 26 additions & 14 deletions src/main/java/io/github/misode/packtest/PackTestFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import java.util.*;

public record PackTestFunction(List<Step> steps, PackTestDirectives directives) {
public record PackTestFunction(List<Step> steps, PackTestDirectives directives, String description) {
public void run(GameTestHelper helper) {
PackTestExecutor executor = new PackTestExecutor(helper, this.directives.maxTicks());
executor.run(this);
Expand All @@ -24,10 +24,11 @@ public static PackTestFunction fromLines(
List<String> lines) throws IllegalArgumentException {
PackTestDirectives directives = new PackTestDirectives();
List<Step> steps = new ArrayList<>();
String description = "";
int i = 0;

while (i < lines.size()) {
final int line = i + 1;
final int lineNumber = i + 1;
StringBuilder builder = new StringBuilder(lines.get(i++).trim());

while (!builder.isEmpty() && builder.charAt(builder.length() - 1) == '\\') {
Expand All @@ -40,26 +41,30 @@ public static PackTestFunction fromLines(
CommandFunction.checkCommandLineLength(builder);
}

String command = builder.toString();
if (command.isEmpty()) continue;
String line = builder.toString();
if (line.isEmpty()) continue;

CommandFunction.checkCommandLineLength(command);
StringReader reader = new StringReader(command);
CommandFunction.checkCommandLineLength(line);
StringReader reader = new StringReader(line);
if (!reader.canRead()) continue;

if (reader.peek() == '#') {
parseDirective(reader, directives);
if (line.length() > 1 && line.charAt(1) == '>') {
description = line.substring(2).trim();
} else {
parseDirective(new StringReader(line), directives);
}
continue;
}

try {
steps.add(new Step(command, parseCommand(dispatcher, context, command), line));
steps.add(parseCommand(dispatcher, context, line, lineNumber));
} catch (CommandSyntaxException e) {
throw new IllegalArgumentException("Whilst parsing command on line " + line + ": " + e.getMessage());
throw new IllegalArgumentException("Whilst parsing command on line " + lineNumber + ": " + e.getMessage());
}
}

return new PackTestFunction(steps, directives);
return new PackTestFunction(steps, directives, description);
}

private static void parseDirective(
Expand All @@ -77,17 +82,24 @@ private static void parseDirective(
}
}

private static ContextChain<CommandSourceStack> parseCommand(
private static Step parseCommand(
CommandDispatcher<CommandSourceStack> dispatcher,
CommandSourceStack context,
String command) throws CommandSyntaxException {
String command,
int sourceLine) throws CommandSyntaxException {
ParseResults<CommandSourceStack> parseResults = dispatcher.parse(command, context);
Commands.validateParseResults(parseResults);
return ContextChain.tryFlatten(parseResults.getContext().build(command))
ContextChain<CommandSourceStack> chain = ContextChain.tryFlatten(parseResults.getContext().build(command))
.orElseThrow(() -> CommandSyntaxException.BUILT_IN_EXCEPTIONS
.dispatcherUnknownCommand()
.createWithContext(parseResults.getReader()));
String effectiveCommandName = chain.getTopContext().getLastChild().getNodes().stream()
.map(n -> n.getNode().getName())
.filter(n -> !n.isEmpty())
.findFirst()
.orElse("");
return new Step(command, chain, sourceLine, effectiveCommandName);
}

public record Step(String command, ContextChain<CommandSourceStack> chain, int line) {}
public record Step(String command, ContextChain<CommandSourceStack> chain, int sourceLine, String commandName) {}
}
Loading
Loading