Skip to content

Improve support for Robolectric #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/plugins.gradle.org/m2/nl.neotech.plugin/android-root-coverage-plugin/maven-metadata.xml.svg?label=Gradle%20Plugin%20Portal)](https://plugins.gradle.org/plugin/nl.neotech.plugin.rootcoverage)
[![Maven Central](https://img.shields.io/maven-central/v/nl.neotech.plugin/android-root-coverage-plugin?label=Maven%20Central)](https://search.maven.org/artifact/nl.neotech.plugin/android-root-coverage-plugin)
[![Build](https://github.com/NeoTech-Software/Android-Root-Coverage-Plugin/actions/workflows/build.yml/badge.svg)](https://github.com/NeoTech-Software/Android-Root-Coverage-Plugin/actions/workflows/build.yml)
[![Build](https://github.com/NeoTech-Software/Android-Root-Coverage-Plugin/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/NeoTech-Software/Android-Root-Coverage-Plugin/actions/workflows/build.yml)

# Android-Root-Coverage-Plugin
**A Gradle plugin for combined code coverage reports for Android projects.**
Expand Down
7 changes: 4 additions & 3 deletions gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ext {
minSdk : 19,
targetSdk : 30,
compileSdk: 30,
kotlin : "1.4.31"
kotlin : "1.4.31",
]
projectDependency = [

Expand All @@ -22,7 +22,8 @@ ext {
espressoCore : "androidx.test.espresso:espresso-core:3.3.0",
androidJUnit : "androidx.test.ext:junit:1.1.2",
commonsCsv : "org.apache.commons:commons-csv:1.8",
kotlinTest : "org.jetbrains.kotlin:kotlin-test:${projectVersion.kotlin}"
kotlinTest : "org.jetbrains.kotlin:kotlin-test:${projectVersion.kotlin}",
robolectric : "org.robolectric:robolectric:4.5.1",
]

// Used by the plugin-version-handler.gradle
Expand All @@ -32,6 +33,6 @@ ext {
"com.github.dcendents.android-maven": "2.1",
"com.gradle.plugin-publish" : "0.13.0",
"org.jetbrains.dokka" : "1.4.30",
"com.vanniktech.maven.publish" : "0.14.2"
"com.vanniktech.maven.publish" : "0.14.2",
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.FileTree
import org.gradle.api.tasks.testing.Test
import org.gradle.testing.jacoco.plugins.JacocoPlugin
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
import org.gradle.testing.jacoco.tasks.JacocoReport

@Suppress("unused")
Expand All @@ -34,7 +36,10 @@ class RootCoveragePlugin : Plugin<Project> {
project.plugins.apply(JacocoPlugin::class.java)
}

project.afterEvaluate { createCoverageTaskForRoot(it) }
project.afterEvaluate {
it.applyConfiguration()
createCoverageTaskForRoot(it)
}
}

private fun getFileFilterPatterns(): List<String> = listOf(
Expand Down Expand Up @@ -157,7 +162,7 @@ class RootCoveragePlugin : Plugin<Project> {
task.reports.html.destination = project.file("${project.buildDir}/reports/jacoco")
task.reports.xml.destination = project.file("${project.buildDir}/reports/jacoco.xml")
task.reports.csv.destination = project.file("${project.buildDir}/reports/jacoco.csv")

// Add some run-time checks.
task.doFirst {
it.project.allprojects.forEach { subProject ->
Expand All @@ -175,6 +180,7 @@ class RootCoveragePlugin : Plugin<Project> {
// Configure the root task with sub-tasks for the sub-projects.
task.project.subprojects.forEach {
it.afterEvaluate { subProject ->
subProject.applyConfiguration()
task.addSubProject(subProject)
createSubProjectCoverageTask(subProject)
}
Expand Down Expand Up @@ -281,4 +287,21 @@ class RootCoveragePlugin : Plugin<Project> {
classDirectories.from(subProject.files(javaClassTrees, kotlinClassTree))
executionData.from(getExecutionDataFileTree(subProject))
}

/**
* Apply configuration from [RootCoveragePluginExtension] to the project.
*/
private fun Project.applyConfiguration() {
tasks.withType(Test::class.java) { testTask ->
testTask.extensions.findByType(JacocoTaskExtension::class.java)?.apply{
isIncludeNoLocationClasses = rootProjectExtension.includeNoLocationClasses
if(isIncludeNoLocationClasses) {
// This Plugin is used for Android development and should support the Robolectric + Jacoco use-case
// flawlessly, therefore this "bugfix" is included in the plugin codebase:
// See: https://github.com/gradle/gradle/issues/5184#issuecomment-457865951
excludes = listOf("jdk.internal.*")
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ open class RootCoveragePluginExtension {
var buildVariant: String = "debug"
var buildVariantOverrides: Map<String, String> = mutableMapOf()
var excludes: List<String> = mutableListOf()

var includeNoLocationClasses: Boolean = false

/**
* Same as executeTests inverted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class IntegrationTest(
report.assertCoverage("org.neotech.library.android", "LibraryAndroidKotlin")
report.assertCoverage("org.neotech.app", "AppJava")
report.assertCoverage("org.neotech.app", "AppKotlin")
report.assertCoverage("org.neotech.app", "RobolectricTestedActivity")
}

private fun BuildResult.assertAppCoverageReport() {
Expand All @@ -73,6 +74,7 @@ class IntegrationTest(

report.assertCoverage("org.neotech.app", "AppJava")
report.assertCoverage("org.neotech.app", "AppKotlin")
report.assertCoverage("org.neotech.app", "RobolectricTestedActivity")
}

private fun BuildResult.assertAndroidLibraryCoverageReport() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,39 @@ class RootCoveragePluginExtensionTest {
}

@Test
fun `non default testTypes overrules include(Unit|Android)TestResults`() {
fun `non default testTypes overrules include(Unit or Android)TestResults`() {
// testTypes overrules includeAndroidTestResults & includeUnitTestResults (when testTypes is not default)
val config = RootCoveragePluginExtension().apply {
includeAndroidTestResults = true
includeUnitTestResults = true
@Suppress("DEPRECATION")
testTypes = listOf()
}
assertEquals(false, config.includeUnitTestResults())
assertEquals(false, config.includeAndroidTestResults())
}

@Test
fun `default testTypes does not overrule include(Unit|Android)TestResults`() {
fun `default testTypes does not overrule include(Unit or Android)TestResults`() {
// when testTypes is default includeAndroidTestResults & includeUnitTestResults overrule testTypes
val config = RootCoveragePluginExtension().apply {
includeAndroidTestResults = false
includeUnitTestResults = false
@Suppress("DEPRECATION")
testTypes = listOf(TestVariantBuildOutput.TestType.UNIT, TestVariantBuildOutput.TestType.ANDROID_TEST)
}
assertEquals(false, config.includeUnitTestResults())
assertEquals(false, config.includeAndroidTestResults())
}

@Test
fun `shouldExecute(Unit|Android)Tests() returns false when include(Unit|Android)TestResults() returns false`() {
fun `shouldExecute(Unit or Android)Tests() returns false when include(Unit or Android)TestResults() returns false`() {
// When test results are not included into the final report (`include*TestResults`), running the tests does not make sense, therefor
// make sure `skip*TestExecution` returns false when this is the case.
val config = RootCoveragePluginExtension().apply {
includeAndroidTestResults = false
includeUnitTestResults = false
@Suppress("DEPRECATION")
testTypes = listOf(TestVariantBuildOutput.TestType.UNIT, TestVariantBuildOutput.TestType.ANDROID_TEST)
}
assertEquals(false, config.includeUnitTestResults())
Expand All @@ -56,7 +59,7 @@ class RootCoveragePluginExtensionTest {
}

@Test
fun `executeTests=false overrules execute(Unit|Android)Tests`() {
fun `executeTests=false overrules execute(Unit or Android)Tests`() {
val config = RootCoveragePluginExtension().apply {
executeTests = false
}
Expand All @@ -77,7 +80,7 @@ class RootCoveragePluginExtensionTest {
}

@Test
fun `executeTests=true does not overrule execute(Unit|AndroidInstrumented)Tests`() {
fun `executeTests=true does not overrule execute(Unit or AndroidInstrumented)Tests`() {
val config = RootCoveragePluginExtension().apply {
executeTests = true
}
Expand Down
9 changes: 8 additions & 1 deletion plugin/src/test/test-fixtures/multi-module/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}

testOptions {
unitTests {
includeAndroidResources = true
}
}

kotlinOptions {
jvmTarget = "1.8"
}
Expand All @@ -47,7 +53,8 @@ dependencies {
implementation projectDependency.kotlinStdlibJdk8
implementation projectDependency.appCompat

testImplementation projectDependency.junit
testImplementation projectDependency.androidJUnit
testImplementation projectDependency.robolectric
androidTestImplementation projectDependency.supportTestRunner
androidTestImplementation projectDependency.espressoCore
androidTestImplementation projectDependency.androidJUnit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="org.neotech.app" />
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.neotech.app"
>

<application>
<activity
android:name=".RobolectricTestedActivity"
android:theme="@style/Theme.AppCompat"
/>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.neotech.app;

import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import java.util.Locale;

/**
* Super simple activity that is unit-tested (non-instrumented) using Robolectric.
*/
public class RobolectricTestedActivity extends AppCompatActivity implements View.OnClickListener {

private TextView textViewCount;
private int count = 0;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_robolectric_tested);

findViewById(R.id.button_increment_count).setOnClickListener(this);
findViewById(R.id.button_decrement_count).setOnClickListener(this);
textViewCount = findViewById(R.id.text_count);
setCount(0);
}

@Override
public void onClick(View v) {
if (v.getId() == R.id.button_increment_count) {
setCount(++count);
} else {
setCount(--count);
}
}

public void setCount(int count) {
textViewCount.setText(String.format(Locale.getDefault(), "Count: %d", count));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
>

<TextView
android:id="@+id/text_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Count: 4"
/>

<Button
android:id="@+id/button_increment_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Increment"
tools:ignore="HardcodedText"
/>

<Button
android:id="@+id/button_decrement_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Decrement"
tools:ignore="HardcodedText"
/>

</LinearLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.neotech.app;

import android.widget.Button;
import android.widget.TextView;

import androidx.test.core.app.ActivityScenario;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

import static org.junit.Assert.assertEquals;

/**
* Super simple Robolectric activity test that is solely used to verify whether coverage is
* correctly reported when enabling jacoco.includeNoLocationClasses.
*/
@RunWith(RobolectricTestRunner.class)
public class RobolectricUnitTest {

@Test
public void counter_is_incremented_after_increment_button_click() {
ActivityScenario.launch(RobolectricTestedActivity.class).onActivity(activity -> {
Button button = activity.findViewById(R.id.button_increment_count);
button.performClick();

TextView textView = activity.findViewById(R.id.text_count);
assertEquals("Count: 1", textView.getText());
});
}

@Test
public void counter_is_decrement_after_decrement_button_click() {
ActivityScenario.launch(RobolectricTestedActivity.class).onActivity(activity -> {
Button button = activity.findViewById(R.id.button_decrement_count);
button.performClick();

TextView textView = activity.findViewById(R.id.text_count);
assertEquals("Count: -1", textView.getText());
});
}
}
1 change: 1 addition & 0 deletions plugin/src/test/test-fixtures/multi-module/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ rootCoverage {
executeTests true
includeUnitTestResults true
includeAndroidTestResults true
includeNoLocationClasses true
}