Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E2E Self-Tests of production server and client #1700

Merged
merged 3 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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