1
0
Fork 0
fenix/buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/LintUnitTestRunner.kt

109 lines
4.5 KiB
Kotlin

/* 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
* and the suggested replacement class' kdoc for details.
*
* Performance note: AS indicates this task takes 50ms-100ms to run. This isn't too concerning because
* it's one task and it only runs for unit test compilation. However, if we add additional lint checks,
* we should considering aggregating them - e.g. this task reads the unit test file tree and we can
* combine all of our tasks such that the file tree only needs to be read once - or finding more
* optimal solutions - e.g. only running on changed files.
*/
open class LintUnitTestRunner : DefaultTask() {
init {
group = "Verification"
description = "Ensures unit tests do not specify an invalid test runner"
attachToCompilationTasks()
}
private fun attachToCompilationTasks() {
project.gradle.projectsEvaluated { project.tasks.let { tasks ->
// We make compile tasks, rather than assemble tasks, depend on us because some tools,
// such as AS' test runner, call the compile task directly rather than going through assemble.
val compileUnitTestTasks = tasks.filter {
it.name.startsWith("compile") && it.name.contains("UnitTest")
}
compileUnitTestTasks.forEach { it.dependsOn(this@LintUnitTestRunner) }
// To return feedback as early as possible, we run before all compile tasks including
// compiling the application.
val compileAllTasks = tasks.filter { it.name.startsWith("compile") }
compileAllTasks.forEach { it.mustRunAfter(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<File>) {
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))
}
}