347 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			347 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| 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.TimeUnit;
 | |
| import java.util.function.BooleanSupplier;
 | |
| import java.util.stream.Stream;
 | |
| import java.util.stream.Stream.Builder;
 | |
| 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.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 final Optional<String> testSelection;
 | |
| 	private final boolean verify;
 | |
| 	private List<GameTestBatch> 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 serverThread, LevelStorageSource.LevelStorageAccess storageSource, PackRepository packRepository, Optional<String> testSelection, boolean verify
 | |
| 	) {
 | |
| 		packRepository.reload();
 | |
| 		ArrayList<String> arrayList = new ArrayList(packRepository.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 packConfig = new WorldLoader.PackConfig(packRepository, worldDataConfiguration, false, true);
 | |
| 		WorldLoader.InitConfig initConfig = new WorldLoader.InitConfig(packConfig, Commands.CommandSelection.DEDICATED, 4);
 | |
| 
 | |
| 		try {
 | |
| 			LOGGER.debug("Starting resource loading");
 | |
| 			Stopwatch stopwatch = Stopwatch.createStarted();
 | |
| 			WorldStem worldStem = (WorldStem)Util.blockUntilDone(
 | |
| 					executor -> WorldLoader.load(
 | |
| 						initConfig,
 | |
| 						dataLoadContext -> {
 | |
| 							Registry<LevelStem> registry = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.stable()).freeze();
 | |
| 							WorldDimensions.Complete complete = dataLoadContext.datapackWorldgen()
 | |
| 								.lookupOrThrow(Registries.WORLD_PRESET)
 | |
| 								.getOrThrow(WorldPresets.FLAT)
 | |
| 								.value()
 | |
| 								.createWorldDimensions()
 | |
| 								.bake(registry);
 | |
| 							return new WorldLoader.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, testSelection, verify);
 | |
| 		} catch (Exception var12) {
 | |
| 			LOGGER.warn("Failed to load vanilla datapack, bit oops", (Throwable)var12);
 | |
| 			System.exit(-1);
 | |
| 			throw new IllegalStateException();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private GameTestServer(
 | |
| 		Thread serverThread,
 | |
| 		LevelStorageSource.LevelStorageAccess storageSource,
 | |
| 		PackRepository packRepository,
 | |
| 		WorldStem worldStem,
 | |
| 		Optional<String> testSelection,
 | |
| 		boolean verify
 | |
| 	) {
 | |
| 		super(
 | |
| 			serverThread,
 | |
| 			storageSource,
 | |
| 			packRepository,
 | |
| 			worldStem,
 | |
| 			Proxy.NO_PROXY,
 | |
| 			DataFixers.getDataFixer(),
 | |
| 			NO_SERVICES,
 | |
| 			LoggerChunkProgressListener::createFromGameruleRadius
 | |
| 		);
 | |
| 		this.testSelection = testSelection;
 | |
| 		this.verify = verify;
 | |
| 	}
 | |
| 
 | |
| 	@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<GameTestBatch> evaluateTestsToRun(ServerLevel level) {
 | |
| 		Registry<GameTestInstance> registry = level.registryAccess().lookupOrThrow(Registries.TEST_INSTANCE);
 | |
| 		Collection<Holder.Reference<GameTestInstance>> collection;
 | |
| 		GameTestBatchFactory.TestDecorator testDecorator;
 | |
| 		if (this.testSelection.isPresent()) {
 | |
| 			collection = getTestsForSelection(level.registryAccess(), (String)this.testSelection.get())
 | |
| 				.filter(reference -> !((GameTestInstance)reference.value()).manualOnly())
 | |
| 				.toList();
 | |
| 			if (this.verify) {
 | |
| 				testDecorator = GameTestServer::rotateAndMultiply;
 | |
| 				LOGGER.info("Verify requested. Will run each test that matches {} {} times", this.testSelection.get(), 100 * Rotation.values().length);
 | |
| 			} else {
 | |
| 				testDecorator = GameTestBatchFactory.DIRECT;
 | |
| 				LOGGER.info("Will run tests matching {} ({} tests)", this.testSelection.get(), collection.size());
 | |
| 			}
 | |
| 		} else {
 | |
| 			collection = registry.listElements().filter(reference -> !((GameTestInstance)reference.value()).manualOnly()).toList();
 | |
| 			testDecorator = GameTestBatchFactory.DIRECT;
 | |
| 		}
 | |
| 
 | |
| 		return GameTestBatchFactory.divideIntoBatches(collection, testDecorator, level);
 | |
| 	}
 | |
| 
 | |
| 	private static Stream<GameTestInfo> rotateAndMultiply(Holder.Reference<GameTestInstance> test, ServerLevel level) {
 | |
| 		Builder<GameTestInfo> builder = Stream.builder();
 | |
| 
 | |
| 		for (Rotation rotation : Rotation.values()) {
 | |
| 			for (int i = 0; i < 100; i++) {
 | |
| 				builder.add(new GameTestInfo(test, rotation, level, RetryOptions.noRetries()));
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return builder.build();
 | |
| 	}
 | |
| 
 | |
| 	public static Stream<Holder.Reference<GameTestInstance>> getTestsForSelection(RegistryAccess registries, String selection) {
 | |
| 		return ResourceSelectorArgument.parse(new StringReader(selection), registries.lookupOrThrow(Registries.TEST_INSTANCE)).stream();
 | |
| 	}
 | |
| 
 | |
| 	@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(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 info) {
 | |
| 		if (info.getRotation() != Rotation.NONE) {
 | |
| 			LOGGER.info("   - {} with rotation {}: {}", info.id(), info.getRotation().getSerializedName(), info.getError().getDescription().getString());
 | |
| 		} else {
 | |
| 			LOGGER.info("   - {}: {}", info.id(), info.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 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 != null ? this.testTracker.getFailedRequiredCount() : -1);
 | |
| 	}
 | |
| 
 | |
| 	@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)
 | |
| 		);
 | |
| 		serverLevel.setDefaultSpawnPos(blockPos, 0.0F);
 | |
| 		GameTestRunner gameTestRunner = GameTestRunner.Builder.fromBatches(this.testBatches, serverLevel)
 | |
| 			.newStructureSpawner(new StructureGridSpawner(blockPos, 8, false))
 | |
| 			.build();
 | |
| 		Collection<GameTestInfo> 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;
 | |
| 	}
 | |
| }
 |