1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-24 22:23:21 +00:00
CC-Tweaked/src/test/java/dan200/computercraft/ingame/mod/TestMod.java
Jonathan Coates 331031be45 Run integration tests in-game
Name a more iconic duo than @SquidDev and over-engineered test
frameworks.

This uses Minecraft's test core[1] plus a home-grown framework to run
tests against computers in-world.

The general idea is:
 - Build a structure in game.
 - Save the structure to a file. This will be spawned in every time the
   test is run.
 - Write some code which asserts the structure behaves in a particular
   way. This is done in Kotlin (shock, horror), as coroutines give us a
   nice way to run asynchronous code while still running on the main
   thread.

As with all my testing efforts, I still haven't actually written any
tests!  It'd be good to go through some of the historic ones and write
some tests though. Turtle block placing and computer redstone
interactions are probably a good place to start.

[1]: https://www.youtube.com/watch?v=vXaWOJTCYNg
2021-01-09 19:50:27 +00:00

118 lines
4.6 KiB
Java

package dan200.computercraft.ingame.mod;
import dan200.computercraft.api.ComputerCraftAPI;
import net.minecraft.command.CommandSource;
import net.minecraft.server.MinecraftServer;
import net.minecraft.test.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.GameRules;
import net.minecraft.world.gen.Heightmap;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.server.ServerLifecycleHooks;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
@Mod( TestMod.MOD_ID )
public class TestMod
{
public static final Path sourceDir = Paths.get( "../../src/test/server-files/" ).toAbsolutePath();
public static final String MOD_ID = "cctest";
public static final Logger log = LogManager.getLogger( MOD_ID );
private TestResultList runningTests = null;
private int countdown = 20;
public TestMod()
{
log.info( "CC: Test initialised" );
ComputerCraftAPI.registerAPIFactory( TestAPI::new );
TestLoader.setup();
StructureHelper.testStructuresDir = sourceDir.resolve( "structures" ).toString();
MinecraftForge.EVENT_BUS.addListener( ( FMLServerStartingEvent event ) -> {
log.info( "Starting server, registering command helpers." );
TestCommand.register( event.getCommandDispatcher() );
CCTestCommand.register( event.getCommandDispatcher() );
} );
MinecraftForge.EVENT_BUS.addListener( ( FMLServerStartedEvent event ) -> {
MinecraftServer server = event.getServer();
GameRules rules = server.getGameRules();
rules.getRule( GameRules.RULE_DAYLIGHT ).set( false, server );
rules.getRule( GameRules.RULE_WEATHER_CYCLE ).set( false, server );
rules.getRule( GameRules.RULE_DOMOBSPAWNING ).set( false, server );
log.info( "Cleaning up after last run" );
CommandSource source = server.createCommandSourceStack();
TestUtils.clearAllTests( source.getLevel(), getStart( source ), TestCollection.singleton, 200 );
log.info( "Importing files" );
CCTestCommand.importFiles( server );
} );
MinecraftForge.EVENT_BUS.addListener( ( TickEvent.ServerTickEvent event ) -> {
if( event.phase != TickEvent.Phase.START ) return;
// Let the world settle a bit before starting tests.
countdown--;
if( countdown == 0 && System.getProperty( "cctest.run", "false" ).equals( "true" ) ) startTests();
TestCollection.singleton.tick();
MainThread.INSTANCE.tick();
if( runningTests != null && runningTests.isDone() ) finishTests();
} );
}
private void startTests()
{
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
CommandSource source = server.createCommandSourceStack();
Collection<TestFunctionInfo> tests = TestRegistry.getAllTestFunctions();
log.info( "Running {} tests...", tests.size() );
runningTests = new TestResultList( TestUtils.runTests( tests, getStart( source ), source.getLevel(), TestCollection.singleton ) );
}
private void finishTests()
{
log.info( "Finished tests - {} were run", runningTests.getTotalCount() );
if( runningTests.hasFailedRequired() )
{
log.error( "{} required tests failed", runningTests.getFailedRequiredCount() );
}
if( runningTests.hasFailedOptional() )
{
log.warn( "{} optional tests failed", runningTests.getFailedOptionalCount() );
}
if( ServerLifecycleHooks.getCurrentServer().isDedicatedServer() )
{
log.info( "Stopping server." );
// We can't exit in the main thread, as Minecraft registers a shutdown hook which results
// in a deadlock. So we do this weird janky thing!
Thread thread = new Thread( () -> System.exit( runningTests.hasFailedRequired() ? 1 : 0 ) );
thread.setDaemon( true );
thread.start();
}
}
private BlockPos getStart( CommandSource source )
{
BlockPos pos = new BlockPos( source.getPosition() );
return new BlockPos( pos.getX(), source.getLevel().getHeightmapPos( Heightmap.Type.WORLD_SURFACE, pos ).getY(), pos.getZ() + 3 );
}
}