Gradle creating a additional task for int tests - gradle

So I found a few tutorials on how to create a seperate task to run integration tests in gradle. My int tests are in a separate folder.. ie src/integrationTest
This is what one of them had set up, I just dont understand the line "val integrationTest by sourceSets.creating"? Is this creating a source set? Other examples have explicit creation of source set in the gradle file..
val integrationTest by sourceSets.creating
dependencies {
"integrationTestImpl"(project)
}
val integrationTestTask = tasks.register<Test>("integrationTest") {
description = "Runs the integration tests."
group = "verification"
testClassesDirs = integrationTest.output.classesDirs
classpath = integrationTest.runtimeClasspath
mustRunAfter(tasks.test)
}
tasks.check {
dependsOn(integrationTestTask)
}

Yes, it is creating a new source set named integrationTest.
Refer to this answer on Kotlin delegated properties. It is not specific to Gradle, but the Kotlin language itself. Gradle provides a variety of extensions to make the Kotlin DSL a rich experience compared to the Groovy DSL.

Related

Custom Configuration dependency declaration

I am trying to convert build.gradle to kotlin dsl. Using gradle 7.4.1.What the right way to declare custom configuration. For custom configuration like
configurations { grafana }
sourceSets { grafana }
and within dependencies block
grafanaImplementation "org.slf4j:slf4j-simple:1.7.36"
grafanaImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
grafanaRuntimeOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
While I am in kotlin-dsl I am doing
val grafana by configurations.creating
val grafanaSourceSet = sourceSets.create("grafana")
and within dependency block
grafana("org.slf4j:slf4j-simple:1.7.36")
grafana("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
When I try to put grafanaImplementation/ grafanaRuntimeOnly within kotlin dsl, it fails.
What is the equivalent of grafanaImplementation/ grafanaRuntimeOnly within kotlin dsl
Quick fix
When you do
val grafanaSourceSet = sourceSets.create("grafana")
behind the scenes Gradle will create the required configurations, grafanaImplementation, grafanaRuntimeOnly, etc, so you can use them without error like this:
val grafanaSourceSet = sourceSets.create("grafana")
dependencies {
"grafanaImplementation"("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
"grafanaRuntimeOnly"("org.slf4j:slf4j-simple:1.7.36")
}
This approach is more like how Groovy works - it basically disables type-checking and the strings will be evaluated during Gradle execution.
Generated DSL accessors
However, string-typing is not why we like Kotlin! We want type-safety and auto completion hints. That's exactly what we see with the implementation() and runtimeOnly(). So how do we get them for grafanaImplementation() and grafanaRuntimeOnly()?
Basically, Gradle will scan the registered config and when it sees that a plugin creates an implementation configuration, it generates Kotlin DSL accessors. However, it can't generate accessors for the build.gradle.kts that contains the definition for the accessors... that's too late. So we need to define the config earlier. We can do that with a buildSrc plugin.
buildSrc Grafana convention plugin
Set up a buildSrc project (this is covered more in the Gradle docs or other StackOverflow answers)
Create a pre-compiled script plugin for Grafana config
// $projectRoot/buildSrc/src/main/kotlin/grafana.convention.gradle.kts
plugins {
// using 'sourceSets' requires the Java plugin, so we must apply it
java
}
val grafanaSourceSet = sourceSets.create("grafana")
Note that this convention plugin is quite opinionated as it applies the Java plugin. In more complex setups you might want to instead react to the Java plugin, rather than always applying it.
Now apply the convention plugin, and Gradle will generate the Kotlin DSL accessors!
// $projectRoot/build.gradle.kts
plugins {
id("grafana.convention")
}
dependencies {
// no string-typing needed!
grafanaImplementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
grafanaRuntimeOnly("org.slf4j:slf4j-simple:1.7.36")
}

Can I create a Gradle plugin that adds a dependency or another plugin based on a Gradle extension value?

Can I create a Gradle plugin that adds a dependency based on an extension value?
I have a convention plugin that I use for libraries various projects, which brings in various dependencies, takes care of boilerplate configuration, configures other plugins etc etc. I want to add an extension to the plugin that can tell the plugin whether or not to add a certain dependency, in this case it happens to be Spock, as not every library module needs the Spock dependency.
So far, my plugin looks like this
interface BasePluginExtension {
Property<Boolean> getUseSpock()
}
class BasePlugin implements Plugin<Project> {
#Override
void apply(Project project) {
BasePluginExtension basePluginExtension = project.extensions.create('basePluginConfig', BasePluginExtension)
// If a value was supplied, use it, otherwise assume we want Spock
if (basePluginExtension?.useSpock?.get() ?: true) {
// Printing for debugging purposes
println "I'm using spock! ${basePluginExtension.useSpock.get()}"
// Currently apply a plugin that applies Spock but could also just add a dependency
project.plugins.apply("test-config")
}
}
}
Then in the build.gradle file that I want to pull my plugin into, I have
plugins {
id 'base-plugin'
}
basePluginConfig {
useSpock = true
}
I'm following the docs on configuring an extension but I am getting the following error:
Cannot query the value of extension 'basePluginConfig' property 'useSpock' because it has no value available.
I've also tried the method of making an abstract class for the extension but I want the ability to have multiple configurable parameters in the future.
Is adding a dependency after plugin extension values have been configured not allowed/out of order for how Gradle works? Or am I possibly missing something obvious?

Aggregate Report for Gradle Multi-Project build

I need to create an aggregate code coverage report for my multiple-module Gradle project using the JaCoCo plugin for Gradle, similar to the one generated by the jacoco:aggregate-report of Maven's jaoco-maven-plugin.
I have been googling for a solution the last couple of days but nothing has worked so far. Most of the proposed solutions involve defining a task of type JacocoReport in the root project, that aggregates the execution data and produces the html code coverage report. However everything I tried so far either fails with an error or does not generate any report.
For example, this code snippet that I have tried:
def publishedProjects = subprojects.findAll()
task jacocoRootReport(type: JacocoReport) {
dependsOn(publishedProjects.test)
additionalSourceDirs = files(publishedProjects.sourceSets.main.allSource.srcDirs)
sourceDirectories = files(publishedProjects.sourceSets.main.allSource.srcDirs)
classDirectories = files(publishedProjects.sourceSets.main.output)
executionData = files(publishedProjects.jacocoTestReport.executionData)
doFirst {
executionData = files(executionData.findAll { it.exists() })
}
reports {
html.enabled = true
xml.enabled = false
}
}
gives the error groovy.lang.GroovyRuntimeException: Cannot set the value of read-only property 'additionalSourceDirs' for task ':jacocoRootReport' of type org.gradle.testing.jacoco.tasks.JacocoReport
The version of Gradle that I am using is 7.3. Unfortunately I'm very new to Gradle and I still cannot fiddle with the code snippets that I've found to make them work for my case.
Any help will be much appreciated. Thank you very much in advance.
There is an official example in the Gradle docs for this use case:
https://docs.gradle.org/7.3.3/samples/sample_jvm_multi_project_with_code_coverage.html
It is a somewhat complex and requires an additional project that will serve as the aggregate. The way you have above is generally discouraged by Gradle for reasons outside of this question/answer.
Edit 2022-09-11
As of Gradle 7.4, Gradle now offers a plugin to aggregate reports for multi-module projects.
See the documentation for the JaCoCo Report Aggregation Plugin for more details.

Gradle Kotlin DSL: get sourceSet of another project

Currently, we're trying to migrate our existing build.gradle scripts to the new Kotlin DSL. Right now, we are struggling with the jar task configuration.
Our project is a simple multi-project. Let's say we've Core and Plugin and Plugin uses classes from Core. Now, when building Plugin, the target jar should include any used classes from Core.
This is how it looked like before:
jar {
from sourceSets.main.output
from project(':Core').sourceSets.main.output
}
And this is the current solution we've with Kotlin DSL:
val jar: Jar by tasks
jar.apply {
from(java.sourceSets["main"].allSource)
from(project(":Core").the<SourceSetContainer>()["main"].allSource)
}
However, the above example just gives me an Extension of type 'SourceSetContainer' does not exist. Currently registered extension types: [ExtraPropertiesExtension] error. I've also tried other code snippets I've found, but none of them have been working so far.
I have also tried this (like suggested in the first answer):
val jar: Jar by tasks
jar.apply {
from(java.sourceSets["main"].allSource)
from(project(":Core").sourceSets.getByName("main").allSource)
}
But then the IDE (and also the jar task) argues that sourceSets is not available: Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: public val KotlinJvmProjectExtension.sourceSets: NamedDomainObjectContainer<DefaultKotlinSourceSet> defined in org.gradle.kotlin.dsl.
I hope that someone can help us, because it is very frustrating to spend hours in configuration instead of writing any usefull code.
Thank you very much in advance.
You can access the SourceSetContainer by
project(":Core").extensions.getByType(SourceSetContainer::class)
it seems <T : Any> Project.the(extensionType: KClass<T>): T looks in the convention of the project, while val Project.sourceSets: SourceSetContainer get() looks in the extensions ExtensionContaier instead. This is somewhat odd, as the documentation for the says "Returns the plugin convention or extension of the specified type."
Note that you may need to do your sourceSet manipulation in gradle.projectsEvaluated, because otherwise the sourceSet in question may not be configured yet if the corresponding project is not yet evaluated.
If you get access to the project, then everything should looks like your actual groovy gradle script:
project(":Core").sourceSets.getByName("main").allSource
So regarding your actual code:
val jar: Jar by tasks
jar.apply {
from(java.sourceSets["main"].allSource)
from(project(":Core").sourceSets.getByName("main").allSource)
}

Intellij, cucumber test and two spring Context Configuration

My project has two different cucumber test and each one needs a different spring context configuration.
The problem I have is that when I run each test individually from Intellij, they load the right Spring Context and the tests are passing, but I press run all test, none of them are passing.
Running a maven test, both test are passing.
this is my code:
#RunWith(FCSCucumber.class)
#Cucumber.Options(strict = true
// , tags = {"~#ignore"}
// , tags = {"#Only"}
, glue ="feature.scenario1"
, features = "src/test/resources/feature/scenario1/"
)
#FCSApplicationProperties(commonProps="config/scenario1/exec.common.properties",
environmentProps="src/test/resources/scenario1-test.properties")
public class TesScenario1Features {
}
#ContextConfiguration("/cucumber-scenario1.xml")
public class scenario1Steps {
......
}
#RunWith(FCSCucumber.class)
#Cucumber.Options(strict = true
// , tags = {"~#ignore"}
// , tags = {"#Only"}
, glue ="feature.scenario2"
, features = "src/test/resources/feature/scenario2/"
)
#FCSApplicationProperties(commonProps="config/scenario2/exec.common.properties",
environmentProps="src/test/resources/scenario2-test.properties")
public class TesScenario2Features {
}
#ContextConfiguration("/cucumber-scenario2.xml")
public class scenario2Steps {
......
}
Thank you very much for your help
The issue is that the IntelliJ cucumber plugin is using the cucumber cli to run tests, without using the JUnit runner at all. This causes several limitations, like requiring the spring annotations on the step definition classes instead of the runner, or by default requiring the steps definitions to be in the same package as the scenario files.
In your example I would actually expect also running a single test to fail, unless the correct application properties are also referenced by the /cucumber-scenario{1,2}.xml files.
The only option I see with the standard cucumber implementation would be to extract the tests into separate projects.
I'm actually working on an alternative implementation of cucumber with an improved spring integration that you might want to try. It's not fully integrated with IntelliJ yet though.

Resources