Skip to content

Commit

Permalink
E2E Self-Tests of production server and client (#1700)
Browse files Browse the repository at this point in the history
  • Loading branch information
shartte authored Nov 28, 2024
1 parent 7730007 commit 95ff222
Show file tree
Hide file tree
Showing 14 changed files with 858 additions and 4 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/test-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ jobs:
- name: Run JUnit tests with Gradle
run: ./gradlew :tests:runUnitTests

- name: Install software OpenGL rendering
run: sudo apt-get install xvfb libgl1-mesa-dri

- name: Run production client self-test
run: xvfb-run ./gradlew :neoforge:testProductionClient

- name: Run production server self-test
run: ./gradlew :neoforge:testProductionServer

- name: Store reports
if: failure()
uses: actions/upload-artifact@v4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@
import javax.inject.Inject;

abstract class CreateCleanArtifacts extends CreateMinecraftArtifacts {
/**
* The unmodified downloaded client jar.
*/
@OutputFile
abstract RegularFileProperty getRawClientJar();

@OutputFile
abstract RegularFileProperty getCleanClientJar();

/**
* The unmodified downloaded server jar.
*/
@OutputFile
abstract RegularFileProperty getRawServerJar();

Expand All @@ -24,6 +33,7 @@ abstract class CreateCleanArtifacts extends CreateMinecraftArtifacts {

@Inject
public CreateCleanArtifacts() {
getAdditionalResults().put("node.downloadClient.output.output", getRawClientJar().getAsFile());
getAdditionalResults().put("node.stripClient.output.output", getCleanClientJar().getAsFile());
getAdditionalResults().put("node.downloadServer.output.output", getRawServerJar().getAsFile());
getAdditionalResults().put("node.stripServer.output.output", getCleanServerJar().getAsFile());
Expand Down
84 changes: 84 additions & 0 deletions buildSrc/src/main/java/net/neoforged/neodev/NeoDevPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
import net.neoforged.minecraftdependencies.MinecraftDependenciesPlugin;
import net.neoforged.moddevgradle.internal.NeoDevFacade;
import net.neoforged.moddevgradle.tasks.JarJar;
import net.neoforged.neodev.e2e.InstallProductionClient;
import net.neoforged.neodev.e2e.InstallProductionServer;
import net.neoforged.neodev.e2e.RunProductionClient;
import net.neoforged.neodev.e2e.RunProductionServer;
import net.neoforged.neodev.e2e.TestProductionClient;
import net.neoforged.neodev.e2e.TestProductionServer;
import net.neoforged.neodev.installer.CreateArgsFile;
import net.neoforged.neodev.installer.CreateInstallerProfile;
import net.neoforged.neodev.installer.CreateLauncherProfile;
import net.neoforged.neodev.installer.IdentifiedFile;
import net.neoforged.neodev.installer.InstallerProcessor;
import net.neoforged.neodev.utils.DependencyUtils;
import net.neoforged.nfrtgradle.CreateMinecraftArtifacts;
Expand All @@ -32,6 +39,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

public class NeoDevPlugin implements Plugin<Project> {
static final String GROUP = "neoforge development";
Expand Down Expand Up @@ -206,7 +214,9 @@ public void apply(Project project) {

var createCleanArtifacts = tasks.register("createCleanArtifacts", CreateCleanArtifacts.class, task -> {
task.setGroup(INTERNAL_GROUP);
task.setDescription("This task retrieves various files for the Minecraft version without applying NeoForge patches to them");
var cleanArtifactsDir = neoDevBuildDir.map(dir -> dir.dir("artifacts/clean"));
task.getRawClientJar().set(cleanArtifactsDir.map(dir -> dir.file("raw-client.jar")));
task.getCleanClientJar().set(cleanArtifactsDir.map(dir -> dir.file("client.jar")));
task.getRawServerJar().set(cleanArtifactsDir.map(dir -> dir.file("raw-server.jar")));
task.getCleanServerJar().set(cleanArtifactsDir.map(dir -> dir.file("server.jar")));
Expand Down Expand Up @@ -404,6 +414,18 @@ public void apply(Project project) {
task.dependsOn(userdevJar);
task.dependsOn(sourcesJarProvider);
});

// Set up E2E testing of the produced installer
setupProductionClientTest(
project,
configurations,
downloadAssets,
installerJar,
minecraftVersion,
neoForgeVersion,
createCleanArtifacts.flatMap(CreateCleanArtifacts::getRawClientJar)
);
setupProductionServerTest(project, installerJar);
}

private static TaskProvider<ApplyAccessTransformer> configureAccessTransformer(
Expand Down Expand Up @@ -528,4 +550,66 @@ static TaskProvider<CreateMinecraftArtifacts> configureMinecraftDecompilation(Pr
task.getNeoFormArtifact().set(mcAndNeoFormVersion.map(version -> "net.neoforged:neoform:" + version + "@zip"));
});
}

private void setupProductionClientTest(Project project,
NeoDevConfigurations configurations,
TaskProvider<? extends DownloadAssets> downloadAssets,
TaskProvider<? extends AbstractArchiveTask> installer,
Provider<String> minecraftVersion,
Provider<String> neoForgeVersion,
Provider<RegularFile> originalClientJar
) {

var installClient = project.getTasks().register("installProductionClient", InstallProductionClient.class, task -> {
task.setGroup(INTERNAL_GROUP);
task.setDescription("Runs the installer produced by this build and installs a production client.");
task.getInstaller().from(installer.flatMap(AbstractArchiveTask::getArchiveFile));

var destinationDir = project.getLayout().getBuildDirectory().dir("production-client");
task.getInstallationDir().set(destinationDir);
});

Consumer<RunProductionClient> configureRunProductionClient = task -> {
task.getLibraryFiles().addAll(IdentifiedFile.listFromConfiguration(project, configurations.neoFormClasspath));
task.getLibraryFiles().addAll(IdentifiedFile.listFromConfiguration(project, configurations.launcherProfileClasspath));
task.getAssetPropertiesFile().set(downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile));
task.getMinecraftVersion().set(minecraftVersion);
task.getNeoForgeVersion().set(neoForgeVersion);
task.getInstallationDir().set(installClient.flatMap(InstallProductionClient::getInstallationDir));
task.getOriginalClientJar().set(originalClientJar);
};
project.getTasks().register("runProductionClient", RunProductionClient.class, task -> {
task.setGroup(INTERNAL_GROUP);
task.setDescription("Runs the production client installed by installProductionClient.");
configureRunProductionClient.accept(task);
});
project.getTasks().register("testProductionClient", TestProductionClient.class, task -> {
task.setGroup(INTERNAL_GROUP);
task.setDescription("Tests the production client installed by installProductionClient.");
configureRunProductionClient.accept(task);
});
}

private void setupProductionServerTest(Project project, TaskProvider<? extends AbstractArchiveTask> installer) {
var installServer = project.getTasks().register("installProductionServer", InstallProductionServer.class, task -> {
task.setGroup(INTERNAL_GROUP);
task.setDescription("Runs the installer produced by this build and installs a production server.");
task.getInstaller().from(installer.flatMap(AbstractArchiveTask::getArchiveFile));

var destinationDir = project.getLayout().getBuildDirectory().dir("production-server");
task.getInstallationDir().set(destinationDir);
});

project.getTasks().register("runProductionServer", RunProductionServer.class, task -> {
task.setGroup(INTERNAL_GROUP);
task.setDescription("Runs the production server installed by installProductionServer.");
task.getInstallationDir().set(installServer.flatMap(InstallProductionServer::getInstallationDir));
});

project.getTasks().register("testProductionServer", TestProductionServer.class, task -> {
task.setGroup(INTERNAL_GROUP);
task.setDescription("Tests the production server installed by installProductionServer.");
task.getInstallationDir().set(installServer.flatMap(InstallProductionServer::getInstallationDir));
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package net.neoforged.neodev.e2e;

import org.gradle.api.GradleException;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;

import javax.inject.Inject;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;

/**
* Downloads and installs a production NeoForge client.
* By extending this task from {@link JavaExec}, it's possible to debug the actual legacy installer
* via IntelliJ directly.
*/
public abstract class InstallProductionClient extends JavaExec {
/**
* This file collection should contain exactly one file:
* The NeoForge Installer Jar-File.
*/
@InputFiles
public abstract ConfigurableFileCollection getInstaller();

/**
* Where NeoForge should be installed.
*/
@OutputDirectory
public abstract DirectoryProperty getInstallationDir();

@Inject
public InstallProductionClient() {
classpath(getInstaller());
}

@TaskAction
@Override
public void exec() {
var installDir = getInstallationDir().getAsFile().get().toPath().toAbsolutePath();

// Installer looks for this file
var profilesJsonPath = installDir.resolve("launcher_profiles.json");
try {
Files.writeString(profilesJsonPath, "{}");
} catch (IOException e) {
throw new GradleException("Failed to write fake launcher profiles file.", e);
}

setWorkingDir(installDir.toFile());
args("--install-client", installDir.toString());
try {
setStandardOutput(new BufferedOutputStream(Files.newOutputStream(installDir.resolve("install.log"))));
} catch (IOException e) {
throw new UncheckedIOException(e);
}

super.exec();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package net.neoforged.neodev.e2e;

import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

import javax.inject.Inject;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;

/**
* Runs the installer produced by the main build to install a dedicated server in a chosen directory.
*/
public abstract class InstallProductionServer extends JavaExec {
/**
* The NeoForge installer jar is expected to be the only file in this file collection.
*/
@InputFiles
public abstract ConfigurableFileCollection getInstaller();

/**
* Where the server should be installed.
*/
@OutputDirectory
public abstract DirectoryProperty getInstallationDir();

/**
* Points to the server.jar produced by the installer.
*/
@OutputFile
public abstract RegularFileProperty getServerLauncher();

@Inject
public InstallProductionServer() {
classpath(getInstaller());
getServerLauncher().set(getInstallationDir().map(id -> id.file("server.jar")));
getServerLauncher().finalizeValueOnRead();
}

@TaskAction
@Override
public void exec() {
var installDir = getInstallationDir().getAsFile().get().toPath().toAbsolutePath();

setWorkingDir(installDir.toFile());
args("--install-server", installDir.toString());
args("--server.jar");
try {
setStandardOutput(new BufferedOutputStream(Files.newOutputStream(installDir.resolve("install.log"))));
} catch (IOException e) {
throw new UncheckedIOException(e);
}

super.exec();
}
}
Loading

0 comments on commit 95ff222

Please sign in to comment.