From b60a21c08d1c7b12287181177f9c53e2d204f416 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Wed, 1 Apr 2020 16:05:09 -0700 Subject: [PATCH] For #9605: Add LintUnitTestRunner gradle task. This will prevent us from regressing on adding older versions of the unit test runner. --- app/build.gradle | 3 + .../fenix/gradle/tasks/LintUnitTestRunner.kt | 103 ++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/LintUnitTestRunner.kt diff --git a/app/build.gradle b/app/build.gradle index 5d47d1aca..79fa15691 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,6 +31,7 @@ apply plugin: 'com.google.android.gms.oss-licenses-plugin' import com.android.build.OutputFile import org.gradle.internal.logging.text.StyledTextOutput.Style import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.mozilla.fenix.gradle.tasks.LintUnitTestRunner import static org.gradle.api.tasks.testing.TestResult.ResultType @@ -684,6 +685,8 @@ task buildTranslationArray { android.defaultConfig.buildConfigField "String[]", "SUPPORTED_LOCALE_ARRAY", foundLocalesString } +task lintUnitTestRunner(type: LintUnitTestRunner) + afterEvaluate { // Format test output. Ported from AC #2401 diff --git a/buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/LintUnitTestRunner.kt b/buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/LintUnitTestRunner.kt new file mode 100644 index 000000000..7283f1b5b --- /dev/null +++ b/buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/LintUnitTestRunner.kt @@ -0,0 +1,103 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.gradle.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.TaskExecutionException +import java.io.File + +private val INVALID_TEST_RUNNERS = setOf( + // When updating this list, also update the violation message. + "AndroidJUnit4", + "RobolectricTestRunner" +) + +/** + * A lint check that ensures unit tests do not specify an invalid test runner. See the error message + * for details. + * + * Related notes: + * - The AndroidJUnit4 runner seems to just delegate to robolectric. Since robolectric non-trivially + * increases test runtime, using AndroidJUnit4 in place of RobolectricTestRunner is very misleading + * about how the test will change. + */ +open class LintUnitTestRunner : DefaultTask() { + init { + group = "Verification" + description = "Ensures unit tests do not specify an invalid test runner" + attachToAssembleUnitTestTasks() + } + + private fun attachToAssembleUnitTestTasks() { + project.gradle.projectsEvaluated { project.tasks.let { tasks -> + val assembleUnitTestTasks = tasks.filter { + it.name.startsWith("assemble") && it.name.endsWith("UnitTest") + } + + assembleUnitTestTasks.forEach { + it.dependsOn(this@LintUnitTestRunner) + } + } } + } + + @TaskAction + fun lint() { + val unitTestDir = File(project.projectDir, "/src/test") + check(unitTestDir.exists()) { + "Error in task impl: expected test directory - ${unitTestDir.absolutePath} - to exist" + } + + val unitTestDirFileWalk = unitTestDir.walk().onEnter { true /* enter all dirs */ }.asSequence() + val kotlinFileWalk = unitTestDirFileWalk.filter { it.name.endsWith(".kt") && it.isFile } + check(kotlinFileWalk.count() > 0) { "Error in task impl: expected to walk > 0 test files" } + + val violatingFiles = kotlinFileWalk.filter { file -> + file.useLines { lines -> lines.any(::isLineInViolation) } + }.sorted().toList() + + if (violatingFiles.isNotEmpty()) { + throwViolation(violatingFiles) + } + } + + private fun isLineInViolation(line: String): Boolean { + val trimmed = line.trimStart() + return INVALID_TEST_RUNNERS.any { invalid -> + trimmed.startsWith("@RunWith($invalid::class)") + } + } + + private fun throwViolation(files: List) { + val failureHeader = """Lint failure: saw unexpected unit test runners. The following code blocks: + | + | @RunWith(AndroidJUnit4::class) + | @Config(application = TestApplication::class) + |OR + | @RunWith(RobolectricTestRunner::class) + | @Config(application = TestApplication::class) + | + |should be replaced with: + | + | @RunWith(FenixRobolectricTestRunner::class) + | + |To reduce redundancy of setting @Config. No @Config specification is necessary because + |the FenixRobolectricTestRunner sets it automatically. + | + |Relatedly, adding robolectric to a test increases its runtime non-trivially so please + |ensure Robolectric is necessary before adding it. + | + |The following files were found to be in violation: + """.trimMargin() + + val filesInViolation = files.map { + " ${it.relativeTo(project.rootDir)}" + } + + val errorMsg = (listOf(failureHeader) + filesInViolation).joinToString("\n") + throw TaskExecutionException(this, GradleException(errorMsg)) + } +}