How to execute a task in unit test for custom plugin

I have unit tests for my custom plugins that use projects created using ProjectBuilder. I can apply my plugins and confirm that the configuration they provide has occurred. Now I would like to actually execute one or more tasks, but cannot figure out how to do that.

If I try this:

Project project = ProjectBuilder.builder().withName("testProject").build()
Project subProject = ProjectBuilder.builder().withName("testSubProject").withParent(project).build()
project.apply plugin: 'my-custom-plugin-that-applies-the-java-plugin-to-subprojects'
... various asserts ...
subProject.tasks["build"].execute()

the result of the call to execute() is:

18:01:09.185 [main] DEBUG o.g.a.i.t.e.ExecuteAtMostOnceTaskExecuter - Starting to execute task ':testSubProject:build'
18:01:09.185 [main] INFO
o.g.a.i.t.e.SkipTaskWithNoActionsExecuter - Skipping task ':testSubProject:build' as it has no actions.
18:01:09.189 [main] DEBUG o.g.a.i.t.e.ExecuteAtMostOnceTaskExecuter - Finished executing task ':testSubProject:build'

What step am I missing such that ‘build’ has no actions?

If I change to execute ‘build’ on the parent project:

project.tasks["build"].execute()

then the task is not even found:

org.gradle.api.UnknownTaskException: Task with name 'build' not found in root project 'testProject'.
    at org.gradle.api.internal.tasks.DefaultTaskCollection.createNotFoundException(DefaultTaskCollection.java:96)
    at org.gradle.api.internal.DefaultNamedDomainObjectCollection.getByName(DefaultNamedDomainObjectCollection.java:183)
    at org.gradle.api.internal.DefaultNamedDomainObjectCollection.getAt(DefaultNamedDomainObjectCollection.java:195)
    at org.gradle.api.internal.tasks.DefaultTaskCollection.getAt(DefaultTaskCollection.java:35)
    at org.gradle.api.tasks.TaskCollection$getAt.call(Unknown Source)

yet at the command line I can execute ‘gradle build’ on a top-level project that is configured using my plugins.

Thanks, Dan

‘ProjectBuilder’ is meant for lower-level tests that do not execute a build. It’s a good fit for testing how plugins configure the object model. More comprehensive testing support will be provided in the future. In the meantime you can, with some effort, use ‘GradleLauncher’ to write tests that execute a build. I believe you can find some more information by searching the forum and mailing list.

Yep, thanks. I looked at GradleLauncher, which pointed me to the Tooling API and GradleConnector. The examples in the Gradle distribution (samples/toolingAPI) were useful and I got a basic multi-project build with my plugin up and running.

GradleConnector connector = GradleConnector.newConnector()
        connector.forProjectDirectory(new File("src/test/resources/basic-project"))
        ProjectConnection connection = connector.connect()
        try {
            BuildLauncher launcher = connection.newBuild()
            launcher.forTasks("build")
            launcher.run()
        } finally {
            connection.close()
        }

I’m running into an issue with the “clean” task of my overall plugins build. This may be because I’m using the tooling API that isn’t exactly meant for my purpose. But I’ll explain in case there is something easy to suggest.

My unit test has a sample project directory with a build.gradle. That script has a buildscript block to include my plugins, and it does this by declaring a repositories.flatDir reference to build/libs where my plugins JAR is assembled. The unit test starts, fires up the daemon, and successfully executes the sample build.

The problem occurs when I run clean on my overall plugins project. I get an exception because the daemon started by the unit test is holding a reference to the JAR in the build/libs directory that is getting cleaned up.

I can proceed by running “gradle --stop” and then running clean, but that is only a workaround. Is there a way to not use the daemon? I don’t see anything obvious on GradleConnector. Or maybe I should not be referencing my plugins via flatDir. I’m using 1.0-milestone-8a.

The problem occurs when I run clean on my overall plugins project. I get an exception because the daemon started by the unit test is holding a reference to the JAR in the build/libs directory that is getting cleaned up.

Can you post the exception you get please.

Sure. Stack trace:

$ gradle --stacktrace
:clean
  FAILURE: Build failed with an exception.
  * What went wrong:
