TestKit somehow excludes compileOnly dependencies

Hi guys,

Gradle Version: 3.1
Operating System and JVM version: Windows / JDK 8

Background: the sonarqube plugin extract some information from other plugins configuration. For Android projects, we are using Android plugin APIs to get build configuration. Still we don’t want our plugin to require a dependency on Android plugin so we have declared it as “compileOnly” and put all Android specific code in a dedicated class that is only used when the project our plugin is applied on is an Android project. This way we can assume that Android plugin is in the classpath and avoid ClassNotFound exception at runtime.

It works fine with real projects.

Now I’m trying to migrate our home made integration tests to use Gradle TestKit. But then I’m getting class not found exception on Android test project.

For example here is the beginning of sample project build script:

buildscript {
  repositories {
    mavenCentral()
    mavenLocal()
    jcenter()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:2.2.0'
  }
}

plugins {
  id 'org.sonarqube'
}

apply plugin: 'com.android.application'

Still, running “sonarqube” task on this project with TestKit produces:

java.lang.NoClassDefFoundError: com/android/build/gradle/api/BaseVariant
	at org.sonarqube.gradle.SonarQubePlugin.lambda$null$6(SonarQubePlugin.java:119)
	at org.sonarqube.gradle.SonarQubePlugin.lambda$createTask$8(SonarQubePlugin.java:127)
	at org.gradle.util.GUtil.uncheckedCall(GUtil.java:401)
	at org.gradle.api.internal.tasks.DefaultTaskDependency.visitDependencies(DefaultTaskDependency.java:77)
[...]

Changing the dependency from compileOnly to compile allows the test to pass, but that’s not acceptable.

Does TestKit support building sample projects suing other (non core) plugins than the plugin under test? If yes, are dependencies properly isolated? As a wild guess it seems that dependencies of plugin under test somehow interfere with sample project own dependencies.

++

Julien

1 Like

If you declare the dependency compileOnly, then it won’t be there at (test-)runtime. If you want to also use it in tests, you can add it as a testRuntime dependency.

You could create your own “optionalPlugins” configuration and let both compileOnly and testRuntime extend from that.

1 Like

My understanding was that TestKit should fork a new Gradle process to analyze the sample project. Here the sample project itself declares a dependency on android plugin. Why should the plugin under test also declare the android plugin as dependency?

Just a guess here but this might be happening

  1. Buildscript classloader inherits from (delegates to) bootstrap classloader
  2. Plugin under test loaded by bootstrap classloader
  3. Android plugin loaded by buildscript classloader
  4. Classes loaded by bootstrap classloader can’t see classes loaded by buildscript classloader
  5. This is different to normal usage where all classes are loaded by buildscript classloader

TestKit injects the plugin’s testRuntime classpath above the buildscript classloader of the example project. So what you would do is:

  • add the optional plugin as a testRuntime dependency
  • remove the buildscript block from the example project in your test
  • just use apply plugin: 'myOptionalPlugin

That’s not very convenient (especially if I want to test sample projects using different versions of the ‘myOptionalPlugin’). But anyway thanks for the answer.

If the injected classpath approach does not work well for you, you can also go with the approach that was common before the classpath injection feature:

Automatically install the plugin into a local file repo before the test and point your example projects to that local file repo to resolve your plugin under development. That gives you more control over which versions of other dependencies your examples use.