From 0507790d7b5292dcb932632b7372d1a93082da88 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 29 Jan 2024 14:59:30 +0100 Subject: [PATCH] Support Java package-level annotations (#175) * Support Java package-level annotations. Closes #156 --- api/binary-compatibility-validator.api | 1 + .../validation/test/NonPublicMarkersTest.kt | 36 +++++++++++++++++++ .../validation/test/PublicMarkersTest.kt | 36 +++++++++++++++++++ .../examples/classes/AnnotatedPackage.dump | 6 ++++ .../classes/ClassFromAnnotatedPackage.kt | 9 +++++ .../examples/classes/PackageAnnotation.java | 11 ++++++ .../examples/classes/package-info.java | 2 ++ .../nonPublicMarkers/packages.gradle.kts | 8 +++++ .../publicMarkers/packages.gradle.kts | 8 +++++ src/main/kotlin/KotlinApiBuildTask.kt | 7 ++-- src/main/kotlin/api/AsmMetadataLoading.kt | 2 ++ .../kotlin/api/KotlinMetadataSignature.kt | 2 ++ .../kotlin/api/KotlinSignaturesLoading.kt | 27 ++++++++++++++ .../cases/packageAnnotations/PrivateApi.kt | 9 +++++ .../packageAnnotations/a/a/ShouldBeDeleted.kt | 9 +++++ .../packageAnnotations/a/b/ShouldBeDeleted.kt | 9 +++++ .../packageAnnotations/a/package-info.java | 9 +++++ .../packageAnnotations/b/a/ShouldBeDeleted.kt | 9 +++++ .../packageAnnotations/b/a/package-info.java | 9 +++++ .../b/b/ShouldRemainPublic.kt | 9 +++++ .../packageAnnotations/b/b/package-info.java | 6 ++++ .../b/c/shouldRemainPublic.kt | 14 ++++++++ .../packageAnnotations/packageAnnotations.txt | 8 +++++ src/test/kotlin/tests/CasesPublicAPITest.kt | 12 +++++-- 24 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 src/functionalTest/resources/examples/classes/AnnotatedPackage.dump create mode 100644 src/functionalTest/resources/examples/classes/ClassFromAnnotatedPackage.kt create mode 100644 src/functionalTest/resources/examples/classes/PackageAnnotation.java create mode 100644 src/functionalTest/resources/examples/classes/package-info.java create mode 100644 src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/packages.gradle.kts create mode 100644 src/functionalTest/resources/examples/gradle/configuration/publicMarkers/packages.gradle.kts create mode 100644 src/test/kotlin/cases/packageAnnotations/PrivateApi.kt create mode 100644 src/test/kotlin/cases/packageAnnotations/a/a/ShouldBeDeleted.kt create mode 100644 src/test/kotlin/cases/packageAnnotations/a/b/ShouldBeDeleted.kt create mode 100644 src/test/kotlin/cases/packageAnnotations/a/package-info.java create mode 100644 src/test/kotlin/cases/packageAnnotations/b/a/ShouldBeDeleted.kt create mode 100644 src/test/kotlin/cases/packageAnnotations/b/a/package-info.java create mode 100644 src/test/kotlin/cases/packageAnnotations/b/b/ShouldRemainPublic.kt create mode 100644 src/test/kotlin/cases/packageAnnotations/b/b/package-info.java create mode 100644 src/test/kotlin/cases/packageAnnotations/b/c/shouldRemainPublic.kt create mode 100644 src/test/kotlin/cases/packageAnnotations/packageAnnotations.txt diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index f78331ac..f226963e 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -67,6 +67,7 @@ public final class kotlinx/validation/api/ClassBinarySignature { public final class kotlinx/validation/api/KotlinSignaturesLoadingKt { public static final fun dump (Ljava/util/List;)Ljava/io/PrintStream; public static final fun dump (Ljava/util/List;Ljava/lang/Appendable;)Ljava/lang/Appendable; + public static final fun extractAnnotatedPackages (Ljava/util/List;Ljava/util/Set;)Ljava/util/List; public static final fun filterOutAnnotated (Ljava/util/List;Ljava/util/Set;)Ljava/util/List; public static final fun filterOutNonPublic (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;)Ljava/util/List; public static synthetic fun filterOutNonPublic$default (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;ILjava/lang/Object;)Ljava/util/List; diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/NonPublicMarkersTest.kt b/src/functionalTest/kotlin/kotlinx/validation/test/NonPublicMarkersTest.kt index a263d350..689d9384 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/NonPublicMarkersTest.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/NonPublicMarkersTest.kt @@ -6,7 +6,9 @@ package kotlinx.validation.test import kotlinx.validation.api.* +import org.assertj.core.api.Assertions import org.junit.* +import kotlin.test.assertTrue class NonPublicMarkersTest : BaseKotlinGradleTest() { @@ -35,4 +37,38 @@ class NonPublicMarkersTest : BaseKotlinGradleTest() { assertTaskSuccess(":apiCheck") } } + + @Test + fun testFiltrationByPackageLevelAnnotations() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/nonPublicMarkers/packages.gradle.kts") + } + java("annotated/PackageAnnotation.java") { + resolve("/examples/classes/PackageAnnotation.java") + } + java("annotated/package-info.java") { + resolve("/examples/classes/package-info.java") + } + kotlin("ClassFromAnnotatedPackage.kt") { + resolve("/examples/classes/ClassFromAnnotatedPackage.kt") + } + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + runner { + arguments.add(":apiDump") + } + } + + runner.build().apply { + assertTaskSuccess(":apiDump") + + assertTrue(rootProjectApiDump.exists(), "api dump file should exist") + + val expected = readFileList("/examples/classes/AnotherBuildConfig.dump") + Assertions.assertThat(rootProjectApiDump.readText()).isEqualToIgnoringNewLines(expected) + } + } } diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/PublicMarkersTest.kt b/src/functionalTest/kotlin/kotlinx/validation/test/PublicMarkersTest.kt index 43fe5fc7..cf4963b8 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/PublicMarkersTest.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/PublicMarkersTest.kt @@ -10,7 +10,9 @@ import kotlinx.validation.api.buildGradleKts import kotlinx.validation.api.kotlin import kotlinx.validation.api.resolve import kotlinx.validation.api.test +import org.assertj.core.api.Assertions import org.junit.Test +import kotlin.test.assertTrue class PublicMarkersTest : BaseKotlinGradleTest() { @@ -43,4 +45,38 @@ class PublicMarkersTest : BaseKotlinGradleTest() { assertTaskSuccess(":apiCheck") } } + + @Test + fun testFiltrationByPackageLevelAnnotations() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/publicMarkers/packages.gradle.kts") + } + java("annotated/PackageAnnotation.java") { + resolve("/examples/classes/PackageAnnotation.java") + } + java("annotated/package-info.java") { + resolve("/examples/classes/package-info.java") + } + kotlin("ClassFromAnnotatedPackage.kt") { + resolve("/examples/classes/ClassFromAnnotatedPackage.kt") + } + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + runner { + arguments.add(":apiDump") + } + } + + runner.build().apply { + assertTaskSuccess(":apiDump") + + assertTrue(rootProjectApiDump.exists(), "api dump file should exist") + + val expected = readFileList("/examples/classes/AnnotatedPackage.dump") + Assertions.assertThat(rootProjectApiDump.readText()).isEqualToIgnoringNewLines(expected) + } + } } diff --git a/src/functionalTest/resources/examples/classes/AnnotatedPackage.dump b/src/functionalTest/resources/examples/classes/AnnotatedPackage.dump new file mode 100644 index 00000000..252a9d14 --- /dev/null +++ b/src/functionalTest/resources/examples/classes/AnnotatedPackage.dump @@ -0,0 +1,6 @@ +public final class annotated/ClassFromAnnotatedPackage { + public fun ()V +} + +public abstract interface annotation class annotated/PackageAnnotation : java/lang/annotation/Annotation { +} diff --git a/src/functionalTest/resources/examples/classes/ClassFromAnnotatedPackage.kt b/src/functionalTest/resources/examples/classes/ClassFromAnnotatedPackage.kt new file mode 100644 index 00000000..a184ff56 --- /dev/null +++ b/src/functionalTest/resources/examples/classes/ClassFromAnnotatedPackage.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2016-2024 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package annotated + +class ClassFromAnnotatedPackage { +} diff --git a/src/functionalTest/resources/examples/classes/PackageAnnotation.java b/src/functionalTest/resources/examples/classes/PackageAnnotation.java new file mode 100644 index 00000000..6e419854 --- /dev/null +++ b/src/functionalTest/resources/examples/classes/PackageAnnotation.java @@ -0,0 +1,11 @@ +package annotated; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PACKAGE) +public @interface PackageAnnotation { +} diff --git a/src/functionalTest/resources/examples/classes/package-info.java b/src/functionalTest/resources/examples/classes/package-info.java new file mode 100644 index 00000000..b15e17df --- /dev/null +++ b/src/functionalTest/resources/examples/classes/package-info.java @@ -0,0 +1,2 @@ +@PackageAnnotation +package annotated; diff --git a/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/packages.gradle.kts b/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/packages.gradle.kts new file mode 100644 index 00000000..061f7561 --- /dev/null +++ b/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/packages.gradle.kts @@ -0,0 +1,8 @@ +/* + * Copyright 2016-2024 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +configure { + nonPublicMarkers.add("annotated.PackageAnnotation") +} diff --git a/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/packages.gradle.kts b/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/packages.gradle.kts new file mode 100644 index 00000000..92c5ba27 --- /dev/null +++ b/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/packages.gradle.kts @@ -0,0 +1,8 @@ +/* + * Copyright 2016-2024 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +configure { + publicMarkers.add("annotated.PackageAnnotation") +} diff --git a/src/main/kotlin/KotlinApiBuildTask.kt b/src/main/kotlin/KotlinApiBuildTask.kt index 6809b6d1..0df744bb 100644 --- a/src/main/kotlin/KotlinApiBuildTask.kt +++ b/src/main/kotlin/KotlinApiBuildTask.kt @@ -95,10 +95,13 @@ public open class KotlinApiBuildTask @Inject constructor( throw GradleException("KotlinApiBuildTask should have either inputClassesDirs, or inputJar property set") } + val publicPackagesNames = signatures.extractAnnotatedPackages(publicMarkers.map(::replaceDots).toSet()) + val ignoredPackagesNames = signatures.extractAnnotatedPackages(nonPublicMarkers.map(::replaceDots).toSet()) val filteredSignatures = signatures - .retainExplicitlyIncludedIfDeclared(publicPackages, publicClasses, publicMarkers) - .filterOutNonPublic(ignoredPackages, ignoredClasses) + .retainExplicitlyIncludedIfDeclared(publicPackages + publicPackagesNames, + publicClasses, publicMarkers) + .filterOutNonPublic(ignoredPackages + ignoredPackagesNames, ignoredClasses) .filterOutAnnotated(nonPublicMarkers.map(::replaceDots).toSet()) outputApiDir.resolve("$projectName.api").bufferedWriter().use { writer -> diff --git a/src/main/kotlin/api/AsmMetadataLoading.kt b/src/main/kotlin/api/AsmMetadataLoading.kt index caba4bc9..b8f473de 100644 --- a/src/main/kotlin/api/AsmMetadataLoading.kt +++ b/src/main/kotlin/api/AsmMetadataLoading.kt @@ -26,6 +26,8 @@ internal fun isProtected(access: Int) = access and Opcodes.ACC_PROTECTED != 0 internal fun isStatic(access: Int) = access and Opcodes.ACC_STATIC != 0 internal fun isFinal(access: Int) = access and Opcodes.ACC_FINAL != 0 internal fun isSynthetic(access: Int) = access and Opcodes.ACC_SYNTHETIC != 0 +internal fun isAbstract(access: Int) = access and Opcodes.ACC_ABSTRACT != 0 +internal fun isInterface(access: Int) = access and Opcodes.ACC_INTERFACE != 0 internal fun ClassNode.isEffectivelyPublic(classVisibility: ClassVisibility?) = isPublic(access) diff --git a/src/main/kotlin/api/KotlinMetadataSignature.kt b/src/main/kotlin/api/KotlinMetadataSignature.kt index e69ffb83..93f571b9 100644 --- a/src/main/kotlin/api/KotlinMetadataSignature.kt +++ b/src/main/kotlin/api/KotlinMetadataSignature.kt @@ -157,6 +157,8 @@ internal data class AccessFlags(val access: Int) { val isStatic: Boolean get() = isStatic(access) val isFinal: Boolean get() = isFinal(access) val isSynthetic: Boolean get() = isSynthetic(access) + val isAbstract: Boolean get() = isAbstract(access) + val isInterface: Boolean get() = isInterface(access) private fun getModifiers(): List = ACCESS_NAMES.entries.mapNotNull { if (access and it.key != 0) it.value else null } diff --git a/src/main/kotlin/api/KotlinSignaturesLoading.kt b/src/main/kotlin/api/KotlinSignaturesLoading.kt index 5cdc7567..ebefee00 100644 --- a/src/main/kotlin/api/KotlinSignaturesLoading.kt +++ b/src/main/kotlin/api/KotlinSignaturesLoading.kt @@ -218,6 +218,33 @@ private fun List.filterOutNotAnnotated( } } +/** + * Extracts name of packages annotated by one of the [targetAnnotations]. + * If there are no such packages, returns an empty list. + * + * Package is checked for being annotated by looking at classes with `package-info` name + * ([see JSL 7.4.1](https://docs.oracle.com/javase/specs/jls/se21/html/jls-7.html#jls-7.4) + * for details about `package-info`). + */ +@ExternalApi +public fun List.extractAnnotatedPackages(targetAnnotations: Set): List { + if (targetAnnotations.isEmpty()) return emptyList() + + return filter { + it.name.endsWith("/package-info") + }.filter { + // package-info classes are private synthetic abstract interfaces since 2005 (JDK-6232928). + it.access.isInterface && it.access.isSynthetic && it.access.isAbstract + }.filter { + it.annotations.any { + ann -> targetAnnotations.any { ann.refersToName(it) } + } + }.map { + val res = it.name.substring(0, it.name.length - "/package-info".length) + res + } +} + @ExternalApi public fun List.filterOutNonPublic( nonPublicPackages: Collection = emptyList(), diff --git a/src/test/kotlin/cases/packageAnnotations/PrivateApi.kt b/src/test/kotlin/cases/packageAnnotations/PrivateApi.kt new file mode 100644 index 00000000..50e8ba5f --- /dev/null +++ b/src/test/kotlin/cases/packageAnnotations/PrivateApi.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package cases.packageAnnotations + +@PrivateApi +annotation class PrivateApi diff --git a/src/test/kotlin/cases/packageAnnotations/a/a/ShouldBeDeleted.kt b/src/test/kotlin/cases/packageAnnotations/a/a/ShouldBeDeleted.kt new file mode 100644 index 00000000..6e1369a5 --- /dev/null +++ b/src/test/kotlin/cases/packageAnnotations/a/a/ShouldBeDeleted.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package cases.packageAnnotations.a.a + +public class ShouldBeDeleted { +} diff --git a/src/test/kotlin/cases/packageAnnotations/a/b/ShouldBeDeleted.kt b/src/test/kotlin/cases/packageAnnotations/a/b/ShouldBeDeleted.kt new file mode 100644 index 00000000..44ab25ef --- /dev/null +++ b/src/test/kotlin/cases/packageAnnotations/a/b/ShouldBeDeleted.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package cases.packageAnnotations.a.b + +public class ShouldBeDeleted { +} diff --git a/src/test/kotlin/cases/packageAnnotations/a/package-info.java b/src/test/kotlin/cases/packageAnnotations/a/package-info.java new file mode 100644 index 00000000..bc4fb96d --- /dev/null +++ b/src/test/kotlin/cases/packageAnnotations/a/package-info.java @@ -0,0 +1,9 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@PrivateApi +package cases.packageAnnotations.a; + +import cases.packageAnnotations.PrivateApi; diff --git a/src/test/kotlin/cases/packageAnnotations/b/a/ShouldBeDeleted.kt b/src/test/kotlin/cases/packageAnnotations/b/a/ShouldBeDeleted.kt new file mode 100644 index 00000000..00705801 --- /dev/null +++ b/src/test/kotlin/cases/packageAnnotations/b/a/ShouldBeDeleted.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package cases.packageAnnotations.b.a + +public class ShouldBeDeleted { +} diff --git a/src/test/kotlin/cases/packageAnnotations/b/a/package-info.java b/src/test/kotlin/cases/packageAnnotations/b/a/package-info.java new file mode 100644 index 00000000..3170ac2b --- /dev/null +++ b/src/test/kotlin/cases/packageAnnotations/b/a/package-info.java @@ -0,0 +1,9 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@PrivateApi @Deprecated +package cases.packageAnnotations.b.a; + +import cases.packageAnnotations.PrivateApi; diff --git a/src/test/kotlin/cases/packageAnnotations/b/b/ShouldRemainPublic.kt b/src/test/kotlin/cases/packageAnnotations/b/b/ShouldRemainPublic.kt new file mode 100644 index 00000000..0f350a25 --- /dev/null +++ b/src/test/kotlin/cases/packageAnnotations/b/b/ShouldRemainPublic.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package cases.packageAnnotations.b.b + +public class ShouldRemainPublic { +} diff --git a/src/test/kotlin/cases/packageAnnotations/b/b/package-info.java b/src/test/kotlin/cases/packageAnnotations/b/b/package-info.java new file mode 100644 index 00000000..d024a35a --- /dev/null +++ b/src/test/kotlin/cases/packageAnnotations/b/b/package-info.java @@ -0,0 +1,6 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package cases.packageAnnotations.b.b; diff --git a/src/test/kotlin/cases/packageAnnotations/b/c/shouldRemainPublic.kt b/src/test/kotlin/cases/packageAnnotations/b/c/shouldRemainPublic.kt new file mode 100644 index 00000000..310d8840 --- /dev/null +++ b/src/test/kotlin/cases/packageAnnotations/b/c/shouldRemainPublic.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2016-2024 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package cases.packageAnnotations.b.c + +import cases.packageAnnotations.PrivateApi + +@PrivateApi +interface `package-info` { +} + +class ShouldNotBeRemoved diff --git a/src/test/kotlin/cases/packageAnnotations/packageAnnotations.txt b/src/test/kotlin/cases/packageAnnotations/packageAnnotations.txt new file mode 100644 index 00000000..d63454ac --- /dev/null +++ b/src/test/kotlin/cases/packageAnnotations/packageAnnotations.txt @@ -0,0 +1,8 @@ +public final class cases/packageAnnotations/b/b/ShouldRemainPublic { + public fun ()V +} + +public final class cases/packageAnnotations/b/c/ShouldNotBeRemoved { + public fun ()V +} + diff --git a/src/test/kotlin/tests/CasesPublicAPITest.kt b/src/test/kotlin/tests/CasesPublicAPITest.kt index f5147f02..d7949b0e 100644 --- a/src/test/kotlin/tests/CasesPublicAPITest.kt +++ b/src/test/kotlin/tests/CasesPublicAPITest.kt @@ -9,6 +9,9 @@ import kotlinx.validation.api.* import org.junit.* import org.junit.rules.TestName import java.io.File +import java.nio.file.Path +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.walk class CasesPublicAPITest { @@ -45,6 +48,8 @@ class CasesPublicAPITest { @Test fun nestedClasses() { snapshotAPIAndCompare(testName.methodName) } + @Test fun packageAnnotations() { snapshotAPIAndCompare(testName.methodName, setOf("cases/packageAnnotations/PrivateApi")) } + @Test fun private() { snapshotAPIAndCompare(testName.methodName) } @Test fun protected() { snapshotAPIAndCompare(testName.methodName) } @@ -57,13 +62,16 @@ class CasesPublicAPITest { @Test fun enums() { snapshotAPIAndCompare(testName.methodName) } + @OptIn(ExperimentalPathApi::class) private fun snapshotAPIAndCompare(testClassRelativePath: String, nonPublicMarkers: Set = emptySet()) { val testClassPaths = baseClassPaths.map { it.resolve(testClassRelativePath) } - val testClasses = testClassPaths.flatMap { it.listFiles().orEmpty().asIterable() } + val testClasses = testClassPaths.flatMap { it.toPath().walk().map(Path::toFile) } check(testClasses.isNotEmpty()) { "No class files are found in paths: $testClassPaths" } val testClassStreams = testClasses.asSequence().filter { it.name.endsWith(".class") }.map { it.inputStream() } - val api = testClassStreams.loadApiFromJvmClasses().filterOutNonPublic().filterOutAnnotated(nonPublicMarkers) + val classes = testClassStreams.loadApiFromJvmClasses() + val additionalPackages = classes.extractAnnotatedPackages(nonPublicMarkers) + val api = classes.filterOutNonPublic(nonPublicPackages = additionalPackages).filterOutAnnotated(nonPublicMarkers) val target = baseOutputPath.resolve(testClassRelativePath).resolve(testName.methodName + ".txt") api.dumpAndCompareWith(target) }