Execution failed for task ':clean'.
> java.io.IOException: Unable to delete file: C:\RUP\workspaces\general\rup-scm\gradle-plugins\build\libs\gradle-plugins-1.0.42-SNAPSHOT.jar
  * Try:
Run with --info or --debug option to get more log output.
  * Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':clean'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:68)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:34)
        at org.gradle.api.internal.changedetection.CacheLockHandlingTaskExecuter$1.run(CacheLockHandlingTaskExecuter.java:34)
        at org.gradle.cache.internal.DefaultCacheAccess$2.create(DefaultCacheAccess.java:200)
        at org.gradle.cache.internal.DefaultCacheAccess.longRunningOperation(DefaultCacheAccess.java:172)
        at org.gradle.cache.internal.DefaultCacheAccess.longRunningOperation(DefaultCacheAccess.java:198)
        at org.gradle.cache.internal.DefaultPersistentDirectoryStore.longRunningOperation(DefaultPersistentDirectoryStore.java:111)
        at org.gradle.api.internal.changedetection.DefaultTaskArtifactStateCacheAccess.longRunningOperation(DefaultTaskArtifactStateCacheAccess.java:83)
        at org.gradle.api.internal.changedetection.CacheLockHandlingTaskExecuter.execute(CacheLockHandlingTaskExecuter.java:32)
        at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:41)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:52)
        at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:42)
        at org.gradle.api.internal.AbstractTask.executeWithoutThrowingTaskFailure(AbstractTask.java:250)
        at org.gradle.execution.DefaultTaskGraphExecuter.executeTask(DefaultTaskGraphExecuter.java:192)
        at org.gradle.execution.DefaultTaskGraphExecuter.doExecute(DefaultTaskGraphExecuter.java:177)
        at org.gradle.execution.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:83)
        at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:36)
        at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:61)
        at org.gradle.execution.DefaultBuildExecuter.access$200(DefaultBuildExecuter.java:23)
        at org.gradle.execution.DefaultBuildExecuter$2.proceed(DefaultBuildExecuter.java:67)
        at org.gradle.api.internal.changedetection.TaskCacheLockHandlingBuildExecuter$1.run(TaskCacheLockHandlingBuildExecuter.java:31)
        at org.gradle.cache.internal.DefaultCacheAccess$1.create(DefaultCacheAccess.java:111)
        at org.gradle.cache.internal.DefaultCacheAccess.useCache(DefaultCacheAccess.java:126)
        at org.gradle.cache.internal.DefaultCacheAccess.useCache(DefaultCacheAccess.java:109)
        at org.gradle.cache.internal.DefaultPersistentDirectoryStore.useCache(DefaultPersistentDirectoryStore.java:103)
        at org.gradle.api.internal.changedetection.DefaultTaskArtifactStateCacheAccess.useCache(DefaultTaskArtifactStateCacheAccess.java:79)
        at org.gradle.api.internal.changedetection.TaskCacheLockHandlingBuildExecuter.execute(TaskCacheLockHandlingBuildExecuter.java:29)
        at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:61)
        at org.gradle.execution.DefaultBuildExecuter.access$200(DefaultBuildExecuter.java:23)
        at org.gradle.execution.DefaultBuildExecuter$2.proceed(DefaultBuildExecuter.java:67)
        at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
        at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:61)
        at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:54)
        at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:155)
        at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:110)
        at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:78)
        at org.gradle.launcher.cli.RunBuildAction.execute(RunBuildAction.java:42)
        at org.gradle.launcher.cli.RunBuildAction.execute(RunBuildAction.java:28)
        at org.gradle.launcher.exec.ExceptionReportingAction.execute(ExceptionReportingAction.java:32)
        at org.gradle.launcher.exec.ExceptionReportingAction.execute(ExceptionReportingAction.java:21)
        at org.gradle.launcher.cli.CommandLineActionFactory$WithLoggingAction.execute(CommandLineActionFactory.java:238)
        at org.gradle.launcher.cli.CommandLineActionFactory$WithLoggingAction.execute(CommandLineActionFactory.java:222)
        at org.gradle.launcher.Main.doAction(Main.java:48)
        at org.gradle.launcher.exec.EntryPoint$1.execute(EntryPoint.java:53)
        at org.gradle.launcher.exec.EntryPoint$1.execute(EntryPoint.java:51)
        at org.gradle.launcher.exec.Execution.execute(Execution.java:28)
        at org.gradle.launcher.exec.EntryPoint.run(EntryPoint.java:39)
        at org.gradle.launcher.Main.main(Main.java:39)
        at org.gradle.launcher.ProcessBootstrap.runNoExit(ProcessBootstrap.java:51)
        at org.gradle.launcher.ProcessBootstrap.run(ProcessBootstrap.java:33)
        at org.gradle.launcher.GradleMain.main(GradleMain.java:24)
