minecraft-src/net/minecraft/gametest/framework/GameTestServer.java
2025-07-04 03:45:38 +03:00

350 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.LayeredRegistryAccess;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.Holder.Reference;
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.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.block.Rotation;
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.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
);
PackConfig packConfig = new PackConfig(packRepository, worldDataConfiguration, false, true);
InitConfig initConfig = new 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();
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, 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<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(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<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;
}
}