FabricMC / fabric-loader

Fabric's mostly-version-independent mod loader.
Apache License 2.0
625 stars 264 forks source link

ClasspathModCandidateFinder AssertionError (fabric-loader-junit) #987

Open almic opened 3 days ago

almic commented 3 days ago

Problem

Using the fabric-loader-junit test runner fails to instantiate the FabricLoaderLauncherSessionListener, caused by an assert added very recently here: https://github.com/FabricMC/fabric-loader/blob/f12e9fd2de9f05758523c2bd3be521037f7c5cf4/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java#L57-L58

Versions

From my gradle.properties

fabric_version=0.77.0+1.18.2
loader_version=0.16.5
minecraft_version=1.18.2
yarn_mappings=1.18.2+build.4

My Fix

The plugin builds fine if I don't include the useJUnitPlatform() for the test task. However, I fixed this by simply changing to regular JUnit dependencies instead of using the fabric-loader-junit in build.gradle:

dependencies {
    // testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}"
    testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.2"
    testImplementation "org.junit.jupiter:junit-jupiter-engine:5.4.2"
}

With this change, my tests run normally. I do not currently use any Minecraft APIs in any of my tests, but I would think that any tests that need them would fail to run properly.

I'm unable to know without modifying the loader source and recompiling it myself, but I suspect it may be some recent change with the use of the assert. I'm on Windows 10.

Suggestion

Add an AssertionError to either of these lines to catch the assert, and provide a more helpful error message about what went wrong. I haven't been able to determine what the problem actually is, so it could very well be something wrong with my project setup. However, it's very simple and almost mirrors exactly the template project I downloaded from the fabricmc website with Minecraft 1.18.2 as the version.

https://github.com/FabricMC/fabric-loader/blob/f12e9fd2de9f05758523c2bd3be521037f7c5cf4/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java#L66-L68

https://github.com/FabricMC/fabric-loader/blob/f12e9fd2de9f05758523c2bd3be521037f7c5cf4/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java#L70-L72

At the moment, this is an unhandled exception and logs an error for every single test class that exists.

Here's the full stack trace of error:

org.gradle.api.internal.tasks.testing.TestSuiteExecutionException: Could not start Gradle Test Executor 15.
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.startProcessing(SuiteTestClassProcessor.java:44)
    at java.base@21.0.4/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base@21.0.4/java.lang.reflect.Method.invoke(Method.java:580)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    at jdk.proxy1/jdk.proxy1.$Proxy2.startProcessing(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$1.run(TestWorker.java:161)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:119)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:66)
    at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: java.util.ServiceConfigurationError: org.junit.platform.launcher.LauncherSessionListener: Provider net.fabricmc.loader.impl.junit.FabricLoaderLauncherSessionListener could not be instantiated
    at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:586)
    at java.base/java.util.ServiceLoader$ProviderImpl.newInstance(ServiceLoader.java:813)
    at java.base/java.util.ServiceLoader$ProviderImpl.get(ServiceLoader.java:729)
    at java.base/java.util.ServiceLoader$3.next(ServiceLoader.java:1403)
    at java.base/java.lang.Iterable.forEach(Iterable.java:74)
    at org.junit.platform.launcher.core.LauncherFactory.createLauncherSessionListener(LauncherFactory.java:169)
    at org.junit.platform.launcher.core.LauncherFactory.lambda$openSession$0(LauncherFactory.java:101)
    at org.junit.platform.launcher.core.DefaultLauncherSession.<init>(DefaultLauncherSession.java:52)
    at org.junit.platform.launcher.core.LauncherFactory.openSession(LauncherFactory.java:100)
    at org.junit.platform.launcher.core.LauncherFactory.openSession(LauncherFactory.java:82)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$BackwardsCompatibleLauncherSession.open(JUnitPlatformTestClassProcessor.java:318)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.createTestExecutor(JUnitPlatformTestClassProcessor.java:81)
    at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.startProcessing(AbstractJUnitTestClassProcessor.java:50)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.startProcessing(SuiteTestClassProcessor.java:42)
    ... 16 more
Caused by: java.lang.AssertionError
    at net.fabricmc.loader.impl.discovery.ClasspathModCandidateFinder.findCandidates(ClasspathModCandidateFinder.java:58)
    at net.fabricmc.loader.impl.discovery.ModDiscoverer.discoverMods(ModDiscoverer.java:109)
    at net.fabricmc.loader.impl.FabricLoaderImpl.setup(FabricLoaderImpl.java:216)
    at net.fabricmc.loader.impl.FabricLoaderImpl.load(FabricLoaderImpl.java:193)
    at net.fabricmc.loader.impl.launch.knot.Knot.init(Knot.java:146)
    at net.fabricmc.loader.impl.junit.FabricLoaderLauncherSessionListener.<init>(FabricLoaderLauncherSessionListener.java:48)
    at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486)
    at java.base/java.util.ServiceLoader$ProviderImpl.newInstance(ServiceLoader.java:789)
    ... 28 more
sfPlayer1 commented 3 days ago

The assert verifies the assumption that the path is absolute and normalized. It'll have to be determined why it isn't. If getCodeSource legitimately yields a non-normalized Path, the normalization has to be added to the regular execution and the check+assert removed, otherwise the cause for it not being returned normalized has to be fixed.

sfPlayer1 commented 3 days ago

I'm pretty sure that in this case we just can't rely on the class path code sources being normalized since they may come from the app class loader. I'll adjust it accordingly.