From a1f9eb5cd45af9d119617708cc5b993fe32c10a5 Mon Sep 17 00:00:00 2001 From: Kevin Brightwell Date: Tue, 25 Apr 2023 21:30:16 -0400 Subject: [PATCH] [Filtering] Add support for filtering classes like Maven plugin (#54) This adds package filters similar to how the Maven plugin filters in its `` block. This supports ant-like patterns similar to how patterns filter. Follows #53 which added similar support for `packages` filtering Closes #16. --- README.md | 24 + .../gradle/tasks/AssertJGenerationTask.kt | 10 +- .../tasks/config/AssertJGeneratorExtension.kt | 7 + .../patterns/JavaClassPatternFilterable.kt | 59 +++ .../gradle/parameter/ClassesFilter.groovy | 437 ++++++++++++++++++ .../gradle/parameter/PackageFilter.groovy | 2 +- 6 files changed, 537 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaClassPatternFilterable.kt create mode 100644 src/test/groovy/org/assertj/generator/gradle/parameter/ClassesFilter.groovy diff --git a/README.md b/README.md index 3221638..73f290d 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,30 @@ sourceSets { See [`PackageFilter` tests](src/test/groovy/org/assertj/generator/gradle/parameter/PackageFilter.groovy) for more examples. +#### classes - `JavaClassPatternFilterable` + +Default: All classes + +Class filters can be used to include, or exclude individual patterns of fully-qualified class names to be generated. +These filters use a +similar pattern to `include`/`exclude` with `SourceSet`. + +```groovy +sourceSets { + main { + assertJ { + classes { + include "org.example.**" // include *all* classes under `org.example` including sub-packages + exclude "org.example.Foo" // exclude class `org.example.Foo` specifically + } + } + } +} +``` + +See [`ClassesFilter` tests](src/test/groovy/org/assertj/generator/gradle/parameter/ClassesFilter.groovy) for more +examples. + #### entryPoints - `EntryPointsGeneratorOptions` Default: Only generate "standard" assertion entrypoint 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 f7a010b..9e0e157 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.JavaClassPatternFilterable import org.assertj.generator.gradle.tasks.config.patterns.JavaPackageNamePatternFilterable import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection @@ -83,6 +84,9 @@ open class AssertJGenerationTask @Inject internal constructor( @get:Input internal val hierarchical: Property = objects.property() + @get:Input + internal val classesFilterable: Property = objects.property() + @get:Input internal val packagesFilterable: Property = objects.property() @@ -114,6 +118,7 @@ open class AssertJGenerationTask @Inject internal constructor( skip.set(project.provider { assertJOptions.skip }) hierarchical.set(project.provider { assertJOptions.hierarchical }) + classesFilterable.set(project.provider { assertJOptions.classes }) packagesFilterable.set(project.provider { assertJOptions.packages }) entryPoints.set(project.provider { assertJOptions.entryPoints.entryPoints }) entryPointsClassPackage.set(project.provider { assertJOptions.entryPoints.classPackage }) @@ -130,6 +135,7 @@ open class AssertJGenerationTask @Inject internal constructor( val classLoader = URLClassLoader((generationClasspath + classDirectories).map { it.toURI().toURL() }.toTypedArray()) + val classesPredicate = classesFilterable.get().asPredicate() val packagesPredicate = packagesFilterable.get().asPredicate() val allClassNames = getClassNames(classDirectories) .filter { packagesPredicate.test(it.substringBeforeLast('.')) } @@ -138,7 +144,9 @@ open class AssertJGenerationTask @Inject internal constructor( val allClasses = ClassUtil.collectClasses( classLoader, *allClassNames.toTypedArray(), - ) + ).asSequence() + .filter { classesPredicate.test(it) } + .toSet() val changedFiles = if (inputs.isIncremental) { inputs.getFileChanges(classDirectories).asSequence().map { it.file }.filter { it.isFile }.toSet() 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 01aabc4..d4f2f10 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.JavaClassPatternFilterable import org.assertj.generator.gradle.tasks.config.patterns.JavaPackageNamePatternFilterable import org.gradle.api.Action import org.gradle.api.Project @@ -35,6 +36,12 @@ open class AssertJGeneratorExtension @Inject internal constructor( val classDirectories: SourceDirectorySet = objects.sourceDirectorySet("assertJClasses", "Classes to generate AssertJ assertions from") + val classes: JavaClassPatternFilterable = objects.newInstance() + + fun classes(action: Action): AssertJGeneratorExtension = apply { + action.execute(classes) + } + val packages: JavaPackageNamePatternFilterable = objects.newInstance() fun packages(action: Action): AssertJGeneratorExtension = apply { diff --git a/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaClassPatternFilterable.kt b/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaClassPatternFilterable.kt new file mode 100644 index 0000000..245cf2e --- /dev/null +++ b/src/main/kotlin/org/assertj/generator/gradle/tasks/config/patterns/JavaClassPatternFilterable.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 JavaClassPatternFilterable internal constructor() : + JavaIdentifierNamePatternFilterableBase, JavaClassPatternFilterable>(), Serializable { + + override fun compilePatterns(patterns: Iterable): Sequence>> { + return patterns.asSequence().map { TypeNamePredicate.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 TypeNamePredicate( + override val pattern: String, + private val namePattern: Regex, + ) : PatternPredicate> { + override fun test(t: TypeToken<*>): Boolean { + return namePattern.matches(t.type.typeName) + } + + companion object { + fun compile(pattern: String): TypeNamePredicate = TypeNamePredicate(pattern, compilePattern(pattern)) + } + } + + companion object { + private const val serialVersionUID = 9418631994L + } +} diff --git a/src/test/groovy/org/assertj/generator/gradle/parameter/ClassesFilter.groovy b/src/test/groovy/org/assertj/generator/gradle/parameter/ClassesFilter.groovy new file mode 100644 index 0000000..da706a1 --- /dev/null +++ b/src/test/groovy/org/assertj/generator/gradle/parameter/ClassesFilter.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 that we can include/exclude classes via the `classes` filter. + */ +class ClassesFilter { + + @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_class_simple() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + classes { + include "org.example.hello.HelloWorld" + } + } + """.stripIndent() + } + + setupTestHelloWorld() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello/sub")).doesNotExist() + } + + @Test + void include_class_pattern() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + classes { + include "org.example.hello.Hello*" + } + } + """.stripIndent() + } + + setupTestHelloWorld() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello/sub")).doesNotExist() + } + + @Test + void include_class_that_does_not_exist_and_valid() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + classes { + include "org.example.hello.*", "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_class_double_wildcard() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + classes { + 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_class_simple() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + classes { + exclude "org.example.other.OtherWorld" + } + } + """.stripIndent() + } + + setupTestHelloAndSub() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("hello/sub")).exists() + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + } + + @Test + void exclude_class_pattern() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + classes { + exclude "org.example.other.*" + } + } + """.stripIndent() + } + + setupTestHelloAndSub() + + runAndAssertBuild() + + assertThat(generatedBasePackagePath.resolve("other")).doesNotExist() + assertThat(generatedBasePackagePath.resolve("hello")).exists() + assertThat(generatedBasePackagePath.resolve("hello/sub")).exists() + } + + @Test + void exclude_class_that_does_not_exist_and_valid() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + classes { + exclude "org.example.other.*", "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_class_double_wildcard() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + classes { + 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_class() { + buildFile { + """ + assertJ { + entryPoints { + classPackage = "org.example" + } + classes { + 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() + } + + 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() + } + } +} \ No newline at end of file diff --git a/src/test/groovy/org/assertj/generator/gradle/parameter/PackageFilter.groovy b/src/test/groovy/org/assertj/generator/gradle/parameter/PackageFilter.groovy index 3c6120a..26dc5f7 100644 --- a/src/test/groovy/org/assertj/generator/gradle/parameter/PackageFilter.groovy +++ b/src/test/groovy/org/assertj/generator/gradle/parameter/PackageFilter.groovy @@ -27,7 +27,7 @@ import java.util.function.Supplier import static org.assertj.core.api.Assertions.assertThat /** - * Checks the behaviour of overriding globals in a project + * Checks that we can include/exclude classes via the `packages` filter. */ class PackageFilter {