---
# System prepended metadata

title: Gradle-poet

---

# Gradle-poet

## Problems

- Integration tests with Gradle TestKit for plugins involve to create a minimal Gradle project from scratch
- Performance tests. Where we want generate big project with complex hierarchy and applied plugins

## Current solutions

1. In [TestKit examples](https://docs.gradle.org/current/userguide/test_kit.html#example_using_gradlerunner_with_groovy_and_spock) it's a string interpolations
2. Writing and maintaining projects without any help from IDE can be very challeging for not so experienced plugin developers. So they develop alterntaive methods:
    1. write a separate gradle project next to plugin and testing against it's folder
    2. create a DSL on top of string interpolation method.

Method 2.1 helps with IDE completion, but not suitable if you need a lot of different combinations of project setup for tests.  
Method 2.2 one ends up with "yet another DSL", usually very naive and hard to extend in future.

## Our solution

Our solution is to unify DSL for generating Gradle projects in single tool. That will be available on MavenCentral for everyone and lowering the burden of writing test for Gradle Plugins.

### Requirements

 - Support both Kotlin and Groovy build scripts generation


### Possible APIs
API is not final, just samples to choose.

Builder style.
No problems with java capability.
```kotlin
@Test
fun test(@TempDir testDirectory: File) {
    GradleRootProject()
        .withSetting(GradleSettingScript()
                .addIncludeProject(GradleProject())
        .withBuild(GradleBuildScript()
                  .addPlugin("my-plugin-id")
                  .configureAndroid(GradleAndroidExtenstion()
                                   .minApiVersion(16)
                                   .targetApiVersion(30))
                  .addDependency("implementation", "com.example:1.0.0")))
        .writeTo(testDirectory)
}
```


Dsl style. Try to mimic kotlin dsl
Problems with java capability.
```kotlin
@Test
fun test(@TempDir testDirectory: File) {
    gradleRootProject {
        settings {
            include(project {
                // ...
            })
        }
        
        build {
            plugins {
                id("my-plugin-id")
            }
            
            android {
                minApiVersion(16)
                targetApiVersion(30)
            }
            
            dependencies {
                implementaion("com.example:1.0.0")
            }
        }
    }.writeTo(testDirectory)
}
```

### Scenarios

#### Developer wants to see generated code

##### Generate in non-temp directory

To see generated project in file system, developer can just use `writeTo(file: File)`.

##### Stop in debugger for temp directory

GradlePoet writes `GradlePoet generated a project in: /tmp/hash/` in stdout during test.  
Directory is available while test is running, so it can be observed while debugger stops at some breakpoint.

##### System.out

Writing whole generated project to System.out can be a valid option for small projects, but can become hard to read as project get bigger.

Example:

```
GradlePoet generated a project:

/settings.gradle

    pluginsManagement{
        ...
    }

    dependencyResoultionMAnagement {
        ...
    }
    
/lib/src/main/kotlin/com/example/Main.kt

    package com.example
    
    fun main() {
        println("Hello World")
    }
    
```

## Additional functionality suitable for same repository

There are functionality that can be done as separate maven artifacts, but be a part of this tool, as it serves the same purpose.  
It can be in same repository, or different.

### Gradle TestKit assertion frameworks integration

[BuildResult](https://docs.gradle.org/current/javadoc/org/gradle/testkit/runner/BuildResult.html) interface provides access to raw output and executed tasks outcomes. It is a great opportunity to build a set of useful assertions on top of it. For example: 

```
outputContains(substring: String)
outputDoesNotContain(substring: String)
configurationCachedReused()
taskWithOutcome(taskPath: String, outcome: TaskOutcome)
tasksShouldBeTriggered(vararg taskPath: String)
tasksShouldNotBeTriggered(vararg taskPath: String)
moduleTasksShouldNotBeTriggered(vararg modulePaths: String)
```

A single assertions kit can be created initially, with any other assertiong frameworks implementations contributed by community later.
Google Truth is a good candidate, because of it's current popularity.

One thing to consider: using `--dry-run` option will not result in getting a task with TaskOutcome.SKIPPED. See: [gradle/#2732](https://github.com/gradle/gradle/issues/2732). It can affect integration's API, because we don't have a control and knowledge of `--dry-run` argument only extending `BuildResult`.


### Git support

Testing CI-oriented plugins often involves git operations, like checking if there is a certaing file affected by git diff. Separate artefact with API for preparing repository git state can be provided.

## Open questions
1. How to pass kt/java/etc sources to test projects?
2. What api should we prefer to use?
3. Should we provide a good api for Java users?
4. Should we use separate repos for gradlepoet and gradle-assert-lib?

## References

 - https://square.github.io/kotlinpoet/
 - https://github.com/square/javapoet
 - https://github.com/avito-tech/avito-android/tree/develop/subprojects/gradle/test-project/src/main/kotlin/com/avito/test/gradle
 - https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin/tree/main/testkit/src/main/kotlin/com/autonomousapps/kit
 - https://github.com/google/protobuf-gradle-plugin (all test* folders in the root are projects for unit tests)