mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-03-28 14:27:00 +00:00
Replace Fabric JUnit hacks with fabric-loader-junit
Also configure some of our common JUnit run configurations via Gradle - I end up setting these up in every worktree anyway - so let's just do it once.
This commit is contained in:
parent
6656da5877
commit
aee382ed70
@ -2,7 +2,11 @@
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
import cc.tweaked.gradle.JUnitExt
|
||||
import net.fabricmc.loom.api.LoomGradleExtensionAPI
|
||||
import net.fabricmc.loom.util.gradle.SourceSetHelper
|
||||
import org.jetbrains.gradle.ext.compiler
|
||||
import org.jetbrains.gradle.ext.runConfigurations
|
||||
import org.jetbrains.gradle.ext.settings
|
||||
|
||||
plugins {
|
||||
@ -38,6 +42,50 @@ githubRelease {
|
||||
|
||||
tasks.publish { dependsOn(tasks.githubRelease) }
|
||||
|
||||
idea.project.settings.runConfigurations {
|
||||
register<JUnitExt>("Core Tests") {
|
||||
vmParameters = "-ea"
|
||||
moduleName = "${idea.project.name}.core.test"
|
||||
packageName = ""
|
||||
}
|
||||
|
||||
register<JUnitExt>("CraftOS Tests") {
|
||||
vmParameters = "-ea"
|
||||
moduleName = "${idea.project.name}.core.test"
|
||||
className = "dan200.computercraft.core.ComputerTestDelegate"
|
||||
}
|
||||
|
||||
register<JUnitExt>("CraftOS Tests (Fast)") {
|
||||
vmParameters = "-ea -Dcc.skip_keywords=slow"
|
||||
moduleName = "${idea.project.name}.core.test"
|
||||
className = "dan200.computercraft.core.ComputerTestDelegate"
|
||||
}
|
||||
|
||||
register<JUnitExt>("Common Tests") {
|
||||
vmParameters = "-ea"
|
||||
moduleName = "${idea.project.name}.common.test"
|
||||
packageName = ""
|
||||
}
|
||||
|
||||
register<JUnitExt>("Fabric Tests") {
|
||||
val fabricProject = evaluationDependsOn(":fabric")
|
||||
val classPathGroup = fabricProject.extensions.getByType<LoomGradleExtensionAPI>().mods
|
||||
.joinToString(File.pathSeparator + File.pathSeparator) { modSettings ->
|
||||
SourceSetHelper.getClasspath(modSettings, project).joinToString(File.pathSeparator) { it.absolutePath }
|
||||
}
|
||||
|
||||
vmParameters = "-ea -Dfabric.classPathGroups=$classPathGroup"
|
||||
moduleName = "${idea.project.name}.fabric.test"
|
||||
packageName = ""
|
||||
}
|
||||
|
||||
register<JUnitExt>("Forge Tests") {
|
||||
vmParameters = "-ea"
|
||||
moduleName = "${idea.project.name}.forge.test"
|
||||
packageName = ""
|
||||
}
|
||||
}
|
||||
|
||||
idea.project.settings.compiler.javac {
|
||||
// We want ErrorProne to be present when compiling via IntelliJ, as it offers some helpful warnings
|
||||
// and errors. Loop through our source sets and find the appropriate flags.
|
||||
|
@ -50,10 +50,11 @@ dependencies {
|
||||
implementation(libs.curseForgeGradle)
|
||||
implementation(libs.fabric.loom)
|
||||
implementation(libs.forgeGradle)
|
||||
implementation(libs.ideaExt)
|
||||
implementation(libs.librarian)
|
||||
implementation(libs.minotaur)
|
||||
implementation(libs.vineflower)
|
||||
implementation(libs.vanillaGradle)
|
||||
implementation(libs.vineflower)
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
|
@ -9,6 +9,10 @@ import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.JavaPlugin
|
||||
import org.gradle.api.plugins.JavaPluginExtension
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
import org.gradle.plugins.ide.idea.model.IdeaModel
|
||||
import org.jetbrains.gradle.ext.IdeaExtPlugin
|
||||
import org.jetbrains.gradle.ext.runConfigurations
|
||||
import org.jetbrains.gradle.ext.settings
|
||||
|
||||
/**
|
||||
* Configures projects to match a shared configuration.
|
||||
@ -21,6 +25,20 @@ class CCTweakedPlugin : Plugin<Project> {
|
||||
val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
|
||||
cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main")))
|
||||
}
|
||||
|
||||
project.plugins.withType(IdeaExtPlugin::class.java) { extendIdea(project) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the [IdeaExtPlugin] plugin's `runConfiguration` container to also support [JUnitExt].
|
||||
*/
|
||||
private fun extendIdea(project: Project) {
|
||||
val ideaModel = project.extensions.findByName("idea") as IdeaModel? ?: return
|
||||
val ideaProject = ideaModel.project ?: return
|
||||
|
||||
ideaProject.settings.runConfigurations {
|
||||
registerFactory(JUnitExt::class.java) { name -> project.objects.newInstance(JUnitExt::class.java, name) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
23
buildSrc/src/main/kotlin/cc/tweaked/gradle/IdeaExt.kt
Normal file
23
buildSrc/src/main/kotlin/cc/tweaked/gradle/IdeaExt.kt
Normal file
@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.jetbrains.gradle.ext.JUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* A version of [JUnit] with a functional [className].
|
||||
*
|
||||
* See [#92](https://github.com/JetBrains/gradle-idea-ext-plugin/issues/92).
|
||||
*/
|
||||
open class JUnitExt @Inject constructor(nameParam: String) : JUnit(nameParam) {
|
||||
override fun toMap(): MutableMap<String, *> {
|
||||
val map = HashMap(super.toMap())
|
||||
// Should be "class" instead of "className".
|
||||
// See https://github.com/JetBrains/intellij-community/blob/9ba394021dc73a3926f13d6d6cdf434f9ee7046d/plugins/junit/src/com/intellij/execution/junit/JUnitRunConfigurationImporter.kt#L39
|
||||
map["class"] = className
|
||||
return map
|
||||
}
|
||||
}
|
@ -45,7 +45,6 @@ rubidium = "0.6.1"
|
||||
sodium = "mc1.20-0.4.10"
|
||||
|
||||
# Testing
|
||||
byteBuddy = "1.14.7"
|
||||
hamcrest = "2.2"
|
||||
jqwik = "1.7.4"
|
||||
junit = "5.10.0"
|
||||
@ -97,6 +96,7 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
# Minecraft mods
|
||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
|
||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
|
||||
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
||||
@ -114,8 +114,6 @@ rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
|
||||
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
|
||||
|
||||
# Testing
|
||||
byteBuddyAgent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "byteBuddy" }
|
||||
byteBuddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byteBuddy" }
|
||||
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
|
||||
jqwik-api = { module = "net.jqwik:jqwik-api", version.ref = "jqwik" }
|
||||
jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
|
||||
@ -135,6 +133,7 @@ errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", versi
|
||||
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
|
||||
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
|
||||
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
|
||||
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
|
||||
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
|
||||
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
|
||||
@ -154,7 +153,6 @@ vineflower = { module = "io.github.juuxel:loom-vineflower", version.ref = "vinef
|
||||
[plugins]
|
||||
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
|
||||
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
|
||||
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
|
||||
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
|
||||
|
@ -87,10 +87,9 @@ dependencies {
|
||||
testModImplementation(testFixtures(project(":core")))
|
||||
testModImplementation(testFixtures(project(":fabric")))
|
||||
|
||||
testImplementation(libs.byteBuddy)
|
||||
testImplementation(libs.byteBuddyAgent)
|
||||
testImplementation(libs.bundles.test)
|
||||
testRuntimeOnly(libs.bundles.testRuntime)
|
||||
testRuntimeOnly(libs.fabric.junit)
|
||||
|
||||
testFixturesImplementation(testFixtures(project(":core")))
|
||||
}
|
||||
|
@ -1,249 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import net.bytebuddy.agent.ByteBuddyAgent;
|
||||
import net.bytebuddy.dynamic.loading.ClassInjector;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.loader.impl.FabricLoaderImpl;
|
||||
import net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider;
|
||||
import net.fabricmc.loader.impl.game.minecraft.Slf4jLogHandler;
|
||||
import net.fabricmc.loader.impl.launch.FabricLauncherBase;
|
||||
import net.fabricmc.loader.impl.launch.FabricMixinBootstrap;
|
||||
import net.fabricmc.loader.impl.launch.knot.MixinServiceKnot;
|
||||
import net.fabricmc.loader.impl.transformer.FabricTransformer;
|
||||
import net.fabricmc.loader.impl.util.LoaderUtil;
|
||||
import net.fabricmc.loader.impl.util.log.Log;
|
||||
import org.junit.jupiter.api.extension.Extension;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.*;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Loads Fabric mods as part of this test run.
|
||||
* <p>
|
||||
* This sets up a minimalistic {@link FabricLauncherBase}, uses that to load mods, and then acquires an
|
||||
* {@link Instrumentation} instance, registering a {@link ClassFileTransformer} to apply mixins and access wideners.
|
||||
*
|
||||
* @see net.fabricmc.loader.impl.launch.knot.Knot
|
||||
*/
|
||||
@AutoService(Extension.class)
|
||||
public class FabricBootstrap implements Extension {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FabricBootstrap.class);
|
||||
|
||||
public FabricBootstrap() throws ReflectiveOperationException, IOException {
|
||||
Log.init(new Slf4jLogHandler());
|
||||
|
||||
readProperties();
|
||||
{
|
||||
var method = FabricLauncherBase.class.getDeclaredMethod("setProperties", Map.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(null, new HashMap<>());
|
||||
}
|
||||
|
||||
var provider = new MinecraftGameProvider();
|
||||
if (!provider.locateGame(new BasicLauncher(), new String[0])) {
|
||||
throw new IllegalStateException("Cannot setup game");
|
||||
}
|
||||
|
||||
var loader = FabricLoaderImpl.INSTANCE;
|
||||
loader.setGameProvider(provider);
|
||||
loader.load();
|
||||
loader.freeze();
|
||||
loader.loadAccessWideners();
|
||||
|
||||
FabricMixinBootstrap.init(EnvType.CLIENT, loader);
|
||||
{
|
||||
var method = FabricLauncherBase.class.getDeclaredMethod("finishMixinBootstrapping");
|
||||
method.setAccessible(true);
|
||||
method.invoke(null);
|
||||
}
|
||||
|
||||
IMixinTransformer transformer;
|
||||
{
|
||||
var method = MixinServiceKnot.class.getDeclaredMethod("getTransformer");
|
||||
method.setAccessible(true);
|
||||
transformer = (IMixinTransformer) method.invoke(null);
|
||||
}
|
||||
|
||||
ByteBuddyAgent.install().addTransformer(new ClassTransformer(transformer));
|
||||
}
|
||||
|
||||
private static void readProperties() throws IOException {
|
||||
try (var reader = Files.newBufferedReader(Path.of(".gradle/loom-cache/launch.cfg"))) {
|
||||
var interesting = false;
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith(" ") || line.startsWith("\t")) {
|
||||
if (!interesting) continue;
|
||||
|
||||
line = line.strip();
|
||||
var index = line.indexOf('=');
|
||||
|
||||
if (index >= 0) {
|
||||
System.setProperty(line.substring(0, index), line.substring(index + 1));
|
||||
} else {
|
||||
System.setProperty(line, "");
|
||||
}
|
||||
} else {
|
||||
interesting = line.equals("commonProperties") || line.equals("clientProperties");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BasicLauncher extends FabricLauncherBase {
|
||||
private final List<Path> classpath = new ArrayList<>();
|
||||
|
||||
BasicLauncher() {
|
||||
for (var entry : Splitter.on(File.pathSeparatorChar).split(System.getProperty("java.class.path"))) {
|
||||
var path = Paths.get(entry);
|
||||
if (Files.exists(path)) classpath.add(LoaderUtil.normalizeExistingPath(path));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToClassPath(Path path, String... allowedPrefixes) {
|
||||
classpath.add(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAllowedPrefixes(Path path, String... prefixes) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValidParentClassPath(Collection<Path> paths) {
|
||||
throw new UnsupportedOperationException("setValidParentClassPath");
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnvType getEnvironmentType() {
|
||||
return EnvType.CLIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClassLoaded(String name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadIntoTarget(String name) {
|
||||
throw new UnsupportedOperationException("loadIntoTarget");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getTargetClassLoader() {
|
||||
return Thread.currentThread().getContextClassLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InputStream getResourceAsStream(String name) {
|
||||
return BasicLauncher.class.getClassLoader().getResourceAsStream(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] getClassByteArray(String name, boolean runTransformers) throws IOException {
|
||||
try (var stream = BasicLauncher.class.getClassLoader().getResourceAsStream(LoaderUtil.getClassFileName(name))) {
|
||||
if (stream == null) return null;
|
||||
return ByteStreams.toByteArray(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest(Path originPath) {
|
||||
throw new UnsupportedOperationException("getManifest");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDevelopment() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntrypoint() {
|
||||
throw new UnsupportedOperationException("getEntrypoint");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTargetNamespace() {
|
||||
return "named";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Path> getClassPath() {
|
||||
return classpath;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ClassTransformer implements ClassFileTransformer {
|
||||
private final IMixinTransformer transformer;
|
||||
private final Set<String> definedClasses = new HashSet<>();
|
||||
private final Set<String> generatedClasses;
|
||||
|
||||
private ClassTransformer(IMixinTransformer transformer) {
|
||||
this.transformer = transformer;
|
||||
|
||||
try {
|
||||
// As we can't hook into classloading itself, we need to track all the classes Mixin has generated. Yes,
|
||||
// this is nasty.
|
||||
var syntheticRegistryField = transformer.getClass().getDeclaredField("syntheticClassRegistry");
|
||||
syntheticRegistryField.setAccessible(true);
|
||||
var syntheticRegistry = syntheticRegistryField.get(transformer);
|
||||
|
||||
var classesField = syntheticRegistry.getClass().getDeclaredField("classes");
|
||||
classesField.setAccessible(true);
|
||||
@SuppressWarnings("unchecked") var classes = (Map<String, ?>) classesField.get(syntheticRegistry);
|
||||
generatedClasses = classes.keySet();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized @Nullable byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
|
||||
var name = className.replace('/', '.');
|
||||
var transformed = FabricTransformer.transform(true, EnvType.CLIENT, name, bytes);
|
||||
transformed = transformer.transformClassBytes(name, name, transformed);
|
||||
|
||||
// Keep track of all generated classes that we've seen, and define any new ones. We use ByteBuddy to inject
|
||||
// the new class definitions, as doing it ourselves is hard.
|
||||
if (generatedClasses.size() > definedClasses.size()) {
|
||||
var toDefine = generatedClasses.stream().filter(definedClasses::add).collect(Collectors.toUnmodifiableMap(
|
||||
genName -> genName.replace('/', '.'),
|
||||
genName -> transformer.generateClass(MixinEnvironment.getDefaultEnvironment(), genName)
|
||||
));
|
||||
|
||||
LOG.info("Defining {}", toDefine.keySet());
|
||||
try {
|
||||
new ClassInjector.UsingReflection(loader).injectRaw(toDefine);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to define {}", className, e);
|
||||
}
|
||||
}
|
||||
|
||||
return transformed == bytes ? null : transformed;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user