diff --git a/.gitignore b/.gitignore index a23c3d61..cc473296 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,6 @@ ehthumbs.db ehthumbs_vista.db # Folder config file -[Dd]esktop.ini \ No newline at end of file +[Dd]esktop.ini + +out/ diff --git a/gradle/versions.gradle b/gradle/versions.gradle index ccb6080f..17609e69 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -83,4 +83,8 @@ ext { mockitoLibraryVersion = "1.2" dexMakerLibraryVersion = "1.2" //#### The ABOVE are TSL version variables - END + + kotlinVersion = '1.5.30' + spotBugsGradlePluginVersion = '4.7.1' + jupiterApiVersion = '5.6.0' } diff --git a/plugins/buildsystem/build.gradle b/plugins/buildsystem/build.gradle index 3c62740d..6bd9cdaf 100644 --- a/plugins/buildsystem/build.gradle +++ b/plugins/buildsystem/build.gradle @@ -1,11 +1,16 @@ +buildscript { + apply from: rootProject.file("gradle/versions.gradle") +} + plugins { id 'java-gradle-plugin' + id 'org.jetbrains.kotlin.jvm' version '1.5.30' id 'maven-publish' id 'com.gradle.plugin-publish' version '0.14.0' } group 'com.microsoft.identity' -version '0.1.1' +version '0.2.0' pluginBundle { @@ -32,10 +37,14 @@ repositories { } dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' + testImplementation "org.junit.jupiter:junit-jupiter-api:${rootProject.ext.jupiterApiVersion}" testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' - implementation 'com.android.tools.build:gradle:4.1.0' - implementation 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.7.1' + implementation "com.android.tools.build:gradle:${rootProject.ext.gradleVersion}" + implementation "gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:${rootProject.ext.spotBugsGradlePluginVersion}" + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${rootProject.ext.kotlinVersion}" + testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${rootProject.ext.kotlinVersion}" + testImplementation "org.jetbrains.kotlin:kotlin-test:${rootProject.ext.kotlinVersion}" } test { diff --git a/plugins/buildsystem/docs/CodeCoverage.md b/plugins/buildsystem/docs/CodeCoverage.md new file mode 100644 index 00000000..5ed60491 --- /dev/null +++ b/plugins/buildsystem/docs/CodeCoverage.md @@ -0,0 +1,63 @@ +# Code Coverage Plugin + + +## Intro +- [Code coverage](https://en.wikipedia.org/wiki/Code_coverage) is a software metric used to measure how many lines of our code are executed during automated tests. + +- [JaCoCo](https://www.eclemma.org/jacoco/trunk/index.html) is a free Java code coverage tool and the [JaCoCo plugin](https://docs.gradle.org/current/userguide/jacoco_plugin.html) provides code coverage metrics for Java code via integration with JaCoCo. + +## Why +In order to generate [JaCoCo](https://www.jacoco.org/jacoco/trunk/doc/index.html) unit test coverage reports for Android projects you need to create `JacocoReport` tasks and configure them by providing paths to source code, execution data and compiled classes. It's not straightforward since Android projects can have different flavors and build types thus requiring additional paths to be set. This plugin configures these `JacocoReport` tasks automatically. + +## Usage +```groovy +plugins { + ... + id 'com.gradle.plugin-publish' version '0.14.0' // or whatever version is most recent +} + +codeCoverageReport{ + html.enabled = true // by default it's true + xml.enabled = true // by default it's true + csv.enabled = true // by default it's true + + unitTests.enabled = true // whether code coverage tasks for unit tests will be generated + androidTests.enabled = true // whether code coverage tasks for instrumentation tests will be generated + + excludeFlavors = [''] // the product flavors to exclude when generating the code coverage tasks + + excludeClasses = [''] // additional classes to exclude - most are already catered for + + destination = '/some/other/directory' // if you want to configure a custom path to save the code coverage reports, by default your report gets saved in `[project]/build/jacoco/{flavor}{build type}{project}{test type}CoverageReport` + + includeNoLocationClasses = true // To include Robolectric tests in the Jacoco report this needs to be true +} + +android { + buildTypes { + debug { + testCoverageEnabled true // this instructs the plugin to generate code coverage reports for this build type + ... + } + release { + testCoverageEnabled false // this instructs the plugin to NOT generate code coverage reports for this build type + ... + } + } + ... + productFlavors { + local {} + dist {} + } +} +``` + +The above configuration creates a `JacocoReport` task for each variant in the form of `{flavor}{build type}{project}{test type}CoverageReport` +``` +distDebugAppAndroidTestCoverageReport +distDebugAppUnitTestCoverageReport +localDebugAppAndroidTestCoverageReport +localDebugAppUnitTestCoverageReport +``` + +By default these are the excluded classes under [Constants](https://github.com/AzureAD/android-complete/blob/paul/code-coverage-plugin/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/codecov/Constants.kt#L25) diff --git a/plugins/buildsystem/gradle/versions.gradle b/plugins/buildsystem/gradle/versions.gradle new file mode 100644 index 00000000..16185145 --- /dev/null +++ b/plugins/buildsystem/gradle/versions.gradle @@ -0,0 +1,7 @@ +// Variables for plugins project +ext { + kotlinVersion = '1.5.30' + spotBugsGradlePluginVersion = '4.7.1' + jupiterApiVersion = '5.6.0' + gradleVersion = '4.1.0' +} diff --git a/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/BuildPlugin.java b/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/BuildPlugin.java index 47d66c2f..32216c28 100644 --- a/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/BuildPlugin.java +++ b/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/BuildPlugin.java @@ -23,6 +23,7 @@ package com.microsoft.identity.buildsystem; import com.android.build.gradle.LibraryExtension; +import com.microsoft.identity.buildsystem.codecov.CodeCoverage; import org.gradle.api.JavaVersion; import org.gradle.api.Plugin; import org.gradle.api.Project; @@ -52,6 +53,8 @@ public void apply(final Project project) { }); SpotBugs.applySpotBugsPlugin(project); + + CodeCoverage.applyCodeCoveragePlugin(project); } private void applyDesugaringToAndroidProject(final Project project){ diff --git a/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/codecov/CodeCoverage.kt b/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/codecov/CodeCoverage.kt new file mode 100644 index 00000000..7912e7f5 --- /dev/null +++ b/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/codecov/CodeCoverage.kt @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.buildsystem.codecov + +import com.android.build.gradle.api.BaseVariant +import com.android.build.gradle.api.SourceKind +import org.gradle.api.Project +import org.gradle.api.tasks.testing.Test +import org.gradle.testing.jacoco.plugins.JacocoPlugin +import org.gradle.testing.jacoco.plugins.JacocoPluginExtension +import org.gradle.testing.jacoco.plugins.JacocoTaskExtension +import org.gradle.testing.jacoco.tasks.JacocoReport +import java.io.File + +/** + * This class creates code coverage tasks in the given project + */ +object CodeCoverage { + + private lateinit var reportExtension: CodeCoverageReportExtension + + /** + * Gets the codeCoverageReport configurations and uses them to create the code coverage tasks + */ + @JvmStatic + fun applyCodeCoveragePlugin(project: Project) { + // get the configurations under codeCoverageReport + reportExtension = project.extensions.create("codeCoverageReport", CodeCoverageReportExtension::class.java) + + // after build file has been evaluated ... add tasks + project.afterEvaluate { evaluatedProject -> + evaluatedProject.configure() + + if (isAndroidProject(evaluatedProject)) { + addJacocoToAndroid(evaluatedProject) + } else if (isJavaProject(evaluatedProject) || isKotlinMultiplatform(evaluatedProject)) { + addJacocoToJava(evaluatedProject) + } + } + } + + /** + * Apply some plugin configuration from [CodeCoverageReportExtension] to the project. + */ + private fun Project.configure() { + project.plugins.apply(JacocoPlugin::class.java) + + project.extensions.findByType(JacocoPluginExtension::class.java)?.apply { + toolVersion = reportExtension.jacocoVersion + } + + tasks.withType(Test::class.java) { testTask -> + testTask.extensions.findByType(JacocoTaskExtension::class.java)?.apply { + // To include Robolectric tests in the Jacoco report, flag -> "includeNolocationClasses" should be set to true + isIncludeNoLocationClasses = reportExtension.includeNoLocationClasses + if (isIncludeNoLocationClasses) { + // This needs to be excluded for JDK 11 + // SEE: https://stackoverflow.com/questions/68065743/cannot-run-gradle-test-tasks-because-of-java-lang-noclassdeffounderror-jdk-inte + excludes = listOf("jdk.internal.*") + } + } + } + } + + /** + * add jacoco tasks to an android project + */ + private fun addJacocoToAndroid(project: Project) { + project.android().jacoco.version = reportExtension.jacocoVersion + + if (reportExtension.unitTests.enabled) { + createTasks(project, TestTypes.UnitTest) + } + + if (reportExtension.androidTests.enabled) { + createTasks(project, TestTypes.AndroidTest) + } + } + + /** + * Creates the code coverage tasks for the different build variants + */ + private fun createTasks(project: Project, testType: String) { + val excludeFlavors = (reportExtension.excludeFlavors ?: emptyList()).map { it.toLowerCase() } + project.android().variants().all { variant -> + if (shouldCreateTaskForVariant(excludeFlavors, variant, testType)) { + createReportTask(project, variant, testType) + } + } + } + + /** + * Creates the code coverage task for the given build variant and adds it to a group (Reporting) + */ + private fun createReportTask(project: Project, variant: BaseVariant, testType: String): JacocoReport { + // get the sources + val sourceDirs = variant.getSourceFolders(SourceKind.JAVA).map { file -> file.dir } + // get the classes + val classesDir = variant.javaCompileProvider.get().destinationDir + // get the test task for this variant + val testTask = getAndroidTestTask(project.tasks, variant, testType) + // get JacocoTaskExtension execution destination + val executionData = getAndroidExecutionDataFile(testTask, variant, testType) + + val taskName = "${variant.name}${project.name.capitalize()}${testType}CoverageReport" + return project.tasks.create(taskName, JacocoReport::class.java) { reportTask -> + // set the task attributes + reportTask.dependsOn(testTask) + reportTask.group = "Reporting" + reportTask.description = "Generates Jacoco coverage reports for the ${variant.name} variant." + reportTask.executionData.setFrom(project.filesTree(project.buildDir, includes = setOf(executionData))) + reportTask.sourceDirectories.setFrom(project.files(sourceDirs)) + + // get the java project tree and exclude the defined excluded classes + val javaTree = project.filesTree(classesDir, excludes = reportExtension.getFileFilterPatterns) + + // if kotlin is available, get the kotlin project tree and exclude the defined excluded classes + if (hasKotlin(project.plugins)) { + val kotlinClassesDir = "${project.buildDir}/tmp/kotlin-classes/${variant.name}" + val kotlinTree = project.filesTree(kotlinClassesDir, excludes = reportExtension.getFileFilterPatterns) + reportTask.classDirectories.setFrom(javaTree + kotlinTree) + } else { + reportTask.classDirectories.setFrom(javaTree) + } + + configureReport(project, reportTask, taskName) + } + } + + private fun addJacocoToJava(project: Project) { + val testTask = project.tasks.getByName("test") + val jacocoTestReportTask = project.tasks.getByName("jacocoTestReport") as JacocoReport + + val taskName = "${project.name.decapitalize()}UnitTestCoverageReport" + project.tasks.create(taskName, JacocoReport::class.java) { reportTask -> + // set the task attributes + reportTask.dependsOn(testTask) + reportTask.group = "Reporting" + reportTask.description = "Generates Jacoco coverage reports" + reportTask.executionData.setFrom(jacocoTestReportTask.executionData) + reportTask.sourceDirectories.setFrom(jacocoTestReportTask.sourceDirectories) + reportTask.additionalSourceDirs.setFrom(jacocoTestReportTask.additionalSourceDirs) + reportTask.classDirectories.setFrom(jacocoTestReportTask.classDirectories) + + // set destination + configureReport(project, reportTask, taskName) + } + } + + private fun configureReport(project: Project, reportTask: JacocoReport, taskName: String) { + reportTask.reports { task -> + // set the outputs enabled according to configs + task.html.isEnabled = reportExtension.html.enabled + task.xml.isEnabled = reportExtension.xml.enabled + task.csv.isEnabled = reportExtension.csv.enabled + + // default reports path + val defaultCommonPath = "${project.buildDir}/reports/jacoco/$taskName" + val configuredDestination = reportExtension.destination + + // configure destination for html code coverage output + if (reportExtension.html.enabled) { + val path = File(if (configuredDestination.isNullOrBlank()) "$defaultCommonPath/html" else "${configuredDestination.trim()}/html") + task.html.destination = path + } + + // configure destination for xml code coverage output + if (reportExtension.xml.enabled) { + val path = File(if (configuredDestination.isNullOrBlank()) "$defaultCommonPath/${taskName}.xml" else "${configuredDestination.trim()}/${taskName}.xml") + task.xml.destination = path + } + + // configure destination for csv code coverage output + if (reportExtension.csv.enabled) { + val path = File(if (configuredDestination.isNullOrBlank()) "$defaultCommonPath/${taskName}.csv" else "${configuredDestination.trim()}/${taskName}.csv") + task.csv.destination = path + } + } + } +} diff --git a/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/codecov/CodeCoverageReportExtension.kt b/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/codecov/CodeCoverageReportExtension.kt new file mode 100644 index 00000000..90263747 --- /dev/null +++ b/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/codecov/CodeCoverageReportExtension.kt @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.buildsystem.codecov + +/** + * This class controls some of the configurations used by the code coverage plugin. + */ +open class CodeCoverageReportExtension { + + var html = ReportConfig(true) // whether code coverage html output is enabled + var xml = ReportConfig(true) // whether code coverage xml output is enabled + var csv = ReportConfig(true) // whether code coverage csv output is enabled + + var unitTests = ReportConfig(true) // whether code coverage targets unit tests + var androidTests = ReportConfig(false) // whether code coverage targets android tests + + var destination: String? = null // the destination of the reports - by default it's buildDir/reports/jacoco + var excludeFlavors: Set? = null // add some product flavours to exclude + var excludeClasses: Set? = null // add some classes to exclude + + var includeNoLocationClasses: Boolean = true // To include Robolectric tests in the Jacoco report, flag -> "includeNolocationClasses" is set to true + + var jacocoVersion: String = "0.8.7" // jacoco version + + /** + * get files to exclude + */ + val getFileFilterPatterns: Set + get() { + return DEFAULT_EXCLUDES + (excludeClasses ?: emptySet()) + } + +} + +open class ReportConfig(var enabled: Boolean) diff --git a/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/codecov/Utils.kt b/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/codecov/Utils.kt new file mode 100644 index 00000000..6d5b2694 --- /dev/null +++ b/plugins/buildsystem/src/main/java/com/microsoft/identity/buildsystem/codecov/Utils.kt @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.buildsystem.codecov + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.api.BaseVariant +import org.gradle.api.DomainObjectSet +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.plugins.PluginContainer +import org.gradle.api.tasks.TaskContainer + +/** + * Check if we are allowed to create code coverage tasks for this variant + */ +fun shouldCreateTaskForVariant(excludeFlavours: List, variant: BaseVariant, testType: String): Boolean { + if (!variant.buildType.isTestCoverageEnabled || excludeFlavours.contains(variant.flavorName.toLowerCase())) { + return false + } + + // AFAIK, the android tests tasks are only generated for debug build types + if (testType == TestTypes.AndroidTest && variant.buildType.name != "debug") { + return false + } + + return true +} + +/** + * Get the test task for this variant + */ +fun getAndroidTestTask(tasks: TaskContainer, variant: BaseVariant, testType: String): Task { + val name = if (testType == TestTypes.UnitTest) "test${variant.name.capitalize()}UnitTest" else "connected${variant.name.capitalize()}AndroidTest" + return tasks.getByName(name) +} + +/** + * get JacocoTaskExtension execution destination + */ +fun getAndroidExecutionDataFile(testTask: Task, variant: BaseVariant, testType: String): String { + // Output of those additional tasks are stored in .exec file for unit tests and .ec file for android tests + val unitTestFile = "jacoco/${testTask.name}.exec" + val androidTestFile = "outputs/code_coverage/${variant.name}AndroidTest/connected/*.ec" + return if (testType == TestTypes.UnitTest) unitTestFile else androidTestFile +} + +fun getJavaExecutionDataFile(project: Project): String { + return if (isKotlinMultiplatform(project)) "${project.buildDir}/jacoco/jvmTest.exec" else "${project.buildDir}/jacoco/test.exec" +} + +/** + * check kotlin is available + */ +fun hasKotlin(plugins: PluginContainer) = plugins.hasPlugin("kotlin-android") + +fun isAndroidProject(project: Project): Boolean { + val isAndroidLibrary = project.plugins.hasPlugin("com.android.library") + val isAndroidApp = project.plugins.hasPlugin("com.android.application") + val isAndroidTest = project.plugins.hasPlugin("com.android.test") + val isAndroidFeature = project.plugins.hasPlugin("com.android.feature") + val isAndroidDynamicFeature = project.plugins.hasPlugin("com.android.dynamic-feature") + val isAndroidInstantApp = project.plugins.hasPlugin("com.android.instantapp") + return isAndroidLibrary || isAndroidApp || isAndroidTest || isAndroidFeature || isAndroidDynamicFeature || isAndroidInstantApp +} + +fun isJavaProject(project: Project): Boolean { + val isJava = project.plugins.hasPlugin("java") + val isJavaLibrary = project.plugins.hasPlugin("java-library") + val isJavaGradlePlugin = project.plugins.hasPlugin("java-gradle-plugin") + return isJava || isJavaLibrary || isJavaGradlePlugin +} + +fun isKotlinMultiplatform(project: Project): Boolean { + return project.plugins.hasPlugin("org.jetbrains.kotlin.multiplatform") +} + +/** + * method to get the android extension - as a Project class extension method! + */ +fun Project.android(): BaseExtension { + val android = project.extensions.findByType(BaseExtension::class.java) + if (android != null) { + return android + } else { + throw GradleException("Project $name is not an Android project") + } +} + +/** + * method to get variants + */ +fun BaseExtension.variants(): DomainObjectSet { + return when (this) { + is AppExtension -> { + applicationVariants + } + + is LibraryExtension -> { + libraryVariants + } + + else -> throw GradleException("Unsupported Android BaseExtension type!") + } +} + +/** + * utility method to get the file tree + */ +fun Project.filesTree(dir: Any, excludes: Set = emptySet(), includes: Set = emptySet()): ConfigurableFileTree = + fileTree(mapOf("dir" to dir, "excludes" to excludes, "includes" to includes)) + +val DEFAULT_EXCLUDES = setOf( + // Core Android generated class filters + "**/R.class", + "**/R2.class", // ButterKnife Gradle Plugin. + "**/R$*.class", + "**/R2$*.class", // ButterKnife Gradle Plugin. + "**/*$$*", + "**/BuildConfig.*", + "**/Manifest*.*", + "android/**/*.*", + "**/*\$Lambda\$*.*", // Jacoco can not handle several "$" in class name. + "**/*\$inlined$*.*", // Kotlin specific, Jacoco can not handle several "$" in class name. + "**/*Dagger*.*", // Dagger auto-generated code. + "**/*MembersInjector*.*", // Dagger auto-generated code. + "**/*_Provide*Factory*.*", // Dagger auto-generated code. + "**/*_Factory*.*", // Dagger auto-generated code. + "**/*\$StateSaver.*", // android-state auto-generated code. + "**/*AutoValue_*.*" // AutoValue auto-generated code. +) + +object TestTypes { + const val UnitTest = "UnitTest" + const val AndroidTest = "AndroidTest" +}