From b8acd2874421197df989f5ca1d4fdef20bf62121 Mon Sep 17 00:00:00 2001 From: Kevin Brightwell Date: Tue, 25 Apr 2023 16:29:05 -0400 Subject: [PATCH] [Filtering] Add support for filtering packages like Maven plugin (#53) This adds package filters similar to how the Maven plugin filters in its `` block. This supports ant-like patterns similar to how patterns filter. Partial implementation of #16. --- README.md | 43 +- .../gradle/tasks/AssertJGenerationTask.kt | 11 +- .../tasks/config/AssertJGeneratorExtension.kt | 13 +- ...JavaIdentifierNamePatternFilterableBase.kt | 90 ++++ .../patterns/JavaNamePatternCompiler.kt | 32 ++ .../JavaPackageNamePatternFilterable.kt | 59 +++ .../gradle/parameter/PackageFilter.groovy | 437 ++++++++++++++++++ 7 files changed, 669 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaIdentifierNamePatternFilterableBase.kt create mode 100644 src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaNamePatternCompiler.kt create mode 100644 src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaPackageNamePatternFilterable.kt create mode 100644 src/test/groovy/org/assertj/generator/gradle/parameter/PackageFilter.groovy diff --git a/README.md b/README.md index 3979cbc..3221638 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,29 @@ sourceSets { } ``` +#### packages - `JavaPackageNamePatternFilterable` + +Default: All packages + +Package filters can be used to include, or exclude individual or groups of packages to be generated. These filters use a +similar pattern to `include`/`exclude` with `SourceSet`. + +```groovy +sourceSets { + main { + assertJ { + packages { + include "org.example**" // include *all* packages in `org.example` + exclude "org.example.foo" // exclude `org.example.foo` specifically + } + } + } +} +``` + +See [`PackageFilter` tests](src/test/groovy/org/assertj/generator/gradle/parameter/PackageFilter.groovy) for more +examples. + #### entryPoints - `EntryPointsGeneratorOptions` Default: Only generate "standard" assertion entrypoint @@ -238,11 +261,11 @@ A useful trick to turn on _only_ a set of values is to just set the `entryPoints ```groovy sourceSets { - main { - assertJ { - entryPoints = ['BDD', 'JUNIT_SOFT'] // turn on _only_ BDD and JUnit Soft - } + main { + assertJ { + entryPoints = ['BDD', 'JUNIT_SOFT'] // turn on _only_ BDD and JUnit Soft } + } } ``` @@ -250,13 +273,13 @@ Or within the `entryPoints` closure: ```groovy sourceSets { - main { - assertJ { - entryPoints { - only 'BDD', 'JUNIT_SOFT' // turn on _only_ BDD and JUnit Soft - } - } + main { + assertJ { + entryPoints { + only 'BDD', 'JUNIT_SOFT' // turn on _only_ BDD and JUnit Soft + } } + } } ``` diff --git a/src/main/kotlin/org/assertj/generator/gradle/tasks/AssertJGenerationTask.kt b/src/main/kotlin/org/assertj/generator/gradle/tasks/AssertJGenerationTask.kt index a82eaec..f7a010b 100644 --- a/src/main/kotlin/org/assertj/generator/gradle/tasks/AssertJGenerationTask.kt +++ b/src/main/kotlin/org/assertj/generator/gradle/tasks/AssertJGenerationTask.kt @@ -20,6 +20,7 @@ import org.assertj.assertions.generator.description.converter.ClassToClassDescri import org.assertj.assertions.generator.util.ClassUtil import org.assertj.generator.gradle.tasks.config.AssertJGeneratorExtension import org.assertj.generator.gradle.tasks.config.SerializedTemplate +import org.assertj.generator.gradle.tasks.config.patterns.JavaPackageNamePatternFilterable import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty @@ -77,10 +78,13 @@ open class AssertJGenerationTask @Inject internal constructor( val outputDir: DirectoryProperty @get:Input - internal val skip: Property = objects.property() + internal val skip: Property = objects.property() @get:Input - internal val hierarchical: Property = objects.property() + internal val hierarchical: Property = objects.property() + + @get:Input + internal val packagesFilterable: Property = objects.property() @get:Input internal val entryPoints: SetProperty = objects.setProperty() @@ -110,6 +114,7 @@ open class AssertJGenerationTask @Inject internal constructor( skip.set(project.provider { assertJOptions.skip }) hierarchical.set(project.provider { assertJOptions.hierarchical }) + packagesFilterable.set(project.provider { assertJOptions.packages }) entryPoints.set(project.provider { assertJOptions.entryPoints.entryPoints }) entryPointsClassPackage.set(project.provider { assertJOptions.entryPoints.classPackage }) } @@ -125,7 +130,9 @@ open class AssertJGenerationTask @Inject internal constructor( val classLoader = URLClassLoader((generationClasspath + classDirectories).map { it.toURI().toURL() }.toTypedArray()) + val packagesPredicate = packagesFilterable.get().asPredicate() val allClassNames = getClassNames(classDirectories) + .filter { packagesPredicate.test(it.substringBeforeLast('.')) } @Suppress("SpreadOperator") // Java interop val allClasses = ClassUtil.collectClasses( diff --git a/src/main/kotlin/org/assertj/generator/gradle/tasks/config/AssertJGeneratorExtension.kt b/src/main/kotlin/org/assertj/generator/gradle/tasks/config/AssertJGeneratorExtension.kt index 00d71f6..01aabc4 100644 --- a/src/main/kotlin/org/assertj/generator/gradle/tasks/config/AssertJGeneratorExtension.kt +++ b/src/main/kotlin/org/assertj/generator/gradle/tasks/config/AssertJGeneratorExtension.kt @@ -13,6 +13,7 @@ package org.assertj.generator.gradle.tasks.config import org.assertj.assertions.generator.AssertionsEntryPointType +import org.assertj.generator.gradle.tasks.config.patterns.JavaPackageNamePatternFilterable import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty @@ -34,6 +35,12 @@ open class AssertJGeneratorExtension @Inject internal constructor( val classDirectories: SourceDirectorySet = objects.sourceDirectorySet("assertJClasses", "Classes to generate AssertJ assertions from") + val packages: JavaPackageNamePatternFilterable = objects.newInstance() + + fun packages(action: Action): AssertJGeneratorExtension = apply { + action.execute(packages) + } + /** * Generate generating Soft Assertions entry point class. * @return templates value, never {@code null} @@ -44,9 +51,8 @@ open class AssertJGeneratorExtension @Inject internal constructor( * Method used for improving configuration DSL * @return {@code this} */ - fun templates(action: Action): AssertJGeneratorExtension { + fun templates(action: Action): AssertJGeneratorExtension = apply { action.execute(templates) - return this } /** @@ -82,9 +88,8 @@ open class AssertJGeneratorExtension @Inject internal constructor( * Used to change "entry point" class generation. * @return this */ - fun entryPoints(action: Action): AssertJGeneratorExtension { + fun entryPoints(action: Action): AssertJGeneratorExtension = apply { action.execute(entryPoints) - return this } /** diff --git a/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaIdentifierNamePatternFilterableBase.kt b/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaIdentifierNamePatternFilterableBase.kt new file mode 100644 index 0000000..b34b2a9 --- /dev/null +++ b/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaIdentifierNamePatternFilterableBase.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2023. assertj-generator-gradle-plugin contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.assertj.generator.gradle.tasks.config.patterns + +import java.io.IOException +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import java.util.function.Predicate + +@Suppress("UNCHECKED_CAST") +sealed class JavaIdentifierNamePatternFilterableBase + where SELF : JavaIdentifierNamePatternFilterableBase { + private var includePredicates = mutableSetOf>() + private var excludePredicates = mutableSetOf>() + + internal abstract fun compilePatterns(patterns: Iterable): Sequence> + + internal fun asPredicate(): Predicate { + return Predicate { t -> + (includePredicates.isEmpty() || includePredicates.any { it.test(t) }) && + (excludePredicates.isEmpty() || excludePredicates.none { it.test(t) }) + } + } + + fun setIncludes(includes: Iterable): SELF { + this.includePredicates = compilePatterns(includes).toMutableSet() + return this as SELF + } + + fun setExcludes(excludes: Iterable): SELF { + this.excludePredicates = compilePatterns(excludes).toMutableSet() + return this as SELF + } + + fun include(vararg includes: String): SELF { + this.includePredicates += compilePatterns(includes.asIterable()) + return this as SELF + } + + fun include(includes: Iterable): SELF { + this.includePredicates += compilePatterns(includes) + return this as SELF + } + + fun exclude(vararg excludes: String): SELF { + this.excludePredicates += compilePatterns(excludes.asIterable()) + return this as SELF + } + + fun exclude(excludes: Iterable): SELF { + this.excludePredicates += compilePatterns(excludes) + return this as SELF + } + + internal interface PatternPredicate : Predicate { + val pattern: String + } + + @Throws(IOException::class) + protected fun writeObjectImpl(o: ObjectOutputStream) { + o.writeInt(includePredicates.size) + for (pattern in includePredicates.map { it.pattern }) { + o.writeUTF(pattern) + } + + o.writeInt(excludePredicates.size) + for (pattern in excludePredicates.map { it.pattern }) { + o.writeUTF(pattern) + } + } + + @Throws(IOException::class, ClassNotFoundException::class) + protected fun readObjectImpl(i: ObjectInputStream) { + val includesSize = i.readInt() + setIncludes((0 until includesSize).map { i.readUTF() }) + + val excludesSize = i.readInt() + setExcludes((0 until excludesSize).map { i.readUTF() }) + } +} diff --git a/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaNamePatternCompiler.kt b/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaNamePatternCompiler.kt new file mode 100644 index 0000000..ca79fd1 --- /dev/null +++ b/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaNamePatternCompiler.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2023. assertj-generator-gradle-plugin contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.assertj.generator.gradle.tasks.config.patterns + +private const val WILDCARD_MARKER = "!!WILDCARD_MARKER!!" + +internal object JavaNamePatternCompiler { + fun compilePattern(pattern: String): Regex { + val escapedPackageCharacters = pattern + .replace(".", "\\.") + .replace("$", "\\$") + + // Order matters because if we do single "*" first we double remove the "**" + val withWildcardMarkers = escapedPackageCharacters + .replace("**", "[\\w\\.]$WILDCARD_MARKER") + .replace("*", "\\w$WILDCARD_MARKER") + + val withRegexWildcards = withWildcardMarkers.replace(WILDCARD_MARKER, "*") + + return Regex(withRegexWildcards) + } +} diff --git a/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaPackageNamePatternFilterable.kt b/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaPackageNamePatternFilterable.kt new file mode 100644 index 0000000..5b4e1fd --- /dev/null +++ b/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaPackageNamePatternFilterable.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2023. assertj-generator-gradle-plugin contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.assertj.generator.gradle.tasks.config.patterns + +import com.google.common.reflect.TypeToken +import org.assertj.generator.gradle.tasks.config.patterns.JavaNamePatternCompiler.compilePattern +import java.io.IOException +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import java.io.Serializable + +/** + * Implements a similar construction to [org.gradle.api.tasks.util.PatternFilterable] that will match + * [TypeToken] instances. + */ +open class JavaPackageNamePatternFilterable internal constructor() : + JavaIdentifierNamePatternFilterableBase(), Serializable { + + override fun compilePatterns(patterns: Iterable): Sequence> { + return patterns.asSequence().map { PackagePatternPredicate.compile(it) } + } + + @Throws(IOException::class) + protected fun writeObject(o: ObjectOutputStream) { + super.writeObjectImpl(o) + } + + @Throws(IOException::class, ClassNotFoundException::class) + protected fun readObject(i: ObjectInputStream) { + super.readObjectImpl(i) + } + + internal data class PackagePatternPredicate( + override val pattern: String, + private val namePattern: Regex, + ) : PatternPredicate { + override fun test(t: String): Boolean { + return namePattern.matches(t) + } + + companion object { + fun compile(pattern: String) = PackagePatternPredicate(pattern, compilePattern(pattern)) + } + } + + companion object { + private const val serialVersionUID = 48634795L + } +} diff --git a/src/test/groovy/org/assertj/generator/gradle/parameter/PackageFilter.groovy b/src/test/groovy/org/assertj/generator/gradle/parameter/PackageFilter.groovy new file mode 100644 index 0000000..3c6120a --- /dev/null +++ b/src/test/groovy/org/assertj/generator/gradle/parameter/PackageFilter.groovy @@ -0,0 +1,437 @@ +/* + * Copyright 2023. assertj-generator-gradle-plugin contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.assertj.generator.gradle.parameter + + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +import java.nio.file.Path +import java.nio.file.Paths +import java.util.function.Supplier + +import static org.assertj.core.api.Assertions.assertThat + +/** + * Checks the behaviour of overriding globals in a project + */ +class PackageFilter { + + @Rule + public final TemporaryFolder testProjectDir = new TemporaryFolder() + + File buildFile + File helloWorldTestJava + Path generatedBasePackagePath + + @Before + void setup() { + buildFile = testProjectDir.newFile('build.gradle') + + File srcDir = testProjectDir.newFolder('src', 'main', 'java') + + Path packagePath = Paths.get("org/example/") + + Path srcPackagePath = srcDir.toPath().resolve(packagePath) + srcPackagePath.toFile().mkdirs() + File helloWorldJava = srcPackagePath.resolve('HelloWorld.java').toFile() + + helloWorldJava << """ + package org.example.hello; + + public final class HelloWorld { + + // Field + public boolean hasSomeBrains = false; + + // Getter + public int getFoo() { + return -1; + } + } + """.stripIndent() + + File subHelloWorldJava = srcPackagePath.resolve('sub/SubHelloWorld.java').toFile() + subHelloWorldJava.parentFile.mkdirs() + + subHelloWorldJava << """ + package org.example.hello.sub; + + public final class SubHelloWorld { + // Getter + public int getFoo() { + return -1; + } + } + """.stripIndent() + + File otherWorldJava = srcPackagePath.resolve('OtherWorld.java').toFile() + + otherWorldJava << """ + package org.example.other; + + public final class OtherWorld { + public boolean isBrainy = false; + } + """.stripIndent() + + File testDir = testProjectDir.newFolder('src', 'test', 'java') + + testDir.toPath().resolve(packagePath).toFile().mkdirs() + def testPackagePath = testDir.toPath().resolve(packagePath) + testPackagePath.toFile().mkdirs() + + helloWorldTestJava = testPackagePath.resolve('HelloWorldTest.java').toFile() + + generatedBasePackagePath = testProjectDir.root.toPath() + .resolve("build/generated-src/main-test/java") + .resolve(packagePath) + } + + @Test + void include_package_simple() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + packages { + include "org.example.hello" + } + } + """.stripIndent() + } + + setupTestHelloWorld() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello/sub")).doesNotExist() + } + + @Test + void include_package_pattern() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + packages { + include "org.example.he*" + } + } + """.stripIndent() + } + + setupTestHelloWorld() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello/sub")).doesNotExist() + } + + @Test + void include_package_that_does_not_exist_and_valid() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + packages { + include "org.example.he*", "org.example.does_not_exist" + } + } + """.stripIndent() + } + + setupTestHelloWorld() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello/sub")).doesNotExist() + } + + @Test + void include_package_double_wildcard() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + packages { + include "org.example.hello**" + } + } + """.stripIndent() + } + + setupTestHelloAndSub() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("hello/sub")).exists() + } + + @Test + void exclude_package_simple() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + packages { + exclude "org.example.other" + } + } + """.stripIndent() + } + + setupTestHelloAndSub() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("hello/sub")).exists() + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + } + + @Test + void exclude_package_pattern() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + packages { + exclude "org.example.ot*" + } + } + """.stripIndent() + } + + setupTestHelloAndSub() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("hello/sub")).exists() + } + + private File setupTestHelloAndSub() { + testFile { + """ + @Test + public void checkHello() { + HelloWorld hw = new HelloWorld(); + assertThat(hw).doesNotHaveSomeBrains(); + } + + @Test + public void checkOther() { + SubHelloWorld shw = new SubHelloWorld(); + assertThat(shw).hasFoo(-1); + } + """.stripIndent() + } + } + + @Test + void exclude_package_that_does_not_exist_and_valid() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + packages { + exclude "org.example.ot*", "org.example.does_not_exist" + } + } + """.stripIndent() + } + + testFile { + """ + @Test + public void checkHello() { + HelloWorld hw = new HelloWorld(); + assertThat(hw).doesNotHaveSomeBrains(); + } + + @Test + public void checkOther() { + SubHelloWorld shw = new SubHelloWorld(); + assertThat(shw).hasFoo(-1); + } + """.stripIndent() + } + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("hello/sub")).exists() + } + + @Test + void exclude_package_double_wildcard() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + packages { + exclude "org.example.**" + } + } + """.stripIndent() + } + + // Since we are excluding _everything_ we need to add a custom test + helloWorldTestJava << """ + package org.example; + + import org.example.hello.*; + import org.example.other.*; + import org.junit.Test; + import static org.assertj.core.api.Assertions.assertThat; + + public final class HelloWorldTest { + @Test + public void checkNull() { + assertThat(true).isTrue(); + } + } + """.stripIndent() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello")).doesNotExist() + } + + @Test + void include_double_wildcard_but_exclude_specific_package() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + packages { + include "org.example.hello**" + exclude "org.example.hello.sub" + } + } + """.stripIndent() + } + + setupTestHelloWorld() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("hello/sub")).doesNotExist() + } + + private File setupTestHelloWorld() { + testFile { + """ + @Test + public void checkHello() { + HelloWorld hw = new HelloWorld(); + assertThat(hw).doesNotHaveSomeBrains(); + } + """.stripIndent() + } + } + + private void runAndAssertBuild() { + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withDebug(true) + .withPluginClasspath() + .withArguments('-i', '-s', 'test') + .build() + + assert result.task(':generateAssertJ').outcome == TaskOutcome.SUCCESS + assert result.task(':test').outcome == TaskOutcome.SUCCESS + } + + private def buildFile(Supplier configuration) { + buildFile << """ + // Add required plugins and source sets to the sub projects + plugins { + id "org.assertj.generator" // Note must use this syntax + id "java" + } + + // Override defaults + sourceSets { + main { + ${configuration.get()} + } + } + + // add some classpath dependencies + repositories { + mavenCentral() + } + + dependencies { + implementation group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2' + + testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.24.2' + testImplementation group: 'junit', name: 'junit', version: '4.13.1' + } + """.stripIndent() + } + + private def testFile(Supplier testContent) { + helloWorldTestJava << """ + package org.example; + + import org.example.hello.*; + import org.example.hello.sub.*; + import org.example.other.*; + import org.junit.Test; + import org.assertj.core.api.Assertions; + import static org.example.Assertions.assertThat; + + public final class HelloWorldTest { + ${testContent.get()} + } + """.stripIndent() + } +} \ No newline at end of file