package net.minecraft.gametest.framework; import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; import com.mojang.authlib.GameProfile; import com.mojang.authlib.yggdrasil.ServicesKeySet; import com.mojang.logging.LogUtils; import com.mojang.serialization.Lifecycle; import java.net.Proxy; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import net.minecraft.CrashReport; import net.minecraft.ReportType; import net.minecraft.SystemReport; import net.minecraft.Util; import net.minecraft.commands.Commands.CommandSelection; import net.minecraft.core.BlockPos; import net.minecraft.core.LayeredRegistryAccess; import net.minecraft.core.MappedRegistry; import net.minecraft.core.Registry; import net.minecraft.core.registries.Registries; import net.minecraft.gametest.framework.GameTestRunner.Builder; import net.minecraft.server.MinecraftServer; import net.minecraft.server.RegistryLayer; import net.minecraft.server.Services; import net.minecraft.server.WorldLoader; import net.minecraft.server.WorldStem; import net.minecraft.server.WorldLoader.DataLoadOutput; import net.minecraft.server.WorldLoader.InitConfig; import net.minecraft.server.WorldLoader.PackConfig; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.progress.LoggerChunkProgressListener; import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.server.players.PlayerList; import net.minecraft.util.datafix.DataFixers; import net.minecraft.util.debugchart.LocalSampleLogger; import net.minecraft.util.debugchart.SampleLogger; import net.minecraft.world.Difficulty; import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.level.DataPackConfig; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.GameType; import net.minecraft.world.level.LevelSettings; import net.minecraft.world.level.WorldDataConfiguration; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.WorldDimensions.Complete; import net.minecraft.world.level.levelgen.presets.WorldPresets; import net.minecraft.world.level.storage.PrimaryLevelData; import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class GameTestServer extends MinecraftServer { private static final Logger LOGGER = LogUtils.getLogger(); private static final int PROGRESS_REPORT_INTERVAL = 20; private static final int TEST_POSITION_RANGE = 14999992; private static final Services NO_SERVICES = new Services(null, ServicesKeySet.EMPTY, null, null); private static final FeatureFlagSet ENABLED_FEATURES = FeatureFlags.REGISTRY .allFlags() .subtract(FeatureFlagSet.of(FeatureFlags.REDSTONE_EXPERIMENTS, FeatureFlags.MINECART_IMPROVEMENTS)); private final LocalSampleLogger sampleLogger = new LocalSampleLogger(4); private List testBatches = new ArrayList(); private final List testFunctions; private final BlockPos spawnPos; private final Stopwatch stopwatch = Stopwatch.createUnstarted(); private static final GameRules TEST_GAME_RULES = Util.make(new GameRules(ENABLED_FEATURES), gameRules -> { gameRules.getRule(GameRules.RULE_DOMOBSPAWNING).set(false, null); gameRules.getRule(GameRules.RULE_WEATHER_CYCLE).set(false, null); gameRules.getRule(GameRules.RULE_RANDOMTICKING).set(0, null); gameRules.getRule(GameRules.RULE_DOFIRETICK).set(false, null); }); private static final WorldOptions WORLD_OPTIONS = new WorldOptions(0L, false, false); @Nullable private MultipleTestTracker testTracker; public static GameTestServer create( Thread serverThread, LevelStorageAccess storageSource, PackRepository packRepository, Collection testBatches, BlockPos spawnPos ) { if (testBatches.isEmpty()) { throw new IllegalArgumentException("No test functions were given!"); } else { packRepository.reload(); WorldDataConfiguration worldDataConfiguration = new WorldDataConfiguration( new DataPackConfig(new ArrayList(packRepository.getAvailableIds()), List.of()), ENABLED_FEATURES ); LevelSettings levelSettings = new LevelSettings("Test Level", GameType.CREATIVE, false, Difficulty.NORMAL, true, TEST_GAME_RULES, worldDataConfiguration); PackConfig packConfig = new PackConfig(packRepository, worldDataConfiguration, false, true); InitConfig initConfig = new InitConfig(packConfig, CommandSelection.DEDICATED, 4); try { LOGGER.debug("Starting resource loading"); Stopwatch stopwatch = Stopwatch.createStarted(); WorldStem worldStem = (WorldStem)Util.blockUntilDone( executor -> WorldLoader.load( initConfig, dataLoadContext -> { Registry registry = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.stable()).freeze(); Complete complete = dataLoadContext.datapackWorldgen() .lookupOrThrow(Registries.WORLD_PRESET) .getOrThrow(WorldPresets.FLAT) .value() .createWorldDimensions() .bake(registry); return new DataLoadOutput<>( new PrimaryLevelData(levelSettings, WORLD_OPTIONS, complete.specialWorldProperty(), complete.lifecycle()), complete.dimensionsRegistryAccess() ); }, WorldStem::new, Util.backgroundExecutor(), executor ) ) .get(); stopwatch.stop(); LOGGER.debug("Finished resource loading after {} ms", stopwatch.elapsed(TimeUnit.MILLISECONDS)); return new GameTestServer(serverThread, storageSource, packRepository, worldStem, testBatches, spawnPos); } catch (Exception var11) { LOGGER.warn("Failed to load vanilla datapack, bit oops", (Throwable)var11); System.exit(-1); throw new IllegalStateException(); } } } private GameTestServer( Thread serverThread, LevelStorageAccess storageSource, PackRepository packRepository, WorldStem worldStem, Collection testBatches, BlockPos spawnPos ) { super( serverThread, storageSource, packRepository, worldStem, Proxy.NO_PROXY, DataFixers.getDataFixer(), NO_SERVICES, LoggerChunkProgressListener::createFromGameruleRadius ); this.testFunctions = Lists.newArrayList(testBatches); this.spawnPos = spawnPos; } @Override public boolean initServer() { this.setPlayerList(new PlayerList(this, this.registries(), this.playerDataStorage, 1) {}); this.loadLevel(); ServerLevel serverLevel = this.overworld(); this.testBatches = Lists.newArrayList(GameTestBatchFactory.fromTestFunction(this.testFunctions, serverLevel)); serverLevel.setDefaultSpawnPos(this.spawnPos, 0.0F); int i = 20000000; serverLevel.setWeatherParameters(20000000, 20000000, false, false); LOGGER.info("Started game test server"); return true; } @Override public void tickServer(BooleanSupplier hasTimeLeft) { super.tickServer(hasTimeLeft); ServerLevel serverLevel = this.overworld(); if (!this.haveTestsStarted()) { this.startTests(serverLevel); } if (serverLevel.getGameTime() % 20L == 0L) { LOGGER.info(this.testTracker.getProgressBar()); } if (this.testTracker.isDone()) { this.halt(false); LOGGER.info(this.testTracker.getProgressBar()); GlobalTestReporter.finish(); LOGGER.info("========= {} GAME TESTS COMPLETE IN {} ======================", this.testTracker.getTotalCount(), this.stopwatch.stop()); if (this.testTracker.hasFailedRequired()) { LOGGER.info("{} required tests failed :(", this.testTracker.getFailedRequiredCount()); this.testTracker.getFailedRequired().forEach(gameTestInfo -> LOGGER.info(" - {}", gameTestInfo.getTestName())); } else { LOGGER.info("All {} required tests passed :)", this.testTracker.getTotalCount()); } if (this.testTracker.hasFailedOptional()) { LOGGER.info("{} optional tests failed", this.testTracker.getFailedOptionalCount()); this.testTracker .getFailedOptional() .forEach(gameTestInfo -> LOGGER.info(" - {} with rotation: {}", gameTestInfo.getTestName(), gameTestInfo.getRotation())); } LOGGER.info("===================================================="); } } @Override public SampleLogger getTickTimeLogger() { return this.sampleLogger; } @Override public boolean isTickTimeLoggingEnabled() { return false; } @Override public void waitUntilNextTick() { this.runAllTasks(); } @Override public SystemReport fillServerSystemReport(SystemReport report) { report.setDetail("Type", "Game test server"); return report; } @Override public void onServerExit() { super.onServerExit(); LOGGER.info("Game test server shutting down"); System.exit(this.testTracker.getFailedRequiredCount()); } @Override public void onServerCrash(CrashReport report) { super.onServerCrash(report); LOGGER.error("Game test server crashed\n{}", report.getFriendlyReport(ReportType.CRASH)); System.exit(1); } private void startTests(ServerLevel serverLevel) { BlockPos blockPos = new BlockPos( serverLevel.random.nextIntBetweenInclusive(-14999992, 14999992), -59, serverLevel.random.nextIntBetweenInclusive(-14999992, 14999992) ); GameTestRunner gameTestRunner = Builder.fromBatches(this.testBatches, serverLevel).newStructureSpawner(new StructureGridSpawner(blockPos, 8, false)).build(); Collection collection = gameTestRunner.getTestInfos(); this.testTracker = new MultipleTestTracker(collection); LOGGER.info("{} tests are now running at position {}!", this.testTracker.getTotalCount(), blockPos.toShortString()); this.stopwatch.reset(); this.stopwatch.start(); gameTestRunner.start(); } private boolean haveTestsStarted() { return this.testTracker != null; } @Override public boolean isHardcore() { return false; } @Override public int getOperatorUserPermissionLevel() { return 0; } @Override public int getFunctionCompilationLevel() { return 4; } @Override public boolean shouldRconBroadcast() { return false; } @Override public boolean isDedicatedServer() { return false; } @Override public int getRateLimitPacketsPerSecond() { return 0; } @Override public boolean isEpollEnabled() { return false; } @Override public boolean isCommandBlockEnabled() { return true; } @Override public boolean isPublished() { return false; } @Override public boolean shouldInformAdmins() { return false; } @Override public boolean isSingleplayerOwner(GameProfile profile) { return false; } }