Testkit Load plugins other than plugin under test

Hello,

I am trying to test a “convention” plugin which configures another the Kotlin gradle plugin when it is applied to a project.

The plugin under test, our “convention” plugin, works fine. The issue we are finding however is that we cannot use Testkit’s withPluginClasspathfunction and continue to use other plugins in the mock gradle project we create.

We dynamically build the gradle.build.kts file when running our tests and we start the gradle runner and write the build.gradle.kts file for the run. This works fine when the plugin under test is loaded (and we have success with other plugins as well) using the plugins syntax and NO OTHER plugin is applied. IE we can successfully test our own plugin logic fine. However, if we attempt to add another plugin in our dynamically created gradle.build.kts file, (the kotlin plugin but any plugin causes the problem too) then the external (not SUT) plugin cannot be found when the gradle runner starts.

We did find a workaround by publishing the plugin and then loading it with an ID but that is not ideal.

Is it possible to load plugins other than whats on the class path while using the withPluginClasspath option?

withPluginClasspath iirc adds your plugin’s runtime classpath to a parent class loader, similar to dependencies in buildSrc. If your plugin then wants to use some plugin classes that are added by the target build those are on a descendant class loader and cannot be found by your plugin, so you would need to add it to the injected classpath to make it work.

You can for example have an extra configuration in your build that contains the things you want to add to that classpath, then add this - transformed to a path - as system property to the test process using a CommandLineArgumentProvider registered as jvmArgumentProvider (so that you don’t need to do the resolution at configuration time and so that the tests get out-of-date if those files change). Then where you use the TestKit GradleRunner, you could for example do

def runner = GradleRunner
        .create()
        .forwardOutput()
        .withPluginClasspath()
runner.withPluginClasspath(runner.pluginClasspath + additionalClasspathExtractedFromTheSystemProperty)

The parameterless withPluginClasspath does the usual magic, then the second line reuses that TestKit set and adds your additional stuff to it so that they end up on the same classpath and so your plugin classes can find the 3rd-party plugin classes at runtime.

Thanks for the reply.

For more context on the error. We will generate a build.gradle.kts file like so (off the top of my head, not exact syntax but close enough i hope to give you the idea)

plugins {
  id("org.jetbrains.kotlin.jvm") version "x.x.x"
  id("plugin.under.test")
}

and we will generate a settings.gradle.kts file like so

pluginManagement {
  repositories {
    mavenCentral()
    gradlePluginPortal()
  }
}

dependencyResolutionManagement {
  repositories {
    mavenCentral()
  }
}

and when test kit runs the gradle build we get the errorno plugin found with the id [“org.jetbrains.kotlin.jvm”]. There is also a bunch of meta space(?) errors with the build cache. I don’t believe this to be the issue cause i delete the cache all the time and it still fails.

The build errors all like this:

What went wrong:
Error resolving plugin [id: ‘org.jetbrains.kotlin.jvm’, version: ‘2.1.21’]
Multiple build operations failed.
Could not read workspace metadata from /private/var/folders/82/vjkj7nhx3ws_k9sd22wl8_x00000gp/T/.gradle-test-kit/caches/8.12/transforms/8b3bae3d496ab5ede56860f97a389836/metadata.bin
Could not isolate parameters org.gradle.api.internal.initialization.transform.MergeInstrumentationAnalysisTransform$Parameters_Decorated@78495e9d of artifact transform MergeInstrumentationAnalysisTransform
``

In the end we are avoiding withPluginClasspath and publishing to a flat dir before tests to work around the error.

Thanks again for your time!