# 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)