package net.minecraft.gametest.framework; import com.google.common.base.Stopwatch; import com.mojang.authlib.GameProfile; import com.mojang.authlib.yggdrasil.ServicesKeySet; import com.mojang.brigadier.StringReader; 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.Optional; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import java.util.stream.Stream; import java.util.stream.Stream.Builder; import javax.annotation.Nullable; import net.minecraft.CrashReport; import net.minecraft.ReportType; import net.minecraft.SystemReport; import net.minecraft.Util; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.ResourceSelectorArgument; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.LayeredRegistryAccess; import net.minecraft.core.MappedRegistry; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; 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.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.block.Rotation; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldDimensions; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.presets.WorldPresets; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; 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 final Optional testSelection; private final boolean verify; private List testBatches = new ArrayList<>(); private final Stopwatch stopwatch = Stopwatch.createUnstarted(); private static final WorldOptions WORLD_OPTIONS = new WorldOptions(0L, false, false); @Nullable private MultipleTestTracker testTracker; public static GameTestServer create( Thread p_206607_, LevelStorageSource.LevelStorageAccess p_206608_, PackRepository p_206609_, Optional p_392741_, boolean p_392375_ ) { p_206609_.reload(); ArrayList arraylist = new ArrayList<>(p_206609_.getAvailableIds()); arraylist.remove("vanilla"); arraylist.addFirst("vanilla"); WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration(new DataPackConfig(arraylist, List.of()), ENABLED_FEATURES); LevelSettings levelsettings = new LevelSettings( "Test Level", GameType.CREATIVE, false, Difficulty.NORMAL, true, new GameRules(ENABLED_FEATURES), worlddataconfiguration ); WorldLoader.PackConfig worldloader$packconfig = new WorldLoader.PackConfig(p_206609_, worlddataconfiguration, false, true); WorldLoader.InitConfig worldloader$initconfig = new WorldLoader.InitConfig(worldloader$packconfig, Commands.CommandSelection.DEDICATED, 4); try { LOGGER.debug("Starting resource loading"); Stopwatch stopwatch = Stopwatch.createStarted(); WorldStem worldstem = Util.blockUntilDone( p_358474_ -> WorldLoader.load( worldloader$initconfig, p_358476_ -> { Registry registry = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.stable()).freeze(); WorldDimensions.Complete worlddimensions$complete = p_358476_.datapackWorldgen() .lookupOrThrow(Registries.WORLD_PRESET) .getOrThrow(WorldPresets.FLAT) .value() .createWorldDimensions() .bake(registry); return new WorldLoader.DataLoadOutput<>( new PrimaryLevelData(levelsettings, WORLD_OPTIONS, worlddimensions$complete.specialWorldProperty(), worlddimensions$complete.lifecycle()), worlddimensions$complete.dimensionsRegistryAccess() ); }, WorldStem::new, Util.backgroundExecutor(), p_358474_ ) ) .get(); stopwatch.stop(); LOGGER.debug("Finished resource loading after {} ms", stopwatch.elapsed(TimeUnit.MILLISECONDS)); return new GameTestServer(p_206607_, p_206608_, p_206609_, worldstem, p_392741_, p_392375_); } catch (Exception exception) { LOGGER.warn("Failed to load vanilla datapack, bit oops", (Throwable)exception); System.exit(-1); throw new IllegalStateException(); } } private GameTestServer( Thread p_206597_, LevelStorageSource.LevelStorageAccess p_206598_, PackRepository p_206599_, WorldStem p_206600_, Optional p_391920_, boolean p_392965_ ) { super(p_206597_, p_206598_, p_206599_, p_206600_, Proxy.NO_PROXY, DataFixers.getDataFixer(), NO_SERVICES, LoggerChunkProgressListener::createFromGameruleRadius); this.testSelection = p_391920_; this.verify = p_392965_; } @Override public boolean initServer() { this.setPlayerList(new PlayerList(this, this.registries(), this.playerDataStorage, 1) {}); this.loadLevel(); ServerLevel serverlevel = this.overworld(); this.testBatches = this.evaluateTestsToRun(serverlevel); LOGGER.info("Started game test server"); return true; } private List evaluateTestsToRun(ServerLevel p_392729_) { Registry registry = p_392729_.registryAccess().lookupOrThrow(Registries.TEST_INSTANCE); Collection> collection; GameTestBatchFactory.TestDecorator gametestbatchfactory$testdecorator; if (this.testSelection.isPresent()) { collection = getTestsForSelection(p_392729_.registryAccess(), this.testSelection.get()).filter(p_389777_ -> !p_389777_.value().manualOnly()).toList(); if (this.verify) { gametestbatchfactory$testdecorator = GameTestServer::rotateAndMultiply; LOGGER.info("Verify requested. Will run each test that matches {} {} times", this.testSelection.get(), 100 * Rotation.values().length); } else { gametestbatchfactory$testdecorator = GameTestBatchFactory.DIRECT; LOGGER.info("Will run tests matching {} ({} tests)", this.testSelection.get(), collection.size()); } } else { collection = registry.listElements().filter(p_389778_ -> !p_389778_.value().manualOnly()).toList(); gametestbatchfactory$testdecorator = GameTestBatchFactory.DIRECT; } return GameTestBatchFactory.divideIntoBatches(collection, gametestbatchfactory$testdecorator, p_392729_); } private static Stream rotateAndMultiply(Holder.Reference p_391602_, ServerLevel p_395202_) { Builder builder = Stream.builder(); for (Rotation rotation : Rotation.values()) { for (int i = 0; i < 100; i++) { builder.add(new GameTestInfo(p_391602_, rotation, p_395202_, RetryOptions.noRetries())); } } return builder.build(); } public static Stream> getTestsForSelection(RegistryAccess p_393329_, String p_393521_) { return ResourceSelectorArgument.parse(new StringReader(p_393521_), p_393329_.lookupOrThrow(Registries.TEST_INSTANCE)).stream(); } @Override public void tickServer(BooleanSupplier p_177619_) { super.tickServer(p_177619_); 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(GameTestServer::logFailedTest); } 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(GameTestServer::logFailedTest); } LOGGER.info("===================================================="); } } private static void logFailedTest(GameTestInfo p_397827_) { if (p_397827_.getRotation() != Rotation.NONE) { LOGGER.info( " - {} with rotation {}: {}", p_397827_.id(), p_397827_.getRotation().getSerializedName(), p_397827_.getError().getDescription().getString() ); } else { LOGGER.info(" - {}: {}", p_397827_.id(), p_397827_.getError().getDescription().getString()); } } @Override public SampleLogger getTickTimeLogger() { return this.sampleLogger; } @Override public boolean isTickTimeLoggingEnabled() { return false; } @Override public void waitUntilNextTick() { this.runAllTasks(); } @Override public SystemReport fillServerSystemReport(SystemReport p_177613_) { p_177613_.setDetail("Type", "Game test server"); return p_177613_; } @Override public void onServerExit() { super.onServerExit(); LOGGER.info("Game test server shutting down"); System.exit(this.testTracker != null ? this.testTracker.getFailedRequiredCount() : -1); } @Override public void onServerCrash(CrashReport p_177623_) { super.onServerCrash(p_177623_); LOGGER.error("Game test server crashed\n{}", p_177623_.getFriendlyReport(ReportType.CRASH)); System.exit(1); } private void startTests(ServerLevel p_177625_) { BlockPos blockpos = new BlockPos(p_177625_.random.nextIntBetweenInclusive(-14999992, 14999992), -59, p_177625_.random.nextIntBetweenInclusive(-14999992, 14999992)); p_177625_.setDefaultSpawnPos(blockpos, 0.0F); GameTestRunner gametestrunner = GameTestRunner.Builder.fromBatches(this.testBatches, p_177625_) .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 p_177617_) { return false; } }