Unit test plugin's afterEvaulate

Lately I started writing my first gradle plugin (gradle 6.4.1) and used gradle init with selection 421 (Gradle Plugin, Java, Groovy) as starting point.

My plugin features an extension (similar to the Java plugin’s sourceSets) with a NamedDomainObjectContainer in it. For each container entry a task will be created like apply${containerName}. Task creation is done the in the project’s afterEvaluate callback:

aProject.afterEvaluate((p) -> {
  final MyExtension myExtension = p.getExtensions().getByType(MyExtension.class);
  myExtension.getConfigurations().forEach((c) -> registerApplyTask(p, c));
});

This works pretty good in real life.

Now I want to unit test the task creation. I want to assert that for each entry in the extension there is a corresponding task. But as my tasks are generated dynamically I can’t simply follow the sample unit test created by gradle init. I somehow need to trigger the evaluation of the project, to get the afterEvaluate callback executed.

Is there any way to do this?

Basically the same question has been posted on SO some month ago, but with no answers:

Maybe this forum has more expertise on gradle :slight_smile:

Hello you should be able to use all method on container which is called during adding configuration to container, so you dont have to use afterEvaluate. Example is here:
https://guides.gradle.org/implementing-gradle-plugins/
Otherwise only way I found to evaluate project is this a little “hacky” solution: cast project to DefaultProject and call evaluate on it.

Not answering your question, but it’s best to avoid afterEvaluate {}. What type of object is myExtension.getConfigurations()? I’m guessing it implements DomainObjectCollection which means that you could use one of the “live” methods (eg all(Closure) or matching(Closure)) instead of afterEvaluate.

Thanks for the answers.

configurations is a NamedDomainObjectContainer. Using

extension.getConfigurations().all((c) -> {
    registerApplyTask(aProject, c);
});

solves this issue.

Sadly I still need to use afterEvaluate for a second type of task that works on plain properties of the extension. I suppose the right way to solve this would be to make use of lazy-configuration and Property?! Actually I found the documentation on this is to be not very intuitive especially all the examples are in Groovy and Kotlin only (despite the recommendation to use Java or Kotlin for plugin development).

Maybe someone could explain how something like this would be done correctly using lazy-configruation:

public class MyExtension { 
  public String property = "value";
  private final NamedDomainObjectContainer<Configuration> configurations;

  @Inject
  public WorkspaceExtension(final ObjectFactory objectFactory) {
    configurations = objectFactory.domainObjectContainer(WorkspaceConfiguration.class);
  }
}

public abstract class MyTask extends DefaultTask {

  @Input
  private final String property = "value";

  public abstract void setProperty(final String aValue);
}

public class MyPlugin implements Plugin<Project> {

  @Override
  public void apply(final Project aProject) {
    aProject.afterEvaluate((p) -> {
      final MyExtension extension = aProject.getExtensions().create("extension", MyExtension.class);
      p.getTasks().register("myTask", MyTask.class).configure((t) -> {
        t.setProperty(workspaceExtension.property);
      });
    });
  }

I simplified the names in the code snippet. They are actually not that generic in the real plugin code…

TIA

It’d look something like

public class MyTask extends DefaultTask {
    @Input
    private final Property<String> property = getProject().getObjects().property(String)

    public Property getProperty() {
        return this.property;
    }

    @TaskAction
    public void doStuff() {
        String propertyValue = property.get();
        // do stuff with propertyValue 
    }
}

public class MyPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        MyExtension extension = project.getExtensions().create("extension", MyExtension.class);
        project.getTasks().register("myTask", MyTask.class).configure(task -> {
            Callable<String> callable = () -> extension.property;
            Provider<String> provider = project.getProviders().provider(callable);
            task.getProperty().set(provider);
        });
    }
}

Thanks.

I now moved the lazy-configuration stuff to the extension and got everything working again:

public class MyExtension { 
  public final Property<String> property;
  public final NamedDomainObjectContainer<Configuration> configurations;

  @Inject
  public WorkspaceExtension(final ObjectFactory objectFactory) {
    property = objectFactory.property(String.class).convention("value");
    configurations = objectFactory.domainObjectContainer(Configuration.class);
  }
}

public abstract class MyTask extends DefaultTask {

  @Input
  private final Property<String> property = getProject().getObjects().property(String.class);

  public Property<String> getProperty() {
    return property;
  }
}

And finally the apply method:

public class MyPlugin implements Plugin<Project> {

