From 826b909d7300cec81c29b416c12a9c31172abe94 Mon Sep 17 00:00:00 2001 From: Sergey Shanshin Date: Mon, 26 Jun 2023 00:33:07 +0300 Subject: [PATCH] Added ability to generate reports even if there are no tests in the project Resolves #409 PR #411 --- .../test/functional/cases/AccessorsTests.kt | 6 +-- .../functional/cases/MultiProjectTests.kt | 22 +++----- .../functional/cases/NoTestReportsTests.kt | 52 +++++++++++++++++++ .../functional/cases/TaskFilteringTests.kt | 6 +-- .../functional/framework/checker/Checker.kt | 4 +- .../framework/runner/StepsRunner.kt | 2 +- .../functional/framework/starter/Commons.kt | 2 +- .../builds/no-tests-jvm/build.gradle.kts | 18 +++++++ .../builds/no-tests-jvm/settings.gradle.kts | 8 +++ .../src/main/kotlin/ExampleClass.kt | 17 ++++++ .../builds/no-tests-mpp/build.gradle.kts | 27 ++++++++++ .../builds/no-tests-mpp/settings.gradle.kts | 8 +++ .../src/jvmMain/kotlin/ExampleClass.kt | 17 ++++++ .../appliers/reports/ReportsVariantApplier.kt | 3 -- .../tasks/reports/AbstractKoverReportTask.kt | 8 --- 15 files changed, 164 insertions(+), 36 deletions(-) create mode 100644 kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/NoTestReportsTests.kt create mode 100644 kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/build.gradle.kts create mode 100644 kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/settings.gradle.kts create mode 100644 kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/src/main/kotlin/ExampleClass.kt create mode 100644 kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/build.gradle.kts create mode 100644 kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/settings.gradle.kts create mode 100644 kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/src/jvmMain/kotlin/ExampleClass.kt diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/AccessorsTests.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/AccessorsTests.kt index f9a01a7a..96ebfbcf 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/AccessorsTests.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/AccessorsTests.kt @@ -43,9 +43,9 @@ internal class AccessorsTests { val result = build.runWithParams("custom") // skipped because there is no tests, but tasks are triggered - assertEquals("SKIPPED", result.taskOutcome(":koverXmlReport")) - assertEquals("SKIPPED", result.taskOutcome(":koverHtmlReport")) - assertEquals("SKIPPED", result.taskOutcome(":koverVerify")) + assertEquals("SUCCESS", result.taskOutcome(":koverXmlReport")) + assertEquals("SUCCESS", result.taskOutcome(":koverHtmlReport")) + assertEquals("SUCCESS", result.taskOutcome(":koverVerify")) } @Test diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/MultiProjectTests.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/MultiProjectTests.kt index f1d53b5b..bbfd9c6f 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/MultiProjectTests.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/MultiProjectTests.kt @@ -58,7 +58,7 @@ internal class MultiProjectTests { } @SlicedGeneratedTest(allTypes = true, allTools = true) - fun BuildConfigurator.testDisabledKover() { + fun SlicedBuildConfigurator.testDisabledKover() { addProjectWithKover(subprojectPath) { sourcesFrom("multiproject-common") kover { @@ -76,16 +76,12 @@ internal class MultiProjectTests { run("koverXmlReport", "koverHtmlReport", "koverVerify") { checkDefaultBinReport(false) + taskNotCalled(defaultTestTaskName(slice.type)) - checkOutcome("koverHtmlReport", "SKIPPED") - checkOutcome("koverXmlReport", "SKIPPED") - checkOutcome("koverVerify", "SKIPPED") subproject(subprojectPath) { checkDefaultBinReport(false) - checkOutcome("koverHtmlReport", "SKIPPED") - checkOutcome("koverXmlReport", "SKIPPED") - checkOutcome("koverVerify", "SKIPPED") + taskNotCalled(defaultTestTaskName(slice.type)) } } } @@ -113,16 +109,14 @@ internal class MultiProjectTests { run("koverXmlReport", "koverHtmlReport", "koverVerify") { checkDefaultBinReport(false) - - checkOutcome("koverHtmlReport", "SKIPPED") - checkOutcome("koverXmlReport", "SKIPPED") - checkOutcome("koverVerify", "SKIPPED") + taskNotCalled(defaultTestTaskName(slice.type)) subproject(subprojectPath) { checkDefaultBinReport(false) - checkOutcome("koverHtmlReport", "SKIPPED") - checkOutcome("koverXmlReport", "SKIPPED") - checkOutcome("koverVerify", "SKIPPED") + taskNotCalled(defaultTestTaskName(slice.type)) + checkOutcome("koverXmlReport", "SUCCESS") + checkOutcome("koverHtmlReport", "SUCCESS") + checkOutcome("koverVerify", "SUCCESS") } } } diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/NoTestReportsTests.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/NoTestReportsTests.kt new file mode 100644 index 00000000..7d69f8e2 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/NoTestReportsTests.kt @@ -0,0 +1,52 @@ +package kotlinx.kover.gradle.plugin.test.functional.cases + +import kotlinx.kover.gradle.plugin.test.functional.framework.checker.createCheckerContext +import kotlinx.kover.gradle.plugin.test.functional.framework.checker.defaultXmlReport +import kotlinx.kover.gradle.plugin.test.functional.framework.runner.BuildSource +import kotlinx.kover.gradle.plugin.test.functional.framework.runner.buildFromTemplate +import kotlinx.kover.gradle.plugin.test.functional.framework.runner.runWithParams +import org.junit.jupiter.api.Test +import kotlin.test.assertContains +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class NoTestReportsTests { + @Test + fun testNoTestsJvm() { + val build = buildFromTemplate("no-tests-jvm") + check(build) + checkWithVerify(build) + } + + @Test + fun testNoTestsMpp() { + val build = buildFromTemplate("no-tests-mpp") + check(build) + checkWithVerify(build) + } + + private fun check(buildSource: BuildSource) { + val build = buildSource.generate() + val buildResult = build.runWithParams("koverXmlReport", "koverHtmlReport") + val checkerContext = build.createCheckerContext(buildResult) + + checkerContext.xml(defaultXmlReport()) { + classCounter("kotlinx.kover.templates.ExampleClass").assertFullyMissed() + } + assertTrue(buildResult.isSuccessful) + } + + private fun checkWithVerify(buildSource: BuildSource) { + val build = buildSource.generate() + val buildResult = build.runWithParams("koverXmlReport", "koverHtmlReport", "koverVerify") + val checkerContext = build.createCheckerContext(buildResult) + + checkerContext.xml(defaultXmlReport()) { + classCounter("kotlinx.kover.templates.ExampleClass").assertFullyMissed() + } + checkerContext.checkHtmlReport() + assertFalse(buildResult.isSuccessful) + assertContains(buildResult.output, "Rule violated: lines covered percentage is 0.000000, but expected minimum is 50") + } + +} \ No newline at end of file diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/TaskFilteringTests.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/TaskFilteringTests.kt index 8dac37da..19b38c17 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/TaskFilteringTests.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/TaskFilteringTests.kt @@ -28,12 +28,10 @@ internal class TaskFilteringTests { // compile tasks must be invoked checkOutcome("compileKotlin", "SUCCESS") checkOutcome("compileJava", "NO-SOURCE") - checkOutcome("koverXmlReport", "SKIPPED") + + taskNotCalled(defaultTestTaskName(slice.type)) // reason must be printed - taskOutput("koverXmlReport") { - contains("Task '${defaultTestTaskName(slice.type)}' will be skipped because no tests were executed") - } // if task `test` is excluded from instrumentation then the binary report is not created for it checkDefaultBinReport(false) diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/Checker.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/Checker.kt index 4c11ba0c..04bd1b3c 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/Checker.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/Checker.kt @@ -30,8 +30,8 @@ internal inline fun CheckerContext.check( } } -internal fun File.createCheckerContext(result: BuildResult): CheckerContext { - return CheckerContextImpl(this.analyzeProject(), result) +internal fun GradleBuild.createCheckerContext(result: BuildResult): CheckerContext { + return CheckerContextImpl(this.targetDir.analyzeProject(), result) } internal fun File.analyzeProject(): ProjectAnalysisData { diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/StepsRunner.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/StepsRunner.kt index fb41745c..a797d14c 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/StepsRunner.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/StepsRunner.kt @@ -26,7 +26,7 @@ ${this.targetDir.buildScript()} when (step) { is TestGradleStep -> { val runResult = this.runWithParams(step.args) - targetDir.createCheckerContext(runResult).check(description, step.errorExpected, step.checker) + createCheckerContext(runResult).check(description, step.errorExpected, step.checker) } is TestFileEditStep -> { diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/starter/Commons.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/starter/Commons.kt index ccd118a5..e99da684 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/starter/Commons.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/starter/Commons.kt @@ -33,7 +33,7 @@ internal abstract class DirectoryBasedGradleTest : BeforeTestExecutionCallback, logInfo("Before building ${args.buildSource.buildType} '${args.buildSource.buildName}' in target directory ${build.targetDir.uri}") val runResult = build.runWithParams(args.gradleArgs) - val checkerContext = build.targetDir.createCheckerContext(runResult) + val checkerContext = build.createCheckerContext(runResult) val store = context.getStore(ExtensionContext.Namespace.GLOBAL) store.put(DIR_PARAM, build) diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/build.gradle.kts b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/build.gradle.kts new file mode 100644 index 00000000..6cc411df --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + kotlin("jvm") version "1.8.20" + id("org.jetbrains.kotlinx.kover") +} + +repositories { + mavenCentral() +} + +koverReport { + defaults { + verify { + rule { + minBound(50) + } + } + } +} diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/settings.gradle.kts b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/settings.gradle.kts new file mode 100644 index 00000000..a423f19c --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/settings.gradle.kts @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + +rootProject.name = "no-tests-jvm" diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/src/main/kotlin/ExampleClass.kt b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/src/main/kotlin/ExampleClass.kt new file mode 100644 index 00000000..559ac4bf --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-jvm/src/main/kotlin/ExampleClass.kt @@ -0,0 +1,17 @@ +package kotlinx.kover.templates + +class ExampleClass { + fun formatInt(i: Int): String { + if (i == 0) return "ZERO" + return if (i > 0) { + "POSITIVE=$i" + } else { + "NEGATIVE=${-i}" + } + } + + fun printClass() { + val name = this::class.qualifiedName + println(name) + } +} diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/build.gradle.kts b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/build.gradle.kts new file mode 100644 index 00000000..d690cf0a --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + kotlin("multiplatform") version ("1.8.20") + id ("org.jetbrains.kotlinx.kover") version "0.7.1" +} + +kotlin { + jvm() + jvmToolchain(8) +} + +repositories { + mavenCentral() +} + +/* + * Kover configs + */ + +koverReport { + defaults { + verify { + rule { + minBound(50) + } + } + } +} diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/settings.gradle.kts b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/settings.gradle.kts new file mode 100644 index 00000000..f889e56d --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/settings.gradle.kts @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + +rootProject.name = "no-tests-mpp" diff --git a/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/src/jvmMain/kotlin/ExampleClass.kt b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/src/jvmMain/kotlin/ExampleClass.kt new file mode 100644 index 00000000..559ac4bf --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/builds/no-tests-mpp/src/jvmMain/kotlin/ExampleClass.kt @@ -0,0 +1,17 @@ +package kotlinx.kover.templates + +class ExampleClass { + fun formatInt(i: Int): String { + if (i == 0) return "ZERO" + return if (i > 0) { + "POSITIVE=$i" + } else { + "NEGATIVE=${-i}" + } + } + + fun printClass() { + val name = this::class.qualifiedName + println(name) + } +} diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/reports/ReportsVariantApplier.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/reports/ReportsVariantApplier.kt index c971ffad..42bf517d 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/reports/ReportsVariantApplier.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/reports/ReportsVariantApplier.kt @@ -187,9 +187,6 @@ internal sealed class ReportsVariantApplier( localArtifact.set(artifactGenTask.flatMap { task -> task.artifactFile }) externalArtifacts.from(dependencies) - - // task can't be executed if where is no raw report files (no any executed test task) - onlyIf { hasBinReportsAndLog() } } return task } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt index 71137fa9..102ed4f6 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt @@ -92,14 +92,6 @@ internal abstract class AbstractKoverReportTask : DefaultTask() { @get:Inject protected abstract val workerExecutor: WorkerExecutor - fun hasBinReportsAndLog(): Boolean { - val hasReports = collectAllFiles().reports.isNotEmpty() - if (!hasReports) { - logger.lifecycle("Task '$name' will be skipped because no tests were executed") - } - return hasReports - } - protected fun context(): ReportContext { val services = GradleReportServices(workerExecutor, ant, obj) return ReportContext(collectAllFiles(), filters.get(), reportClasspath, temporaryDir, projectPath, services)