Caused by: org.gradle.api.UncheckedIOException: java.io.IOException: Unable to delete file: C:\RUP\workspaces\general\rup-scm\gradle-plugins\build\libs\gradle-plugins-1.0.42-SNAPSHOT.jar
        at org.gradle.util.GFileUtils.deleteDirectory(GFileUtils.java:234)
        at org.gradle.api.internal.file.copy.DeleteActionImpl.delete(DeleteActionImpl.java:50)
        at org.gradle.api.internal.file.DefaultFileOperations.delete(DefaultFileOperations.java:128)
        at org.gradle.api.internal.project.AbstractProject.delete(AbstractProject.java:720)
        at org.gradle.api.tasks.Delete.clean(Delete.java:40)
        at org.gradle.api.internal.BeanDynamicObject$MetaClassAdapter.invokeMethod(BeanDynamicObject.java:196)
        at org.gradle.api.internal.BeanDynamicObject.invokeMethod(BeanDynamicObject.java:102)
        at org.gradle.api.internal.CompositeDynamicObject.invokeMethod(CompositeDynamicObject.java:93)
        at org.gradle.api.tasks.Delete_Decorated.invokeMethod(Unknown Source)
        at org.gradle.util.ReflectionUtil.invoke(ReflectionUtil.groovy:23)
        at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$4.execute(AnnotationProcessingTaskFactory.java:150)
        at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$4.execute(AnnotationProcessingTaskFactory.java:145)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:495)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:484)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:60)
        ... 54 more
Caused by: java.io.IOException: Unable to delete file: C:\RUP\workspaces\general\rup-scm\gradle-plugins\build\libs\gradle-plugins-1.0.42-SNAPSHOT.jar
        at org.apache.commons.io.FileUtils.forceDelete(FileUtils.java:1390)
        at org.apache.commons.io.FileUtils.cleanDirectory(FileUtils.java:1044)
        at org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.java:977)
        at org.apache.commons.io.FileUtils.forceDelete(FileUtils.java:1381)
        at org.apache.commons.io.FileUtils.cleanDirectory(FileUtils.java:1044)
        at org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.java:977)
        at org.gradle.util.GFileUtils.deleteDirectory(GFileUtils.java:232)
        ... 68 more

Version info:

$ gradle --version
  ------------------------------------------------------------
Gradle 1.0-milestone-8a
------------------------------------------------------------
  Gradle build time: Monday, February 20, 2012 4:00:18 PM UTC
Groovy: 1.8.4
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.7.0_01 (Oracle Corporation 21.1-b02)
OS: Windows 7 6.1 amd64

Thanks… this is half of what I needed. The other half is how to get my current project’s classpath on the classpath of the “project under test”?

Here an excerpt of my build.gradle. The repositories block has an ugly hard-coded flatDir, but that was the only approach I could think of.

buildscript {
    repositories {
        maven { /* our company repo */ }
                  // need to get up to the working directory of gradle-plugins build
        flatDir ( dirs: "../../../../build/libs" )
      }
    dependencies {
        classpath( /* coordinates for our custom plugins */ )
                  /* this is a dependency of our custom plugins, which seem to require explicit declaration */
        // explicit declaration seems to be required when using tooling API
        // this is handled as a transitive dependency when using our plugins in regular build scripts
        classpath("org.apache.commons:commons-lang3:3.1")
    }
}
  apply plugin: 'my-custom-plugin'

This file is located at [my-plugins-project]/src/test/resources/basic-project/build.gradle

Of course, this approach has the issue discussed above - exception thrown when running clean.