  @Override
  public void apply(final Project aProject) {
    final MyExtension extension = aProject.getExtensions().create("extension", MyExtension.class);
    aProject.getTasks().register("myTask", MyTask.class).configure((t) -> {
      t.getProperty().set(extension.property);
    });
    extension.configurations.all((c) -> {
      aProject.getTasks().register("mySubTask" + c.getName(), MySubTask.class).configure((t) -> {
        //t.dependsOn(aProject.findTasksByName("myTask", false)); // throws Exception in test
        t.dependsOn(aProject.getTasks().getByName("myTask")); // works
        t.getProperty().set(extension.property);
      });
    });
  }
}

I first used Project.findTaskByName in the configuration action. While this worked in a test project it threw an Exception in a Unit test that is doing

assertNotNull(project.getTasks().findByName("mySubTaskTest"),
        "Task 'mySubTaskTest' should have been added");

So I switched to Project.getTasks().getByName. I’m however unsure about the exact exception cause and why getTasks().getByName is working while findTaskByName is not…

See Stacktrace below:

org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreationException: Could not create task ':mySubTaskTest'.
	at org.gradle.api.internal.tasks.DefaultTaskContainer.taskCreationException(DefaultTaskContainer.java:719)
	at org.gradle.api.internal.tasks.DefaultTaskContainer.access$600(DefaultTaskContainer.java:77)
	at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider.domainObjectCreationException(DefaultTaskContainer.java:711)
	at org.gradle.api.internal.DefaultNamedDomainObjectCollection$AbstractDomainObjectCreatingProvider.tryCreate(DefaultNamedDomainObjectCollection.java:948)
	at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider.access$1401(DefaultTaskContainer.java:658)
	at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider$1.run(DefaultTaskContainer.java:684)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
	at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider.tryCreate(DefaultTaskContainer.java:680)
	at org.gradle.api.internal.DefaultNamedDomainObjectCollection$AbstractDomainObjectCreatingProvider.calculateOwnValue(DefaultNamedDomainObjectCollection.java:929)
	at org.gradle.api.internal.provider.AbstractMinimalProvider.getOrNull(AbstractMinimalProvider.java:103)
	at org.gradle.api.internal.DefaultNamedDomainObjectCollection.findByName(DefaultNamedDomainObjectCollection.java:295)
	at org.gradle.api.internal.tasks.DefaultTaskContainer.findByName(DefaultTaskContainer.java:562)
	at org.gradle.api.internal.tasks.DefaultTaskContainer.findByName(DefaultTaskContainer.java:76)
	at de.dpeger.gradle.MyPluginTest.pluginRegistersATask(MyPluginTest.java:44)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:567)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:124)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:583)
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:719)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:989)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
	at org.testng.TestRunner.privateRun(TestRunner.java:648)
	at org.testng.TestRunner.run(TestRunner.java:505)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
	at org.testng.SuiteRunner.run(SuiteRunner.java:364)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:84)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1208)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1137)
	at org.testng.TestNG.runSuites(TestNG.java:1049)
	at org.testng.TestNG.run(TestNG.java:1017)
	at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.runTests(TestNGTestClassProcessor.java:141)
	at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.stop(TestNGTestClassProcessor.java:90)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:567)
	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 com.sun.proxy.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:132)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:567)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:413)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.base/java.lang.Thread.run(Thread.java:830)
Caused by: org.gradle.api.ProjectConfigurationException: A problem occurred configuring root project 'test'.
	at org.gradle.configuration.project.LifecycleProjectEvaluator.wrapException(LifecycleProjectEvaluator.java:75)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.addConfigurationFailure(LifecycleProjectEvaluator.java:68)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.access$600(LifecycleProjectEvaluator.java:51)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject$1.run(LifecycleProjectEvaluator.java:104)
	at org.gradle.internal.Factories$1.create(Factories.java:26)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withMutableState(DefaultProjectStateRegistry.java:245)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withMutableState(DefaultProjectStateRegistry.java:226)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:91)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:63)
	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:708)
	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:144)
	at org.gradle.api.internal.project.DefaultProject$4.execute(DefaultProject.java:838)
	at org.gradle.api.internal.project.DefaultProject$4.execute(DefaultProject.java:834)
	at org.gradle.api.internal.project.DefaultProject.getTasksByName(DefaultProject.java:849)
	at de.dpeger.gradle.MyPlugin.lambda$registerApplyTask$2(MyPlugin.java:51)
	at org.gradle.api.internal.DefaultMutationGuard$2.execute(DefaultMutationGuard.java:42)
	at org.gradle.api.internal.DefaultMutationGuard$2.execute(DefaultMutationGuard.java:42)
	at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction$1$1.run(DefaultCollectionCallbackActionDecorator.java:100)
	at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.reapply(DefaultUserCodeApplicationContext.java:60)
	at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction$1.run(DefaultCollectionCallbackActionDecorator.java:97)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
	at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction.execute(DefaultCollectionCallbackActionDecorator.java:94)
	at org.gradle.internal.ImmutableActionSet$SingletonSet.execute(ImmutableActionSet.java:225)
	at org.gradle.api.internal.DefaultDomainObjectCollection.doAdd(DefaultDomainObjectCollection.java:264)
	at org.gradle.api.internal.DefaultNamedDomainObjectCollection.doAdd(DefaultNamedDomainObjectCollection.java:113)
	at org.gradle.api.internal.DefaultDomainObjectCollection.add(DefaultDomainObjectCollection.java:258)
	at org.gradle.api.internal.DefaultNamedDomainObjectCollection$AbstractDomainObjectCreatingProvider.tryCreate(DefaultNamedDomainObjectCollection.java:944)
	... 66 more
