package net.minecraft.gametest.framework; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.longs.LongArraySet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import net.minecraft.Util; import net.minecraft.core.Holder; import net.minecraft.network.protocol.game.DebugPackets; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class GameTestRunner { public static final int DEFAULT_TESTS_PER_ROW = 8; private static final Logger LOGGER = LogUtils.getLogger(); final ServerLevel level; private final GameTestTicker testTicker; private final List allTestInfos; private ImmutableList batches; final List batchListeners = Lists.newArrayList(); private final List scheduledForRerun = Lists.newArrayList(); private final GameTestRunner.GameTestBatcher testBatcher; private boolean stopped = true; @Nullable private Holder currentEnvironment; private final GameTestRunner.StructureSpawner existingStructureSpawner; private final GameTestRunner.StructureSpawner newStructureSpawner; final boolean haltOnError; protected GameTestRunner( GameTestRunner.GameTestBatcher testBatcher, Collection batches, ServerLevel level, GameTestTicker testTicker, GameTestRunner.StructureSpawner existingStructureSpawner, GameTestRunner.StructureSpawner newStructureSpawner, boolean haltOnError ) { this.level = level; this.testTicker = testTicker; this.testBatcher = testBatcher; this.existingStructureSpawner = existingStructureSpawner; this.newStructureSpawner = newStructureSpawner; this.batches = ImmutableList.copyOf(batches); this.haltOnError = haltOnError; this.allTestInfos = (List)this.batches.stream().flatMap(gameTestBatch -> gameTestBatch.gameTestInfos().stream()).collect(Util.toMutableList()); testTicker.setRunner(this); this.allTestInfos.forEach(gameTestInfo -> gameTestInfo.addListener(new ReportGameListener())); } public List getTestInfos() { return this.allTestInfos; } public void start() { this.stopped = false; this.runBatch(0); } public void stop() { this.stopped = true; if (this.currentEnvironment != null) { this.endCurrentEnvironment(); } } public void rerunTest(GameTestInfo test) { GameTestInfo gameTestInfo = test.copyReset(); test.getListeners().forEach(gameTestListener -> gameTestListener.testAddedForRerun(test, gameTestInfo, this)); this.allTestInfos.add(gameTestInfo); this.scheduledForRerun.add(gameTestInfo); if (this.stopped) { this.runScheduledRerunTests(); } } void runBatch(int index) { if (index >= this.batches.size()) { this.endCurrentEnvironment(); this.runScheduledRerunTests(); } else { final GameTestBatch gameTestBatch = (GameTestBatch)this.batches.get(index); this.existingStructureSpawner.onBatchStart(this.level); this.newStructureSpawner.onBatchStart(this.level); Collection collection = this.createStructuresForBatch(gameTestBatch.gameTestInfos()); LOGGER.info( "Running test environment '{}' batch {} ({} tests)...", gameTestBatch.environment().getRegisteredName(), gameTestBatch.index(), collection.size() ); if (this.currentEnvironment != gameTestBatch.environment()) { this.endCurrentEnvironment(); this.currentEnvironment = gameTestBatch.environment(); this.currentEnvironment.value().setup(this.level); } this.batchListeners.forEach(gameTestBatchListener -> gameTestBatchListener.testBatchStarting(gameTestBatch)); final MultipleTestTracker multipleTestTracker = new MultipleTestTracker(); collection.forEach(multipleTestTracker::addTestToTrack); multipleTestTracker.addListener(new GameTestListener() { private void testCompleted() { if (multipleTestTracker.isDone()) { GameTestRunner.this.batchListeners.forEach(gameTestBatchListener -> gameTestBatchListener.testBatchFinished(gameTestBatch)); LongSet longSet = new LongArraySet(GameTestRunner.this.level.getForceLoadedChunks()); longSet.forEach(l -> GameTestRunner.this.level.setChunkForced(ChunkPos.getX(l), ChunkPos.getZ(l), false)); GameTestRunner.this.runBatch(index + 1); } } @Override public void testStructureLoaded(GameTestInfo testInfo) { } @Override public void testPassed(GameTestInfo test, GameTestRunner runner) { this.testCompleted(); } @Override public void testFailed(GameTestInfo test, GameTestRunner runner) { if (GameTestRunner.this.haltOnError) { GameTestRunner.this.endCurrentEnvironment(); LongSet longSet = new LongArraySet(GameTestRunner.this.level.getForceLoadedChunks()); longSet.forEach(l -> GameTestRunner.this.level.setChunkForced(ChunkPos.getX(l), ChunkPos.getZ(l), false)); GameTestTicker.SINGLETON.clear(); } else { this.testCompleted(); } } @Override public void testAddedForRerun(GameTestInfo oldTest, GameTestInfo newTest, GameTestRunner runner) { } }); collection.forEach(this.testTicker::add); } } void endCurrentEnvironment() { if (this.currentEnvironment != null) { this.currentEnvironment.value().teardown(this.level); this.currentEnvironment = null; } } private void runScheduledRerunTests() { if (!this.scheduledForRerun.isEmpty()) { LOGGER.info( "Starting re-run of tests: {}", this.scheduledForRerun.stream().map(gameTestInfo -> gameTestInfo.id().toString()).collect(Collectors.joining(", ")) ); this.batches = ImmutableList.copyOf(this.testBatcher.batch(this.scheduledForRerun)); this.scheduledForRerun.clear(); this.stopped = false; this.runBatch(0); } else { this.batches = ImmutableList.of(); this.stopped = true; } } public void addListener(GameTestBatchListener listener) { this.batchListeners.add(listener); } private Collection createStructuresForBatch(Collection batch) { return batch.stream().map(this::spawn).flatMap(Optional::stream).toList(); } private Optional spawn(GameTestInfo test) { return test.getTestBlockPos() == null ? this.newStructureSpawner.spawnStructure(test) : this.existingStructureSpawner.spawnStructure(test); } public static void clearMarkers(ServerLevel serverLevel) { DebugPackets.sendGameTestClearPacket(serverLevel); } public static class Builder { private final ServerLevel level; private final GameTestTicker testTicker = GameTestTicker.SINGLETON; private GameTestRunner.GameTestBatcher batcher = GameTestBatchFactory.fromGameTestInfo(); private GameTestRunner.StructureSpawner existingStructureSpawner = GameTestRunner.StructureSpawner.IN_PLACE; private GameTestRunner.StructureSpawner newStructureSpawner = GameTestRunner.StructureSpawner.NOT_SET; private final Collection batches; private boolean haltOnError = false; private Builder(Collection batches, ServerLevel level) { this.batches = batches; this.level = level; } public static GameTestRunner.Builder fromBatches(Collection batches, ServerLevel level) { return new GameTestRunner.Builder(batches, level); } public static GameTestRunner.Builder fromInfo(Collection infos, ServerLevel level) { return fromBatches(GameTestBatchFactory.fromGameTestInfo().batch(infos), level); } public GameTestRunner.Builder haltOnError(boolean haltOnError) { this.haltOnError = haltOnError; return this; } public GameTestRunner.Builder newStructureSpawner(GameTestRunner.StructureSpawner newStructureSpawner) { this.newStructureSpawner = newStructureSpawner; return this; } public GameTestRunner.Builder existingStructureSpawner(StructureGridSpawner existingStructureSpawner) { this.existingStructureSpawner = existingStructureSpawner; return this; } public GameTestRunner.Builder batcher(GameTestRunner.GameTestBatcher batcher) { this.batcher = batcher; return this; } public GameTestRunner build() { return new GameTestRunner(this.batcher, this.batches, this.level, this.testTicker, this.existingStructureSpawner, this.newStructureSpawner, this.haltOnError); } } public interface GameTestBatcher { Collection batch(Collection collection); } public interface StructureSpawner { GameTestRunner.StructureSpawner IN_PLACE = gameTestInfo -> Optional.of(gameTestInfo.prepareTestStructure().startExecution(1)); GameTestRunner.StructureSpawner NOT_SET = gameTestInfo -> Optional.empty(); Optional spawnStructure(GameTestInfo gameTestInfo); default void onBatchStart(ServerLevel level) { } } }