Create custom Test task with Gradle 7 throws exception

Hello, I hope someone can help me. I try to create a custom Test task like

task abc {
	Test internalTest = task(type:Test) {
	}
	internalTest.executeTests()
}

In Gradle 6 this works and the internalTest task is executed. In Gradle 7 I get an exception.

org.gradle.api.GradleScriptException: A problem occurred evaluating root project 'Project'.
	at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:93)
	at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.lambda$apply$0(DefaultScriptPluginFactory.java:133)
...
Caused by: java.util.NoSuchElementException: No value present
	at org.gradle.tooling.internal.provider.runner.TestTaskExecutionTracker.getTaskPath(TestTaskExecutionTracker.java:45)
	at org.gradle.tooling.internal.provider.runner.TestOperationMapper.toTestDescriptorForSuite(TestOperationMapper.java:89)
	at org.gradle.tooling.internal.provider.runner.TestOperationMapper.createDescriptor(TestOperationMapper.java:69)
	at org.gradle.tooling.internal.provider.runner.TestOperationMapper.createDescriptor(TestOperationMapper.java:44)
	at org.gradle.tooling.internal.provider.runner.ClientBuildEventGenerator$Enabled.accept(ClientBuildEventGenerator.java:193)
	at org.gradle.tooling.internal.provider.runner.ClientBuildEventGenerator.started(ClientBuildEventGenerator.java:79)
	at org.gradle.internal.operations.DefaultBuildOperationListenerManager$ProgressShieldingBuildOperationListener.started(DefaultBuildOperationListenerManager.java:114)
	at org.gradle.internal.operations.DefaultBuildOperationListenerManager$1.started(DefaultBuildOperationListenerManager.java:43)
	at org.gradle.api.internal.tasks.testing.operations.TestListenerBuildOperationAdapter.started(TestListenerBuildOperationAdapter.java:65)
	at jdk.internal.reflect.GeneratedMethodAccessor146.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)

Did someone know what the problem is?

Stefan

Well, there are at least two bad things with this short snippet.

  1. Why do you register a task in the configuration phase of another task? That does not make much sense and is bad practice.
  2. You should never try to “call” a task like you do with invoking executeTests. This will just execute the method that is called when the task would also be executed. But nothing else, no dependency tasks are executed, no pre-conditions are ensured, … And actually you do that during configuration time. “calling” a task is not something you do with Gradle.

Hello Björn, thanks for the answer and maybe you are right and the snippet is really not helpful.
Actually we are using the task to execute tests like this:

ext.internalTestingWithProperties = { properties, String... testClasses ->

    Test internalTest = task(type:Test) {
        jvmArgs '-XX:+HeapDumpOnOutOfMemoryError'
    }

    if ( properties.forkEvery ) {
        internalTest.forkEvery = properties.forkEvery
        properties.remove( 'forkEvery' )
    }
    internalTest.ignoreFailures = true
    internalTest.scanForTestClasses = false
    internalTest.include testClasses
    internalTest.systemProperties  = properties

    internalTest.setTestClassesDirs( files( "${buildDir}/internalTesting/" ) )
    internalTest.classpath = files( "${buildDir}/internalTesting/" )
    internalTest.classpath += project.configurations.inetTests
    internalTest.executeTests()
    println '\tTesting done.'
}

task runRDCTest() {
    doLast {
        internalTestingWithProperties( [
            "test.reports" : "${test_root_resources}",
        ],  "**/rdc/*Tests.class" )
    }
}

With Gradle 6 this works fine.

Well, it doesn’t change much.
Calling the task action of a task is not something that is supported or maintained and it can break anytime as you can see.
Just don’t do it. :slight_smile:

Actually also using ext is almost always a code smell and should be avoided but done properly. :slight_smile:
If it is something that is local to the current build script, just define it as local variable or method.
If you need it at other places as property of the thing where you add the ext property, then in most cases it is better to add a proper extension where you then also have type-safety, especially if ever using Kotlin DSL.

What you actually want is something like this, judging from your new snippet:

def configureInternalTestTask(Test task) {
    configure(task) {
        jvmArgs '-XX:+HeapDumpOnOutOfMemoryError'
        ignoreFailures = true
        scanForTestClasses = false
        testClassesDirs = files("${buildDir}/internalTesting/")
        classpath = files("${buildDir}/internalTesting/")
        classpath += configurations.inetTests

        doLast {
            println '\tTesting done.'
        }
    }
}

task runRDCTest(type: Test) {
    configureInternalTestTask(it)
    forkEvery = 3
    include '**/rdc/*Tests.class'
    systemProperty 'test.reports', test_root_resources
}