Caused by: org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin [id 'org.gradle.build-init']
	at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:167)
	at org.gradle.api.internal.plugins.DefaultPluginManager.apply(DefaultPluginManager.java:136)
	at org.gradle.buildinit.plugins.internal.action.BuildInitAutoApplyAction.execute(BuildInitAutoApplyAction.java:26)
	at org.gradle.buildinit.plugins.internal.action.BuildInitAutoApplyAction.execute(BuildInitAutoApplyAction.java:22)
	at org.gradle.configuration.project.PluginsProjectConfigureActions.execute(PluginsProjectConfigureActions.java:47)
	at org.gradle.configuration.project.PluginsProjectConfigureActions.execute(PluginsProjectConfigureActions.java:25)
	at org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:35)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject$1.run(LifecycleProjectEvaluator.java:102)
	... 101 more
Caused by: org.gradle.api.internal.AbstractMutationGuard$IllegalMutationException: DefaultTaskContainer#register(String, Class, Action) on task set cannot be executed in the current context.
	at org.gradle.api.internal.AbstractMutationGuard.createIllegalStateException(AbstractMutationGuard.java:39)
	at org.gradle.api.internal.AbstractMutationGuard.assertMutationAllowed(AbstractMutationGuard.java:27)
	at org.gradle.api.internal.DefaultDomainObjectCollection.assertMutable(DefaultDomainObjectCollection.java:452)
	at org.gradle.api.internal.tasks.DefaultTaskContainer.register(DefaultTaskContainer.java:378)
	at org.gradle.buildinit.plugins.BuildInitPlugin.apply(BuildInitPlugin.java:37)
	at org.gradle.buildinit.plugins.BuildInitPlugin.apply(BuildInitPlugin.java:33)
	at org.gradle.api.internal.plugins.ImperativeOnlyPluginTarget.applyImperative(ImperativeOnlyPluginTarget.java:43)
	at org.gradle.api.internal.plugins.RuleBasedPluginTarget.applyImperative(RuleBasedPluginTarget.java:51)
	at org.gradle.api.internal.plugins.DefaultPluginManager.addPlugin(DefaultPluginManager.java:181)
	at org.gradle.api.internal.plugins.DefaultPluginManager.access$300(DefaultPluginManager.java:51)
	at org.gradle.api.internal.plugins.DefaultPluginManager$AddPluginBuildOperation.run(DefaultPluginManager.java:276)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
	at org.gradle.api.internal.plugins.DefaultPluginManager$2.execute(DefaultPluginManager.java:159)
	at org.gradle.api.internal.plugins.DefaultPluginManager$2.execute(DefaultPluginManager.java:156)
	at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:49)
	at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:156)
	... 108 more

I can’t see a need for any finder methods here, you could just do

public class MyPlugin implements Plugin<Project> {

  @Override
  public void apply(Project project) {
    MyExtension extension = project.getExtensions().create("extension", MyExtension.class);
    TaskProvider<MyTask> myTaskProvider = project.getTasks().register("myTask", MyTask.class)
    myTaskProvider.configure(task -> task.getProperty().set(extension.property));
    extension.configurations.all(c -> {
      project.getTasks().register("mySubTask" + c.getName(), MySubTask.class).configure(task -> {
        task.dependsOn(myTaskProvider);
        task.getProperty().set(extension.property);
      });
    });
  }
}

Or more simply you could use task.dependsOn(String)

      project.getTasks().register("mySubTask" + c.getName(), MySubTask.class).configure(task -> {
        task.dependsOn("myTask");
        task.getProperty().set(extension.property);
      });
1 Like