Remove Unnecesary @Nested (#4740)

This commit is contained in:
Brais Gabín
2022-04-25 23:04:26 +02:00
committed by GitHub
parent 7880f4f237
commit a2734b18c1
238 changed files with 27537 additions and 28638 deletions

View File

@@ -10,16 +10,37 @@ import java.util.concurrent.atomic.AtomicInteger
class ConfigPropertySpec {
@Nested
inner class `Config property delegate` {
inner class `simple property` {
@Nested
inner class `simple property` {
inner class `String property` {
private val configValue = "value"
private val defaultValue = "default"
private val subject = object : TestConfigAware("present" to configValue) {
val present: String by config(defaultValue)
val notPresent: String by config(defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(configValue)
}
@Test
fun `uses the default value if not present`() {
assertThat(subject.notPresent).isEqualTo(defaultValue)
}
}
@Nested
inner class `Int property` {
private val configValue = 99
private val defaultValue = -1
@Nested
inner class `String property` {
private val configValue = "value"
private val defaultValue = "default"
inner class `defined as number` {
private val subject = object : TestConfigAware("present" to configValue) {
val present: String by config(defaultValue)
val notPresent: String by config(defaultValue)
val present: Int by config(defaultValue)
val notPresent: Int by config(defaultValue)
}
@Test
@@ -34,394 +55,370 @@ class ConfigPropertySpec {
}
@Nested
inner class `Int property` {
private val configValue = 99
private val defaultValue = -1
@Nested
inner class `defined as number` {
private val subject = object : TestConfigAware("present" to configValue) {
val present: Int by config(defaultValue)
val notPresent: Int by config(defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(configValue)
}
@Test
fun `uses the default value if not present`() {
assertThat(subject.notPresent).isEqualTo(defaultValue)
}
inner class `defined as string` {
private val subject = object : TestConfigAware("present" to "$configValue") {
val present: Int by config(defaultValue)
}
@Nested
inner class `defined as string` {
private val subject = object : TestConfigAware("present" to "$configValue") {
val present: Int by config(defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(configValue)
}
}
}
@Nested
inner class `Boolean property` {
private val configValue = false
private val defaultValue = true
@Nested
inner class `defined as Boolean` {
private val subject = object : TestConfigAware("present" to configValue) {
val present: Boolean by config(defaultValue)
val notPresent: Boolean by config(defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(configValue)
}
@Test
fun `uses the default value if not present`() {
assertThat(subject.notPresent).isEqualTo(defaultValue)
}
}
@Nested
inner class `defined as string` {
private val subject = object : TestConfigAware("present" to "$configValue") {
val present: Boolean by config(defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(configValue)
}
}
}
@Nested
inner class `List property` {
private val defaultValue = listOf("x")
@Nested
inner class `defined as list` {
private val subject = object : TestConfigAware("present" to "a,b,c") {
val present: List<String> by config(defaultValue)
val notPresent: List<String> by config(defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(listOf("a", "b", "c"))
}
@Test
fun `uses the default value if not present`() {
assertThat(subject.notPresent).isEqualTo(defaultValue)
}
}
@Nested
inner class `defined as comma separated string` {
private val subject = object : TestConfigAware("present" to "a,b,c") {
val present: List<String> by config(defaultValue)
val notPresent: List<String> by config(defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(listOf("a", "b", "c"))
}
@Test
fun `uses the default value if not present`() {
assertThat(subject.notPresent).isEqualTo(defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(configValue)
}
}
}
@Nested
inner class `invalid type` {
@Nested
inner class `Long property` {
private val defaultValue = 1L
private val subject = object : TestConfigAware() {
val prop: Long by config(defaultValue)
}
@Test
fun `throws`() {
assertThatThrownBy { subject.prop }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("is not supported")
}
}
inner class `Boolean property` {
private val configValue = false
private val defaultValue = true
@Nested
inner class `Regex property` {
private val defaultValue = Regex("a")
private val subject = object : TestConfigAware() {
val prop: Regex by config(defaultValue)
inner class `defined as Boolean` {
private val subject = object : TestConfigAware("present" to configValue) {
val present: Boolean by config(defaultValue)
val notPresent: Boolean by config(defaultValue)
}
@Test
fun `throws`() {
assertThatThrownBy { subject.prop }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("is not supported")
}
}
@Nested
inner class `Set property` {
private val defaultValue = setOf("a")
private val subject = object : TestConfigAware() {
val prop: Set<String> by config(defaultValue)
}
@Test
fun `throws`() {
assertThatThrownBy { subject.prop }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("is not supported")
}
}
@Nested
inner class `List of Int` {
private val defaultValue = listOf(1)
private val subject = object : TestConfigAware() {
val prop: List<Int> by config(defaultValue)
}
@Test
fun `throws`() {
assertThatThrownBy { subject.prop }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("lists of strings are supported")
}
}
}
@Nested
inner class `transform` {
@Nested
inner class `primitive` {
@Nested
inner class `String property is transformed to regex` {
private val defaultValue = ".*"
private val configValue = "[a-z]+"
private val subject = object : TestConfigAware("present" to configValue) {
val present: Regex by config(defaultValue) { it.toRegex() }
val notPresent: Regex by config(defaultValue) { it.toRegex() }
}
@Test
fun `applies the mapping function to the configured value`() {
assertThat(subject.present.matches("abc")).isTrue
assertThat(subject.present.matches("123")).isFalse()
}
@Test
fun `applies the mapping function to the default`() {
assertThat(subject.notPresent.matches("abc")).isTrue
assertThat(subject.notPresent.matches("123")).isTrue
}
}
@Nested
inner class `Int property is transformed to String` {
private val configValue = 99
private val defaultValue = -1
private val subject = object : TestConfigAware("present" to configValue) {
val present: String by config(defaultValue) { it.toString() }
val notPresent: String by config(defaultValue) { it.toString() }
}
@Test
fun `applies the mapping function to the configured value`() {
assertThat(subject.present).isEqualTo("$configValue")
}
@Test
fun `applies the mapping function to the default`() {
assertThat(subject.notPresent).isEqualTo("$defaultValue")
}
}
@Nested
inner class `Boolean property is transformed to String` {
private val configValue = true
private val defaultValue = false
private val subject = object : TestConfigAware("present" to configValue) {
val present: String by config(defaultValue) { it.toString() }
val notPresent: String by config(defaultValue) { it.toString() }
}
@Test
fun `applies the mapping function to the configured value`() {
assertThat(subject.present).isEqualTo("$configValue")
}
@Test
fun `applies the mapping function to the default`() {
assertThat(subject.notPresent).isEqualTo("$defaultValue")
}
}
@Nested
inner class `Boolean property is transformed to String with function reference` {
private val defaultValue = false
private val subject = object : TestConfigAware() {
val prop1: String by config(defaultValue, Boolean::toString)
val prop2: String by config(transformer = Boolean::toString, defaultValue = defaultValue)
}
@Test
fun `transforms properties`() {
assertThat(subject.prop1).isEqualTo("$defaultValue")
assertThat(subject.prop2).isEqualTo("$defaultValue")
}
}
}
@Nested
inner class `list of strings` {
private val defaultValue = listOf("99")
private val subject = object : TestConfigAware("present" to "1,2,3") {
val present: Int by config(defaultValue) { it.sumOf(String::toInt) }
val notPresent: Int by config(defaultValue) { it.sumOf(String::toInt) }
}
@Test
fun `applies transformer to list configured`() {
assertThat(subject.present).isEqualTo(6)
}
@Test
fun `applies transformer to default list`() {
assertThat(subject.notPresent).isEqualTo(99)
}
}
@Nested
inner class `empty list of strings` {
private val subject = object : TestConfigAware() {
val defaultValue: List<String> = emptyList()
val prop1: List<Int> by config(defaultValue) { it.map(String::toInt) }
val prop2: List<Int> by config(emptyList<String>()) { it.map(String::toInt) }
}
@Test
fun `can be defined as variable`() {
assertThat(subject.prop1).isEmpty()
}
@Test
fun `can be defined using listOf`() {
assertThat(subject.prop2).isEmpty()
}
}
@Nested
inner class `memoization` {
private val subject = object : TestConfigAware() {
val counter = AtomicInteger(0)
val prop: String by config(1) {
counter.getAndIncrement()
it.toString()
}
fun useProperty(): String {
return "something with $prop"
}
}
@Test
fun `transformer is called only once`() {
repeat(5) {
assertThat(subject.useProperty()).isEqualTo("something with 1")
}
assertThat(subject.counter.get()).isEqualTo(1)
}
}
}
@OptIn(UnstableApi::class)
@Nested
inner class `configWithFallback` {
@Nested
inner class `primitive` {
private val configValue = 99
private val defaultValue = 0
private val fallbackValue = -1
private val subject =
object : TestConfigAware("present" to "$configValue", "fallback" to fallbackValue) {
private val fallback: Int by config(42)
private val missing: Int by config(42)
val present: Int by configWithFallback(::fallback, defaultValue)
val notPresentWithFallback: Int by configWithFallback(::fallback, defaultValue)
val notPresentFallbackMissing: Int by configWithFallback(::missing, defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(configValue)
}
@Test
fun `uses the value from fallback property if value is missing and fallback exists`() {
assertThat(subject.notPresentWithFallback).isEqualTo(fallbackValue)
}
@Test
fun `uses the default value if not present`() {
assertThat(subject.notPresentFallbackMissing).isEqualTo(defaultValue)
assertThat(subject.notPresent).isEqualTo(defaultValue)
}
}
@Nested
inner class `with transformation` {
private val configValue = 99
private val defaultValue = 0
private val fallbackValue = -1
private val fallbackOffset = 10
private val subject = object : TestConfigAware("present" to configValue, "fallback" to fallbackValue) {
private val fallback: String by config(42) { (it + fallbackOffset).toString() }
private val missing: String by config(42) { (it + fallbackOffset).toString() }
val present: String by configWithFallback(::fallback, defaultValue) { v ->
v.toString()
}
val notPresentWithFallback: String by configWithFallback(::fallback, defaultValue) { v ->
v.toString()
}
val notPresentFallbackMissing: String by configWithFallback(::missing, defaultValue) { v ->
v.toString()
}
inner class `defined as string` {
private val subject = object : TestConfigAware("present" to "$configValue") {
val present: Boolean by config(defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo("$configValue")
assertThat(subject.present).isEqualTo(configValue)
}
}
}
@Nested
inner class `List property` {
private val defaultValue = listOf("x")
@Nested
inner class `defined as list` {
private val subject = object : TestConfigAware("present" to "a,b,c") {
val present: List<String> by config(defaultValue)
val notPresent: List<String> by config(defaultValue)
}
@Test
fun `transforms the value from fallback property if value is missing and fallback exists`() {
assertThat(subject.notPresentWithFallback).isEqualTo("${fallbackValue + fallbackOffset}")
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(listOf("a", "b", "c"))
}
@Test
fun `uses the default value if not present`() {
assertThat(subject.notPresentFallbackMissing).isEqualTo("$defaultValue")
assertThat(subject.notPresent).isEqualTo(defaultValue)
}
}
@Nested
inner class `defined as comma separated string` {
private val subject = object : TestConfigAware("present" to "a,b,c") {
val present: List<String> by config(defaultValue)
val notPresent: List<String> by config(defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(listOf("a", "b", "c"))
}
@Test
fun `uses the default value if not present`() {
assertThat(subject.notPresent).isEqualTo(defaultValue)
}
}
}
}
@Nested
inner class `invalid type` {
@Nested
inner class `Long property` {
private val defaultValue = 1L
private val subject = object : TestConfigAware() {
val prop: Long by config(defaultValue)
}
@Test
fun `throws`() {
assertThatThrownBy { subject.prop }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("is not supported")
}
}
@Nested
inner class `Regex property` {
private val defaultValue = Regex("a")
private val subject = object : TestConfigAware() {
val prop: Regex by config(defaultValue)
}
@Test
fun `throws`() {
assertThatThrownBy { subject.prop }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("is not supported")
}
}
@Nested
inner class `Set property` {
private val defaultValue = setOf("a")
private val subject = object : TestConfigAware() {
val prop: Set<String> by config(defaultValue)
}
@Test
fun `throws`() {
assertThatThrownBy { subject.prop }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("is not supported")
}
}
@Nested
inner class `List of Int` {
private val defaultValue = listOf(1)
private val subject = object : TestConfigAware() {
val prop: List<Int> by config(defaultValue)
}
@Test
fun `throws`() {
assertThatThrownBy { subject.prop }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("lists of strings are supported")
}
}
}
@Nested
inner class `transform` {
@Nested
inner class `primitive` {
@Nested
inner class `String property is transformed to regex` {
private val defaultValue = ".*"
private val configValue = "[a-z]+"
private val subject = object : TestConfigAware("present" to configValue) {
val present: Regex by config(defaultValue) { it.toRegex() }
val notPresent: Regex by config(defaultValue) { it.toRegex() }
}
@Test
fun `applies the mapping function to the configured value`() {
assertThat(subject.present.matches("abc")).isTrue
assertThat(subject.present.matches("123")).isFalse()
}
@Test
fun `applies the mapping function to the default`() {
assertThat(subject.notPresent.matches("abc")).isTrue
assertThat(subject.notPresent.matches("123")).isTrue
}
}
@Nested
inner class `Int property is transformed to String` {
private val configValue = 99
private val defaultValue = -1
private val subject = object : TestConfigAware("present" to configValue) {
val present: String by config(defaultValue) { it.toString() }
val notPresent: String by config(defaultValue) { it.toString() }
}
@Test
fun `applies the mapping function to the configured value`() {
assertThat(subject.present).isEqualTo("$configValue")
}
@Test
fun `applies the mapping function to the default`() {
assertThat(subject.notPresent).isEqualTo("$defaultValue")
}
}
@Nested
inner class `Boolean property is transformed to String` {
private val configValue = true
private val defaultValue = false
private val subject = object : TestConfigAware("present" to configValue) {
val present: String by config(defaultValue) { it.toString() }
val notPresent: String by config(defaultValue) { it.toString() }
}
@Test
fun `applies the mapping function to the configured value`() {
assertThat(subject.present).isEqualTo("$configValue")
}
@Test
fun `applies the mapping function to the default`() {
assertThat(subject.notPresent).isEqualTo("$defaultValue")
}
}
@Nested
inner class `Boolean property is transformed to String with function reference` {
private val defaultValue = false
private val subject = object : TestConfigAware() {
val prop1: String by config(defaultValue, Boolean::toString)
val prop2: String by config(transformer = Boolean::toString, defaultValue = defaultValue)
}
@Test
fun `transforms properties`() {
assertThat(subject.prop1).isEqualTo("$defaultValue")
assertThat(subject.prop2).isEqualTo("$defaultValue")
}
}
}
@Nested
inner class `list of strings` {
private val defaultValue = listOf("99")
private val subject = object : TestConfigAware("present" to "1,2,3") {
val present: Int by config(defaultValue) { it.sumOf(String::toInt) }
val notPresent: Int by config(defaultValue) { it.sumOf(String::toInt) }
}
@Test
fun `applies transformer to list configured`() {
assertThat(subject.present).isEqualTo(6)
}
@Test
fun `applies transformer to default list`() {
assertThat(subject.notPresent).isEqualTo(99)
}
}
@Nested
inner class `empty list of strings` {
private val subject = object : TestConfigAware() {
val defaultValue: List<String> = emptyList()
val prop1: List<Int> by config(defaultValue) { it.map(String::toInt) }
val prop2: List<Int> by config(emptyList<String>()) { it.map(String::toInt) }
}
@Test
fun `can be defined as variable`() {
assertThat(subject.prop1).isEmpty()
}
@Test
fun `can be defined using listOf`() {
assertThat(subject.prop2).isEmpty()
}
}
@Nested
inner class `memoization` {
private val subject = object : TestConfigAware() {
val counter = AtomicInteger(0)
val prop: String by config(1) {
counter.getAndIncrement()
it.toString()
}
fun useProperty(): String {
return "something with $prop"
}
}
@Test
fun `transformer is called only once`() {
repeat(5) {
assertThat(subject.useProperty()).isEqualTo("something with 1")
}
assertThat(subject.counter.get()).isEqualTo(1)
}
}
}
@OptIn(UnstableApi::class)
@Nested
inner class `configWithFallback` {
@Nested
inner class `primitive` {
private val configValue = 99
private val defaultValue = 0
private val fallbackValue = -1
private val subject =
object : TestConfigAware("present" to "$configValue", "fallback" to fallbackValue) {
private val fallback: Int by config(42)
private val missing: Int by config(42)
val present: Int by configWithFallback(::fallback, defaultValue)
val notPresentWithFallback: Int by configWithFallback(::fallback, defaultValue)
val notPresentFallbackMissing: Int by configWithFallback(::missing, defaultValue)
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo(configValue)
}
@Test
fun `uses the value from fallback property if value is missing and fallback exists`() {
assertThat(subject.notPresentWithFallback).isEqualTo(fallbackValue)
}
@Test
fun `uses the default value if not present`() {
assertThat(subject.notPresentFallbackMissing).isEqualTo(defaultValue)
}
}
@Nested
inner class `with transformation` {
private val configValue = 99
private val defaultValue = 0
private val fallbackValue = -1
private val fallbackOffset = 10
private val subject = object : TestConfigAware("present" to configValue, "fallback" to fallbackValue) {
private val fallback: String by config(42) { (it + fallbackOffset).toString() }
private val missing: String by config(42) { (it + fallbackOffset).toString() }
val present: String by configWithFallback(::fallback, defaultValue) { v ->
v.toString()
}
val notPresentWithFallback: String by configWithFallback(::fallback, defaultValue) { v ->
v.toString()
}
val notPresentFallbackMissing: String by configWithFallback(::missing, defaultValue) { v ->
v.toString()
}
}
@Test
fun `uses the value provided in config if present`() {
assertThat(subject.present).isEqualTo("$configValue")
}
@Test
fun `transforms the value from fallback property if value is missing and fallback exists`() {
assertThat(subject.notPresentWithFallback).isEqualTo("${fallbackValue + fallbackOffset}")
}
@Test
fun `uses the default value if not present`() {
assertThat(subject.notPresentFallbackMissing).isEqualTo("$defaultValue")
}
}
}
}

View File

@@ -12,84 +12,81 @@ import java.nio.file.Paths
class EntitySpec {
private val path = Paths.get("/full/path/to/Test.kt")
private val code = compileContentForTest(
"""
package test
class C : Any() {
private fun memberFun(): Int = 5
}
fun topLevelFun(number: Int) = Unit
""".trimIndent(),
path.toString()
)
@Nested
inner class `entity signatures` {
private val path = Paths.get("/full/path/to/Test.kt")
private val code = compileContentForTest(
"""
package test
inner class `Named functions` {
class C : Any() {
private val functions = code.collectDescendantsOfType<KtNamedFunction>()
private fun memberFun(): Int = 5
}
@Test
fun `includes full function header, class name and filename`() {
val memberFunction = functions.first { it.name == "memberFun" }
fun topLevelFun(number: Int) = Unit
""".trimIndent(),
path.toString()
)
@Nested
inner class `Named functions` {
private val functions = code.collectDescendantsOfType<KtNamedFunction>()
@Test
fun `includes full function header, class name and filename`() {
val memberFunction = functions.first { it.name == "memberFun" }
assertThat(Entity.atName(memberFunction).signature)
.isEqualTo("Test.kt\$C\$private fun memberFun(): Int")
}
@Test
fun `includes full function header and filename for a top level function`() {
val topLevelFunction = functions.first { it.name == "topLevelFun" }
assertThat(Entity.atName(topLevelFunction).signature)
.isEqualTo("Test.kt\$fun topLevelFun(number: Int)")
}
@Test
fun `includes function name in entity compact`() {
val memberFunction = functions.first { it.name == "memberFun" }
assertThat(Entity.atName(memberFunction).compact())
.isEqualTo("[memberFun] at $path:5:17")
}
assertThat(Entity.atName(memberFunction).signature)
.isEqualTo("Test.kt\$C\$private fun memberFun(): Int")
}
@Nested
inner class `Classes` {
private val clazz = requireNotNull(code.findDescendantOfType<KtClass>())
@Test
fun `includes full function header and filename for a top level function`() {
val topLevelFunction = functions.first { it.name == "topLevelFun" }
@Test
fun `includes full class signature`() {
assertThat(Entity.atName(clazz).signature).isEqualTo("Test.kt\$C : Any")
}
@Test
fun `includes class name in entity compact`() {
assertThat(Entity.atName(clazz).compact()).isEqualTo("[C] at $path:3:7")
}
assertThat(Entity.atName(topLevelFunction).signature)
.isEqualTo("Test.kt\$fun topLevelFun(number: Int)")
}
@Nested
inner class `Files` {
@Test
fun `includes function name in entity compact`() {
val memberFunction = functions.first { it.name == "memberFun" }
@Test
fun `includes package and file name in entity signature`() {
assertThat(Entity.from(code).signature).isEqualTo("Test.kt\$test.Test.kt")
assertThat(Entity.atPackageOrFirstDecl(code).signature).isEqualTo("Test.kt\$test.Test.kt")
}
assertThat(Entity.atName(memberFunction).compact())
.isEqualTo("[memberFun] at $path:5:17")
}
}
@Test
fun `includes file name in entity compact`() {
val expectedResult = "[Test.kt] at $path:1:1"
@Nested
inner class `Classes` {
private val clazz = requireNotNull(code.findDescendantOfType<KtClass>())
assertThat(Entity.from(code).compact()).isEqualTo(expectedResult)
assertThat(Entity.atPackageOrFirstDecl(code).compact()).isEqualTo(expectedResult)
}
@Test
fun `includes full class signature`() {
assertThat(Entity.atName(clazz).signature).isEqualTo("Test.kt\$C : Any")
}
@Test
fun `includes class name in entity compact`() {
assertThat(Entity.atName(clazz).compact()).isEqualTo("[C] at $path:3:7")
}
}
@Nested
inner class `Files` {
@Test
fun `includes package and file name in entity signature`() {
assertThat(Entity.from(code).signature).isEqualTo("Test.kt\$test.Test.kt")
assertThat(Entity.atPackageOrFirstDecl(code).signature).isEqualTo("Test.kt\$test.Test.kt")
}
@Test
fun `includes file name in entity compact`() {
val expectedResult = "[Test.kt] at $path:1:1"
assertThat(Entity.from(code).compact()).isEqualTo(expectedResult)
assertThat(Entity.atPackageOrFirstDecl(code).compact()).isEqualTo(expectedResult)
}
}
}

View File

@@ -2,26 +2,21 @@ package io.gitlab.arturbosch.detekt.api
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalStateException
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class MetricSpec {
@Nested
inner class `Metrics` {
@Test
fun `should convert double values to int`() {
val metric = Metric("LOC", 0.33, 0.10, 100)
assertThat(metric.doubleValue()).isEqualTo(0.33)
assertThat(metric.doubleThreshold()).isEqualTo(0.10)
}
@Test
fun `should convert double values to int`() {
val metric = Metric("LOC", 0.33, 0.10, 100)
assertThat(metric.doubleValue()).isEqualTo(0.33)
assertThat(metric.doubleThreshold()).isEqualTo(0.10)
}
@Test
fun `should throw error if double value is asked for int metric`() {
assertThatIllegalStateException().isThrownBy {
Metric("LOC", 100, 50).doubleValue()
}
@Test
fun `should throw error if double value is asked for int metric`() {
assertThatIllegalStateException().isThrownBy {
Metric("LOC", 100, 50).doubleValue()
}
}
}

View File

@@ -2,54 +2,49 @@ package io.gitlab.arturbosch.detekt.api
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatCode
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import kotlin.random.Random
@OptIn(UnstableApi::class)
class PropertiesAwareSpec {
@Nested
inner class `Implementations can store and retrieve properties` {
private val hash = Random(1).nextInt()
private val store = object : PropertiesAware {
override val properties: MutableMap<String, Any> = HashMap()
override fun register(key: String, value: Any) {
properties[key] = value
private val hash = Random(1).nextInt()
private val store = object : PropertiesAware {
override val properties: MutableMap<String, Any> = HashMap()
override fun register(key: String, value: Any) {
properties[key] = value
}
}.apply {
register("bool", true)
register("string", "test")
register("number", 5)
register("set", setOf(1, 2, 3))
register(
"any",
object : Any() {
override fun equals(other: Any?): Boolean = hashCode() == other.hashCode()
override fun hashCode(): Int = hash
}
}.apply {
register("bool", true)
register("string", "test")
register("number", 5)
register("set", setOf(1, 2, 3))
register(
"any",
object : Any() {
override fun equals(other: Any?): Boolean = hashCode() == other.hashCode()
override fun hashCode(): Int = hash
}
)
}
)
}
@Test
fun `can retrieve the actual typed values`() {
assertThat(store.getOrNull<Boolean>("bool")).isEqualTo(true)
assertThat(store.getOrNull<String>("string")).isEqualTo("test")
assertThat(store.getOrNull<Int>("number")).isEqualTo(5)
assertThat(store.getOrNull<Set<Int>>("set")).isEqualTo(setOf(1, 2, 3))
assertThat(store.getOrNull<Any>("any").hashCode()).isEqualTo(hash)
}
@Test
fun `can retrieve the actual typed values`() {
assertThat(store.getOrNull<Boolean>("bool")).isEqualTo(true)
assertThat(store.getOrNull<String>("string")).isEqualTo("test")
assertThat(store.getOrNull<Int>("number")).isEqualTo(5)
assertThat(store.getOrNull<Set<Int>>("set")).isEqualTo(setOf(1, 2, 3))
assertThat(store.getOrNull<Any>("any").hashCode()).isEqualTo(hash)
}
@Test
fun `returns null on absent values`() {
assertThat(store.getOrNull<Boolean>("absent")).isEqualTo(null)
}
@Test
fun `returns null on absent values`() {
assertThat(store.getOrNull<Boolean>("absent")).isEqualTo(null)
}
@Test
fun `throws an error on wrong type`() {
assertThatCode { store.getOrNull<Double>("bool") }
.isInstanceOf(IllegalStateException::class.java)
}
@Test
fun `throws an error on wrong type`() {
assertThatCode { store.getOrNull<Double>("bool") }
.isInstanceOf(IllegalStateException::class.java)
}
}

View File

@@ -11,48 +11,45 @@ import org.junit.jupiter.api.Test
class RuleSetConfigPropertySpec {
@Nested
inner class `Rule set config property delegate` {
@Nested
inner class `boolean property` {
@Test
fun `reads the value in config if present`() {
assertThat(TestRuleSetProvider.android.value(TestConfig("android" to "false")))
.isEqualTo(false)
}
@Test
fun `uses the default value in config if not present`() {
assertThat(TestRuleSetProvider.android.value(TestConfig()))
.isEqualTo(true)
}
inner class `boolean property` {
@Test
fun `reads the value in config if present`() {
assertThat(TestRuleSetProvider.android.value(TestConfig("android" to "false")))
.isEqualTo(false)
}
@Nested
inner class `int property` {
@Test
fun `reads the value in config if present`() {
assertThat(TestRuleSetProvider.number.value(TestConfig("number" to "37"))).isEqualTo(37)
}
@Test
fun `uses the default value in config if not present`() {
assertThat(TestRuleSetProvider.android.value(TestConfig()))
.isEqualTo(true)
}
}
@Test
fun `uses the default value in config if not present`() {
assertThat(TestRuleSetProvider.number.value(TestConfig())).isEqualTo(42)
}
@Nested
inner class `int property` {
@Test
fun `reads the value in config if present`() {
assertThat(TestRuleSetProvider.number.value(TestConfig("number" to "37"))).isEqualTo(37)
}
@Nested
inner class `string property` {
@Test
fun `reads the value in config if present`() {
assertThat(TestRuleSetProvider.fileName.value(TestConfig("fileName" to "main.kt")))
.isEqualTo("main.kt")
}
@Test
fun `uses the default value in config if not present`() {
assertThat(TestRuleSetProvider.number.value(TestConfig())).isEqualTo(42)
}
}
@Test
fun `uses the default value in config if not present`() {
assertThat(TestRuleSetProvider.fileName.value(TestConfig()))
.isEqualTo("test.kt")
}
@Nested
inner class `string property` {
@Test
fun `reads the value in config if present`() {
assertThat(TestRuleSetProvider.fileName.value(TestConfig("fileName" to "main.kt")))
.isEqualTo("main.kt")
}
@Test
fun `uses the default value in config if not present`() {
assertThat(TestRuleSetProvider.fileName.value(TestConfig()))
.isEqualTo("test.kt")
}
}
}

View File

@@ -7,274 +7,271 @@ import org.junit.jupiter.api.Test
class SimpleGlobSpec {
@Nested
inner class `glob` {
@Nested
inner class `empty pattern` {
private val subject = SimpleGlob.of("")
inner class `empty pattern` {
private val subject = SimpleGlob.of("")
@Test
fun `matches an empty string`() {
val actual = subject.matches("")
assertThat(actual).isTrue()
@Test
fun `matches an empty string`() {
val actual = subject.matches("")
assertThat(actual).isTrue()
}
@Test
fun `does not match a blank string`() {
val actual = subject.matches(" ")
assertThat(actual).isFalse()
}
}
@Nested
inner class `blank pattern` {
private val subject = SimpleGlob.of(" \t")
@Test
fun `matches an empty string`() {
val actual = subject.matches(" \t")
assertThat(actual).isTrue()
}
@Test
fun `does not match a different string`() {
val actual = subject.matches(" ")
assertThat(actual).isFalse()
}
}
@Nested
inner class `Static pattern` {
private val subject = SimpleGlob.of("abc")
@Test
fun `matches the same string`() {
val actual = subject.matches("abc")
assertThat(actual).isTrue()
}
@Test
fun `does not match a other string`() {
val actual = subject.matches("aaa")
assertThat(actual).isFalse()
}
}
@Nested
inner class `Asterisk wildcard` {
@Nested
inner class `single wildcard` {
@Nested
inner class `pattern with wildcard at the beginning` {
private val subject = SimpleGlob.of("*xyz")
@Test
fun `matches pattern exactly`() {
val actual = subject.matches("xyz")
assertThat(actual).isTrue()
}
@Test
fun `matches pattern with anything before`() {
val actual = subject.matches("abcxyz")
assertThat(actual).isTrue()
}
@Test
fun `does not match with anything after`() {
val actual = subject.matches("xyzabc")
assertThat(actual).isFalse()
}
}
@Test
fun `does not match a blank string`() {
val actual = subject.matches(" ")
assertThat(actual).isFalse()
@Nested
inner class `Pattern with wildcard at the end` {
private val subject = SimpleGlob.of("xyz*")
@Test
fun `matches pattern exactly`() {
val actual = subject.matches("xyz")
assertThat(actual).isTrue()
}
@Test
fun `matches pattern with anything after`() {
val actual = subject.matches("xyzabc")
assertThat(actual).isTrue()
}
@Test
fun `does not match with anything before`() {
val actual = subject.matches("abcxyz")
assertThat(actual).isFalse()
}
}
@Nested
inner class `Pattern with wildcard at the middle` {
private val subject = SimpleGlob.of("x*yz")
@Test
fun `matches pattern exactly`() {
val actual = subject.matches("xyz")
assertThat(actual).isTrue()
}
@Test
fun `matches pattern with anything in between`() {
val actual = subject.matches("xaaaayz")
assertThat(actual).isTrue()
}
@Test
fun `does not match with anything before`() {
val actual = subject.matches("axyz")
assertThat(actual).isFalse()
}
@Test
fun `does not match with anything after`() {
val actual = subject.matches("xyza")
assertThat(actual).isFalse()
}
}
}
@Nested
inner class `blank pattern` {
private val subject = SimpleGlob.of(" \t")
inner class `multiple wildcards` {
private val subject = SimpleGlob.of("x*yz*")
@Test
fun `matches an empty string`() {
val actual = subject.matches(" \t")
fun `matches pattern`() {
val actual = subject.matches("x.aaa.yz.bbb")
assertThat(actual).isTrue()
}
}
}
@Test
fun `does not match a different string`() {
val actual = subject.matches(" ")
assertThat(actual).isFalse()
@Nested
inner class `Questionmark wildcard` {
@Nested
inner class `single wildcard` {
@Nested
inner class `pattern with wildcard at the beginning` {
private val subject = SimpleGlob.of("?xyz")
@Test
fun `matches with any character before`() {
val actual = subject.matches("_xyz")
assertThat(actual).isTrue()
}
@Test
fun `does not match with anything before`() {
val actual = subject.matches("xyz")
assertThat(actual).isFalse()
}
@Test
fun `does not match with more than on character before`() {
val actual = subject.matches("aaxyz")
assertThat(actual).isFalse()
}
}
@Nested
inner class `pattern with wildcard at the end` {
private val subject = SimpleGlob.of("xyz?")
@Test
fun `matches with any character after`() {
val actual = subject.matches("xyz_")
assertThat(actual).isTrue()
}
@Test
fun `does not match with anything after`() {
val actual = subject.matches("xyz")
assertThat(actual).isFalse()
}
@Test
fun `does not match with more than on character after`() {
val actual = subject.matches("xyz_a")
assertThat(actual).isFalse()
}
}
@Nested
inner class `pattern with wildcard at the middle` {
private val subject = SimpleGlob.of("x?yz")
@Test
fun `matches with any single character`() {
val actual = subject.matches("x_yz")
assertThat(actual).isTrue()
}
@Test
fun `does not match with more than one character`() {
val actual = subject.matches("x_a_yz")
assertThat(actual).isFalse()
}
}
}
@Nested
inner class `Static pattern` {
private val subject = SimpleGlob.of("abc")
inner class `multiple wildcards` {
private val subject = SimpleGlob.of("x?y?z")
@Test
fun `matches pattern`() {
val actual = subject.matches("x.y.z")
assertThat(actual).isTrue()
}
}
}
@Nested
inner class `characters that have a special meaning in regular expression must be escaped` {
@Nested
inner class `Period` {
private val subject = SimpleGlob.of("a.b.c")
@Test
fun `matches the same string`() {
val actual = subject.matches("abc")
val actual = subject.matches("a.b.c")
assertThat(actual).isTrue()
}
@Test
fun `does not match a other string`() {
val actual = subject.matches("aaa")
val actual = subject.matches("a_b_c")
assertThat(actual).isFalse()
}
}
@Nested
inner class `Asterisk wildcard` {
@Nested
inner class `single wildcard` {
@Nested
inner class `pattern with wildcard at the beginning` {
private val subject = SimpleGlob.of("*xyz")
inner class `Backslash` {
private val subject = SimpleGlob.of("""ab\d""")
@Test
fun `matches pattern exactly`() {
val actual = subject.matches("xyz")
assertThat(actual).isTrue()
}
@Test
fun `matches pattern with anything before`() {
val actual = subject.matches("abcxyz")
assertThat(actual).isTrue()
}
@Test
fun `does not match with anything after`() {
val actual = subject.matches("xyzabc")
assertThat(actual).isFalse()
}
}
@Nested
inner class `Pattern with wildcard at the end` {
private val subject = SimpleGlob.of("xyz*")
@Test
fun `matches pattern exactly`() {
val actual = subject.matches("xyz")
assertThat(actual).isTrue()
}
@Test
fun `matches pattern with anything after`() {
val actual = subject.matches("xyzabc")
assertThat(actual).isTrue()
}
@Test
fun `does not match with anything before`() {
val actual = subject.matches("abcxyz")
assertThat(actual).isFalse()
}
}
@Nested
inner class `Pattern with wildcard at the middle` {
private val subject = SimpleGlob.of("x*yz")
@Test
fun `matches pattern exactly`() {
val actual = subject.matches("xyz")
assertThat(actual).isTrue()
}
@Test
fun `matches pattern with anything in between`() {
val actual = subject.matches("xaaaayz")
assertThat(actual).isTrue()
}
@Test
fun `does not match with anything before`() {
val actual = subject.matches("axyz")
assertThat(actual).isFalse()
}
@Test
fun `does not match with anything after`() {
val actual = subject.matches("xyza")
assertThat(actual).isFalse()
}
}
}
@Nested
inner class `multiple wildcards` {
private val subject = SimpleGlob.of("x*yz*")
@Test
fun `matches pattern`() {
val actual = subject.matches("x.aaa.yz.bbb")
assertThat(actual).isTrue()
}
}
}
@Nested
inner class `Questionmark wildcard` {
@Nested
inner class `single wildcard` {
@Nested
inner class `pattern with wildcard at the beginning` {
private val subject = SimpleGlob.of("?xyz")
@Test
fun `matches with any character before`() {
val actual = subject.matches("_xyz")
assertThat(actual).isTrue()
}
@Test
fun `does not match with anything before`() {
val actual = subject.matches("xyz")
assertThat(actual).isFalse()
}
@Test
fun `does not match with more than on character before`() {
val actual = subject.matches("aaxyz")
assertThat(actual).isFalse()
}
}
@Nested
inner class `pattern with wildcard at the end` {
private val subject = SimpleGlob.of("xyz?")
@Test
fun `matches with any character after`() {
val actual = subject.matches("xyz_")
assertThat(actual).isTrue()
}
@Test
fun `does not match with anything after`() {
val actual = subject.matches("xyz")
assertThat(actual).isFalse()
}
@Test
fun `does not match with more than on character after`() {
val actual = subject.matches("xyz_a")
assertThat(actual).isFalse()
}
}
@Nested
inner class `pattern with wildcard at the middle` {
private val subject = SimpleGlob.of("x?yz")
@Test
fun `matches with any single character`() {
val actual = subject.matches("x_yz")
assertThat(actual).isTrue()
}
@Test
fun `does not match with more than one character`() {
val actual = subject.matches("x_a_yz")
assertThat(actual).isFalse()
}
}
}
@Nested
inner class `multiple wildcards` {
private val subject = SimpleGlob.of("x?y?z")
@Test
fun `matches pattern`() {
val actual = subject.matches("x.y.z")
assertThat(actual).isTrue()
}
}
}
@Nested
inner class `characters that have a special meaning in regular expression must be escaped` {
@Nested
inner class `Period` {
private val subject = SimpleGlob.of("a.b.c")
@Test
fun `matches the same string`() {
val actual = subject.matches("a.b.c")
assertThat(actual).isTrue()
}
@Test
fun `does not match a other string`() {
val actual = subject.matches("a_b_c")
assertThat(actual).isFalse()
}
}
@Nested
inner class `Backslash` {
private val subject = SimpleGlob.of("""ab\d""")
@Test
fun `matches the same string`() {
val actual = subject.matches("""ab\d""")
assertThat(actual).isTrue()
}
@Test
fun `does not match a other string`() {
val actual = subject.matches("ab5")
assertThat(actual).isFalse()
}
}
}
@Nested
inner class `invalid pattern` {
@Test
fun `fails during creation`() {
assertThatThrownBy { SimpleGlob.of("""a[b""") }
.isInstanceOf(IllegalArgumentException::class.java)
fun `matches the same string`() {
val actual = subject.matches("""ab\d""")
assertThat(actual).isTrue()
}
@Test
fun `does not match a other string`() {
val actual = subject.matches("ab5")
assertThat(actual).isFalse()
}
}
}
@Nested
inner class `invalid pattern` {
@Test
fun `fails during creation`() {
assertThatThrownBy { SimpleGlob.of("""a[b""") }
.isInstanceOf(IllegalArgumentException::class.java)
}
}
}

View File

@@ -23,7 +23,8 @@ class MainSpec {
@Nested
inner class `Build runner` {
fun runnerConfigs(): List<Arguments> {
@Suppress("UnusedPrivateMember")
private fun runnerConfigs(): List<Arguments> {
return listOf(
arguments(arrayOf("--generate-config"), ConfigExporter::class),
arguments(arrayOf("--run-rule", "RuleSet:Rule"), Runner::class),

View File

@@ -9,101 +9,97 @@ import java.nio.file.Paths
class PathFiltersSpec {
@Test
fun `should load single filter`() {
val filters = CliArgs { excludes = "**/one/**" }.toSpecFilters()
assertThat(filters?.isIgnored(Paths.get("/one/path"))).isTrue()
assertThat(filters?.isIgnored(Paths.get("/two/path"))).isFalse()
}
@Nested
inner class `parse different filter settings` {
inner class `parsing with different nullability combinations of path filters` {
@Test
fun `should load single filter`() {
val filters = CliArgs { excludes = "**/one/**" }.toSpecFilters()
assertThat(filters?.isIgnored(Paths.get("/one/path"))).isTrue()
assertThat(filters?.isIgnored(Paths.get("/two/path"))).isFalse()
}
@Nested
inner class `parsing with different nullability combinations of path filters` {
@Test
fun `returns an empty path filter when includes are empty and excludes are empty`() {
val pathFilter = PathFilters.of(emptyList(), emptyList())
assertThat(pathFilter).isNull()
}
@Test
fun `parses includes correctly`() {
val pathFilter = PathFilters.of(listOf("**/one/**", "**/two/**"), emptyList())
assertThat(pathFilter).isNotNull
assertThat(pathFilter?.isIgnored(Paths.get("/one/path"))).isFalse
assertThat(pathFilter?.isIgnored(Paths.get("/two/path"))).isFalse
assertThat(pathFilter?.isIgnored(Paths.get("/three/path"))).isTrue
}
@Test
fun `parses excludes correctly`() {
val pathFilter = PathFilters.of(emptyList(), listOf("**/one/**", "**/two/**"))
assertThat(pathFilter).isNotNull
assertThat(pathFilter?.isIgnored(Paths.get("/one/path"))).isTrue
assertThat(pathFilter?.isIgnored(Paths.get("/two/path"))).isTrue
assertThat(pathFilter?.isIgnored(Paths.get("/three/path"))).isFalse
}
@Test
fun `parses both includes and excludes correctly`() {
val pathFilter = PathFilters.of(listOf("**/one/**"), listOf("**/two/**"))
assertThat(pathFilter).isNotNull
assertThat(pathFilter?.isIgnored(Paths.get("/one/path"))).isFalse
assertThat(pathFilter?.isIgnored(Paths.get("/two/path"))).isTrue
assertThat(pathFilter?.isIgnored(Paths.get("/three/path"))).isTrue
}
}
@Nested
inner class `parsing with different separators` {
@Test
fun `should load multiple comma-separated filters with no spaces around commas`() {
val filters = CliArgs { excludes = "**/one/**,**/two/**,**/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
@Test
fun `should load multiple semicolon-separated filters with no spaces around semicolons`() {
val filters = CliArgs { excludes = "**/one/**;**/two/**;**/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
@Test
fun `should load multiple comma-separated filters with spaces around commas`() {
val filters = CliArgs { excludes = "**/one/** ,**/two/**, **/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
@Test
fun `should load multiple semicolon-separated filters with spaces around semicolons`() {
val filters = CliArgs { excludes = "**/one/** ;**/two/**; **/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
@Test
fun `should load multiple mixed-separated filters with no spaces around separators`() {
val filters = CliArgs { excludes = "**/one/**,**/two/**;**/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
@Test
fun `should load multiple mixed-separated filters with spaces around separators`() {
val filters = CliArgs { excludes = "**/one/** ,**/two/**; **/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
fun `returns an empty path filter when includes are empty and excludes are empty`() {
val pathFilter = PathFilters.of(emptyList(), emptyList())
assertThat(pathFilter).isNull()
}
@Test
fun `should ignore empty and blank filters`() {
val filters = CliArgs { excludes = " ,,**/three" }.toSpecFilters()
assertThat(filters?.isIgnored(Paths.get("/three"))).isTrue()
assertThat(filters?.isIgnored(Paths.get("/root/three"))).isTrue()
assertThat(filters?.isIgnored(Paths.get("/one/path"))).isFalse()
assertThat(filters?.isIgnored(Paths.get("/two/path"))).isFalse()
assertThat(filters?.isIgnored(Paths.get("/three/path"))).isFalse()
fun `parses includes correctly`() {
val pathFilter = PathFilters.of(listOf("**/one/**", "**/two/**"), emptyList())
assertThat(pathFilter).isNotNull
assertThat(pathFilter?.isIgnored(Paths.get("/one/path"))).isFalse
assertThat(pathFilter?.isIgnored(Paths.get("/two/path"))).isFalse
assertThat(pathFilter?.isIgnored(Paths.get("/three/path"))).isTrue
}
@Test
fun `parses excludes correctly`() {
val pathFilter = PathFilters.of(emptyList(), listOf("**/one/**", "**/two/**"))
assertThat(pathFilter).isNotNull
assertThat(pathFilter?.isIgnored(Paths.get("/one/path"))).isTrue
assertThat(pathFilter?.isIgnored(Paths.get("/two/path"))).isTrue
assertThat(pathFilter?.isIgnored(Paths.get("/three/path"))).isFalse
}
@Test
fun `parses both includes and excludes correctly`() {
val pathFilter = PathFilters.of(listOf("**/one/**"), listOf("**/two/**"))
assertThat(pathFilter).isNotNull
assertThat(pathFilter?.isIgnored(Paths.get("/one/path"))).isFalse
assertThat(pathFilter?.isIgnored(Paths.get("/two/path"))).isTrue
assertThat(pathFilter?.isIgnored(Paths.get("/three/path"))).isTrue
}
}
@Nested
inner class `parsing with different separators` {
@Test
fun `should load multiple comma-separated filters with no spaces around commas`() {
val filters = CliArgs { excludes = "**/one/**,**/two/**,**/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
@Test
fun `should load multiple semicolon-separated filters with no spaces around semicolons`() {
val filters = CliArgs { excludes = "**/one/**;**/two/**;**/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
@Test
fun `should load multiple comma-separated filters with spaces around commas`() {
val filters = CliArgs { excludes = "**/one/** ,**/two/**, **/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
@Test
fun `should load multiple semicolon-separated filters with spaces around semicolons`() {
val filters = CliArgs { excludes = "**/one/** ;**/two/**; **/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
@Test
fun `should load multiple mixed-separated filters with no spaces around separators`() {
val filters = CliArgs { excludes = "**/one/**,**/two/**;**/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
@Test
fun `should load multiple mixed-separated filters with spaces around separators`() {
val filters = CliArgs { excludes = "**/one/** ,**/two/**; **/three" }.toSpecFilters()
assertSameFiltersIndependentOfSpacingAndSeparater(filters)
}
}
@Test
fun `should ignore empty and blank filters`() {
val filters = CliArgs { excludes = " ,,**/three" }.toSpecFilters()
assertThat(filters?.isIgnored(Paths.get("/three"))).isTrue()
assertThat(filters?.isIgnored(Paths.get("/root/three"))).isTrue()
assertThat(filters?.isIgnored(Paths.get("/one/path"))).isFalse()
assertThat(filters?.isIgnored(Paths.get("/two/path"))).isFalse()
assertThat(filters?.isIgnored(Paths.get("/three/path"))).isFalse()
}
}

View File

@@ -12,109 +12,106 @@ import java.nio.file.Paths
class ReportPathSpec {
@EnabledOnOs(OS.WINDOWS)
@Nested
inner class `report paths` {
@EnabledOnOs(OS.WINDOWS)
@Nested
inner class `a Windows path` {
@Test
fun `parses a valid absolute path correctly`() {
val reportPath = ReportPath.from("test:C:\\tmp\\valid\\report")
inner class `a Windows path` {
@Test
fun `parses a valid absolute path correctly`() {
val reportPath = ReportPath.from("test:C:\\tmp\\valid\\report")
assertThat(reportPath.path).isEqualTo(Paths.get("C:\\tmp\\valid\\report"))
}
@Test
fun `parses a valid relative path correctly`() {
val reportPath = ReportPath.from("test:valid\\report")
assertThat(reportPath.path).isEqualTo(Paths.get("valid\\report"))
}
@Test
fun `fails when the path is empty`() {
assertThatIllegalArgumentException()
.isThrownBy { ReportPath.from("test:") }
}
@Test
fun `fails when the path is malformed`() {
assertThatIllegalArgumentException()
.isThrownBy { ReportPath.from("test:a*a") }
}
assertThat(reportPath.path).isEqualTo(Paths.get("C:\\tmp\\valid\\report"))
}
@DisabledOnOs(OS.WINDOWS)
@Nested
inner class `a POSIX path` {
@Test
fun `parses a valid absolute path correctly`() {
val reportPath = ReportPath.from("test:/tmp/valid/report")
@Test
fun `parses a valid relative path correctly`() {
val reportPath = ReportPath.from("test:valid\\report")
assertThat(reportPath.path).isEqualTo(Paths.get("/tmp/valid/report"))
}
@Test
fun `parses a valid relative path correctly`() {
val reportPath = ReportPath.from("test:valid/report")
assertThat(reportPath.path).isEqualTo(Paths.get("valid/report"))
}
@Test
fun `fails when the path is empty`() {
assertThatIllegalArgumentException()
.isThrownBy { ReportPath.from("test:") }
}
@Test
fun `fails when the path is malformed`() {
assertThatIllegalArgumentException()
.isThrownBy { ReportPath.from("test:a${0.toChar()}a") }
}
assertThat(reportPath.path).isEqualTo(Paths.get("valid\\report"))
}
@Nested
inner class `_kind_ processing` {
@Test
fun `parses and maps the txt kind correctly`() {
val reportPath = ReportPath.from("txt:/tmp/valid/report")
@Test
fun `fails when the path is empty`() {
assertThatIllegalArgumentException()
.isThrownBy { ReportPath.from("test:") }
}
assertThat(reportPath.kind).isEqualTo("txt")
}
@Test
fun `fails when the path is malformed`() {
assertThatIllegalArgumentException()
.isThrownBy { ReportPath.from("test:a*a") }
}
}
@Test
fun `parses and maps the xml kind correctly`() {
val reportPath = ReportPath.from("xml:/tmp/valid/report")
@DisabledOnOs(OS.WINDOWS)
@Nested
inner class `a POSIX path` {
@Test
fun `parses a valid absolute path correctly`() {
val reportPath = ReportPath.from("test:/tmp/valid/report")
assertThat(reportPath.kind).isEqualTo("xml")
}
assertThat(reportPath.path).isEqualTo(Paths.get("/tmp/valid/report"))
}
@Test
fun `parses and maps the html kind correctly`() {
val reportPath = ReportPath.from("html:/tmp/valid/report")
@Test
fun `parses a valid relative path correctly`() {
val reportPath = ReportPath.from("test:valid/report")
assertThat(reportPath.kind).isEqualTo("html")
}
assertThat(reportPath.path).isEqualTo(Paths.get("valid/report"))
}
@Test
fun `parses a non-default kind correctly`() {
val reportPath = ReportPath.from("test:/tmp/valid/report")
@Test
fun `fails when the path is empty`() {
assertThatIllegalArgumentException()
.isThrownBy { ReportPath.from("test:") }
}
assertThat(reportPath.kind).isEqualTo("test")
}
@Test
fun `fails when the path is malformed`() {
assertThatIllegalArgumentException()
.isThrownBy { ReportPath.from("test:a${0.toChar()}a") }
}
}
@Test
fun `fails when the kind is empty`() {
assertThatIllegalArgumentException()
.isThrownBy { ReportPath.from(":/tmp/anything") }
}
@Nested
inner class `_kind_ processing` {
@Test
fun `parses and maps the txt kind correctly`() {
val reportPath = ReportPath.from("txt:/tmp/valid/report")
@Test
fun `fails when part size is illegal`() {
assertThatIllegalStateException()
.isThrownBy { ReportPath.from("") }
}
assertThat(reportPath.kind).isEqualTo("txt")
}
@Test
fun `parses and maps the xml kind correctly`() {
val reportPath = ReportPath.from("xml:/tmp/valid/report")
assertThat(reportPath.kind).isEqualTo("xml")
}
@Test
fun `parses and maps the html kind correctly`() {
val reportPath = ReportPath.from("html:/tmp/valid/report")
assertThat(reportPath.kind).isEqualTo("html")
}
@Test
fun `parses a non-default kind correctly`() {
val reportPath = ReportPath.from("test:/tmp/valid/report")
assertThat(reportPath.kind).isEqualTo("test")
}
@Test
fun `fails when the kind is empty`() {
assertThatIllegalArgumentException()
.isThrownBy { ReportPath.from(":/tmp/anything") }
}
@Test
fun `fails when part size is illegal`() {
assertThatIllegalStateException()
.isThrownBy { ReportPath.from("") }
}
}
}

View File

@@ -12,49 +12,45 @@ import java.io.PrintStream
class AstPrinterSpec {
val path = resourceAsPath("cases").toString()
@Nested
inner class `element printer` {
val path = resourceAsPath("cases").toString()
@Nested
inner class `successful AST printing` {
@Test
fun `should print the AST as string`() {
val output = ByteArrayOutputStream()
val args = CliArgs()
args.input = resourceAsPath("cases/Poko.kt").toString()
val printer = AstPrinter(args, PrintStream(output))
printer.execute()
assertThat(output.toString()).isNotEmpty()
}
}
inner class `successful AST printing` {
@Test
fun `throws an exception when declaring multiple input files`() {
val multiplePaths = "$path,$path"
fun `should print the AST as string`() {
val output = ByteArrayOutputStream()
val args = CliArgs()
args.input = multiplePaths
val printer = AstPrinter(args, NullPrintStream())
args.input = resourceAsPath("cases/Poko.kt").toString()
val printer = AstPrinter(args, PrintStream(output))
assertThatIllegalArgumentException()
.isThrownBy { printer.execute() }
.withMessage("More than one input path specified. Printing AST is only supported for single files.")
}
printer.execute()
@Test
fun `throws an exception when trying to print the AST of a directory`() {
val args = CliArgs()
args.input = path
val printer = AstPrinter(args, NullPrintStream())
assertThatIllegalArgumentException()
.isThrownBy { printer.execute() }
.withMessageStartingWith("Input path ")
.withMessageEndingWith(" must be a kotlin file and not a directory.")
assertThat(output.toString()).isNotEmpty()
}
}
@Test
fun `throws an exception when declaring multiple input files`() {
val multiplePaths = "$path,$path"
val args = CliArgs()
args.input = multiplePaths
val printer = AstPrinter(args, NullPrintStream())
assertThatIllegalArgumentException()
.isThrownBy { printer.execute() }
.withMessage("More than one input path specified. Printing AST is only supported for single files.")
}
@Test
fun `throws an exception when trying to print the AST of a directory`() {
val args = CliArgs()
args.input = path
val printer = AstPrinter(args, NullPrintStream())
assertThatIllegalArgumentException()
.isThrownBy { printer.execute() }
.withMessageStartingWith("Input path ")
.withMessageEndingWith(" must be a kotlin file and not a directory.")
}
}

View File

@@ -3,23 +3,18 @@ package io.gitlab.arturbosch.detekt.cli.runners
import io.github.detekt.test.utils.compileForTest
import io.github.detekt.test.utils.resourceAsPath
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class ElementPrinterSpec {
@Nested
inner class `element printer` {
@Test
fun `should print the ast as string`() {
val case = resourceAsPath("cases/Poko.kt")
val ktFile = compileForTest(case)
@Test
fun `should print the ast as string`() {
val case = resourceAsPath("cases/Poko.kt")
val ktFile = compileForTest(case)
val dump = ElementPrinter.dump(ktFile)
val dump = ElementPrinter.dump(ktFile)
assertThat(dump.trimIndent()).isEqualTo(expected)
}
assertThat(dump.trimIndent()).isEqualTo(expected)
}
}

View File

@@ -11,47 +11,42 @@ import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.test.yamlConfig
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.psi.KtClass
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class CorrectableRulesFirstSpec {
@Nested
inner class `correctable rules are run first` {
@Test
fun `runs rule with id 'NonCorrectable' last`() {
var actualLastRuleId = ""
@Test
fun `runs rule with id 'NonCorrectable' last`() {
var actualLastRuleId = ""
class First(config: Config) : Rule(config) {
override val issue: Issue = justAnIssue.copy(id = "NonCorrectable")
override fun visitClass(klass: KtClass) {
actualLastRuleId = issue.id
}
class First(config: Config) : Rule(config) {
override val issue: Issue = justAnIssue.copy(id = "NonCorrectable")
override fun visitClass(klass: KtClass) {
actualLastRuleId = issue.id
}
class Last(config: Config) : Rule(config) {
override val issue: Issue = justAnIssue.copy(id = "Correctable")
override fun visitClass(klass: KtClass) {
actualLastRuleId = issue.id
}
}
val testFile = path.resolve("Test.kt")
val settings = createProcessingSettings(testFile, yamlConfig("configs/one-correctable-rule.yml"))
val detector = Analyzer(
settings,
listOf(object : RuleSetProvider {
override val ruleSetId: String = "Test"
override fun instance(config: Config) = RuleSet(ruleSetId, listOf(Last(config), First(config)))
}),
emptyList()
)
settings.use { detector.run(listOf(compileForTest(testFile))) }
assertThat(actualLastRuleId).isEqualTo("NonCorrectable")
}
class Last(config: Config) : Rule(config) {
override val issue: Issue = justAnIssue.copy(id = "Correctable")
override fun visitClass(klass: KtClass) {
actualLastRuleId = issue.id
}
}
val testFile = path.resolve("Test.kt")
val settings = createProcessingSettings(testFile, yamlConfig("configs/one-correctable-rule.yml"))
val detector = Analyzer(
settings,
listOf(object : RuleSetProvider {
override val ruleSetId: String = "Test"
override fun instance(config: Config) = RuleSet(ruleSetId, listOf(Last(config), First(config)))
}),
emptyList()
)
settings.use { detector.run(listOf(compileForTest(testFile))) }
assertThat(actualLastRuleId).isEqualTo("NonCorrectable")
}
}

View File

@@ -4,7 +4,6 @@ import io.github.detekt.test.utils.resourceAsPath
import io.gitlab.arturbosch.detekt.core.rules.RuleSetLocator
import io.gitlab.arturbosch.detekt.core.tooling.withSettings
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
/**
@@ -20,22 +19,18 @@ import org.junit.jupiter.api.Test
*/
class CustomRuleSetProviderSpec {
@Nested
inner class `custom rule sets should be loadable through jars` {
@Test
fun `should load the sample provider`() {
val sampleRuleSet = resourceAsPath("sample-rule-set.jar")
val spec = createNullLoggingSpec {
extensions {
disableDefaultRuleSets = true
fromPaths { listOf(sampleRuleSet) }
}
@Test
fun `custom rule sets should be loadable through jars should load the sample provider`() {
val sampleRuleSet = resourceAsPath("sample-rule-set.jar")
val spec = createNullLoggingSpec {
extensions {
disableDefaultRuleSets = true
fromPaths { listOf(sampleRuleSet) }
}
val providers = spec.withSettings { RuleSetLocator(this).load() }
assertThat(providers).filteredOn { it.ruleSetId == "sample" }.hasSize(1)
}
val providers = spec.withSettings { RuleSetLocator(this).load() }
assertThat(providers).filteredOn { it.ruleSetId == "sample" }.hasSize(1)
}
}

View File

@@ -5,7 +5,6 @@ import io.gitlab.arturbosch.detekt.api.FileProcessListener
import io.gitlab.arturbosch.detekt.test.yamlConfig
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.fail
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.reflections.Reflections
import java.lang.reflect.Modifier
@@ -15,28 +14,24 @@ import java.lang.reflect.Modifier
*/
class FileProcessorLocatorSpec {
@Nested
inner class `file processor locator` {
private val path = resourceAsPath("")
val path = resourceAsPath("")
@Test
fun `contains all processors`() {
val processors = createProcessingSettings(path).use { FileProcessorLocator(it).load() }
val processorClasses = getProcessorClasses()
@Test
fun `contains all processors`() {
val processors = createProcessingSettings(path).use { FileProcessorLocator(it).load() }
val processorClasses = getProcessorClasses()
assertThat(processorClasses).isNotEmpty
processorClasses
.filter { clazz -> processors.none { clazz == it.javaClass } }
.forEach { fail("$it processor is not loaded by the FileProcessorLocator") }
}
assertThat(processorClasses).isNotEmpty
processorClasses
.filter { clazz -> processors.none { clazz == it.javaClass } }
.forEach { fail("$it processor is not loaded by the FileProcessorLocator") }
}
@Test
fun `has disabled processors`() {
val config = yamlConfig("configs/disabled-processors.yml")
val processors = createProcessingSettings(path, config).use { FileProcessorLocator(it).load() }
assertThat(processors).isEmpty()
}
@Test
fun `has disabled processors`() {
val config = yamlConfig("configs/disabled-processors.yml")
val processors = createProcessingSettings(path, config).use { FileProcessorLocator(it).load() }
assertThat(processors).isEmpty()
}
}

View File

@@ -5,80 +5,75 @@ import io.gitlab.arturbosch.detekt.core.tooling.withSettings
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import org.jetbrains.kotlin.psi.KtFile
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.nio.file.Paths
class KtTreeCompilerSpec {
@Nested
inner class `tree compiler functionality` {
@Test
fun `should compile all files`() {
val (ktFiles, _) = fixture { compile(path) }
assertThat(ktFiles.size)
.describedAs("It should compile at least three files, but did ${ktFiles.size}")
.isGreaterThanOrEqualTo(3)
}
@Test
fun `should compile all files`() {
val (ktFiles, _) = fixture { compile(path) }
assertThat(ktFiles.size)
.describedAs("It should compile at least three files, but did ${ktFiles.size}")
.isGreaterThanOrEqualTo(3)
}
@Test
fun `should filter the file 'Default_kt'`() {
val (ktFiles, output) = fixture("**/Default.kt", loggingDebug = true) { compile(path) }
val ktFile = ktFiles.find { it.name == "Default.kt" }
assertThat(ktFile).describedAs("It should have no Default.kt file").isNull()
@Test
fun `should filter the file 'Default_kt'`() {
val (ktFiles, output) = fixture("**/Default.kt", loggingDebug = true) { compile(path) }
val ktFile = ktFiles.find { it.name == "Default.kt" }
assertThat(ktFile).describedAs("It should have no Default.kt file").isNull()
assertThat(output).contains("Ignoring file ")
}
assertThat(output).contains("Ignoring file ")
}
@Test
fun `should work with two or more filters`() {
val (ktFiles, _) = fixture(
"**/Default.kt",
"**/*Test*",
"**/*Complex*",
"**/*KotlinScript*"
) { compile(path) }
assertThat(ktFiles).isEmpty()
}
@Test
fun `should work with two or more filters`() {
val (ktFiles, _) = fixture(
"**/Default.kt",
"**/*Test*",
"**/*Complex*",
"**/*KotlinScript*"
) { compile(path) }
assertThat(ktFiles).isEmpty()
}
@Test
fun `should also compile regular files`() {
val (ktFiles, _) = fixture { compile(path.resolve("Default.kt")) }
assertThat(ktFiles.size).isEqualTo(1)
}
@Test
fun `should also compile regular files`() {
val (ktFiles, _) = fixture { compile(path.resolve("Default.kt")) }
assertThat(ktFiles.size).isEqualTo(1)
}
@Test
fun `throws an exception if given file does not exist`() {
val invalidPath = "NOTHERE"
assertThatIllegalArgumentException()
.isThrownBy { fixture { compile(Paths.get(invalidPath)) } }
.withMessage("Given path $invalidPath does not exist!")
}
@Test
fun `throws an exception if given file does not exist`() {
val invalidPath = "NOTHERE"
assertThatIllegalArgumentException()
.isThrownBy { fixture { compile(Paths.get(invalidPath)) } }
.withMessage("Given path $invalidPath does not exist!")
}
@Test
fun `does not compile a folder with a css file`() {
val cssPath = resourceAsPath("css")
val (ktFiles, output) = fixture { compile(cssPath) }
assertThat(ktFiles).isEmpty()
assertThat(output).isEmpty()
}
@Test
fun `does not compile a folder with a css file`() {
val cssPath = resourceAsPath("css")
val (ktFiles, output) = fixture { compile(cssPath) }
assertThat(ktFiles).isEmpty()
assertThat(output).isEmpty()
}
@Test
fun `does not compile a css file`() {
val cssPath = resourceAsPath("css").resolve("test.css")
val (ktFiles, output) = fixture { compile(cssPath) }
assertThat(ktFiles).isEmpty()
assertThat(output).isEmpty()
}
@Test
fun `does not compile a css file`() {
val cssPath = resourceAsPath("css").resolve("test.css")
val (ktFiles, output) = fixture { compile(cssPath) }
assertThat(ktFiles).isEmpty()
assertThat(output).isEmpty()
}
@Test
fun `does not compile a css file but show log if debug is enabled`() {
val cssPath = resourceAsPath("css").resolve("test.css")
val (ktFiles, output) = fixture(loggingDebug = true) { compile(cssPath) }
assertThat(ktFiles).isEmpty()
assertThat(output).contains("Ignoring a file detekt cannot handle: ")
}
@Test
fun `does not compile a css file but show log if debug is enabled`() {
val cssPath = resourceAsPath("css").resolve("test.css")
val (ktFiles, output) = fixture(loggingDebug = true) { compile(cssPath) }
assertThat(ktFiles).isEmpty()
assertThat(output).contains("Ignoring a file detekt cannot handle: ")
}
}

View File

@@ -22,53 +22,48 @@ import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.psi.KtAnnotation
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class TopLevelAutoCorrectSpec {
@Nested
inner class `autoCorrect_ false on top level` {
@Test
fun `should format the test file but not print the modified content to disc`() {
val fileContentBeforeAutoCorrect = readResourceContent("cases/Test.kt")
val fileUnderTest = resourceAsPath("cases/Test.kt")
val spec = ProcessingSpec {
project {
inputPaths = listOf(fileUnderTest)
}
config {
resources = listOf(resourceUrl("configs/rule-and-ruleset-autocorrect-true.yaml"))
}
rules {
autoCorrect = false // fixture
}
logging {
outputChannel = NullPrintStream()
errorChannel = NullPrintStream()
}
@Test
fun `should format the test file but not print the modified content to disc`() {
val fileContentBeforeAutoCorrect = readResourceContent("cases/Test.kt")
val fileUnderTest = resourceAsPath("cases/Test.kt")
val spec = ProcessingSpec {
project {
inputPaths = listOf(fileUnderTest)
}
val contentChangedListener = object : FileProcessListener {
override fun onFinish(files: List<KtFile>, result: Detektion, bindingContext: BindingContext) {
assertThat(files).hasSize(1)
assertThat(files[0].text).isNotEqualToIgnoringWhitespace(fileContentBeforeAutoCorrect)
}
config {
resources = listOf(resourceUrl("configs/rule-and-ruleset-autocorrect-true.yaml"))
}
AnalysisFacade(spec).runAnalysis {
DefaultLifecycle(
mockk(),
it,
inputPathsToKtFiles,
processorsProvider = { listOf(contentChangedListener) },
ruleSetsProvider = { listOf(TopLevelAutoCorrectProvider()) }
)
rules {
autoCorrect = false // fixture
}
logging {
outputChannel = NullPrintStream()
errorChannel = NullPrintStream()
}
assertThat(readResourceContent("cases/Test.kt")).isEqualTo(fileContentBeforeAutoCorrect)
}
val contentChangedListener = object : FileProcessListener {
override fun onFinish(files: List<KtFile>, result: Detektion, bindingContext: BindingContext) {
assertThat(files).hasSize(1)
assertThat(files[0].text).isNotEqualToIgnoringWhitespace(fileContentBeforeAutoCorrect)
}
}
AnalysisFacade(spec).runAnalysis {
DefaultLifecycle(
mockk(),
it,
inputPathsToKtFiles,
processorsProvider = { listOf(contentChangedListener) },
ruleSetsProvider = { listOf(TopLevelAutoCorrectProvider()) }
)
}
assertThat(readResourceContent("cases/Test.kt")).isEqualTo(fileContentBeforeAutoCorrect)
}
}

View File

@@ -6,85 +6,80 @@ import io.gitlab.arturbosch.detekt.test.TestDetektion
import io.gitlab.arturbosch.detekt.test.createFinding
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.nio.file.Files
class BaselineFacadeSpec {
@Nested
inner class `a baseline facade` {
private val baselineFile = createTempDirectoryForTest("baseline_format").resolve("baseline.xml")
private val validBaseline = resourceAsPath("/baseline_feature/valid-baseline.xml")
private val baselineFile = createTempDirectoryForTest("baseline_format").resolve("baseline.xml")
private val validBaseline = resourceAsPath("/baseline_feature/valid-baseline.xml")
@AfterEach
fun tearDown() {
Files.deleteIfExists(baselineFile)
}
@AfterEach
fun tearDown() {
Files.deleteIfExists(baselineFile)
}
@Test
fun `returns a BaselineFilteredResult when the baseline exists`() {
val detektion = BaselineFacade().transformResult(validBaseline, TestDetektion())
@Test
fun `returns a BaselineFilteredResult when the baseline exists`() {
val detektion = BaselineFacade().transformResult(validBaseline, TestDetektion())
assertThat(detektion).isInstanceOf(BaselineFilteredResult::class.java)
}
assertThat(detektion).isInstanceOf(BaselineFilteredResult::class.java)
}
@Test
fun `returns the same detektion when the baseline doesn't exist`() {
val initialDetektion = TestDetektion()
val detektion = BaselineFacade().transformResult(baselineFile, initialDetektion)
@Test
fun `returns the same detektion when the baseline doesn't exist`() {
val initialDetektion = TestDetektion()
val detektion = BaselineFacade().transformResult(baselineFile, initialDetektion)
assertThat(detektion).isEqualTo(initialDetektion)
}
assertThat(detektion).isEqualTo(initialDetektion)
}
@Test
fun `doesn't create a baseline file without findings`() {
BaselineFacade().createOrUpdate(baselineFile, emptyList())
@Test
fun `doesn't create a baseline file without findings`() {
BaselineFacade().createOrUpdate(baselineFile, emptyList())
assertThat(baselineFile).doesNotExist()
}
assertThat(baselineFile).doesNotExist()
}
@Test
fun `creates on top of an existing a baseline file without findings`() {
Files.copy(validBaseline, baselineFile)
@Test
fun `creates on top of an existing a baseline file without findings`() {
Files.copy(validBaseline, baselineFile)
BaselineFacade().createOrUpdate(baselineFile, emptyList())
BaselineFacade().createOrUpdate(baselineFile, emptyList())
assertThat(baselineFile).hasContent(
"""
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues>
<ID>LongParameterList:Signature</ID>
<ID>LongMethod:Signature</ID>
</ManuallySuppressedIssues>
<CurrentIssues></CurrentIssues>
</SmellBaseline>
""".trimIndent()
)
}
assertThat(baselineFile).hasContent(
"""
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues>
<ID>LongParameterList:Signature</ID>
<ID>LongMethod:Signature</ID>
</ManuallySuppressedIssues>
<CurrentIssues></CurrentIssues>
</SmellBaseline>
""".trimIndent()
)
}
@Test
fun `creates on top of an existing a baseline file with findings`() {
Files.copy(validBaseline, baselineFile)
@Test
fun `creates on top of an existing a baseline file with findings`() {
Files.copy(validBaseline, baselineFile)
BaselineFacade().createOrUpdate(baselineFile, listOf(createFinding()))
BaselineFacade().createOrUpdate(baselineFile, listOf(createFinding()))
assertThat(baselineFile).hasContent(
"""
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues>
<ID>LongParameterList:Signature</ID>
<ID>LongMethod:Signature</ID>
</ManuallySuppressedIssues>
<CurrentIssues>
<ID>TestSmell:TestEntitySignature</ID>
</CurrentIssues>
</SmellBaseline>
""".trimIndent()
)
}
assertThat(baselineFile).hasContent(
"""
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues>
<ID>LongParameterList:Signature</ID>
<ID>LongMethod:Signature</ID>
</ManuallySuppressedIssues>
<CurrentIssues>
<ID>TestSmell:TestEntitySignature</ID>
</CurrentIssues>
</SmellBaseline>
""".trimIndent()
)
}
}

View File

@@ -5,44 +5,39 @@ import io.gitlab.arturbosch.detekt.test.TestDetektion
import io.mockk.every
import io.mockk.mockk
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class BaselineFilteredResultSpec {
@Nested
inner class `baseline based result transformation` {
private val baselineFile = resourceAsPath("/baseline_feature/valid-baseline.xml")
private val baselineFile = resourceAsPath("/baseline_feature/valid-baseline.xml")
private val result = TestDetektion(
mockk {
every { id }.returns("LongParameterList")
every { signature }.returns("Signature")
},
mockk {
every { id }.returns("LongMethod")
every { signature }.returns("Signature")
},
mockk {
every { id }.returns("FeatureEnvy")
every { signature }.returns("Signature")
},
)
private val result = TestDetektion(
mockk {
every { id }.returns("LongParameterList")
every { signature }.returns("Signature")
},
mockk {
every { id }.returns("LongMethod")
every { signature }.returns("Signature")
},
mockk {
every { id }.returns("FeatureEnvy")
every { signature }.returns("Signature")
},
)
@Test
fun `does return the same finding on empty baseline`() {
val actual = BaselineFilteredResult(result, Baseline(emptySet(), emptySet()))
assertThat(actual.findings).hasSize(3)
}
@Test
fun `does return the same finding on empty baseline`() {
val actual = BaselineFilteredResult(result, Baseline(emptySet(), emptySet()))
assertThat(actual.findings).hasSize(3)
}
@Test
fun `filters with an existing baseline file`() {
val baseline = Baseline.load(baselineFile)
val actual = BaselineFilteredResult(result, baseline)
// Note: Detektion works with Map<RuleSetId, List<Finding>
// but the TestDetektion maps the RuleId as RuleSetId
actual.findings.forEach { (_, value) -> assertThat(value).isEmpty() }
}
@Test
fun `filters with an existing baseline file`() {
val baseline = Baseline.load(baselineFile)
val actual = BaselineFilteredResult(result, baseline)
// Note: Detektion works with Map<RuleSetId, List<Finding>
// but the TestDetektion maps the RuleId as RuleSetId
actual.findings.forEach { (_, value) -> assertThat(value).isEmpty() }
}
}

View File

@@ -12,78 +12,74 @@ import java.nio.file.Files
class BaselineFormatSpec {
@Nested
inner class `baseline format` {
inner class `read a baseline file` {
@Nested
inner class `read a baseline file` {
@Test
fun `loads the baseline file`() {
val path = resourceAsPath("/baseline_feature/valid-baseline.xml")
val (manuallySuppressedIssues, currentIssues) = BaselineFormat().read(path)
@Test
fun `loads the baseline file`() {
val path = resourceAsPath("/baseline_feature/valid-baseline.xml")
val (manuallySuppressedIssues, currentIssues) = BaselineFormat().read(path)
assertThat(manuallySuppressedIssues).hasSize(2)
assertThat(manuallySuppressedIssues).anySatisfy { it.startsWith("LongParameterList") }
assertThat(manuallySuppressedIssues).anySatisfy { it.startsWith("LongMethod") }
assertThat(currentIssues).hasSize(1)
assertThat(currentIssues).anySatisfy { it.startsWith("FeatureEnvy") }
}
@Test
fun `throws on an invalid baseline file extension`() {
val path = resourceAsPath("/baseline_feature/invalid-txt-baseline.txt")
assertThatThrownBy { BaselineFormat().read(path) }
.isInstanceOf(BaselineFormat.InvalidState::class.java)
}
@Test
fun `throws on an invalid baseline ID declaration`() {
val path = resourceAsPath("/baseline_feature/missing-temporary-suppressed-baseline.xml")
assertThatIllegalStateException()
.isThrownBy { BaselineFormat().read(path) }
.withMessage("The content of the ID element must not be empty")
}
@Test
fun `supports deprecated baseline values`() {
val path = resourceAsPath("/baseline_feature/deprecated-baseline.xml")
val (manuallySuppressedIssues, currentIssues) = BaselineFormat().read(path)
assertThat(manuallySuppressedIssues).hasSize(2)
assertThat(manuallySuppressedIssues).anySatisfy { it.startsWith("LongParameterList") }
assertThat(manuallySuppressedIssues).anySatisfy { it.startsWith("LongMethod") }
assertThat(currentIssues).hasSize(1)
assertThat(currentIssues).anySatisfy { it.startsWith("FeatureEnvy") }
}
assertThat(manuallySuppressedIssues).hasSize(2)
assertThat(manuallySuppressedIssues).anySatisfy { it.startsWith("LongParameterList") }
assertThat(manuallySuppressedIssues).anySatisfy { it.startsWith("LongMethod") }
assertThat(currentIssues).hasSize(1)
assertThat(currentIssues).anySatisfy { it.startsWith("FeatureEnvy") }
}
@Nested
inner class `writes a baseline file` {
@Test
fun `throws on an invalid baseline file extension`() {
val path = resourceAsPath("/baseline_feature/invalid-txt-baseline.txt")
assertThatThrownBy { BaselineFormat().read(path) }
.isInstanceOf(BaselineFormat.InvalidState::class.java)
}
private val savedBaseline = Baseline(setOf("4", "2", "2"), setOf("1", "2", "3"))
@Test
fun `throws on an invalid baseline ID declaration`() {
val path = resourceAsPath("/baseline_feature/missing-temporary-suppressed-baseline.xml")
assertThatIllegalStateException()
.isThrownBy { BaselineFormat().read(path) }
.withMessage("The content of the ID element must not be empty")
}
@Test
fun `has a new line at the end of the written baseline file`() {
val tempFile = createTempFileForTest("baseline1", ".xml")
@Test
fun `supports deprecated baseline values`() {
val path = resourceAsPath("/baseline_feature/deprecated-baseline.xml")
val (manuallySuppressedIssues, currentIssues) = BaselineFormat().read(path)
val format = BaselineFormat()
format.write(savedBaseline, tempFile)
val bytes = Files.readAllBytes(tempFile)
val content = String(bytes, Charsets.UTF_8)
assertThat(manuallySuppressedIssues).hasSize(2)
assertThat(manuallySuppressedIssues).anySatisfy { it.startsWith("LongParameterList") }
assertThat(manuallySuppressedIssues).anySatisfy { it.startsWith("LongMethod") }
assertThat(currentIssues).hasSize(1)
assertThat(currentIssues).anySatisfy { it.startsWith("FeatureEnvy") }
}
}
assertThat(content).endsWith(">\n")
}
@Nested
inner class `writes a baseline file` {
@Test
fun `asserts that the saved and loaded baseline files are equal`() {
val tempFile = createTempFileForTest("baseline-saved", ".xml")
private val savedBaseline = Baseline(setOf("4", "2", "2"), setOf("1", "2", "3"))
val format = BaselineFormat()
format.write(savedBaseline, tempFile)
val loadedBaseline = format.read(tempFile)
@Test
fun `has a new line at the end of the written baseline file`() {
val tempFile = createTempFileForTest("baseline1", ".xml")
assertThat(loadedBaseline).isEqualTo(savedBaseline)
}
val format = BaselineFormat()
format.write(savedBaseline, tempFile)
val bytes = Files.readAllBytes(tempFile)
val content = String(bytes, Charsets.UTF_8)
assertThat(content).endsWith(">\n")
}
@Test
fun `asserts that the saved and loaded baseline files are equal`() {
val tempFile = createTempFileForTest("baseline-saved", ".xml")
val format = BaselineFormat()
format.write(savedBaseline, tempFile)
val loadedBaseline = format.read(tempFile)
assertThat(loadedBaseline).isEqualTo(savedBaseline)
}
}
}

View File

@@ -13,7 +13,6 @@ import io.mockk.mockk
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.io.PrintStream
import java.net.URI
@@ -23,106 +22,102 @@ import java.nio.file.Path
@OptIn(UnstableApi::class)
class BaselineResultMappingSpec {
@Nested
inner class `a baseline result mapping` {
private val dir = createTempDirectoryForTest("baseline_format")
private val baselineFile = dir.resolve("baseline.xml")
private val existingBaselineFile = resourceAsPath("/baseline_feature/valid-baseline.xml")
private lateinit var findings: Map<String, List<Finding>>
private lateinit var finding: Finding
private val dir = createTempDirectoryForTest("baseline_format")
private val baselineFile = dir.resolve("baseline.xml")
private val existingBaselineFile = resourceAsPath("/baseline_feature/valid-baseline.xml")
private lateinit var findings: Map<String, List<Finding>>
private lateinit var finding: Finding
@BeforeEach
fun setupMocks() {
finding = mockk()
every { finding.id }.returns("SomeIssueId")
every { finding.signature }.returns("SomeSignature")
findings = mapOf("RuleSet" to listOf(finding))
}
@BeforeEach
fun setupMocks() {
finding = mockk()
every { finding.id }.returns("SomeIssueId")
every { finding.signature }.returns("SomeSignature")
findings = mapOf("RuleSet" to listOf(finding))
}
@AfterEach
fun tearDown() {
Files.deleteIfExists(baselineFile)
}
@AfterEach
fun tearDown() {
Files.deleteIfExists(baselineFile)
}
@Test
fun `should not create a new baseline file when no findings occurred`() {
val mapping = resultMapping(
baselineFile = baselineFile,
createBaseline = true,
)
@Test
fun `should not create a new baseline file when no findings occurred`() {
val mapping = resultMapping(
baselineFile = baselineFile,
createBaseline = true,
)
mapping.transformFindings(emptyMap())
mapping.transformFindings(emptyMap())
assertThat(baselineFile.exists()).isFalse()
}
assertThat(baselineFile.exists()).isFalse()
}
@Test
fun `should not update an existing baseline file if option configured as false`() {
val existing = Baseline.load(existingBaselineFile)
val mapping = resultMapping(
baselineFile = existingBaselineFile,
createBaseline = false,
)
@Test
fun `should not update an existing baseline file if option configured as false`() {
val existing = Baseline.load(existingBaselineFile)
val mapping = resultMapping(
baselineFile = existingBaselineFile,
createBaseline = false,
)
mapping.transformFindings(findings)
mapping.transformFindings(findings)
val changed = Baseline.load(existingBaselineFile)
assertThat(existing).isEqualTo(changed)
}
val changed = Baseline.load(existingBaselineFile)
assertThat(existing).isEqualTo(changed)
}
@Test
fun `should not update an existing baseline file if option is not configured`() {
val existing = Baseline.load(existingBaselineFile)
val mapping = resultMapping(
baselineFile = existingBaselineFile,
createBaseline = null,
)
@Test
fun `should not update an existing baseline file if option is not configured`() {
val existing = Baseline.load(existingBaselineFile)
val mapping = resultMapping(
baselineFile = existingBaselineFile,
createBaseline = null,
)
mapping.transformFindings(findings)
mapping.transformFindings(findings)
val changed = Baseline.load(existingBaselineFile)
assertThat(existing).isEqualTo(changed)
}
val changed = Baseline.load(existingBaselineFile)
assertThat(existing).isEqualTo(changed)
}
@Test
fun `should not create a new baseline file if no file is configured`() {
val mapping = resultMapping(
baselineFile = null,
createBaseline = false,
)
@Test
fun `should not create a new baseline file if no file is configured`() {
val mapping = resultMapping(
baselineFile = null,
createBaseline = false,
)
mapping.transformFindings(findings)
mapping.transformFindings(findings)
assertThat(baselineFile.exists()).isFalse()
}
assertThat(baselineFile.exists()).isFalse()
}
@Test
fun `should create a new baseline file if a file is configured`() {
val mapping = resultMapping(
baselineFile = baselineFile,
createBaseline = true,
)
@Test
fun `should create a new baseline file if a file is configured`() {
val mapping = resultMapping(
baselineFile = baselineFile,
createBaseline = true,
)
mapping.transformFindings(findings)
mapping.transformFindings(findings)
assertThat(baselineFile.exists()).isTrue()
}
assertThat(baselineFile.exists()).isTrue()
}
@Test
fun `should update an existing baseline file if a file is configured`() {
Files.copy(existingBaselineFile, baselineFile)
val existing = Baseline.load(baselineFile)
val mapping = resultMapping(
baselineFile = baselineFile,
createBaseline = true,
)
@Test
fun `should update an existing baseline file if a file is configured`() {
Files.copy(existingBaselineFile, baselineFile)
val existing = Baseline.load(baselineFile)
val mapping = resultMapping(
baselineFile = baselineFile,
createBaseline = true,
)
mapping.transformFindings(findings)
mapping.transformFindings(findings)
val changed = Baseline.load(baselineFile)
assertThat(existing).isNotEqualTo(changed)
}
val changed = Baseline.load(baselineFile)
assertThat(existing).isNotEqualTo(changed)
}
}

View File

@@ -13,85 +13,80 @@ import io.gitlab.arturbosch.detekt.core.createProcessingSettings
import io.gitlab.arturbosch.detekt.core.tooling.getDefaultConfiguration
import io.gitlab.arturbosch.detekt.test.yamlConfigFromContent
import org.assertj.core.api.Assertions.assertThatCode
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class SupportConfigValidationSpec {
@Nested
inner class `support config validation` {
private val testDir = createTempDirectoryForTest("detekt-sample")
private val spec = createNullLoggingSpec {}
private val testDir = createTempDirectoryForTest("detekt-sample")
private val spec = createNullLoggingSpec {}
@Test
fun `fails when unknown properties are found`() {
val config = yamlConfigFromContent(
"""
# Properties of custom rule sets get excluded by default.
sample-rule-set:
TooManyFunctions:
active: true
@Test
fun `fails when unknown properties are found`() {
val config = yamlConfigFromContent(
"""
# Properties of custom rule sets get excluded by default.
sample-rule-set:
TooManyFunctions:
active: true
# This properties are unknown to detekt and must be excluded.
my_additional_properties:
magic_number: 7
magic_string: 'Hello World'
"""
)
createProcessingSettings(testDir, config).use {
assertThatCode { checkConfiguration(it, spec.getDefaultConfiguration()) }
.isInstanceOf(InvalidConfig::class.java)
.hasMessageContaining("Run failed with 1 invalid config property.")
.hasMessageContaining("my_additional_properties")
}
# This properties are unknown to detekt and must be excluded.
my_additional_properties:
magic_number: 7
magic_string: 'Hello World'
"""
)
createProcessingSettings(testDir, config).use {
assertThatCode { checkConfiguration(it, spec.getDefaultConfiguration()) }
.isInstanceOf(InvalidConfig::class.java)
.hasMessageContaining("Run failed with 1 invalid config property.")
.hasMessageContaining("my_additional_properties")
}
}
@Test
fun `fails due to custom config validator want active to be booleans`() {
val config = yamlConfigFromContent(
"""
# Properties of custom rule sets get excluded by default.
sample-rule-set:
TooManyFunctions:
# This property is tested via the SampleConfigValidator
active: 1 # should be true
"""
)
createProcessingSettings(testDir, config).use {
assertThatCode { checkConfiguration(it, spec.getDefaultConfiguration()) }
.isInstanceOf(InvalidConfig::class.java)
.hasMessageContaining("Run failed with 1 invalid config property.")
}
@Test
fun `fails due to custom config validator want active to be booleans`() {
val config = yamlConfigFromContent(
"""
# Properties of custom rule sets get excluded by default.
sample-rule-set:
TooManyFunctions:
# This property is tested via the SampleConfigValidator
active: 1 # should be true
"""
)
createProcessingSettings(testDir, config).use {
assertThatCode { checkConfiguration(it, spec.getDefaultConfiguration()) }
.isInstanceOf(InvalidConfig::class.java)
.hasMessageContaining("Run failed with 1 invalid config property.")
}
}
@Test
fun `passes with excluded new properties`() {
val config = yamlConfigFromContent(
"""
config:
validation: true
# Additional properties can be useful when writing custom extensions.
# However only properties defined in the default config are known to detekt.
# All unknown properties are treated as errors if not excluded.
excludes: 'my_additional_properties'
@Test
fun `passes with excluded new properties`() {
val config = yamlConfigFromContent(
"""
config:
validation: true
# Additional properties can be useful when writing custom extensions.
# However only properties defined in the default config are known to detekt.
# All unknown properties are treated as errors if not excluded.
excludes: 'my_additional_properties'
# Properties of custom rule sets get excluded by default.
# If you want to validate them further, consider implementing a ConfigValidator.
sample-rule-set:
TooManyFunctions:
active: true
# Properties of custom rule sets get excluded by default.
# If you want to validate them further, consider implementing a ConfigValidator.
sample-rule-set:
TooManyFunctions:
active: true
# This properties are unknown to detekt and must be excluded.
my_additional_properties:
magic_number: 7
magic_string: 'Hello World'
"""
)
createProcessingSettings(testDir, config).use {
assertThatCode { checkConfiguration(it, spec.getDefaultConfiguration()) }
.doesNotThrowAnyException()
}
# This properties are unknown to detekt and must be excluded.
my_additional_properties:
magic_number: 7
magic_string: 'Hello World'
"""
)
createProcessingSettings(testDir, config).use {
assertThatCode { checkConfiguration(it, spec.getDefaultConfiguration()) }
.doesNotThrowAnyException()
}
}
}

View File

@@ -3,54 +3,49 @@ package io.gitlab.arturbosch.detekt.core.config
import io.gitlab.arturbosch.detekt.test.yamlConfig
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class CompositeConfigSpec {
@Nested
inner class `both configs should be considered` {
private val second = yamlConfig("composite-test.yml")
private val first = yamlConfig("detekt.yml")
private val compositeConfig = CompositeConfig(second, first)
private val second = yamlConfig("composite-test.yml")
private val first = yamlConfig("detekt.yml")
private val compositeConfig = CompositeConfig(second, first)
@Test
fun `should have style sub config with active false which is overridden in second config regardless of default value`() {
val styleConfig = compositeConfig.subConfig("style").subConfig("WildcardImport")
assertThat(styleConfig.valueOrDefault("active", true)).isEqualTo(false)
assertThat(styleConfig.valueOrDefault("active", false)).isEqualTo(false)
}
@Test
fun `should have style sub config with active false which is overridden in second config regardless of default value`() {
val styleConfig = compositeConfig.subConfig("style").subConfig("WildcardImport")
assertThat(styleConfig.valueOrDefault("active", true)).isEqualTo(false)
assertThat(styleConfig.valueOrDefault("active", false)).isEqualTo(false)
}
@Test
fun `should have code smell sub config with LongMethod threshold 20 from _first_ config`() {
val codeSmellConfig = compositeConfig.subConfig("code-smell").subConfig("LongMethod")
assertThat(codeSmellConfig.valueOrDefault("threshold", -1)).isEqualTo(20)
}
@Test
fun `should have code smell sub config with LongMethod threshold 20 from _first_ config`() {
val codeSmellConfig = compositeConfig.subConfig("code-smell").subConfig("LongMethod")
assertThat(codeSmellConfig.valueOrDefault("threshold", -1)).isEqualTo(20)
}
@Test
fun `should use the default as both part configurations do not have the value`() {
assertThat(compositeConfig.valueOrDefault("TEST", 42)).isEqualTo(42)
}
@Test
fun `should use the default as both part configurations do not have the value`() {
assertThat(compositeConfig.valueOrDefault("TEST", 42)).isEqualTo(42)
}
@Test
fun `should return a string based on default value`() {
val config = compositeConfig.subConfig("style").subConfig("MagicNumber")
val value = config.valueOrDefault("ignoreNumbers", emptyList<String>())
assertThat(value).isEqualTo(listOf("-1", "0", "1", "2", "100", "1000"))
}
@Test
fun `should return a string based on default value`() {
val config = compositeConfig.subConfig("style").subConfig("MagicNumber")
val value = config.valueOrDefault("ignoreNumbers", emptyList<String>())
assertThat(value).isEqualTo(listOf("-1", "0", "1", "2", "100", "1000"))
}
@Test
fun `should fail with a meaningful exception when boolean property is invalid`() {
val config = compositeConfig.subConfig("style").subConfig("LargeClass")
@Test
fun `should fail with a meaningful exception when boolean property is invalid`() {
val config = compositeConfig.subConfig("style").subConfig("LargeClass")
val expectedErrorMessage = "Value \"truuu\" set for config parameter \"style > LargeClass > active\" " +
"is not of required type Boolean"
val expectedErrorMessage = "Value \"truuu\" set for config parameter \"style > LargeClass > active\" " +
"is not of required type Boolean"
assertThatThrownBy {
config.valueOrDefault("active", true)
}.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining(expectedErrorMessage)
}
assertThatThrownBy {
config.valueOrDefault("active", true)
}.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining(expectedErrorMessage)
}
}

View File

@@ -2,24 +2,19 @@ package io.gitlab.arturbosch.detekt.core.config
import io.gitlab.arturbosch.detekt.test.yamlConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class DefaultConfigValidationSpec {
@Nested
inner class `default configuration is valid` {
private val baseline = yamlConfig("default-detekt-config.yml")
private val baseline = yamlConfig("default-detekt-config.yml")
@Test
fun `is valid comparing itself`() {
assertThat(validateConfig(baseline, baseline)).isEmpty()
}
@Test
fun `is valid comparing itself`() {
assertThat(validateConfig(baseline, baseline)).isEmpty()
}
@Test
fun `does not flag common known config sub sections`() {
assertThat(validateConfig(yamlConfig("common_known_sections.yml"), baseline)).isEmpty()
}
@Test
fun `does not flag common known config sub sections`() {
assertThat(validateConfig(yamlConfig("common_known_sections.yml"), baseline)).isEmpty()
}
}

View File

@@ -16,42 +16,121 @@ import org.junit.jupiter.params.provider.ValueSource
class ValidateConfigSpec {
private val baseline = yamlConfig("config_validation/baseline.yml")
@Test
fun `passes for same config test`() {
val result = validateConfig(baseline, baseline)
assertThat(result).isEmpty()
}
@Test
fun `passes for properties which may appear on rules and rule sets but may be not present in default config`() {
val result = validateConfig(
yamlConfig("config_validation/default-excluded-properties.yml"),
baseline
)
assertThat(result).isEmpty()
}
@Test
fun `reports different rule set name`() {
val result = validateConfig(
yamlConfig("config_validation/other-ruleset-name.yml"),
baseline
)
assertThat(result).contains(propertyDoesNotExists("code-smell"))
}
@Test
fun `reports different nested property names`() {
val result = validateConfig(
yamlConfig("config_validation/other-nested-property-names.yml"),
baseline
)
assertThat(result).contains(
propertyDoesNotExists("complexity>LongLongMethod"),
propertyDoesNotExists("complexity>LongParameterList>enabled"),
propertyDoesNotExists("complexity>LargeClass>howMany"),
propertyDoesNotExists("complexity>InnerMap>InnerKey"),
propertyDoesNotExists("complexity>InnerMap>Inner2>nestedActive")
)
}
@Test
fun `reports nested configuration expected`() {
val result = validateConfig(
yamlConfig("config_validation/no-nested-config.yml"),
baseline
)
assertThat(result).contains(
nestedConfigurationExpected("complexity"),
nestedConfigurationExpected("style>WildcardImport")
)
}
@Test
fun `reports unexpected nested configs`() {
// note that the baseline config is now the first argument
val result = validateConfig(baseline, yamlConfig("config_validation/no-value.yml"))
assertThat(result).contains(
unexpectedNestedConfiguration("style"),
unexpectedNestedConfiguration("comments")
)
}
@Test
fun `returns an error for an invalid config type`() {
val invalidConfig = TestConfig()
assertThatIllegalStateException().isThrownBy {
validateConfig(invalidConfig, baseline)
}.withMessageStartingWith("Unsupported config type for validation")
}
@Test
fun `returns an error for an invalid baseline`() {
val invalidBaseline = TestConfig()
assertThatIllegalArgumentException().isThrownBy {
validateConfig(Config.empty, invalidBaseline)
}.withMessageStartingWith("Only supported baseline config is the YamlConfig.")
}
@Test
fun `returns an error for an empty baseline`() {
val invalidBaseline = Config.empty
assertThatIllegalArgumentException().isThrownBy {
validateConfig(Config.empty, invalidBaseline)
}.withMessageStartingWith("Cannot validate configuration based on an empty baseline config.")
}
@Nested
inner class `validate configuration file` {
private val baseline = yamlConfig("config_validation/baseline.yml")
inner class `validate composite configurations` {
@Test
fun `passes for same config test`() {
val result = validateConfig(baseline, baseline)
fun `passes for same left, right and baseline config`() {
val result = validateConfig(CompositeConfig(baseline, baseline), baseline)
assertThat(result).isEmpty()
}
@Test
fun `passes for properties which may appear on rules and rule sets but may be not present in default config`() {
val result = validateConfig(
yamlConfig("config_validation/default-excluded-properties.yml"),
baseline
)
fun `passes for empty configs`() {
val result = validateConfig(CompositeConfig(Config.empty, Config.empty), baseline)
assertThat(result).isEmpty()
}
@Test
fun `reports different rule set name`() {
fun `finds accumulated errors`() {
val result = validateConfig(
yamlConfig("config_validation/other-ruleset-name.yml"),
CompositeConfig(
yamlConfig("config_validation/other-nested-property-names.yml"),
yamlConfig("config_validation/no-nested-config.yml")
),
baseline
)
assertThat(result).contains(propertyDoesNotExists("code-smell"))
}
@Test
fun `reports different nested property names`() {
val result = validateConfig(
yamlConfig("config_validation/other-nested-property-names.yml"),
baseline
)
assertThat(result).contains(
nestedConfigurationExpected("complexity"),
nestedConfigurationExpected("style>WildcardImport"),
propertyDoesNotExists("complexity>LongLongMethod"),
propertyDoesNotExists("complexity>LongParameterList>enabled"),
propertyDoesNotExists("complexity>LargeClass>howMany"),
@@ -59,173 +138,90 @@ class ValidateConfigSpec {
propertyDoesNotExists("complexity>InnerMap>Inner2>nestedActive")
)
}
}
@Nested
inner class `configure additional exclude paths` {
private fun patterns(str: String) = CommaSeparatedPattern(str).mapToRegex()
@Test
fun `reports nested configuration expected`() {
fun `does not report any complexity properties`() {
val result = validateConfig(
yamlConfig("config_validation/no-nested-config.yml"),
baseline
yamlConfig("config_validation/other-nested-property-names.yml"),
baseline,
patterns("complexity")
)
assertThat(result).isEmpty()
}
@Test
fun `does not report 'complexity_LargeClass_howMany'`() {
val result = validateConfig(
yamlConfig("config_validation/other-nested-property-names.yml"),
baseline,
patterns(".*>.*>howMany")
)
assertThat(result).contains(
nestedConfigurationExpected("complexity"),
nestedConfigurationExpected("style>WildcardImport")
propertyDoesNotExists("complexity>LongLongMethod"),
propertyDoesNotExists("complexity>LongParameterList>enabled"),
propertyDoesNotExists("complexity>InnerMap>InnerKey"),
propertyDoesNotExists("complexity>InnerMap>Inner2>nestedActive")
)
assertThat(result).doesNotContain(
propertyDoesNotExists("complexity>LargeClass>howMany")
)
}
@Test
fun `reports unexpected nested configs`() {
// note that the baseline config is now the first argument
val result = validateConfig(baseline, yamlConfig("config_validation/no-value.yml"))
@DisplayName("does not report .*>InnerMap")
fun `does not report innerMap`() {
val result = validateConfig(
yamlConfig("config_validation/other-nested-property-names.yml"),
baseline,
patterns(".*>InnerMap")
)
assertThat(result).contains(
unexpectedNestedConfiguration("style"),
unexpectedNestedConfiguration("comments")
propertyDoesNotExists("complexity>LargeClass>howMany"),
propertyDoesNotExists("complexity>LongLongMethod"),
propertyDoesNotExists("complexity>LongParameterList>enabled")
)
assertThat(result).doesNotContain(
propertyDoesNotExists("complexity>InnerMap>InnerKey"),
propertyDoesNotExists("complexity>InnerMap>Inner2>nestedActive")
)
}
}
@Test
fun `returns an error for an invalid config type`() {
val invalidConfig = TestConfig()
assertThatIllegalStateException().isThrownBy {
validateConfig(invalidConfig, baseline)
}.withMessageStartingWith("Unsupported config type for validation")
}
@Nested
inner class `deprecated configuration option` {
@Test
fun `returns an error for an invalid baseline`() {
val invalidBaseline = TestConfig()
assertThatIllegalArgumentException().isThrownBy {
validateConfig(Config.empty, invalidBaseline)
}.withMessageStartingWith("Only supported baseline config is the YamlConfig.")
}
@ParameterizedTest
@ValueSource(booleans = [true, false])
fun `reports a deprecated property as a warning`(warningsAsErrors: Boolean) {
val config = yamlConfigFromContent(
"""
config:
warningsAsErrors: $warningsAsErrors
naming:
FunctionParameterNaming:
ignoreOverriddenFunctions: ''
""".trimIndent()
)
@Test
fun `returns an error for an empty baseline`() {
val invalidBaseline = Config.empty
assertThatIllegalArgumentException().isThrownBy {
validateConfig(Config.empty, invalidBaseline)
}.withMessageStartingWith("Cannot validate configuration based on an empty baseline config.")
}
val result = validateConfig(config, config)
@Nested
inner class `validate composite configurations` {
@Test
fun `passes for same left, right and baseline config`() {
val result = validateConfig(CompositeConfig(baseline, baseline), baseline)
assertThat(result).isEmpty()
}
@Test
fun `passes for empty configs`() {
val result = validateConfig(CompositeConfig(Config.empty, Config.empty), baseline)
assertThat(result).isEmpty()
}
@Test
fun `finds accumulated errors`() {
val result = validateConfig(
CompositeConfig(
yamlConfig("config_validation/other-nested-property-names.yml"),
yamlConfig("config_validation/no-nested-config.yml")
),
baseline
assertThat(result).contains(
propertyIsDeprecated(
"naming>FunctionParameterNaming>ignoreOverriddenFunctions",
"Use `ignoreOverridden` instead",
reportAsError = warningsAsErrors
)
assertThat(result).contains(
nestedConfigurationExpected("complexity"),
nestedConfigurationExpected("style>WildcardImport"),
propertyDoesNotExists("complexity>LongLongMethod"),
propertyDoesNotExists("complexity>LongParameterList>enabled"),
propertyDoesNotExists("complexity>LargeClass>howMany"),
propertyDoesNotExists("complexity>InnerMap>InnerKey"),
propertyDoesNotExists("complexity>InnerMap>Inner2>nestedActive")
)
}
}
@Nested
inner class `configure additional exclude paths` {
fun patterns(str: String) = CommaSeparatedPattern(str).mapToRegex()
@Test
fun `does not report any complexity properties`() {
val result = validateConfig(
yamlConfig("config_validation/other-nested-property-names.yml"),
baseline,
patterns("complexity")
)
assertThat(result).isEmpty()
}
@Test
fun `does not report 'complexity_LargeClass_howMany'`() {
val result = validateConfig(
yamlConfig("config_validation/other-nested-property-names.yml"),
baseline,
patterns(".*>.*>howMany")
)
assertThat(result).contains(
propertyDoesNotExists("complexity>LongLongMethod"),
propertyDoesNotExists("complexity>LongParameterList>enabled"),
propertyDoesNotExists("complexity>InnerMap>InnerKey"),
propertyDoesNotExists("complexity>InnerMap>Inner2>nestedActive")
)
assertThat(result).doesNotContain(
propertyDoesNotExists("complexity>LargeClass>howMany")
)
}
@Test
@DisplayName("does not report .*>InnerMap")
fun `does not report innerMap`() {
val result = validateConfig(
yamlConfig("config_validation/other-nested-property-names.yml"),
baseline,
patterns(".*>InnerMap")
)
assertThat(result).contains(
propertyDoesNotExists("complexity>LargeClass>howMany"),
propertyDoesNotExists("complexity>LongLongMethod"),
propertyDoesNotExists("complexity>LongParameterList>enabled")
)
assertThat(result).doesNotContain(
propertyDoesNotExists("complexity>InnerMap>InnerKey"),
propertyDoesNotExists("complexity>InnerMap>Inner2>nestedActive")
)
}
}
@Nested
inner class `deprecated configuration option` {
@ParameterizedTest
@ValueSource(booleans = [true, false])
fun `reports a deprecated property as a warning`(warningsAsErrors: Boolean) {
val config = yamlConfigFromContent(
"""
config:
warningsAsErrors: $warningsAsErrors
naming:
FunctionParameterNaming:
ignoreOverriddenFunctions: ''
""".trimIndent()
)
val result = validateConfig(config, config)
assertThat(result).contains(
propertyIsDeprecated(
"naming>FunctionParameterNaming>ignoreOverriddenFunctions",
"Use `ignoreOverridden` instead",
reportAsError = warningsAsErrors
)
)
}
)
}
}
}

View File

@@ -20,102 +20,98 @@ import java.util.function.Predicate
class OutputReportsSpec {
@Nested
inner class `reports` {
inner class `arguments for spec` {
@Nested
inner class `arguments for spec` {
private val reportUnderTest = TestOutputReport::class.java.simpleName
private val reports = ReportsSpecBuilder().apply {
report { "xml" to Paths.get("/tmp/path1") }
report { "txt" to Paths.get("/tmp/path2") }
report { reportUnderTest to Paths.get("/tmp/path3") }
report { "html" to Paths.get("D:_Gradle\\xxx\\xxx\\build\\reports\\detekt\\detekt.html") }
}.build().reports.toList()
private val reportUnderTest = TestOutputReport::class.java.simpleName
private val reports = ReportsSpecBuilder().apply {
report { "xml" to Paths.get("/tmp/path1") }
report { "txt" to Paths.get("/tmp/path2") }
report { reportUnderTest to Paths.get("/tmp/path3") }
report { "html" to Paths.get("D:_Gradle\\xxx\\xxx\\build\\reports\\detekt\\detekt.html") }
}.build().reports.toList()
@Test
fun `should parse multiple report entries`() {
assertThat(reports).hasSize(4)
}
@Test
fun `should parse multiple report entries`() {
assertThat(reports).hasSize(4)
}
@Test
fun `it should properly parse XML report entry`() {
val xmlReport = reports[0]
assertThat(xmlReport.type).isEqualTo(defaultReportMapping(XmlOutputReport::class.java.simpleName))
assertThat(xmlReport.path).isEqualTo(Paths.get("/tmp/path1"))
}
@Test
fun `it should properly parse XML report entry`() {
val xmlReport = reports[0]
assertThat(xmlReport.type).isEqualTo(defaultReportMapping(XmlOutputReport::class.java.simpleName))
assertThat(xmlReport.path).isEqualTo(Paths.get("/tmp/path1"))
}
@Test
fun `it should properly parse TXT report entry`() {
val txtRepot = reports[1]
assertThat(txtRepot.type).isEqualTo(defaultReportMapping(TxtOutputReport::class.java.simpleName))
assertThat(txtRepot.path).isEqualTo(Paths.get("/tmp/path2"))
}
@Test
fun `it should properly parse TXT report entry`() {
val txtRepot = reports[1]
assertThat(txtRepot.type).isEqualTo(defaultReportMapping(TxtOutputReport::class.java.simpleName))
assertThat(txtRepot.path).isEqualTo(Paths.get("/tmp/path2"))
}
@Test
fun `it should properly parse custom report entry`() {
val customReport = reports[2]
assertThat(customReport.type).isEqualTo(reportUnderTest)
assertThat(defaultReportMapping(customReport.type)).isEqualTo(reportUnderTest)
assertThat(customReport.path).isEqualTo(Paths.get("/tmp/path3"))
}
@Test
fun `it should properly parse custom report entry`() {
val customReport = reports[2]
assertThat(customReport.type).isEqualTo(reportUnderTest)
assertThat(defaultReportMapping(customReport.type)).isEqualTo(reportUnderTest)
assertThat(customReport.path).isEqualTo(Paths.get("/tmp/path3"))
}
@Test
fun `it should properly parse HTML report entry`() {
val htmlReport = reports[3]
assertThat(htmlReport.type).isEqualTo(defaultReportMapping(HtmlOutputReport::class.java.simpleName))
assertThat(htmlReport.path).isEqualTo(
Paths.get("D:_Gradle\\xxx\\xxx\\build\\reports\\detekt\\detekt.html")
)
}
@Nested
inner class `default report ids` {
private val extensions = createProcessingSettings().use { OutputReportLocator(it).load() }
private val extensionsIds = extensions.mapTo(HashSet()) { defaultReportMapping(it.id) }
@Test
fun `should be able to convert to output reports`() {
assertThat(reports).allMatch { it.type in extensionsIds }
}
@Test
fun `should recognize custom output format`() {
assertThat(reports).haveExactly(
1,
Condition(
Predicate { it.type == reportUnderTest },
"Corresponds exactly to the test output report."
)
)
assertThat(extensions).haveExactly(
1,
Condition(
Predicate { it is TestOutputReport && it.ending == "yml" },
"Is exactly the test output report."
)
)
}
}
@Test
fun `it should properly parse HTML report entry`() {
val htmlReport = reports[3]
assertThat(htmlReport.type).isEqualTo(defaultReportMapping(HtmlOutputReport::class.java.simpleName))
assertThat(htmlReport.path).isEqualTo(
Paths.get("D:_Gradle\\xxx\\xxx\\build\\reports\\detekt\\detekt.html")
)
}
@Nested
inner class `empty reports` {
inner class `default report ids` {
private val extensions = createProcessingSettings().use { OutputReportLocator(it).load() }
private val extensionsIds = extensions.mapTo(HashSet()) { defaultReportMapping(it.id) }
@Test
fun `yields empty extension list`() {
val spec = createNullLoggingSpec {
config {
configPaths = listOf(resourceAsPath("/reporting/disabled-reports.yml"))
}
}
val extensions = spec.withSettings { ConsoleReportLocator(this).load() }
assertThat(extensions).isEmpty()
fun `should be able to convert to output reports`() {
assertThat(reports).allMatch { it.type in extensionsIds }
}
@Test
fun `should recognize custom output format`() {
assertThat(reports).haveExactly(
1,
Condition(
Predicate { it.type == reportUnderTest },
"Corresponds exactly to the test output report."
)
)
assertThat(extensions).haveExactly(
1,
Condition(
Predicate { it is TestOutputReport && it.ending == "yml" },
"Is exactly the test output report."
)
)
}
}
}
@Nested
inner class `empty reports` {
@Test
fun `yields empty extension list`() {
val spec = createNullLoggingSpec {
config {
configPaths = listOf(resourceAsPath("/reporting/disabled-reports.yml"))
}
}
val extensions = spec.withSettings { ConsoleReportLocator(this).load() }
assertThat(extensions).isEmpty()
}
}
}

View File

@@ -11,33 +11,24 @@ import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.core.DetektResult
import io.gitlab.arturbosch.detekt.test.createFinding
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class ComplexityReportSpec {
@Nested
inner class `complexity report` {
@Test
fun `successfully generates a complexity report`() {
val report = ComplexityReport()
val expectedContent = readResourceContent("/reporting/complexity-report.txt")
val detektion = createDetektion()
addData(detektion)
assertThat(report.render(detektion)).isEqualTo(expectedContent)
}
@Nested
inner class `several complexity metrics` {
@Test
fun `successfully generates a complexity report`() {
val report = ComplexityReport()
val expectedContent = readResourceContent("/reporting/complexity-report.txt")
val detektion = createDetektion()
addData(detektion)
assertThat(report.render(detektion)).isEqualTo(expectedContent)
}
@Test
fun `returns null for missing complexity metrics in report`() {
val report = ComplexityReport()
val detektion = createDetektion()
assertThat(report.render(detektion)).isNull()
}
}
@Test
fun `returns null for missing complexity metrics in report`() {
val report = ComplexityReport()
val detektion = createDetektion()
assertThat(report.render(detektion)).isNull()
}
}

View File

@@ -16,53 +16,49 @@ class FileBasedFindingsReportSpec {
private val subject = createFileBasedFindingsReport()
@Nested
inner class `findings report` {
@Nested
inner class `reports the debt per file and rule set with the overall debt` {
@Test
fun `has the reference content`() {
val expectedContent = readResourceContent("/reporting/grouped-findings-report.txt")
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"Ruleset1" to listOf(
createFinding(fileName = "File1.kt"),
createFinding(fileName = "File2.kt")
),
"EmptyRuleset" to emptyList(),
"Ruleset2" to listOf(createFinding(fileName = "File1.kt"))
)
}
val output = subject.render(detektion)?.decolorized()
assertThat(output).isEqualTo(expectedContent)
}
}
inner class `reports the debt per file and rule set with the overall debt` {
@Test
fun `reports no findings`() {
val detektion = TestDetektion()
assertThat(subject.render(detektion)).isNull()
}
@Test
fun `reports no findings when no rule set contains smells`() {
fun `has the reference content`() {
val expectedContent = readResourceContent("/reporting/grouped-findings-report.txt")
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"EmptySmells" to emptyList()
"Ruleset1" to listOf(
createFinding(fileName = "File1.kt"),
createFinding(fileName = "File2.kt")
),
"EmptyRuleset" to emptyList(),
"Ruleset2" to listOf(createFinding(fileName = "File1.kt"))
)
}
assertThat(subject.render(detektion)).isNull()
}
@Test
fun `should not add auto corrected issues to report`() {
val report = FileBasedFindingsReport()
AutoCorrectableIssueAssert.isReportNull(report)
val output = subject.render(detektion)?.decolorized()
assertThat(output).isEqualTo(expectedContent)
}
}
@Test
fun `reports no findings`() {
val detektion = TestDetektion()
assertThat(subject.render(detektion)).isNull()
}
@Test
fun `reports no findings when no rule set contains smells`() {
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"EmptySmells" to emptyList()
)
}
assertThat(subject.render(detektion)).isNull()
}
@Test
fun `should not add auto corrected issues to report`() {
val report = FileBasedFindingsReport()
AutoCorrectableIssueAssert.isReportNull(report)
}
}
private fun createFileBasedFindingsReport(): FileBasedFindingsReport {

View File

@@ -19,75 +19,71 @@ class FindingsReportSpec {
private val subject = createFindingsReport()
@Nested
inner class `findings report` {
inner class `reports the debt per rule set and the overall debt` {
private val expectedContent = readResourceContent("/reporting/findings-report.txt")
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"Ruleset1" to listOf(createFinding(), createFinding()),
"EmptyRuleset" to emptyList(),
"Ruleset2" to listOf(createFinding())
)
}
@Nested
inner class `reports the debt per rule set and the overall debt` {
private val expectedContent = readResourceContent("/reporting/findings-report.txt")
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"Ruleset1" to listOf(createFinding(), createFinding()),
"EmptyRuleset" to emptyList(),
"Ruleset2" to listOf(createFinding())
)
}
var output: String? = null
var output: String? = null
@BeforeEach
fun setUp() {
output = subject.render(detektion)?.decolorized()
}
@Test
fun `has the reference content`() {
assertThat(output).isEqualTo(expectedContent)
}
@Test
fun `does contain the rule set id of rule sets with findings`() {
assertThat(output).contains("TestSmell")
}
@BeforeEach
fun setUp() {
output = subject.render(detektion)?.decolorized()
}
@Test
fun `reports no findings`() {
val detektion = TestDetektion()
assertThat(subject.render(detektion)).isNull()
fun `has the reference content`() {
assertThat(output).isEqualTo(expectedContent)
}
@Test
fun `reports no findings with rule set containing no smells`() {
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"Ruleset" to emptyList()
)
}
assertThat(subject.render(detektion)).isNull()
fun `does contain the rule set id of rule sets with findings`() {
assertThat(output).contains("TestSmell")
}
}
@Test
fun `should not add auto corrected issues to report`() {
val report = FindingsReport()
AutoCorrectableIssueAssert.isReportNull(report)
}
@Test
fun `reports no findings`() {
val detektion = TestDetektion()
assertThat(subject.render(detektion)).isNull()
}
@Test
fun `truncates long message`() {
val expectedContent = readResourceContent("/reporting/long-messages-report.txt")
val longMessage = "This is just a long message that should be truncated after a given " +
"threshold is reached."
val multilineMessage = "A multiline\n\r\tmessage.\t "
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"Ruleset" to listOf(
createFinding(createIssue("LongRule"), createEntity("File.kt"), longMessage),
createFinding(createIssue("MultilineRule"), createEntity("File.kt"), multilineMessage),
),
)
}
assertThat(subject.render(detektion)?.decolorized()).isEqualTo(expectedContent)
@Test
fun `reports no findings with rule set containing no smells`() {
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"Ruleset" to emptyList()
)
}
assertThat(subject.render(detektion)).isNull()
}
@Test
fun `should not add auto corrected issues to report`() {
val report = FindingsReport()
AutoCorrectableIssueAssert.isReportNull(report)
}
@Test
fun `truncates long message`() {
val expectedContent = readResourceContent("/reporting/long-messages-report.txt")
val longMessage = "This is just a long message that should be truncated after a given " +
"threshold is reached."
val multilineMessage = "A multiline\n\r\tmessage.\t "
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"Ruleset" to listOf(
createFinding(createIssue("LongRule"), createEntity("File.kt"), longMessage),
createFinding(createIssue("MultilineRule"), createEntity("File.kt"), multilineMessage),
),
)
}
assertThat(subject.render(detektion)?.decolorized()).isEqualTo(expectedContent)
}
}

View File

@@ -7,49 +7,45 @@ import io.gitlab.arturbosch.detekt.core.reporting.AutoCorrectableIssueAssert
import io.gitlab.arturbosch.detekt.test.TestDetektion
import io.gitlab.arturbosch.detekt.test.createFinding
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class LiteFindingsReportSpec {
private val subject = createFindingsReport()
@Nested
inner class `findings report` {
@Test
fun `reports non-empty findings`() {
assertThat(
subject
.render(
TestDetektion(
createFinding("SpacingBetweenPackageAndImports"),
createFinding("UnnecessarySafeCall")
)
@Test
fun `reports non-empty findings`() {
assertThat(
subject
.render(
TestDetektion(
createFinding("SpacingBetweenPackageAndImports"),
createFinding("UnnecessarySafeCall")
)
).isEqualTo(readResourceContent("/reporting/lite-findings-report.txt"))
}
@Test
fun `reports no findings`() {
val detektion = TestDetektion()
assertThat(subject.render(detektion)).isNull()
}
@Test
fun `reports no findings with rule set containing no smells`() {
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"Ruleset" to emptyList()
)
}
assertThat(subject.render(detektion)).isNull()
}
).isEqualTo(readResourceContent("/reporting/lite-findings-report.txt"))
}
@Test
fun `should not add auto corrected issues to report`() {
val report = LiteFindingsReport()
AutoCorrectableIssueAssert.isReportNull(report)
@Test
fun `reports no findings`() {
val detektion = TestDetektion()
assertThat(subject.render(detektion)).isNull()
}
@Test
fun `reports no findings with rule set containing no smells`() {
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"Ruleset" to emptyList()
)
}
assertThat(subject.render(detektion)).isNull()
}
@Test
fun `should not add auto corrected issues to report`() {
val report = LiteFindingsReport()
AutoCorrectableIssueAssert.isReportNull(report)
}
}

View File

@@ -4,28 +4,23 @@ import io.gitlab.arturbosch.detekt.api.internal.SimpleNotification
import io.gitlab.arturbosch.detekt.core.NL
import io.gitlab.arturbosch.detekt.test.TestDetektion
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class NotificationReportSpec {
private val subject = NotificationReport()
@Nested
inner class `notification report` {
@Test
fun `reports two notifications`() {
val detektion = object : TestDetektion() {
override val notifications = listOf(SimpleNotification("test"), SimpleNotification("test"))
}
assertThat(subject.render(detektion)).isEqualTo("test${NL}test")
@Test
fun `reports two notifications`() {
val detektion = object : TestDetektion() {
override val notifications = listOf(SimpleNotification("test"), SimpleNotification("test"))
}
assertThat(subject.render(detektion)).isEqualTo("test${NL}test")
}
@Test
fun `reports no findings`() {
val detektion = TestDetektion()
assertThat(subject.render(detektion)).isNull()
}
@Test
fun `reports no findings`() {
val detektion = TestDetektion()
assertThat(subject.render(detektion)).isNull()
}
}

View File

@@ -3,31 +3,26 @@ package io.gitlab.arturbosch.detekt.core.reporting.console
import io.gitlab.arturbosch.detekt.api.ProjectMetric
import io.gitlab.arturbosch.detekt.test.TestDetektion
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class ProjectStatisticsReportSpec {
private val subject = ProjectStatisticsReport()
@Nested
inner class `project statistics` {
@Test
fun `reports the project statistics`() {
val expected = "Project Statistics:\n\t- M2: 2\n\t- M1: 1\n"
val detektion = object : TestDetektion() {
override val metrics: Collection<ProjectMetric> = listOf(
ProjectMetric("M1", 1, priority = 1),
ProjectMetric("M2", 2, priority = 2)
)
}
assertThat(subject.render(detektion)).isEqualTo(expected)
@Test
fun `reports the project statistics`() {
val expected = "Project Statistics:\n\t- M2: 2\n\t- M1: 1\n"
val detektion = object : TestDetektion() {
override val metrics: Collection<ProjectMetric> = listOf(
ProjectMetric("M1", 1, priority = 1),
ProjectMetric("M2", 2, priority = 2)
)
}
assertThat(subject.render(detektion)).isEqualTo(expected)
}
@Test
fun `does not report anything for zero metrics`() {
assertThat(subject.render(TestDetektion())).isNull()
}
@Test
fun `does not report anything for zero metrics`() {
assertThat(subject.render(TestDetektion())).isNull()
}
}

View File

@@ -16,61 +16,52 @@ import io.gitlab.arturbosch.detekt.test.loadRuleSet
import io.gitlab.arturbosch.detekt.test.yamlConfig
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.psi.KtFile
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class MultiRuleSpec {
@Nested
inner class `a multi rule` {
private val file = compileForTest(resourceAsPath("/cases/Default.kt"))
private val file = compileForTest(resourceAsPath("/cases/Default.kt"))
@Test
fun `should not run any rules if rule set defines the filter`() {
val config = yamlConfig("/pathFilters/multi-rule-with-excludes-on-ruleset.yml")
assertThat(config.subConfig("TestMultiRule").shouldAnalyzeFile(file)).isFalse()
}
@Nested
inner class `runs once on a KtFile for every rules and respects configured path filters` {
@Test
fun `should not run any rules if rule set defines the filter with string`() {
val config = yamlConfig("/pathFilters/multi-rule-with-excludes-on-ruleset-string.yml")
assertThat(config.subConfig("TestMultiRule").shouldAnalyzeFile(file)).isFalse()
}
@Test
fun `should not run any rules if rule set defines the filter`() {
val config = yamlConfig("/pathFilters/multi-rule-with-excludes-on-ruleset.yml")
assertThat(config.subConfig("TestMultiRule").shouldAnalyzeFile(file)).isFalse()
}
@Test
fun `should only run one rule as the other is filtered`() {
val config = yamlConfig("/pathFilters/multi-rule-with-one-exclude.yml")
assertThat(loadRuleSet<MultiRuleProvider>(config).visitFile(file)).hasSize(1)
}
@Test
fun `should not run any rules if rule set defines the filter with string`() {
val config = yamlConfig("/pathFilters/multi-rule-with-excludes-on-ruleset-string.yml")
assertThat(config.subConfig("TestMultiRule").shouldAnalyzeFile(file)).isFalse()
}
@Test
fun `should only run one rule as the other is filtered with string`() {
val config = yamlConfig("/pathFilters/multi-rule-with-one-exclude-string.yml")
assertThat(loadRuleSet<MultiRuleProvider>(config).visitFile(file)).hasSize(1)
}
@Test
fun `should only run one rule as the other is filtered`() {
val config = yamlConfig("/pathFilters/multi-rule-with-one-exclude.yml")
assertThat(loadRuleSet<MultiRuleProvider>(config).visitFile(file)).hasSize(1)
}
@Test
fun `should run both when no filter is applied`() {
val config = yamlConfig("/pathFilters/multi-rule-without-excludes.yml")
assertThat(loadRuleSet<MultiRuleProvider>(config).visitFile(file)).hasSize(2)
}
@Test
fun `should only run one rule as the other is filtered with string`() {
val config = yamlConfig("/pathFilters/multi-rule-with-one-exclude-string.yml")
assertThat(loadRuleSet<MultiRuleProvider>(config).visitFile(file)).hasSize(1)
}
@Test
fun `should run none when both rules are filtered`() {
val config = yamlConfig("/pathFilters/multi-rule-with-excludes.yml")
assertThat(loadRuleSet<MultiRuleProvider>(config).visitFile(file)).isEmpty()
}
@Test
fun `should run both when no filter is applied`() {
val config = yamlConfig("/pathFilters/multi-rule-without-excludes.yml")
assertThat(loadRuleSet<MultiRuleProvider>(config).visitFile(file)).hasSize(2)
}
@Test
fun `should run none when both rules are filtered`() {
val config = yamlConfig("/pathFilters/multi-rule-with-excludes.yml")
assertThat(loadRuleSet<MultiRuleProvider>(config).visitFile(file)).isEmpty()
}
@Test
fun `should run none when both rules are filtered with string`() {
val config = yamlConfig("/pathFilters/multi-rule-with-excludes-string.yml")
assertThat(loadRuleSet<MultiRuleProvider>(config).visitFile(file)).isEmpty()
}
}
@Test
fun `should run none when both rules are filtered with string`() {
val config = yamlConfig("/pathFilters/multi-rule-with-excludes-string.yml")
assertThat(loadRuleSet<MultiRuleProvider>(config).visitFile(file)).isEmpty()
}
}

View File

@@ -5,36 +5,31 @@ import io.gitlab.arturbosch.detekt.core.createNullLoggingSpec
import io.gitlab.arturbosch.detekt.core.tooling.withSettings
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.fail
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.reflections.Reflections
import java.lang.reflect.Modifier
class RuleSetLocatorSpec {
@Nested
inner class `locating RuleSetProvider's` {
@Test
fun `contains all RuleSetProviders`() {
val providers = createNullLoggingSpec().withSettings { RuleSetLocator(this).load() }
val providerClasses = getProviderClasses()
@Test
fun `contains all RuleSetProviders`() {
val providers = createNullLoggingSpec().withSettings { RuleSetLocator(this).load() }
val providerClasses = getProviderClasses()
assertThat(providerClasses).isNotEmpty
providerClasses
.filter { clazz -> providers.none { it.javaClass == clazz } }
.forEach { fail("$it rule set is not loaded by the RuleSetLocator") }
}
assertThat(providerClasses).isNotEmpty
providerClasses
.filter { clazz -> providers.none { it.javaClass == clazz } }
.forEach { fail("$it rule set is not loaded by the RuleSetLocator") }
}
@Test
fun `does not load any default rule set provider when opt out`() {
val providers = createNullLoggingSpec { extensions { disableDefaultRuleSets = true } }
.withSettings { RuleSetLocator(this).load() }
@Test
fun `does not load any default rule set provider when opt out`() {
val providers = createNullLoggingSpec { extensions { disableDefaultRuleSets = true } }
.withSettings { RuleSetLocator(this).load() }
val defaultProviders = getProviderClasses().toSet()
val defaultProviders = getProviderClasses().toSet()
assertThat(providers).noneMatch { it.javaClass in defaultProviders }
}
assertThat(providers).noneMatch { it.javaClass in defaultProviders }
}
}

View File

@@ -13,49 +13,45 @@ import org.junit.jupiter.api.Test
class RuleSetSpec {
@Nested
inner class `rule sets` {
inner class `should rule set be used` {
@Nested
inner class `should rule set be used` {
@Test
fun `is explicitly deactivated`() {
val config = yamlConfig("configs/deactivated_ruleset.yml")
assertThat(config.subConfig("comments").isActive()).isFalse()
}
@Test
fun `is active with an empty config`() {
assertThat(Config.empty.isActive()).isTrue()
}
@Test
fun `is explicitly deactivated`() {
val config = yamlConfig("configs/deactivated_ruleset.yml")
assertThat(config.subConfig("comments").isActive()).isFalse()
}
@Nested
inner class `should rule analyze a file` {
@Test
fun `is active with an empty config`() {
assertThat(Config.empty.isActive()).isTrue()
}
}
private val file = compileForTest(resourceAsPath("/cases/Default.kt"))
@Nested
inner class `should rule analyze a file` {
@Test
fun `analyzes file with an empty config`() {
val config = Config.empty
assertThat(config.subConfig("comments").shouldAnalyzeFile(file)).isTrue()
}
private val file = compileForTest(resourceAsPath("/cases/Default.kt"))
@Test
@DisplayName("should not analyze file with **/*.kt excludes")
fun ignoreExcludedKt() {
val config = TestConfig(Config.EXCLUDES_KEY to "**/*.kt")
assertThat(config.subConfig("comments").shouldAnalyzeFile(file)).isFalse()
}
@Test
fun `analyzes file with an empty config`() {
val config = Config.empty
assertThat(config.subConfig("comments").shouldAnalyzeFile(file)).isTrue()
}
@Test
fun `should analyze file as it's path is first excluded but then included`() {
val config = TestConfig(
Config.EXCLUDES_KEY to "**/*.kt",
Config.INCLUDES_KEY to "**/*.kt"
)
assertThat(config.subConfig("comments").shouldAnalyzeFile(file)).isTrue()
}
@Test
@DisplayName("should not analyze file with **/*.kt excludes")
fun ignoreExcludedKt() {
val config = TestConfig(Config.EXCLUDES_KEY to "**/*.kt")
assertThat(config.subConfig("comments").shouldAnalyzeFile(file)).isFalse()
}
@Test
fun `should analyze file as it's path is first excluded but then included`() {
val config = TestConfig(
Config.EXCLUDES_KEY to "**/*.kt",
Config.INCLUDES_KEY to "**/*.kt"
)
assertThat(config.subConfig("comments").shouldAnalyzeFile(file)).isTrue()
}
}
}

View File

@@ -9,50 +9,41 @@ import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.test.yamlConfigFromContent
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
class SingleRuleProviderSpec {
@Nested
inner class `SingleRuleProvider` {
private val provider = SingleRuleProvider(
"MagicNumber",
object : RuleSetProvider {
override val ruleSetId: String = "style"
override fun instance(config: Config): RuleSet {
val rule = object : Rule(config) {
override val issue = Issue(
"MagicNumber",
Severity.CodeSmell,
"",
Debt.FIVE_MINS
)
}
return RuleSet(ruleSetId, listOf(rule))
private val provider = SingleRuleProvider(
"MagicNumber",
object : RuleSetProvider {
override val ruleSetId: String = "style"
override fun instance(config: Config): RuleSet {
val rule = object : Rule(config) {
override val issue = Issue(
"MagicNumber",
Severity.CodeSmell,
"",
Debt.FIVE_MINS
)
}
}
)
@Nested
inner class `the right sub config is passed to the rule` {
@ParameterizedTest
@ValueSource(booleans = [true, false])
fun `configures rule with active=$value`(value: Boolean) {
val config = yamlConfigFromContent(
"""
style:
MagicNumber:
active: $value
""".trimIndent()
)
assertThat(produceRule(provider, config).active).isEqualTo(value)
return RuleSet(ruleSetId, listOf(rule))
}
}
)
@ParameterizedTest
@ValueSource(booleans = [true, false])
fun `the right sub config is passed to the rule configures rule with active=$value`(value: Boolean) {
val config = yamlConfigFromContent(
"""
style:
MagicNumber:
active: $value
""".trimIndent()
)
assertThat(produceRule(provider, config).active).isEqualTo(value)
}
}

View File

@@ -4,25 +4,20 @@ import io.gitlab.arturbosch.detekt.core.createNullLoggingSpec
import io.gitlab.arturbosch.detekt.core.createProcessingSettings
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.konan.file.File
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class EnvironmentFacadeSpec {
@Nested
inner class `classpath entries should be separated by platform-specific separator` {
private val classpath = when (File.pathSeparator) {
":" -> "/path/to/file1:/path/to/file2:/path/to/file3"
";" -> """C:\path\to\file1;C:\path\to\file2;C:\path\to\file3"""
else -> ""
}
val classpath = when (File.pathSeparator) {
":" -> "/path/to/file1:/path/to/file2:/path/to/file3"
";" -> """C:\path\to\file1;C:\path\to\file2;C:\path\to\file3"""
else -> ""
}
@Test
fun `supports ${File_pathSeparator}`() {
testSettings(classpath).use {
assertThat(it.classpath).hasSize(3)
}
@Test
fun `classpath entries should be separated by platform-specific separator supports ${File_pathSeparator}`() {
testSettings(classpath).use {
assertThat(it.classpath).hasSize(3)
}
}
}

View File

@@ -4,57 +4,52 @@ import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.formatting.wrappers.ArgumentListWrapping
import io.gitlab.arturbosch.detekt.test.TestConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class ArgumentListWrappingSpec {
@Nested
inner class `ArgumentListWrapping rule` {
@Test
fun `reports wrong argument wrapping`() {
val code = """
val x = f(
1,
2, 3
)
""".trimIndent()
assertThat(ArgumentListWrapping(Config.empty).lint(code)).hasSize(1)
}
@Test
fun `reports wrong argument wrapping`() {
val code = """
val x = f(
1,
2, 3
)
""".trimIndent()
assertThat(ArgumentListWrapping(Config.empty).lint(code)).hasSize(1)
}
@Test
fun `does not report correct argument list wrapping`() {
val code = """
val x = f(
1,
2,
3
)
""".trimIndent()
assertThat(ArgumentListWrapping(Config.empty).lint(code)).isEmpty()
}
@Test
fun `does not report correct argument list wrapping`() {
val code = """
val x = f(
1,
2,
3
)
""".trimIndent()
assertThat(ArgumentListWrapping(Config.empty).lint(code)).isEmpty()
}
@Test
fun `does not report when overriding an indentation level config of 1`() {
val code = """
val x = f(
1,
2,
3
)
""".trimIndent()
val config = TestConfig("indentSize" to "1")
assertThat(ArgumentListWrapping(config).lint(code)).isEmpty()
}
@Test
fun `does not report when overriding an indentation level config of 1`() {
val code = """
val x = f(
1,
2,
3
)
""".trimIndent()
val config = TestConfig("indentSize" to "1")
assertThat(ArgumentListWrapping(config).lint(code)).isEmpty()
}
@Test
fun `reports when max line length is exceeded`() {
val code = """
val x = f(1111, 2222, 3333)
""".trimIndent()
val config = TestConfig("maxLineLength" to "10")
assertThat(ArgumentListWrapping(config).lint(code)).hasSize(4)
}
@Test
fun `reports when max line length is exceeded`() {
val code = """
val x = f(1111, 2222, 3333)
""".trimIndent()
val config = TestConfig("maxLineLength" to "10")
assertThat(ArgumentListWrapping(config).lint(code)).hasSize(4)
}
}

View File

@@ -6,69 +6,48 @@ import io.gitlab.arturbosch.detekt.test.loadRuleSet
import io.gitlab.arturbosch.detekt.test.yamlConfig
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.psi.KtFile
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class AutoCorrectLevelSpec {
@Nested
inner class `test different autoCorrect levels in configuration` {
@Test
fun `autoCorrect_ true on all levels should reformat the test file`() {
val config = yamlConfig("/autocorrect/autocorrect-all-true.yml")
@Nested
inner class `autoCorrect_ true on all levels` {
val (file, findings) = runRule(config)
@Test
fun `should reformat the test file`() {
val config = yamlConfig("/autocorrect/autocorrect-all-true.yml")
assertThat(wasLinted(findings)).isTrue()
assertThat(wasFormatted(file)).isTrue()
}
val (file, findings) = runRule(config)
@Test
fun `autoCorrect_ false on ruleSet level should not reformat the test file`() {
val config = yamlConfig("/autocorrect/autocorrect-ruleset-false.yml")
assertThat(wasLinted(findings)).isTrue()
assertThat(wasFormatted(file)).isTrue()
}
}
val (file, findings) = runRule(config)
@Nested
inner class `autoCorrect_ false on ruleSet level` {
assertThat(wasLinted(findings)).isTrue()
assertThat(wasFormatted(file)).isFalse()
}
@Test
fun `should not reformat the test file`() {
val config = yamlConfig("/autocorrect/autocorrect-ruleset-false.yml")
@Test
fun `autoCorrect_ false on rule level should not reformat the test file`() {
val config = yamlConfig("/autocorrect/autocorrect-rule-false.yml")
val (file, findings) = runRule(config)
val (file, findings) = runRule(config)
assertThat(wasLinted(findings)).isTrue()
assertThat(wasFormatted(file)).isFalse()
}
}
assertThat(wasLinted(findings)).isTrue()
assertThat(wasFormatted(file)).isFalse()
}
@Nested
inner class `autoCorrect_ false on rule level` {
@Test
fun `autoCorrect_ true but rule active false should not reformat the test file`() {
val config = yamlConfig("/autocorrect/autocorrect-true-rule-active-false.yml")
@Test
fun `should not reformat the test file`() {
val config = yamlConfig("/autocorrect/autocorrect-rule-false.yml")
val (file, findings) = runRule(config)
val (file, findings) = runRule(config)
assertThat(wasLinted(findings)).isTrue()
assertThat(wasFormatted(file)).isFalse()
}
}
@Nested
inner class `autoCorrect_ true but rule active false` {
@Test
fun `should not reformat the test file`() {
val config = yamlConfig("/autocorrect/autocorrect-true-rule-active-false.yml")
val (file, findings) = runRule(config)
assertThat(wasLinted(findings)).isFalse()
assertThat(wasFormatted(file)).isFalse()
}
}
assertThat(wasLinted(findings)).isFalse()
assertThat(wasFormatted(file)).isFalse()
}
}

View File

@@ -3,23 +3,18 @@ package io.gitlab.arturbosch.detekt.formatting
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.formatting.wrappers.ChainWrapping
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class ChainWrappingSpec {
@Nested
inner class `tests integration of formatting` {
@Test
fun `should work like KtLint`() {
val subject = loadFile("configTests/chain-wrapping-before.kt")
val expected = loadFileContent("configTests/chain-wrapping-after.kt")
@Test
fun `should work like KtLint`() {
val subject = loadFile("configTests/chain-wrapping-before.kt")
val expected = loadFileContent("configTests/chain-wrapping-after.kt")
val findings = ChainWrapping(Config.empty).lint(subject.text)
val findings = ChainWrapping(Config.empty).lint(subject.text)
assertThat(findings).isNotEmpty
assertThat(subject.text).isEqualTo(expected)
}
assertThat(findings).isNotEmpty
assertThat(subject.text).isEqualTo(expected)
}
}

View File

@@ -4,54 +4,49 @@ import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.formatting.wrappers.FinalNewline
import io.gitlab.arturbosch.detekt.test.TestConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class FinalNewlineSpec {
@Nested
inner class `FinalNewline rule` {
@Test
fun `should report missing new line by default`() {
val findings = FinalNewline(Config.empty)
.lint("fun main() = Unit")
@Test
fun `should report missing new line by default`() {
val findings = FinalNewline(Config.empty)
.lint("fun main() = Unit")
assertThat(findings).hasSize(1)
}
assertThat(findings).hasSize(1)
}
@Test
fun `should not report as new line is present`() {
val findings = FinalNewline(Config.empty).lint(
"""
fun main() = Unit
@Test
fun `should not report as new line is present`() {
val findings = FinalNewline(Config.empty).lint(
"""
)
assertThat(findings).isEmpty()
}
@Test
fun `should report new line when configured`() {
val findings = FinalNewline(TestConfig(INSERT_FINAL_NEWLINE_KEY to "false"))
.lint(
"""
fun main() = Unit
fun main() = Unit
"""
)
assertThat(findings).isEmpty()
}
assertThat(findings).hasSize(1)
}
@Test
fun `should report new line when configured`() {
val findings = FinalNewline(TestConfig(INSERT_FINAL_NEWLINE_KEY to "false"))
.lint(
"""
fun main() = Unit
@Test
fun `should not report when no new line is configured and not present`() {
val findings = FinalNewline(TestConfig(INSERT_FINAL_NEWLINE_KEY to "false"))
.lint("fun main() = Unit")
"""
)
assertThat(findings).hasSize(1)
}
@Test
fun `should not report when no new line is configured and not present`() {
val findings = FinalNewline(TestConfig(INSERT_FINAL_NEWLINE_KEY to "false"))
.lint("fun main() = Unit")
assertThat(findings).isEmpty()
}
assertThat(findings).isEmpty()
}
}

View File

@@ -16,22 +16,103 @@ import org.junit.jupiter.api.Test
*/
class ImportOrderingSpec {
@Test
fun `defaults to the idea layout`() {
val findings = ImportOrdering(Config.empty).lint(
"""
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import kotlinx.coroutines.CoroutineDispatcher
import ru.example.a
import java.util.List
import javax.net.ssl.SSLHandshakeException
import kotlin.concurrent.Thread
import kotlin.io.Closeable
import android.content.Context as Ctx
import androidx.fragment.app.Fragment as F
""".trimIndent()
)
assertThat(findings).isEmpty()
}
@Nested
inner class `different import ordering layouts` {
inner class `can be configured to use the ascii one` {
val negativeCase = """
import a.A
import a.AB
import b.C
""".trimIndent()
val positiveCase = """
import a.A
import java.util.ArrayList
import a.AB
""".trimIndent()
@Test
fun `defaults to the idea layout`() {
val findings = ImportOrdering(Config.empty).lint(
fun `passes for alphabetical order`() {
val findings = ImportOrdering(TestConfig("layout" to ImportOrdering.ASCII_PATTERN))
.lint(negativeCase)
assertThat(findings).isEmpty()
}
@Test
fun `fails for non alphabetical order`() {
val findings = ImportOrdering(TestConfig("layout" to ImportOrdering.ASCII_PATTERN))
.lint(positiveCase)
assertThat(findings).hasSize(1)
}
@Nested
inner class `defaults to ascii if 'android'' property is set to true` {
@Test
fun `passes for alphabetical order`() {
assertThat(ImportOrdering(TestConfig("android" to "true")).lint(negativeCase)).isEmpty()
}
@Test
fun `fails for non alphabetical order`() {
assertThat(ImportOrdering(TestConfig("android" to "true")).lint(positiveCase)).hasSize(1)
}
}
}
@Nested
inner class `supports custom patterns` {
@Test
fun `misses a empty line between aliases and other imports`() {
val findings = ImportOrdering(TestConfig("layout" to "*,|,^*")).lint(
"""
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import kotlinx.coroutines.CoroutineDispatcher
import ru.example.a
import java.util.List
import javax.net.ssl.SSLHandshakeException
import kotlin.concurrent.Thread
import kotlin.io.Closeable
import android.content.Context as Ctx
import androidx.fragment.app.Fragment as F
""".trimIndent()
)
assertThat(findings).hasSize(1)
}
@Test
fun `passes for empty line between aliases and other imports`() {
val findings = ImportOrdering(TestConfig("layout" to "*,|,^*")).lint(
"""
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import java.util.List
import kotlin.concurrent.Thread
import android.content.Context as Ctx
import androidx.fragment.app.Fragment as F
""".trimIndent()
@@ -39,90 +120,5 @@ class ImportOrderingSpec {
assertThat(findings).isEmpty()
}
@Nested
inner class `can be configured to use the ascii one` {
val negativeCase = """
import a.A
import a.AB
import b.C
""".trimIndent()
val positiveCase = """
import a.A
import java.util.ArrayList
import a.AB
""".trimIndent()
@Test
fun `passes for alphabetical order`() {
val findings = ImportOrdering(TestConfig("layout" to ImportOrdering.ASCII_PATTERN))
.lint(negativeCase)
assertThat(findings).isEmpty()
}
@Test
fun `fails for non alphabetical order`() {
val findings = ImportOrdering(TestConfig("layout" to ImportOrdering.ASCII_PATTERN))
.lint(positiveCase)
assertThat(findings).hasSize(1)
}
@Nested
inner class `defaults to ascii if 'android'' property is set to true` {
@Test
fun `passes for alphabetical order`() {
assertThat(ImportOrdering(TestConfig("android" to "true")).lint(negativeCase)).isEmpty()
}
@Test
fun `fails for non alphabetical order`() {
assertThat(ImportOrdering(TestConfig("android" to "true")).lint(positiveCase)).hasSize(1)
}
}
}
@Nested
inner class `supports custom patterns` {
@Test
fun `misses a empty line between aliases and other imports`() {
val findings = ImportOrdering(TestConfig("layout" to "*,|,^*")).lint(
"""
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import java.util.List
import kotlin.concurrent.Thread
import android.content.Context as Ctx
import androidx.fragment.app.Fragment as F
""".trimIndent()
)
assertThat(findings).hasSize(1)
}
@Test
fun `passes for empty line between aliases and other imports`() {
val findings = ImportOrdering(TestConfig("layout" to "*,|,^*")).lint(
"""
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import java.util.List
import kotlin.concurrent.Thread
import android.content.Context as Ctx
import androidx.fragment.app.Fragment as F
""".trimIndent()
)
assertThat(findings).isEmpty()
}
}
}
}

View File

@@ -19,61 +19,57 @@ class IndentationSpec {
}
@Nested
inner class `Indentation rule` {
inner class `indentation level equals 1` {
val code = "fun main() {\n println()\n}"
@Nested
inner class `indentation level equals 1` {
val code = "fun main() {\n println()\n}"
@Nested
inner class `indentation level config of default` {
@Test
fun `reports wrong indentation level`() {
assertThat(subject.lint(code)).hasSize(1)
}
@Test
fun `places finding location to the indentation`() {
subject.lint(code).assert()
.hasSourceLocation(2, 1)
.hasTextLocations(13 to 14)
}
}
inner class `indentation level config of default` {
@Test
fun `does not report when using an indentation level config of 1`() {
val config = TestConfig("indentSize" to "1")
assertThat(Indentation(config).lint(code)).isEmpty()
}
}
@Test
fun `does not report correct indentation level`() {
val code = "fun main() {\n println()\n}"
assertThat(subject.lint(code)).isEmpty()
}
@Nested
inner class `parameter list indent size equals 1` {
val code = """
fun f(
a: Int
) {}
""".trimIndent()
@Test
fun `reports wrong indent size`() {
fun `reports wrong indentation level`() {
assertThat(subject.lint(code)).hasSize(1)
}
@Test
fun `does not report when using an indentation level config of 1`() {
val config = TestConfig("indentSize" to "1")
assertThat(Indentation(config).lint(code)).isEmpty()
fun `places finding location to the indentation`() {
subject.lint(code).assert()
.hasSourceLocation(2, 1)
.hasTextLocations(13 to 14)
}
}
@Test
fun `does not report when using an indentation level config of 1`() {
val config = TestConfig("indentSize" to "1")
assertThat(Indentation(config).lint(code)).isEmpty()
}
}
@Test
fun `does not report correct indentation level`() {
val code = "fun main() {\n println()\n}"
assertThat(subject.lint(code)).isEmpty()
}
@Nested
inner class `parameter list indent size equals 1` {
val code = """
fun f(
a: Int
) {}
""".trimIndent()
@Test
fun `reports wrong indent size`() {
assertThat(subject.lint(code)).hasSize(1)
}
@Test
fun `does not report when using an indentation level config of 1`() {
val config = TestConfig("indentSize" to "1")
assertThat(Indentation(config).lint(code)).isEmpty()
}
}
}

View File

@@ -3,29 +3,24 @@ package io.gitlab.arturbosch.detekt.formatting
import io.github.detekt.test.utils.compileContentForTest
import io.gitlab.arturbosch.detekt.api.Config
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class KtLintMultiRuleSpec {
@Nested
inner class `KtLintMultiRule rule` {
@Test
fun `sorts rules correctly`() {
val ktlintRule = KtLintMultiRule(Config.empty)
ktlintRule.visitFile(compileContentForTest(""))
val sortedRules = ktlintRule.getSortedRules()
assertThat(sortedRules).isNotEmpty
assertThat(sortedRules.indexOfFirst { it.runOnRootNodeOnly })
.isGreaterThan(-1)
.isLessThan(sortedRules.indexOfFirst { !it.runOnRootNodeOnly })
assertThat(sortedRules.indexOfFirst { !it.runOnRootNodeOnly })
.isGreaterThan(-1)
.isLessThan(sortedRules.indexOfFirst { it.runOnRootNodeOnly && it.runAsLateAsPossible })
assertThat(sortedRules.indexOfFirst { it.runOnRootNodeOnly && it.runAsLateAsPossible })
.isGreaterThan(-1)
.isLessThan(sortedRules.indexOfFirst { it.runAsLateAsPossible && !it.runOnRootNodeOnly })
}
@Test
fun `sorts rules correctly`() {
val ktlintRule = KtLintMultiRule(Config.empty)
ktlintRule.visitFile(compileContentForTest(""))
val sortedRules = ktlintRule.getSortedRules()
assertThat(sortedRules).isNotEmpty
assertThat(sortedRules.indexOfFirst { it.runOnRootNodeOnly })
.isGreaterThan(-1)
.isLessThan(sortedRules.indexOfFirst { !it.runOnRootNodeOnly })
assertThat(sortedRules.indexOfFirst { !it.runOnRootNodeOnly })
.isGreaterThan(-1)
.isLessThan(sortedRules.indexOfFirst { it.runOnRootNodeOnly && it.runAsLateAsPossible })
assertThat(sortedRules.indexOfFirst { it.runOnRootNodeOnly && it.runAsLateAsPossible })
.isGreaterThan(-1)
.isLessThan(sortedRules.indexOfFirst { it.runAsLateAsPossible && !it.runOnRootNodeOnly })
}
}

View File

@@ -18,71 +18,67 @@ class MaximumLineLengthSpec {
}
@Nested
inner class `MaximumLineLength rule` {
inner class `a single function` {
@Nested
inner class `a single function` {
val code = """
package home.test
fun f() { /* 123456789012345678901234567890 */ }
""".trimIndent()
val code = """
package home.test
fun f() { /* 123456789012345678901234567890 */ }
""".trimIndent()
@Test
fun `reports line which exceeds the threshold`() {
assertThat(subject.lint(code)).hasSize(1)
}
@Test
fun `reports line which exceeds the threshold`() {
assertThat(subject.lint(code)).hasSize(1)
}
@Test
fun `reports issues with the filename and package as signature`() {
val finding = subject.lint(
code,
Paths.get("home", "test", "Test.kt").toString()
).first()
@Test
fun `reports issues with the filename and package as signature`() {
val finding = subject.lint(
code,
Paths.get("home", "test", "Test.kt").toString()
).first()
assertThat(finding.entity.signature).isEqualTo("home.test.Test.kt:2")
}
@Test
fun `does not report line which does not exceed the threshold`() {
val config = TestConfig(MAX_LINE_LENGTH to code.length)
assertThat(MaximumLineLength(config).lint(code)).isEmpty()
}
assertThat(finding.entity.signature).isEqualTo("home.test.Test.kt:2")
}
@Test
fun `does not report line which does not exceed the threshold`() {
val code = "val a = 1"
assertThat(subject.lint(code)).isEmpty()
val config = TestConfig(MAX_LINE_LENGTH to code.length)
assertThat(MaximumLineLength(config).lint(code)).isEmpty()
}
}
@Test
fun `reports correct line numbers`() {
val findings = subject.lint(longLines)
@Test
fun `does not report line which does not exceed the threshold`() {
val code = "val a = 1"
assertThat(subject.lint(code)).isEmpty()
}
// Note that KtLint's MaximumLineLength rule, in contrast to detekt's MaxLineLength rule, does not report
// exceeded lines in block comments.
assertThat(findings).hasSize(2)
@Test
fun `reports correct line numbers`() {
val findings = subject.lint(longLines)
assertThat(findings[0].entity.location.source.line).isEqualTo(8)
assertThat(findings[1].entity.location.source.line).isEqualTo(14)
}
// Note that KtLint's MaximumLineLength rule, in contrast to detekt's MaxLineLength rule, does not report
// exceeded lines in block comments.
assertThat(findings).hasSize(2)
@Test
fun `does not report back ticked line which exceeds the threshold`() {
val code = """
package home.test
fun `this is a test method that has more than 30 characters inside the back ticks`() {
}
""".trimIndent()
val findings = MaximumLineLength(
TestConfig(
MAX_LINE_LENGTH to "30",
"ignoreBackTickedIdentifier" to "true"
)
).lint(code)
assertThat(findings).isEmpty()
}
assertThat(findings[0].entity.location.source.line).isEqualTo(8)
assertThat(findings[1].entity.location.source.line).isEqualTo(14)
}
@Test
fun `does not report back ticked line which exceeds the threshold`() {
val code = """
package home.test
fun `this is a test method that has more than 30 characters inside the back ticks`() {
}
""".trimIndent()
val findings = MaximumLineLength(
TestConfig(
MAX_LINE_LENGTH to "30",
"ignoreBackTickedIdentifier" to "true"
)
).lint(code)
assertThat(findings).isEmpty()
}
}

View File

@@ -5,7 +5,6 @@ import io.gitlab.arturbosch.detekt.formatting.wrappers.ParameterListWrapping
import io.gitlab.arturbosch.detekt.test.TestConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class ParameterListWrappingSpec {
@@ -17,26 +16,22 @@ class ParameterListWrappingSpec {
subject = ParameterListWrapping(Config.empty)
}
@Nested
inner class `ParameterListWrapping rule` {
@Test
fun `does not report correct ParameterListWrapping level`() {
val code = """
fun f(
a: Int
) {}
""".trimIndent()
assertThat(subject.lint(code)).isEmpty()
}
@Test
fun `does not report correct ParameterListWrapping level`() {
val code = """
fun f(
a: Int
) {}
""".trimIndent()
assertThat(subject.lint(code)).isEmpty()
}
@Test
fun `reports when max line length is exceeded`() {
val code = """
fun f(a: Int, b: Int, c: Int) {}
""".trimIndent()
val config = TestConfig("maxLineLength" to "10")
assertThat(ParameterListWrapping(config).lint(code)).hasSize(4)
}
@Test
fun `reports when max line length is exceeded`() {
val code = """
fun f(a: Int, b: Int, c: Int) {}
""".trimIndent()
val config = TestConfig("maxLineLength" to "10")
assertThat(ParameterListWrapping(config).lint(code)).hasSize(4)
}
}

View File

@@ -17,65 +17,62 @@ private val defaultConfiguration = Configuration(
class ConfigurationSpec {
@Nested
inner class `default value to list conversion` {
@Nested
inner class `empty default value` {
private val subject = defaultConfiguration.copy(defaultValue = of(""))
inner class `empty default value` {
private val subject = defaultConfiguration.copy(defaultValue = of(""))
@Test
fun `identifies default as not a list`() {
assertThat(subject.isDefaultValueNonEmptyList()).isFalse()
}
@Test
fun `fails when attempting conversion`() {
assertThatIllegalStateException().isThrownBy { subject.getDefaultValueAsList() }
}
@Test
fun `identifies default as not a list`() {
assertThat(subject.isDefaultValueNonEmptyList()).isFalse()
}
@Nested
inner class `non list default value` {
private val subject = defaultConfiguration.copy(defaultValue = of("abc"))
@Test
fun `fails when attempting conversion`() {
assertThatIllegalStateException().isThrownBy { subject.getDefaultValueAsList() }
}
}
@Test
fun `identifies default as not a list`() {
assertThat(subject.isDefaultValueNonEmptyList()).isFalse()
}
@Nested
inner class `non list default value` {
private val subject = defaultConfiguration.copy(defaultValue = of("abc"))
@Test
fun `fails when attempting conversion`() {
assertThatIllegalStateException().isThrownBy { subject.getDefaultValueAsList() }
}
@Test
fun `identifies default as not a list`() {
assertThat(subject.isDefaultValueNonEmptyList()).isFalse()
}
@Nested
inner class `empty list default value` {
private val subject = defaultConfiguration.copy(defaultValue = of(emptyList()))
@Test
fun `fails when attempting conversion`() {
assertThatIllegalStateException().isThrownBy { subject.getDefaultValueAsList() }
}
}
@Test
fun `identifies default as not a non empty list`() {
assertThat(subject.isDefaultValueNonEmptyList()).isFalse()
}
@Nested
inner class `empty list default value` {
private val subject = defaultConfiguration.copy(defaultValue = of(emptyList()))
@Test
fun `fails when attempting conversion`() {
assertThatIllegalStateException().isThrownBy { subject.getDefaultValueAsList() }
}
@Test
fun `identifies default as not a non empty list`() {
assertThat(subject.isDefaultValueNonEmptyList()).isFalse()
}
@Nested
inner class `bracket list default value` {
private val subject = defaultConfiguration.copy(defaultValue = of(listOf("a", "b")))
@Test
fun `fails when attempting conversion`() {
assertThatIllegalStateException().isThrownBy { subject.getDefaultValueAsList() }
}
}
@Test
fun `identifies default as a non empty list`() {
assertThat(subject.isDefaultValueNonEmptyList()).isTrue()
}
@Nested
inner class `bracket list default value` {
private val subject = defaultConfiguration.copy(defaultValue = of(listOf("a", "b")))
@Test
fun `converts to a list`() {
assertThat(subject.getDefaultValueAsList()).isEqualTo(listOf("a", "b"))
}
@Test
fun `identifies default as a non empty list`() {
assertThat(subject.isDefaultValueNonEmptyList()).isTrue()
}
@Test
fun `converts to a list`() {
assertThat(subject.getDefaultValueAsList()).isEqualTo(listOf("a", "b"))
}
}
}

View File

@@ -5,7 +5,6 @@ import io.gitlab.arturbosch.detekt.generator.util.run
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class MultiRuleCollectorSpec {
@@ -17,47 +16,43 @@ class MultiRuleCollectorSpec {
subject = MultiRuleCollector()
}
@Nested
inner class `a MultiRuleCollector` {
@Test
fun `collects no MultiRule when no class is extended`() {
val code = "class MyRule"
assertThat(subject.run(code)).isEmpty()
}
@Test
fun `collects no MultiRule when no class is extended`() {
val code = "class MyRule"
assertThat(subject.run(code)).isEmpty()
}
@Test
fun `collects no rules when no MultiRule class is extended`() {
val code = "class MyRule : Other"
assertThat(subject.run(code)).isEmpty()
}
@Test
fun `collects no rules when no MultiRule class is extended`() {
val code = "class MyRule : Other"
assertThat(subject.run(code)).isEmpty()
}
@Test
fun `throws when no rules are added`() {
val code = "class MyRule : MultiRule"
assertThatExceptionOfType(InvalidDocumentationException::class.java).isThrownBy {
subject.run(code)
}
}
@Test
fun `collects all rules in fields and in the rule property`() {
val code = """
class MyRule : MultiRule {
val p1 = Rule3()
val p2 = Rule4()
override val rules: List<Rule> = listOf(
Rule1(),
Rule2(),
p1,
p2
)
}
"""
val items = subject.run(code)
assertThat(items[0].rules).hasSize(4)
assertThat(items[0].rules).contains("Rule1", "Rule2", "Rule3", "Rule4")
@Test
fun `throws when no rules are added`() {
val code = "class MyRule : MultiRule"
assertThatExceptionOfType(InvalidDocumentationException::class.java).isThrownBy {
subject.run(code)
}
}
@Test
fun `collects all rules in fields and in the rule property`() {
val code = """
class MyRule : MultiRule {
val p1 = Rule3()
val p2 = Rule4()
override val rules: List<Rule> = listOf(
Rule1(),
Rule2(),
p1,
p2
)
}
"""
val items = subject.run(code)
assertThat(items[0].rules).hasSize(4)
assertThat(items[0].rules).contains("Rule1", "Rule2", "Rule3", "Rule4")
}
}

View File

@@ -20,410 +20,407 @@ class RuleSetProviderCollectorSpec {
}
@Nested
inner class `RuleSetProviderCollector rule` {
@Nested
inner class `a non-RuleSetProvider class extending nothing` {
val code = """
package foo
inner class `a non-RuleSetProvider class extending nothing` {
val code = """
package foo
class SomeRandomClass {
fun logSomething(message: String) {
println(message)
}
}
"""
@Test
fun `collects no rulesets`() {
val items = subject.run(code)
assertThat(items).isEmpty()
class SomeRandomClass {
fun logSomething(message: String) {
println(message)
}
}
"""
@Nested
inner class `a non-RuleSetProvider class extending a class that is not related to rules` {
val code = """
package foo
@Test
fun `collects no rulesets`() {
val items = subject.run(code)
assertThat(items).isEmpty()
}
}
class SomeRandomClass: SomeOtherClass {
fun logSomething(message: String) {
println(message)
}
}
"""
@Nested
inner class `a non-RuleSetProvider class extending a class that is not related to rules` {
val code = """
package foo
@Test
fun `collects no rulesets`() {
val items = subject.run(code)
assertThat(items).isEmpty()
class SomeRandomClass: SomeOtherClass {
fun logSomething(message: String) {
println(message)
}
}
"""
@Nested
inner class `a RuleSetProvider without documentation` {
val code = """
package foo
@Test
fun `collects no rulesets`() {
val items = subject.run(code)
assertThat(items).isEmpty()
}
}
class TestProvider: RuleSetProvider {
fun logSomething(message: String) {
println(message)
}
}
"""
@Nested
inner class `a RuleSetProvider without documentation` {
val code = """
package foo
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
class TestProvider: RuleSetProvider {
fun logSomething(message: String) {
println(message)
}
}
"""
@Nested
inner class `a correct RuleSetProvider class extending RuleSetProvider but missing parameters` {
val code = """
package foo
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
}
}
class TestProvider: RuleSetProvider {
fun logSomething(message: String) {
println(message)
}
}
"""
@Nested
inner class `a correct RuleSetProvider class extending RuleSetProvider but missing parameters` {
val code = """
package foo
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
class TestProvider: RuleSetProvider {
fun logSomething(message: String) {
println(message)
}
}
"""
@Nested
inner class `a correct RuleSetProvider class with full parameters` {
val description = "This is a description"
val ruleSetId = "test"
val ruleName = "TestRule"
val code = """
package foo
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
}
}
/**
* $description
*
*
*/
@ActiveByDefault("1.0.0")
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "$ruleSetId"
@Nested
inner class `a correct RuleSetProvider class with full parameters` {
val description = "This is a description"
val ruleSetId = "test"
val ruleName = "TestRule"
val code = """
package foo
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
$ruleName(config)
))
}
}
"""
/**
* $description
*
*
*/
@ActiveByDefault("1.0.0")
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "$ruleSetId"
@Test
fun `collects a RuleSetProvider`() {
val items = subject.run(code)
assertThat(items).hasSize(1)
}
@Test
fun `has one rule`() {
val items = subject.run(code)
val provider = items[0]
assertThat(provider.rules).hasSize(1)
assertThat(provider.rules[0]).isEqualTo(ruleName)
}
@Test
fun `has correct name`() {
val items = subject.run(code)
val provider = items[0]
assertThat(provider.name).isEqualTo(ruleSetId)
}
@Test
fun `has correct description`() {
val items = subject.run(code)
val provider = items[0]
assertThat(provider.description).isEqualTo(description)
}
@Test
fun `is active`() {
val items = subject.run(code)
val provider = items[0]
assertThat(provider.defaultActivationStatus.active).isTrue()
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
$ruleName(config)
))
}
}
"""
@Nested
inner class `an inactive RuleSetProvider` {
val description = "This is a description"
val ruleSetId = "test"
val ruleName = "TestRule"
val code = """
package foo
/**
* $description
*/
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "$ruleSetId"
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
$ruleName(config)
))
}
}
"""
@Test
fun `is not active`() {
val items = subject.run(code)
val provider = items[0]
assertThat(provider.defaultActivationStatus.active).isFalse()
}
@Test
fun `collects a RuleSetProvider`() {
val items = subject.run(code)
assertThat(items).hasSize(1)
}
@Nested
inner class `a RuleSetProvider with missing name` {
val description = "This is a description"
val ruleName = "TestRule"
val code = """
package foo
/**
* $description
*/
class TestProvider: RuleSetProvider {
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
$ruleName(config)
))
}
}
"""
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
}
@Test
fun `has one rule`() {
val items = subject.run(code)
val provider = items[0]
assertThat(provider.rules).hasSize(1)
assertThat(provider.rules[0]).isEqualTo(ruleName)
}
@Nested
inner class `a RuleSetProvider with missing description` {
val ruleSetId = "test"
val ruleName = "TestRule"
val code = """
package foo
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "$ruleSetId"
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
$ruleName(config)
))
}
}
"""
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
}
@Test
fun `has correct name`() {
val items = subject.run(code)
val provider = items[0]
assertThat(provider.name).isEqualTo(ruleSetId)
}
@Nested
inner class `a RuleSetProvider with invalid activation version` {
val code = """
package foo
/**
* description
*/
@ActiveByDefault(since = "1.2.xyz")
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "ruleSetId"
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
TestRule(config)
))
}
}
"""
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
}
@Test
fun `has correct description`() {
val items = subject.run(code)
val provider = items[0]
assertThat(provider.description).isEqualTo(description)
}
@Nested
inner class `a RuleSetProvider with no rules` {
val ruleSetId = "test"
val code = """
package foo
@Test
fun `is active`() {
val items = subject.run(code)
val provider = items[0]
assertThat(provider.defaultActivationStatus.active).isTrue()
}
}
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "$ruleSetId"
@Nested
inner class `an inactive RuleSetProvider` {
val description = "This is a description"
val ruleSetId = "test"
val ruleName = "TestRule"
val code = """
package foo
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, emptyListOf())
}
}
"""
/**
* $description
*/
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "$ruleSetId"
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
$ruleName(config)
))
}
}
"""
@Nested
inner class `a correct RuleSetProvider class with full parameters and multiple rules` {
val description = "This is a description"
val ruleSetId = "test"
val ruleName = "TestRule"
val secondRuleName = "SecondRule"
val code = """
package foo
@Test
fun `is not active`() {
val items = subject.run(code)
val provider = items[0]
assertThat(provider.defaultActivationStatus.active).isFalse()
}
}
/**
* $description
*/
@ActiveByDefault("1.0.0")
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "$ruleSetId"
@Nested
inner class `a RuleSetProvider with missing name` {
val description = "This is a description"
val ruleName = "TestRule"
val code = """
package foo
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
$ruleName(config),
$secondRuleName(config)
))
}
}
"""
@Test
fun `collects multiple rules`() {
val items = subject.run(code)
assertThat(items[0].rules).containsExactly(ruleName, secondRuleName)
/**
* $description
*/
class TestProvider: RuleSetProvider {
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
$ruleName(config)
))
}
}
"""
@Nested
inner class `a RuleSetProvider with configurations in kdoc` {
val code = """
package foo
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
}
}
/**
* description
* @configuration android - if android style guides should be preferred (default: `false`)
*/
class TestProvider: RuleSetProvider {
""".trimIndent()
@Nested
inner class `a RuleSetProvider with missing description` {
val ruleSetId = "test"
val ruleName = "TestRule"
val code = """
package foo
@Test
fun `throws exception for configuration in kdoc`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "$ruleSetId"
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
$ruleName(config)
))
}
}
"""
@Nested
inner class `a RuleSetProvider with configurations` {
val code = """
package foo
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
}
}
/**
* description
*/
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "ruleSetId"
@Nested
inner class `a RuleSetProvider with invalid activation version` {
val code = """
package foo
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(RruleName(config)))
}
/**
* description
*/
@ActiveByDefault(since = "1.2.xyz")
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "ruleSetId"
companion object {
@Configuration("bool description")
val aBool by ruleSetConfig(true)
@Configuration("int description")
val anInt by ruleSetConfig(99)
@Deprecated("use something else")
@Configuration("string description")
val aString by ruleSetConfig("a")
}
}
"""
private val items = subject.run(code)
@Test
fun `extracts boolean configuration option`() {
val conf = items[0].configuration[0]
assertThat(conf.name).isEqualTo("aBool")
assertThat(conf.description).isEqualTo("bool description")
assertThat(conf.defaultValue).isEqualTo(of(true))
assertThat(conf.deprecated).isNull()
}
@Test
fun `extracts int configuration option`() {
val conf = items[0].configuration[1]
assertThat(conf.name).isEqualTo("anInt")
assertThat(conf.description).isEqualTo("int description")
assertThat(conf.defaultValue).isEqualTo(of(99))
}
@Test
fun `extracts string configuration option`() {
val conf = items[0].configuration[2]
assertThat(conf.name).isEqualTo("aString")
assertThat(conf.description).isEqualTo("string description")
assertThat(conf.defaultValue).isEqualTo(of("a"))
assertThat(conf.deprecated).isEqualTo("use something else")
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
TestRule(config)
))
}
}
"""
@Nested
inner class `a RuleSetProvider with unsupported configuration format` {
val code = """
package foo
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
}
}
/**
* description
*/
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "ruleSetId"
@Nested
inner class `a RuleSetProvider with no rules` {
val ruleSetId = "test"
val code = """
package foo
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(RruleName(config)))
}
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "$ruleSetId"
companion object {
@Configuration("a description")
val aConfig by ruleSetConfig(listOf("a"))
}
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, emptyListOf())
}
"""
}
"""
@Test
fun `fails`() {
assertThatThrownBy { subject.run(code) }
.isInstanceOf(InvalidDocumentationException::class.java)
.hasMessageContaining("""Unsupported default value format 'listOf("a")'""")
@Test
fun `throws an exception`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
}
}
@Nested
inner class `a correct RuleSetProvider class with full parameters and multiple rules` {
val description = "This is a description"
val ruleSetId = "test"
val ruleName = "TestRule"
val secondRuleName = "SecondRule"
val code = """
package foo
/**
* $description
*/
@ActiveByDefault("1.0.0")
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "$ruleSetId"
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(
$ruleName(config),
$secondRuleName(config)
))
}
}
"""
@Test
fun `collects multiple rules`() {
val items = subject.run(code)
assertThat(items[0].rules).containsExactly(ruleName, secondRuleName)
}
}
@Nested
inner class `a RuleSetProvider with configurations in kdoc` {
val code = """
package foo
/**
* description
* @configuration android - if android style guides should be preferred (default: `false`)
*/
class TestProvider: RuleSetProvider {
""".trimIndent()
@Test
fun `throws exception for configuration in kdoc`() {
assertThatExceptionOfType(InvalidDocumentationException::class.java)
.isThrownBy { subject.run(code) }
}
}
@Nested
inner class `a RuleSetProvider with configurations` {
val code = """
package foo
/**
* description
*/
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "ruleSetId"
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(RruleName(config)))
}
companion object {
@Configuration("bool description")
val aBool by ruleSetConfig(true)
@Configuration("int description")
val anInt by ruleSetConfig(99)
@Deprecated("use something else")
@Configuration("string description")
val aString by ruleSetConfig("a")
}
}
"""
private val items = subject.run(code)
@Test
fun `extracts boolean configuration option`() {
val conf = items[0].configuration[0]
assertThat(conf.name).isEqualTo("aBool")
assertThat(conf.description).isEqualTo("bool description")
assertThat(conf.defaultValue).isEqualTo(of(true))
assertThat(conf.deprecated).isNull()
}
@Test
fun `extracts int configuration option`() {
val conf = items[0].configuration[1]
assertThat(conf.name).isEqualTo("anInt")
assertThat(conf.description).isEqualTo("int description")
assertThat(conf.defaultValue).isEqualTo(of(99))
}
@Test
fun `extracts string configuration option`() {
val conf = items[0].configuration[2]
assertThat(conf.name).isEqualTo("aString")
assertThat(conf.description).isEqualTo("string description")
assertThat(conf.defaultValue).isEqualTo(of("a"))
assertThat(conf.deprecated).isEqualTo("use something else")
}
}
@Nested
inner class `a RuleSetProvider with unsupported configuration format` {
val code = """
package foo
/**
* description
*/
class TestProvider: RuleSetProvider {
override val ruleSetId: String = "ruleSetId"
override fun instance(config: Config): RuleSet {
return RuleSet(ruleSetId, listOf(RruleName(config)))
}
companion object {
@Configuration("a description")
val aConfig by ruleSetConfig(listOf("a"))
}
}
"""
@Test
fun `fails`() {
assertThatThrownBy { subject.run(code) }
.isInstanceOf(InvalidDocumentationException::class.java)
.hasMessageContaining("""Unsupported default value format 'listOf("a")'""")
}
}
}

View File

@@ -2,22 +2,17 @@ package io.gitlab.arturbosch.detekt.generator.printer
import io.github.detekt.test.utils.createTempFileForTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class CliOptionsPrinterSpec {
@Nested
inner class `Cli Options Printer` {
@Test
fun `prints the correct cli-options_md`() {
val cliOptionsFile = createTempFileForTest("cli-options", ".md")
CliOptionsPrinter().print(cliOptionsFile.toAbsolutePath())
val markdownString = cliOptionsFile.toFile().readText()
@Test
fun `prints the correct cli-options_md`() {
val cliOptionsFile = createTempFileForTest("cli-options", ".md")
CliOptionsPrinter().print(cliOptionsFile.toAbsolutePath())
val markdownString = cliOptionsFile.toFile().readText()
assertThat(markdownString).contains("Usage: detekt [options]")
assertThat(markdownString).contains("--input, -i")
}
assertThat(markdownString).contains("Usage: detekt [options]")
assertThat(markdownString).contains("--input, -i")
}
}

View File

@@ -2,22 +2,18 @@ package io.gitlab.arturbosch.detekt.generator.printer
import io.gitlab.arturbosch.detekt.generator.util.createRuleSetPage
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class DeprecatedPrinterSpec {
@Nested
inner class `Deprecated page printer` {
@Test
fun `prints the correct properties`() {
val markdownString = DeprecatedPrinter.print(listOf(createRuleSetPage()))
val expectedMarkdownString = """
style>WildcardImport>conf2=use conf1 instead
style>WildcardImport>conf4=use conf3 instead
@Test
fun `prints the correct properties`() {
val markdownString = DeprecatedPrinter.print(listOf(createRuleSetPage()))
val expectedMarkdownString = """
style>WildcardImport>conf2=use conf1 instead
style>WildcardImport>conf4=use conf3 instead
""".trimIndent()
assertThat(markdownString).isEqualTo(expectedMarkdownString)
}
""".trimIndent()
assertThat(markdownString).isEqualTo(expectedMarkdownString)
}
}

View File

@@ -3,54 +3,50 @@ package io.gitlab.arturbosch.detekt.generator.printer.defaultconfig
import io.github.detekt.test.utils.readResourceContent
import io.gitlab.arturbosch.detekt.generator.util.createRuleSetPage
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class ConfigPrinterSpec {
@Nested
inner class `Config printer` {
val ruleSetPage = createRuleSetPage()
val yamlString = ConfigPrinter.print(listOf(ruleSetPage))
private val ruleSetPage = createRuleSetPage()
private val yamlString = ConfigPrinter.print(listOf(ruleSetPage))
@Test
fun `prints the rule set in the correct yaml format`() {
val expectedRulePart = readResourceContent("RuleSetConfig.yml")
@Test
fun `prints the rule set in the correct yaml format`() {
val expectedRulePart = readResourceContent("RuleSetConfig.yml")
assertThat(yamlString).contains(expectedRulePart)
}
assertThat(yamlString).contains(expectedRulePart)
}
@Test
fun `prints default build configuration`() {
assertThat(yamlString).contains("build:")
}
@Test
fun `prints default build configuration`() {
assertThat(yamlString).contains("build:")
}
@Test
fun `prints default config configuration`() {
assertThat(yamlString).contains("config:")
}
@Test
fun `prints default config configuration`() {
assertThat(yamlString).contains("config:")
}
@Test
fun `prints default processor configuration`() {
assertThat(yamlString).contains("processors:")
}
@Test
fun `prints default processor configuration`() {
assertThat(yamlString).contains("processors:")
}
@Test
fun `prints default report configuration`() {
assertThat(yamlString).contains("output-reports:")
assertThat(yamlString).contains("console-reports:")
}
@Test
fun `prints default report configuration`() {
assertThat(yamlString).contains("output-reports:")
assertThat(yamlString).contains("console-reports:")
}
@Test
fun `omits deprecated ruleset properties`() {
assertThat(yamlString).doesNotContain("deprecatedSimpleConfig")
assertThat(yamlString).doesNotContain("deprecatedListConfig")
}
@Test
fun `omits deprecated ruleset properties`() {
assertThat(yamlString).doesNotContain("deprecatedSimpleConfig")
assertThat(yamlString).doesNotContain("deprecatedListConfig")
}
@Test
fun `omits deprecated rule properties`() {
assertThat(yamlString).doesNotContain("conf2")
assertThat(yamlString).doesNotContain("conf4")
}
@Test
fun `omits deprecated rule properties`() {
assertThat(yamlString).doesNotContain("conf2")
assertThat(yamlString).doesNotContain("conf4")
}
}

View File

@@ -10,417 +10,414 @@ import org.junit.jupiter.api.condition.EnabledForJreRange
import org.junit.jupiter.api.condition.EnabledIf
import org.junit.jupiter.api.condition.JRE
@EnabledForJreRange(min = JRE.JAVA_11, disabledReason = "Android Gradle Plugin 7.0+ requires JDK 11 or newer")
@EnabledIf("io.gitlab.arturbosch.detekt.DetektAndroidSpecKt#isAndroidSdkInstalled")
class DetektAndroidSpec {
@Nested
@EnabledForJreRange(min = JRE.JAVA_11, disabledReason = "Android Gradle Plugin 7.0+ requires JDK 11 or newer")
@EnabledIf("io.gitlab.arturbosch.detekt.DetektAndroidSpecKt#isAndroidSdkInstalled")
inner class `When applying detekt in an Android project` {
inner class `configures android tasks for android application` {
val projectLayout = ProjectLayout(
numberOfSourceFilesInRootPerSourceDir = 0,
).apply {
addSubmodule(
name = "app",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$APP_PLUGIN_BLOCK
$ANDROID_BLOCK
$DETEKT_REPORTS_BLOCK
""".trimIndent(),
srcDirs = listOf(
"src/main/java",
"src/debug/java",
"src/test/java",
"src/androidTest/java",
"src/main/kotlin",
"src/debug/kotlin",
"src/test/kotlin",
"src/androidTest/kotlin",
),
baselineFiles = listOf(
"detekt-baseline.xml",
"detekt-baseline-release.xml",
"detekt-baseline-debug.xml",
"detekt-baseline-releaseUnitTest.xml",
"detekt-baseline-debugUnitTest.xml",
"detekt-baseline-debugAndroidTest.xml"
)
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("app/src/main/AndroidManifest.xml", manifestContent())
}
@Nested
inner class `configures android tasks for android application` {
val projectLayout = ProjectLayout(
numberOfSourceFilesInRootPerSourceDir = 0,
).apply {
addSubmodule(
name = "app",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$APP_PLUGIN_BLOCK
$ANDROID_BLOCK
$DETEKT_REPORTS_BLOCK
""".trimIndent(),
srcDirs = listOf(
"src/main/java",
"src/debug/java",
"src/test/java",
"src/androidTest/java",
"src/main/kotlin",
"src/debug/kotlin",
"src/test/kotlin",
"src/androidTest/kotlin",
),
baselineFiles = listOf(
"detekt-baseline.xml",
"detekt-baseline-release.xml",
"detekt-baseline-debug.xml",
"detekt-baseline-releaseUnitTest.xml",
"detekt-baseline-debugUnitTest.xml",
"detekt-baseline-debugAndroidTest.xml"
@Test
@DisplayName("task :app:detektMain")
fun appDetektMain() {
gradleRunner.runTasksAndCheckResult(":app:detektMain") { buildResult ->
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-release.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debug.xml """)
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]main[/\\]java""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]debug[/\\]java""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]main[/\\]kotlin""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]debug[/\\]kotlin""")
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":app:detektMain",
":app:detektDebug"
)
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("app/src/main/AndroidManifest.xml", manifestContent())
}
@Test
@DisplayName("task :app:detektMain")
fun appDetektMain() {
gradleRunner.runTasksAndCheckResult(":app:detektMain") { buildResult ->
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-release.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debug.xml """)
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]main[/\\]java""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]debug[/\\]java""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]main[/\\]kotlin""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]debug[/\\]kotlin""")
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":app:detektMain",
":app:detektDebug"
)
)
}
}
@Test
@DisplayName("task :app:detektTest")
fun appDetektTest() {
gradleRunner.runTasksAndCheckResult(":app:detektTest") { buildResult ->
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-releaseUnitTest.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debugUnitTest.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debugAndroidTest.xml """)
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]test[/\\]java""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]androidTest[/\\]java""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]test[/\\]kotlin""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]androidTest[/\\]kotlin""")
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":app:detektDebugUnitTest",
":app:detektDebugAndroidTest"
)
)
}
}
}
@Nested
inner class `does not configures android tasks if user opts out` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "app",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$APP_PLUGIN_BLOCK
$ANDROID_BLOCK
$DETEKT_REPORTS_BLOCK
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
@Test
@DisplayName("task :app:detektTest")
fun appDetektTest() {
gradleRunner.runTasksAndCheckResult(":app:detektTest") { buildResult ->
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-releaseUnitTest.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debugUnitTest.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debugAndroidTest.xml """)
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]test[/\\]java""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]androidTest[/\\]java""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]test[/\\]kotlin""")
assertThat(buildResult.output).containsPattern("""--input \S*[/\\]app[/\\]src[/\\]androidTest[/\\]kotlin""")
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":app:detektDebugUnitTest",
":app:detektDebugAndroidTest"
)
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("gradle.properties", "detekt.android.disabled=true")
it.writeProjectFile("app/src/main/AndroidManifest.xml", manifestContent())
}
}
}
@Test
@DisplayName("task :app:detekt")
fun appDetekt() {
gradleRunner.runTasks(":app:detekt")
}
@Nested
inner class `does not configures android tasks if user opts out` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "app",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$APP_PLUGIN_BLOCK
$ANDROID_BLOCK
$DETEKT_REPORTS_BLOCK
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("gradle.properties", "detekt.android.disabled=true")
it.writeProjectFile("app/src/main/AndroidManifest.xml", manifestContent())
}
@Test
@DisplayName("task :app:detektMain")
fun appDetektMain() {
gradleRunner.runTasksAndExpectFailure(":app:detektMain") { result ->
assertThat(result.output).contains("Task 'detektMain' not found in project")
}
}
@Test
@DisplayName("task :app:detekt")
fun appDetekt() {
gradleRunner.runTasks(":app:detekt")
}
@Test
@DisplayName("task :app:detektTest")
fun appDetektTest() {
gradleRunner.runTasksAndExpectFailure(":app:detektTest") { result ->
assertThat(result.output).contains("Task 'detektTest' not found in project")
}
@Test
@DisplayName("task :app:detektMain")
fun appDetektMain() {
gradleRunner.runTasksAndExpectFailure(":app:detektMain") { result ->
assertThat(result.output).contains("Task 'detektMain' not found in project")
}
}
@Nested
inner class `configures android tasks for android library` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$LIB_PLUGIN_BLOCK
$ANDROID_BLOCK
$DETEKT_REPORTS_BLOCK
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java"),
baselineFiles = listOf(
"detekt-baseline.xml",
"detekt-baseline-release.xml",
"detekt-baseline-debug.xml",
"detekt-baseline-releaseUnitTest.xml",
"detekt-baseline-debugUnitTest.xml",
"detekt-baseline-debugAndroidTest.xml"
@Test
@DisplayName("task :app:detektTest")
fun appDetektTest() {
gradleRunner.runTasksAndExpectFailure(":app:detektTest") { result ->
assertThat(result.output).contains("Task 'detektTest' not found in project")
}
}
}
@Nested
inner class `configures android tasks for android library` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$LIB_PLUGIN_BLOCK
$ANDROID_BLOCK
$DETEKT_REPORTS_BLOCK
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java"),
baselineFiles = listOf(
"detekt-baseline.xml",
"detekt-baseline-release.xml",
"detekt-baseline-debug.xml",
"detekt-baseline-releaseUnitTest.xml",
"detekt-baseline-debugUnitTest.xml",
"detekt-baseline-debugAndroidTest.xml"
)
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("lib/src/main/AndroidManifest.xml", manifestContent())
}
@Test
@DisplayName("task :lib:detektMain")
fun libDetektMain() {
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-release.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debug.xml """)
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektMain",
":lib:detektDebug"
)
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("lib/src/main/AndroidManifest.xml", manifestContent())
}
@Test
@DisplayName("task :lib:detektMain")
fun libDetektMain() {
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-release.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debug.xml """)
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektMain",
":lib:detektDebug"
)
)
}
}
@Test
@DisplayName("task :lib:detektTest")
fun libDetektTest() {
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-releaseUnitTest.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debugUnitTest.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debugAndroidTest.xml """)
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektDebugUnitTest",
":lib:detektDebugAndroidTest"
)
)
}
}
}
@Nested
inner class `configures android tasks for different build variants` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$LIB_PLUGIN_BLOCK
$ANDROID_BLOCK_WITH_FLAVOR
$DETEKT_REPORTS_BLOCK
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
@Test
@DisplayName("task :lib:detektTest")
fun libDetektTest() {
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-releaseUnitTest.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debugUnitTest.xml """)
assertThat(buildResult.output).containsPattern("""--baseline \S*[/\\]detekt-baseline-debugAndroidTest.xml """)
assertThat(buildResult.output).contains("--report xml:")
assertThat(buildResult.output).contains("--report sarif:")
assertThat(buildResult.output).doesNotContain("--report txt:")
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektDebugUnitTest",
":lib:detektDebugAndroidTest"
)
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("lib/src/main/AndroidManifest.xml", manifestContent())
}
@Test
@DisplayName("task :lib:detektMain")
fun libDetektMain() {
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebug",
":lib:detektOldHarryDebug",
":lib:detektOldHarryRelease"
)
)
}
}
@Test
@DisplayName("task :lib:detektTest")
fun libDetektTest() {
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryReleaseUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryDebugAndroidTest"
)
)
}
}
}
}
@Nested
inner class `configures android tasks for different build variants excluding ignored build types` {
@Nested
inner class `configures android tasks for different build variants` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$LIB_PLUGIN_BLOCK
$ANDROID_BLOCK_WITH_FLAVOR
detekt {
ignoredBuildTypes = listOf("release")
}
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("lib/src/main/AndroidManifest.xml", manifestContent())
}
@Test
@DisplayName("task :lib:detektMain")
fun libDetektMain() {
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebug",
":lib:detektOldHarryDebug"
)
).doesNotContain(
":lib:detektOldHarryRelease"
)
}
}
@Test
@DisplayName("task :lib:detektTest")
fun libDetektTest() {
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektOldHarryDebugUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryDebugAndroidTest"
)
).doesNotContain(
":lib:detektOldHarryReleaseUnitTest"
)
}
}
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$LIB_PLUGIN_BLOCK
$ANDROID_BLOCK_WITH_FLAVOR
$DETEKT_REPORTS_BLOCK
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("lib/src/main/AndroidManifest.xml", manifestContent())
}
@Nested
inner class `configures android tasks for different build variants excluding ignored variants` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$LIB_PLUGIN_BLOCK
$ANDROID_BLOCK_WITH_FLAVOR
detekt {
ignoredVariants = listOf("youngHarryDebug", "oldHarryRelease")
}
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("lib/src/main/AndroidManifest.xml", manifestContent())
}
@Test
@DisplayName("task :lib:detektMain")
fun libDetektMain() {
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebug"
)
).doesNotContain(
@Test
@DisplayName("task :lib:detektMain")
fun libDetektMain() {
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebug",
":lib:detektOldHarryDebug",
":lib:detektOldHarryRelease"
)
}
}
@Test
@DisplayName("task :lib:detektTest")
fun libDetektTest() {
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryDebugAndroidTest"
)
).doesNotContain(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryReleaseUnitTest"
)
}
)
}
}
@Nested
inner class `configures android tasks for different build variants excluding ignored flavors` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$LIB_PLUGIN_BLOCK
$ANDROID_BLOCK_WITH_FLAVOR
detekt {
ignoredFlavors = listOf("youngHarry")
}
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
@Test
@DisplayName("task :lib:detektTest")
fun libDetektTest() {
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryReleaseUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryDebugAndroidTest"
)
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("lib/src/main/AndroidManifest.xml", manifestContent())
}
}
}
@Test
@DisplayName("task :lib:detektMain")
fun libDetektMain() {
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebug",
":lib:detektOldHarryRelease"
)
).doesNotContain(
":lib:detektYoungHarryDebug"
@Nested
inner class `configures android tasks for different build variants excluding ignored build types` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$LIB_PLUGIN_BLOCK
$ANDROID_BLOCK_WITH_FLAVOR
detekt {
ignoredBuildTypes = listOf("release")
}
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("lib/src/main/AndroidManifest.xml", manifestContent())
}
@Test
@DisplayName("task :lib:detektMain")
fun libDetektMain() {
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebug",
":lib:detektOldHarryDebug"
)
}
).doesNotContain(
":lib:detektOldHarryRelease"
)
}
}
@Test
@DisplayName("task :lib:detektTest")
fun libDetektTest() {
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryDebugAndroidTest",
":lib:detektOldHarryReleaseUnitTest"
)
).doesNotContain(
@Test
@DisplayName("task :lib:detektTest")
fun libDetektTest() {
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektYoungHarryDebugAndroidTest"
":lib:detektOldHarryDebugUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryDebugAndroidTest"
)
}
).doesNotContain(
":lib:detektOldHarryReleaseUnitTest"
)
}
}
}
@Nested
inner class `configures android tasks for different build variants excluding ignored variants` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$LIB_PLUGIN_BLOCK
$ANDROID_BLOCK_WITH_FLAVOR
detekt {
ignoredVariants = listOf("youngHarryDebug", "oldHarryRelease")
}
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("lib/src/main/AndroidManifest.xml", manifestContent())
}
@Test
@DisplayName("task :lib:detektMain")
fun libDetektMain() {
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebug"
)
).doesNotContain(
":lib:detektYoungHarryDebug",
":lib:detektOldHarryRelease"
)
}
}
@Test
@DisplayName("task :lib:detektTest")
fun libDetektTest() {
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryDebugAndroidTest"
)
).doesNotContain(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektYoungHarryDebugAndroidTest",
":lib:detektOldHarryReleaseUnitTest"
)
}
}
}
@Nested
inner class `configures android tasks for different build variants excluding ignored flavors` {
val projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 0).apply {
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
numberOfCodeSmells = 1,
buildFileContent = """
$LIB_PLUGIN_BLOCK
$ANDROID_BLOCK_WITH_FLAVOR
detekt {
ignoredFlavors = listOf("youngHarry")
}
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
)
}
val gradleRunner = createGradleRunnerAndSetupProject(projectLayout).also {
it.writeProjectFile("lib/src/main/AndroidManifest.xml", manifestContent())
}
@Test
@DisplayName("task :lib:detektMain")
fun libDetektMain() {
gradleRunner.runTasksAndCheckResult(":lib:detektMain") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebug",
":lib:detektOldHarryRelease"
)
).doesNotContain(
":lib:detektYoungHarryDebug"
)
}
}
@Test
@DisplayName("task :lib:detektTest")
fun libDetektTest() {
gradleRunner.runTasksAndCheckResult(":lib:detektTest") { buildResult ->
assertThat(buildResult.tasks.map { it.path }).containsAll(
listOf(
":lib:detektOldHarryDebugUnitTest",
":lib:detektOldHarryDebugAndroidTest",
":lib:detektOldHarryReleaseUnitTest"
)
).doesNotContain(
":lib:detektYoungHarryDebugUnitTest",
":lib:detektYoungHarryDebugAndroidTest"
)
}
}
}

View File

@@ -7,82 +7,79 @@ import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class DetektJvmSpec {
@Nested
inner class `When applying detekt in a JVM project` {
@Nested
inner class `report location set on extension & task` {
val gradleRunner = DslGradleRunner(
projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 1),
buildFileName = "build.gradle.kts",
mainBuildFileContent = """
plugins {
kotlin("jvm")
id("io.gitlab.arturbosch.detekt")
}
repositories {
mavenCentral()
mavenLocal()
}
detekt {
reports {
txt.destination = file("output-path.txt")
}
}
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
reports {
txt.destination = file("output-path2.txt")
}
}
""".trimIndent(),
dryRun = false
).also {
it.setupProject()
}
@Test
fun `logs a warning`() {
gradleRunner.runTasksAndCheckResult(":detektMain") { buildResult ->
assertThat(buildResult.output).contains("TXT report location set on detekt {} extension will be ignored for detektMain task.")
inner class `report location set on extension & task` {
val gradleRunner = DslGradleRunner(
projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 1),
buildFileName = "build.gradle.kts",
mainBuildFileContent = """
plugins {
kotlin("jvm")
id("io.gitlab.arturbosch.detekt")
}
}
repositories {
mavenCentral()
mavenLocal()
}
detekt {
reports {
txt.destination = file("output-path.txt")
}
}
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
reports {
txt.destination = file("output-path2.txt")
}
}
""".trimIndent(),
dryRun = false
).also {
it.setupProject()
}
@Nested
inner class `report location set on task only` {
val gradleRunner = DslGradleRunner(
projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 1),
buildFileName = "build.gradle.kts",
mainBuildFileContent = """
plugins {
kotlin("jvm")
id("io.gitlab.arturbosch.detekt")
}
repositories {
mavenCentral()
mavenLocal()
}
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
reports {
txt.destination = file("output-path2.txt")
}
}
""".trimIndent(),
dryRun = false
).also {
it.setupProject()
@Test
fun `logs a warning`() {
gradleRunner.runTasksAndCheckResult(":detektMain") { buildResult ->
assertThat(buildResult.output).contains("TXT report location set on detekt {} extension will be ignored for detektMain task.")
}
}
}
@Test
fun `logs a warning`() {
gradleRunner.runTasksAndCheckResult(":detektMain") { buildResult ->
assertThat(buildResult.output).doesNotContain("report location set on detekt {} extension will be ignored")
@Nested
inner class `report location set on task only` {
val gradleRunner = DslGradleRunner(
projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 1),
buildFileName = "build.gradle.kts",
mainBuildFileContent = """
plugins {
kotlin("jvm")
id("io.gitlab.arturbosch.detekt")
}
repositories {
mavenCentral()
mavenLocal()
}
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
reports {
txt.destination = file("output-path2.txt")
}
}
""".trimIndent(),
dryRun = false
).also {
it.setupProject()
}
@Test
fun `logs a warning`() {
gradleRunner.runTasksAndCheckResult(":detektMain") { buildResult ->
assertThat(buildResult.output).doesNotContain("report location set on detekt {} extension will be ignored")
}
}
}

View File

@@ -6,231 +6,226 @@ import io.gitlab.arturbosch.detekt.testkit.ProjectLayout
import org.assertj.core.api.Assertions.assertThat
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class DetektTaskMultiModuleSpec {
@Nested
inner class `The Detekt Gradle plugin used in a multi module project` {
@Test
@DisplayName(
"it is applied with defaults to all subprojects individually without " +
"sources in root project using the subprojects block"
)
fun applyToSubprojectsWithoutSources() {
val projectLayout = ProjectLayout(0).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4)
}
val builder = DslTestBuilder.kotlin()
val mainBuildFileContent: String = """
|${builder.gradlePlugins}
|
|allprojects {
| ${builder.gradleRepositories}
|}
|subprojects {
| ${builder.gradleSubprojectsApplyPlugins}
|}
|
""".trimMargin()
val gradleRunner = DslGradleRunner(projectLayout, builder.gradleBuildName, mainBuildFileContent)
gradleRunner.setupProject()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.NO_SOURCE)
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
assertThat(projectFile("build/reports/detekt/detekt.xml")).doesNotExist()
assertThat(projectFile("build/reports/detekt/detekt.html")).doesNotExist()
assertThat(projectFile("build/reports/detekt/detekt.txt")).doesNotExist()
projectLayout.submodules.forEach {
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.xml")).exists()
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.html")).exists()
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.txt")).exists()
}
}
@Test
@DisplayName(
"it is applied with defaults to all subprojects individually without " +
"sources in root project using the subprojects block"
)
fun applyToSubprojectsWithoutSources() {
val projectLayout = ProjectLayout(0).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4)
}
@Test
@DisplayName(
"it is applied with defaults to main project and subprojects " +
"individually using the allprojects block"
)
fun applyWithAllprojectsBlock() {
val projectLayout = ProjectLayout(1).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4)
val builder = DslTestBuilder.kotlin()
val mainBuildFileContent: String = """
|${builder.gradlePlugins}
|
|allprojects {
| ${builder.gradleRepositories}
|}
|subprojects {
| ${builder.gradleSubprojectsApplyPlugins}
|}
|
""".trimMargin()
val gradleRunner = DslGradleRunner(projectLayout, builder.gradleBuildName, mainBuildFileContent)
gradleRunner.setupProject()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.NO_SOURCE)
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
val builder = DslTestBuilder.kotlin()
val mainBuildFileContent: String = """
|${builder.gradlePlugins}
|
|allprojects {
| ${builder.gradleRepositories}
| ${builder.gradleSubprojectsApplyPlugins}
|}
|
""".trimMargin()
val gradleRunner = DslGradleRunner(projectLayout, builder.gradleBuildName, mainBuildFileContent)
gradleRunner.setupProject()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
assertThat(projectFile("build/reports/detekt/detekt.xml")).exists()
assertThat(projectFile("build/reports/detekt/detekt.html")).exists()
assertThat(projectFile("build/reports/detekt/detekt.txt")).exists()
projectLayout.submodules.forEach {
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.xml")).exists()
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.html")).exists()
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.txt")).exists()
}
assertThat(projectFile("build/reports/detekt/detekt.xml")).doesNotExist()
assertThat(projectFile("build/reports/detekt/detekt.html")).doesNotExist()
assertThat(projectFile("build/reports/detekt/detekt.txt")).doesNotExist()
projectLayout.submodules.forEach {
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.xml")).exists()
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.html")).exists()
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.txt")).exists()
}
}
}
@Test
fun `it uses custom configs when configured in allprojects block`() {
val projectLayout = ProjectLayout(1).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4)
}
val builder = DslTestBuilder.kotlin()
val mainBuildFileContent: String = """
|${builder.gradlePlugins}
|
|allprojects {
| ${builder.gradleRepositories}
| ${builder.gradleSubprojectsApplyPlugins}
|
| detekt {
| reportsDir = file("build/detekt-reports")
| }
|}
|
""".trimMargin()
val gradleRunner = DslGradleRunner(projectLayout, builder.gradleBuildName, mainBuildFileContent)
gradleRunner.setupProject()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
assertThat(projectFile("build/detekt-reports/detekt.xml")).exists()
assertThat(projectFile("build/detekt-reports/detekt.html")).exists()
assertThat(projectFile("build/detekt-reports/detekt.txt")).exists()
projectLayout.submodules.forEach {
assertThat(projectFile("${it.name}/build/detekt-reports/detekt.xml")).exists()
assertThat(projectFile("${it.name}/build/detekt-reports/detekt.html")).exists()
assertThat(projectFile("${it.name}/build/detekt-reports/detekt.txt")).exists()
}
}
@Test
@DisplayName(
"it is applied with defaults to main project and subprojects " +
"individually using the allprojects block"
)
fun applyWithAllprojectsBlock() {
val projectLayout = ProjectLayout(1).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4)
}
@Test
@DisplayName("it allows changing defaults in allprojects block that can be overwritten in subprojects")
fun allowsChangingDefaultsInAllProjectsThatAreOverwrittenInSubprojects() {
val child2DetektConfig = """
|detekt {
| reportsDir = file("build/custom")
|}
|
""".trimMargin()
val builder = DslTestBuilder.kotlin()
val projectLayout = ProjectLayout(1).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4, buildFileContent = child2DetektConfig)
val mainBuildFileContent: String = """
|${builder.gradlePlugins}
|
|allprojects {
| ${builder.gradleRepositories}
| ${builder.gradleSubprojectsApplyPlugins}
|}
|
""".trimMargin()
val gradleRunner = DslGradleRunner(projectLayout, builder.gradleBuildName, mainBuildFileContent)
gradleRunner.setupProject()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
val builder = DslTestBuilder.kotlin()
val mainBuildFileContent: String = """
|${builder.gradlePlugins}
|
|allprojects {
| ${builder.gradleRepositories}
| ${builder.gradleSubprojectsApplyPlugins}
|
| detekt {
| reportsDir = file("build/detekt-reports")
| }
|}
|
""".trimMargin()
val gradleRunner = DslGradleRunner(projectLayout, builder.gradleBuildName, mainBuildFileContent)
gradleRunner.setupProject()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
assertThat(projectFile("build/detekt-reports/detekt.xml")).exists()
assertThat(projectFile("build/detekt-reports/detekt.html")).exists()
assertThat(projectFile("build/detekt-reports/detekt.txt")).exists()
assertThat(projectFile("child1/build/detekt-reports/detekt.xml")).exists()
assertThat(projectFile("child1/build/detekt-reports/detekt.html")).exists()
assertThat(projectFile("child1/build/detekt-reports/detekt.txt")).exists()
assertThat(projectFile("child2/build/custom/detekt.xml")).exists()
assertThat(projectFile("child2/build/custom/detekt.html")).exists()
assertThat(projectFile("child2/build/custom/detekt.txt")).exists()
assertThat(projectFile("build/reports/detekt/detekt.xml")).exists()
assertThat(projectFile("build/reports/detekt/detekt.html")).exists()
assertThat(projectFile("build/reports/detekt/detekt.txt")).exists()
projectLayout.submodules.forEach {
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.xml")).exists()
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.html")).exists()
assertThat(projectFile("${it.name}/build/reports/detekt/detekt.txt")).exists()
}
}
}
@Test
fun `it can be applied to all files in entire project resulting in 1 report`() {
val projectLayout = ProjectLayout(1).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4)
@Test
fun `it uses custom configs when configured in allprojects block`() {
val projectLayout = ProjectLayout(1).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4)
}
val builder = DslTestBuilder.kotlin()
val mainBuildFileContent: String = """
|${builder.gradlePlugins}
|
|allprojects {
| ${builder.gradleRepositories}
| ${builder.gradleSubprojectsApplyPlugins}
|
| detekt {
| reportsDir = file("build/detekt-reports")
| }
|}
|
""".trimMargin()
val gradleRunner = DslGradleRunner(projectLayout, builder.gradleBuildName, mainBuildFileContent)
gradleRunner.setupProject()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
val detektConfig: String = """
|detekt {
| source = files(
| "${"$"}projectDir/src",
| "${"$"}projectDir/child1/src",
| "${"$"}projectDir/child2/src"
| )
|}
""".trimMargin()
val gradleRunner = DslTestBuilder.kotlin()
.withProjectLayout(projectLayout)
.withDetektConfig(detektConfig)
.build()
assertThat(projectFile("build/detekt-reports/detekt.xml")).exists()
assertThat(projectFile("build/detekt-reports/detekt.html")).exists()
assertThat(projectFile("build/detekt-reports/detekt.txt")).exists()
projectLayout.submodules.forEach {
assertThat(projectFile("${it.name}/build/detekt-reports/detekt.xml")).exists()
assertThat(projectFile("${it.name}/build/detekt-reports/detekt.html")).exists()
assertThat(projectFile("${it.name}/build/detekt-reports/detekt.txt")).exists()
}
}
}
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detekt")).isNull()
}
@Test
@DisplayName("it allows changing defaults in allprojects block that can be overwritten in subprojects")
fun allowsChangingDefaultsInAllProjectsThatAreOverwrittenInSubprojects() {
val child2DetektConfig = """
|detekt {
| reportsDir = file("build/custom")
|}
|
""".trimMargin()
assertThat(projectFile("build/reports/detekt/detekt.xml")).exists()
assertThat(projectFile("build/reports/detekt/detekt.html")).exists()
assertThat(projectFile("build/reports/detekt/detekt.txt")).exists()
projectLayout.submodules.forEach { submodule ->
assertThat(projectFile("${submodule.name}/build/reports/detekt/detekt.xml")).doesNotExist()
assertThat(projectFile("${submodule.name}/build/reports/detekt/detekt.html")).doesNotExist()
assertThat(projectFile("${submodule.name}/build/reports/detekt/detekt.txt")).doesNotExist()
}
val projectLayout = ProjectLayout(1).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4, buildFileContent = child2DetektConfig)
}
val builder = DslTestBuilder.kotlin()
val mainBuildFileContent: String = """
|${builder.gradlePlugins}
|
|allprojects {
| ${builder.gradleRepositories}
| ${builder.gradleSubprojectsApplyPlugins}
|
| detekt {
| reportsDir = file("build/detekt-reports")
| }
|}
|
""".trimMargin()
val gradleRunner = DslGradleRunner(projectLayout, builder.gradleBuildName, mainBuildFileContent)
gradleRunner.setupProject()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
assertThat(projectFile("build/detekt-reports/detekt.xml")).exists()
assertThat(projectFile("build/detekt-reports/detekt.html")).exists()
assertThat(projectFile("build/detekt-reports/detekt.txt")).exists()
assertThat(projectFile("child1/build/detekt-reports/detekt.xml")).exists()
assertThat(projectFile("child1/build/detekt-reports/detekt.html")).exists()
assertThat(projectFile("child1/build/detekt-reports/detekt.txt")).exists()
assertThat(projectFile("child2/build/custom/detekt.xml")).exists()
assertThat(projectFile("child2/build/custom/detekt.html")).exists()
assertThat(projectFile("child2/build/custom/detekt.txt")).exists()
}
}
@Test
fun `it can be applied to all files in entire project resulting in 1 report`() {
val projectLayout = ProjectLayout(1).apply {
addSubmodule("child1", 2)
addSubmodule("child2", 4)
}
val detektConfig: String = """
|detekt {
| source = files(
| "${"$"}projectDir/src",
| "${"$"}projectDir/child1/src",
| "${"$"}projectDir/child2/src"
| )
|}
""".trimMargin()
val gradleRunner = DslTestBuilder.kotlin()
.withProjectLayout(projectLayout)
.withDetektConfig(detektConfig)
.build()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detekt")).isNull()
}
assertThat(projectFile("build/reports/detekt/detekt.xml")).exists()
assertThat(projectFile("build/reports/detekt/detekt.html")).exists()
assertThat(projectFile("build/reports/detekt/detekt.txt")).exists()
projectLayout.submodules.forEach { submodule ->
assertThat(projectFile("${submodule.name}/build/reports/detekt/detekt.xml")).doesNotExist()
assertThat(projectFile("${submodule.name}/build/reports/detekt/detekt.html")).doesNotExist()
assertThat(projectFile("${submodule.name}/build/reports/detekt/detekt.txt")).doesNotExist()
}
}
}

View File

@@ -5,7 +5,6 @@ import io.gitlab.arturbosch.detekt.testkit.DslTestBuilder.Companion.kotlin
import org.assertj.core.api.Assertions.assertThat
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
/**
@@ -24,106 +23,103 @@ class PluginTaskBehaviorSpec {
|}
"""
@Nested
inner class `The Detekt Gradle Plugin 'detekt' Task` {
lateinit var gradleRunner: DslGradleRunner
lateinit var gradleRunner: DslGradleRunner
@BeforeEach
fun setupGradleRunner() {
gradleRunner = kotlin()
.withDetektConfig(detektConfig)
.withBaseline(baselineFileName)
.withConfigFile(configFileName)
.build()
@BeforeEach
fun setupGradleRunner() {
gradleRunner = kotlin()
.withDetektConfig(detektConfig)
.withBaseline(baselineFileName)
.withConfigFile(configFileName)
.build()
}
@Test
fun `should be UP-TO-DATE the 2nd run without changes`() {
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.UP_TO_DATE)
}
}
@Test
fun `should pick up build artifacts from the build cache on a 2nd run after deleting the build dir`() {
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
@Test
fun `should be UP-TO-DATE the 2nd run without changes`() {
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.UP_TO_DATE)
}
gradleRunner.projectFile("build").deleteRecursively()
// Running detekt again should pick up artifacts from Build Cache
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.FROM_CACHE)
}
}
@Test
fun `should pick up build artifacts from the build cache on a 2nd run after running 'clean'`() {
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
gradleRunner.runTasksAndCheckResult("clean", "detekt") { result ->
assertThat(result.task(":clean")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.FROM_CACHE)
}
}
@Test
fun `should run again after changing config`() {
val configFileWithCommentsDisabled = """
|comments:
| active: false
""".trimMargin()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
@Test
fun `should pick up build artifacts from the build cache on a 2nd run after deleting the build dir`() {
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
// update config file
gradleRunner.writeProjectFile(configFileName, configFileWithCommentsDisabled)
gradleRunner.projectFile("build").deleteRecursively()
gradleRunner.runTasksAndCheckResult("detekt") { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
}
// Running detekt again should pick up artifacts from Build Cache
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.FROM_CACHE)
}
@Test
fun `should run again after changing baseline`() {
val changedBaselineContent = """
|<some>
| <more/>
| <xml/>
|</some>
""".trimMargin()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
@Test
fun `should pick up build artifacts from the build cache on a 2nd run after running 'clean'`() {
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
gradleRunner.runTasksAndCheckResult("clean", "detekt") { result ->
assertThat(result.task(":clean")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.FROM_CACHE)
}
// update baseline file
gradleRunner.writeProjectFile(baselineFileName, changedBaselineContent)
gradleRunner.runTasksAndCheckResult("detekt") { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
}
@Test
fun `should run again after changing inputs`() {
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
@Test
fun `should run again after changing config`() {
val configFileWithCommentsDisabled = """
|comments:
| active: false
""".trimMargin()
// add a new File
gradleRunner.writeKtFile(gradleRunner.projectLayout.srcDirs.first(), "OtherKotlinClass")
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
// update config file
gradleRunner.writeProjectFile(configFileName, configFileWithCommentsDisabled)
gradleRunner.runTasksAndCheckResult("detekt") { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
}
@Test
fun `should run again after changing baseline`() {
val changedBaselineContent = """
|<some>
| <more/>
| <xml/>
|</some>
""".trimMargin()
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
// update baseline file
gradleRunner.writeProjectFile(baselineFileName, changedBaselineContent)
gradleRunner.runTasksAndCheckResult("detekt") { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
}
@Test
fun `should run again after changing inputs`() {
gradleRunner.runDetektTaskAndCheckResult { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
// add a new File
gradleRunner.writeKtFile(gradleRunner.projectLayout.srcDirs.first(), "OtherKotlinClass")
gradleRunner.runTasksAndCheckResult("detekt") { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
gradleRunner.runTasksAndCheckResult("detekt") { result ->
assertThat(result.task(":detekt")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
}
}

View File

@@ -6,7 +6,6 @@ import io.gitlab.arturbosch.detekt.testkit.DslTestBuilder
import io.gitlab.arturbosch.detekt.testkit.ProjectLayout
import org.assertj.core.api.Assertions.assertThat
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.EnabledForJreRange
import org.junit.jupiter.api.condition.EnabledIf
@@ -14,187 +13,183 @@ import org.junit.jupiter.api.condition.JRE.JAVA_11
class ReportMergeSpec {
@Nested
inner class `Merging reports in a multi module projects` {
@Suppress("LongMethod")
@Test
fun `for jvm detekt`() {
val builder = DslTestBuilder.kotlin()
val projectLayout = ProjectLayout(0).apply {
addSubmodule(
"child1",
numberOfSourceFilesPerSourceDir = 2,
buildFileContent = """
${builder.gradleSubprojectsApplyPlugins}
|plugins.apply("java-library")
""".trimMargin()
)
addSubmodule(
"child2",
numberOfSourceFilesPerSourceDir = 2,
buildFileContent = """
${builder.gradleSubprojectsApplyPlugins}
|plugins.apply("java-library")
""".trimMargin()
)
}
val mainBuildFileContent: String = """
|plugins {
| id("io.gitlab.arturbosch.detekt")
|}
|
|allprojects {
| ${builder.gradleRepositories}
|}
|
|val reportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) {
| output.set(project.layout.buildDirectory.file("reports/detekt/merge.xml"))
| outputs.cacheIf { false }
| outputs.upToDateWhen { false }
|}
|
|subprojects {
| apply(plugin = "org.jetbrains.kotlin.jvm")
| apply(plugin = "io.gitlab.arturbosch.detekt")
|
| detekt {
| reports.xml.enabled = true
| }
|
| plugins.withType<io.gitlab.arturbosch.detekt.DetektPlugin> {
| tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
| finalizedBy(reportMerge)
| reportMerge.configure { input.from(xmlReportFile) }
| }
| }
|}
""".trimMargin()
val gradleRunner = DslGradleRunner(
projectLayout = projectLayout,
buildFileName = builder.gradleBuildName,
mainBuildFileContent = mainBuildFileContent
@Suppress("LongMethod")
@Test
fun `for jvm detekt`() {
val builder = DslTestBuilder.kotlin()
val projectLayout = ProjectLayout(0).apply {
addSubmodule(
"child1",
numberOfSourceFilesPerSourceDir = 2,
buildFileContent = """
${builder.gradleSubprojectsApplyPlugins}
|plugins.apply("java-library")
""".trimMargin()
)
addSubmodule(
"child2",
numberOfSourceFilesPerSourceDir = 2,
buildFileContent = """
${builder.gradleSubprojectsApplyPlugins}
|plugins.apply("java-library")
""".trimMargin()
)
gradleRunner.setupProject()
gradleRunner.runTasksAndCheckResult("detektMain", "reportMerge", "--continue") { result ->
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detektMain")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
projectLayout.submodules.forEach {
assertThat(projectFile("${it.name}/build/reports/detekt/main.xml")).exists()
}
assertThat(projectFile("build/reports/detekt/merge.xml")).exists()
}
}
val mainBuildFileContent: String = """
|plugins {
| id("io.gitlab.arturbosch.detekt")
|}
|
|allprojects {
| ${builder.gradleRepositories}
|}
|
|val reportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) {
| output.set(project.layout.buildDirectory.file("reports/detekt/merge.xml"))
| outputs.cacheIf { false }
| outputs.upToDateWhen { false }
|}
|
|subprojects {
| apply(plugin = "org.jetbrains.kotlin.jvm")
| apply(plugin = "io.gitlab.arturbosch.detekt")
|
| detekt {
| reports.xml.enabled = true
| }
|
| plugins.withType<io.gitlab.arturbosch.detekt.DetektPlugin> {
| tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
| finalizedBy(reportMerge)
| reportMerge.configure { input.from(xmlReportFile) }
| }
| }
|}
""".trimMargin()
@Suppress("LongMethod")
@Test
@EnabledForJreRange(min = JAVA_11, disabledReason = "Android Gradle Plugin 7.0+ requires JDK 11 or newer")
@EnabledIf("io.gitlab.arturbosch.detekt.DetektAndroidSpecKt#isAndroidSdkInstalled")
fun `for android detekt`() {
val builder = DslTestBuilder.kotlin()
val projectLayout = ProjectLayout(0).apply {
addSubmodule(
name = "app",
numberOfSourceFilesPerSourceDir = 1,
buildFileContent = """
plugins {
id("com.android.application")
kotlin("android")
id("io.gitlab.arturbosch.detekt")
}
android {
compileSdkVersion(30)
}
dependencies {
implementation(project(":lib"))
}
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java"),
)
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
buildFileContent = """
plugins {
id("com.android.library")
kotlin("android")
}
android {
compileSdkVersion(30)
}
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
)
val gradleRunner = DslGradleRunner(
projectLayout = projectLayout,
buildFileName = builder.gradleBuildName,
mainBuildFileContent = mainBuildFileContent
)
gradleRunner.setupProject()
gradleRunner.runTasksAndCheckResult("detektMain", "reportMerge", "--continue") { result ->
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detektMain")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
val mainBuildFileContent: String = """
|plugins {
| id("io.gitlab.arturbosch.detekt")
|}
|
|allprojects {
| repositories {
| mavenCentral()
| google()
| mavenLocal()
| }
|}
|
|val reportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) {
| output.set(project.layout.buildDirectory.file("reports/detekt/merge.xml"))
| outputs.cacheIf { false }
| outputs.upToDateWhen { false }
|}
|
|subprojects {
| apply(plugin = "io.gitlab.arturbosch.detekt")
|
| detekt {
| reports.xml.enabled = true
| }
|
| plugins.withType<io.gitlab.arturbosch.detekt.DetektPlugin> {
| tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
| finalizedBy(reportMerge)
| reportMerge.configure { input.from(xmlReportFile) }
| }
| }
|}
""".trimMargin()
val jvmArgs = "-Xmx2g -XX:MaxMetaspaceSize=1g"
val gradleRunner = DslGradleRunner(
projectLayout = projectLayout,
buildFileName = builder.gradleBuildName,
mainBuildFileContent = mainBuildFileContent,
jvmArgs = jvmArgs
)
gradleRunner.setupProject()
gradleRunner.writeProjectFile(
"app/src/main/AndroidManifest.xml",
manifestContent("io.github.detekt.app")
)
gradleRunner.writeProjectFile(
"lib/src/main/AndroidManifest.xml",
manifestContent("io.github.detekt.lib")
)
gradleRunner.runTasksAndCheckResult("detektMain", "reportMerge", "--continue") { result ->
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detektMain")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
projectLayout.submodules.forEach {
assertThat(projectFile("${it.name}/build/reports/detekt/debug.xml")).exists()
}
// #4192 this should exist by default
assertThat(projectFile("build/reports/detekt/merge.xml")).doesNotExist()
projectLayout.submodules.forEach {
assertThat(projectFile("${it.name}/build/reports/detekt/main.xml")).exists()
}
assertThat(projectFile("build/reports/detekt/merge.xml")).exists()
}
}
@Suppress("LongMethod")
@Test
@EnabledForJreRange(min = JAVA_11, disabledReason = "Android Gradle Plugin 7.0+ requires JDK 11 or newer")
@EnabledIf("io.gitlab.arturbosch.detekt.DetektAndroidSpecKt#isAndroidSdkInstalled")
fun `for android detekt`() {
val builder = DslTestBuilder.kotlin()
val projectLayout = ProjectLayout(0).apply {
addSubmodule(
name = "app",
numberOfSourceFilesPerSourceDir = 1,
buildFileContent = """
plugins {
id("com.android.application")
kotlin("android")
id("io.gitlab.arturbosch.detekt")
}
android {
compileSdkVersion(30)
}
dependencies {
implementation(project(":lib"))
}
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java"),
)
addSubmodule(
name = "lib",
numberOfSourceFilesPerSourceDir = 1,
buildFileContent = """
plugins {
id("com.android.library")
kotlin("android")
}
android {
compileSdkVersion(30)
}
""".trimIndent(),
srcDirs = listOf("src/main/java", "src/debug/java", "src/test/java", "src/androidTest/java")
)
}
val mainBuildFileContent: String = """
|plugins {
| id("io.gitlab.arturbosch.detekt")
|}
|
|allprojects {
| repositories {
| mavenCentral()
| google()
| mavenLocal()
| }
|}
|
|val reportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) {
| output.set(project.layout.buildDirectory.file("reports/detekt/merge.xml"))
| outputs.cacheIf { false }
| outputs.upToDateWhen { false }
|}
|
|subprojects {
| apply(plugin = "io.gitlab.arturbosch.detekt")
|
| detekt {
| reports.xml.enabled = true
| }
|
| plugins.withType<io.gitlab.arturbosch.detekt.DetektPlugin> {
| tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
| finalizedBy(reportMerge)
| reportMerge.configure { input.from(xmlReportFile) }
| }
| }
|}
""".trimMargin()
val jvmArgs = "-Xmx2g -XX:MaxMetaspaceSize=1g"
val gradleRunner = DslGradleRunner(
projectLayout = projectLayout,
buildFileName = builder.gradleBuildName,
mainBuildFileContent = mainBuildFileContent,
jvmArgs = jvmArgs
)
gradleRunner.setupProject()
gradleRunner.writeProjectFile(
"app/src/main/AndroidManifest.xml",
manifestContent("io.github.detekt.app")
)
gradleRunner.writeProjectFile(
"lib/src/main/AndroidManifest.xml",
manifestContent("io.github.detekt.lib")
)
gradleRunner.runTasksAndCheckResult("detektMain", "reportMerge", "--continue") { result ->
projectLayout.submodules.forEach { submodule ->
assertThat(result.task(":${submodule.name}:detektMain")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
projectLayout.submodules.forEach {
assertThat(projectFile("${it.name}/build/reports/detekt/debug.xml")).exists()
}
// #4192 this should exist by default
assertThat(projectFile("build/reports/detekt/merge.xml")).doesNotExist()
}
}
}

View File

@@ -6,62 +6,54 @@ import org.assertj.core.api.Assertions.assertThat
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.repositories
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class DetektJvmSpec {
@Nested
inner class `When applying detekt in a JVM project` {
@Nested
inner class `disabled TXT report` {
val gradleRunner = DslGradleRunner(
projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 1),
buildFileName = "build.gradle.kts",
baselineFiles = listOf("detekt-baseline.xml", "detekt-baseline-main.xml", "detekt-baseline-test.xml"),
projectScript = {
apply<KotlinPluginWrapper>()
apply<DetektPlugin>()
repositories {
mavenCentral()
mavenLocal()
}
tasks.withType(Detekt::class.java).configureEach {
it.reports { reports ->
reports.txt.required.set(false)
}
}
},
).also(DslGradleRunner::setupProject)
@Test
fun `configures detekt type resolution task main`() {
val project = gradleRunner.buildProject()
val detektTask = project.tasks.getByPath("detektMain") as Detekt
val argumentString = detektTask.arguments.get().joinToString(" ")
assertThat(argumentString).containsPattern("""--baseline \S*[/\\]detekt-baseline-main.xml """)
assertThat(argumentString).contains("--report xml:")
assertThat(argumentString).contains("--report sarif:")
assertThat(argumentString).doesNotContain("--report txt:")
assertThat(argumentString).contains("--classpath")
val gradleRunner = DslGradleRunner(
projectLayout = ProjectLayout(numberOfSourceFilesInRootPerSourceDir = 1),
buildFileName = "build.gradle.kts",
baselineFiles = listOf("detekt-baseline.xml", "detekt-baseline-main.xml", "detekt-baseline-test.xml"),
projectScript = {
apply<KotlinPluginWrapper>()
apply<DetektPlugin>()
repositories {
mavenCentral()
mavenLocal()
}
@Test
fun `configures detekt type resolution task test`() {
val project = gradleRunner.buildProject()
val detektTask = project.tasks.getByPath("detektTest") as Detekt
val argumentString = detektTask.arguments.get().joinToString(" ")
assertThat(argumentString).containsPattern("""--baseline \S*[/\\]detekt-baseline-test.xml """)
assertThat(argumentString).contains("--report xml:")
assertThat(argumentString).contains("--report sarif:")
assertThat(argumentString).doesNotContain("--report txt:")
assertThat(argumentString).contains("--classpath")
tasks.withType(Detekt::class.java).configureEach {
it.reports { reports ->
reports.txt.required.set(false)
}
}
}
},
).also(DslGradleRunner::setupProject)
@Test
fun `configures detekt type resolution task main`() {
val project = gradleRunner.buildProject()
val detektTask = project.tasks.getByPath("detektMain") as Detekt
val argumentString = detektTask.arguments.get().joinToString(" ")
assertThat(argumentString).containsPattern("""--baseline \S*[/\\]detekt-baseline-main.xml """)
assertThat(argumentString).contains("--report xml:")
assertThat(argumentString).contains("--report sarif:")
assertThat(argumentString).doesNotContain("--report txt:")
assertThat(argumentString).contains("--classpath")
}
@Test
fun `configures detekt type resolution task test`() {
val project = gradleRunner.buildProject()
val detektTask = project.tasks.getByPath("detektTest") as Detekt
val argumentString = detektTask.arguments.get().joinToString(" ")
assertThat(argumentString).containsPattern("""--baseline \S*[/\\]detekt-baseline-test.xml """)
assertThat(argumentString).contains("--report xml:")
assertThat(argumentString).contains("--report sarif:")
assertThat(argumentString).doesNotContain("--report txt:")
assertThat(argumentString).contains("--classpath")
}
}

View File

@@ -3,7 +3,6 @@ package io.gitlab.arturbosch.detekt.internal
import io.gitlab.arturbosch.detekt.gradle.TestFileCollection
import org.assertj.core.api.Assertions.assertThat
import org.gradle.api.internal.file.AbstractFileCollection
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.io.File
import java.util.concurrent.CompletableFuture
@@ -14,66 +13,62 @@ import java.util.function.Supplier
class ClassLoaderCacheSpec {
@Nested
inner class ClasspathChanges {
@Test
fun `same classloader is returned for the same files`() {
val cache = DefaultClassLoaderCache()
val initialClassLoader = cache.getOrCreate(TestFileCollection(File("a/b/c")))
val secondClassLoader = cache.getOrCreate(TestFileCollection(File("a/b/c")))
@Test
fun `same classloader is returned for the same files`() {
val cache = DefaultClassLoaderCache()
val initialClassLoader = cache.getOrCreate(TestFileCollection(File("a/b/c")))
val secondClassLoader = cache.getOrCreate(TestFileCollection(File("a/b/c")))
assertThat(initialClassLoader === secondClassLoader).isTrue()
}
assertThat(initialClassLoader === secondClassLoader).isTrue()
}
@Test
fun `different classloaders are returned for different files`() {
val cache = DefaultClassLoaderCache()
val firstClassLoader = cache.getOrCreate(TestFileCollection(File("a/b/c")))
val secondClassLoader = cache.getOrCreate(TestFileCollection(File("c/b/a")))
@Test
fun `different classloaders are returned for different files`() {
val cache = DefaultClassLoaderCache()
val firstClassLoader = cache.getOrCreate(TestFileCollection(File("a/b/c")))
val secondClassLoader = cache.getOrCreate(TestFileCollection(File("c/b/a")))
assertThat(firstClassLoader === secondClassLoader).isFalse()
}
assertThat(firstClassLoader === secondClassLoader).isFalse()
}
@Test
fun `same classloader for the same files in different order`() {
val cache = DefaultClassLoaderCache()
val firstClassLoader = cache.getOrCreate(TestFileCollection(File("a/b/c"), File("d/e/f")))
val secondClassLoader = cache.getOrCreate(TestFileCollection(File("d/e/f"), File("a/b/c")))
@Test
fun `same classloader for the same files in different order`() {
val cache = DefaultClassLoaderCache()
val firstClassLoader = cache.getOrCreate(TestFileCollection(File("a/b/c"), File("d/e/f")))
val secondClassLoader = cache.getOrCreate(TestFileCollection(File("d/e/f"), File("a/b/c")))
assertThat(firstClassLoader === secondClassLoader).isTrue()
}
assertThat(firstClassLoader === secondClassLoader).isTrue()
}
@Test
fun `resolves files without synchronization`() {
val file1 = File("/a/b/c")
val collection1 = CountdownFileCollection(file1)
@Test
fun `resolves files without synchronization`() {
val file1 = File("/a/b/c")
val collection1 = CountdownFileCollection(file1)
val file2 = File("/c/b/a")
val collection2 = TestFileCollection(file2)
val file2 = File("/c/b/a")
val collection2 = TestFileCollection(file2)
val cache = DefaultClassLoaderCache()
val executor = Executors.newSingleThreadExecutor()
val latch = CountDownLatch(1)
try {
val supplier = Supplier {
latch.countDown()
cache.getOrCreate(collection1)
}
val task = CompletableFuture.supplyAsync(supplier, executor)
@Suppress("UsePropertyAccessSyntax")
assertThat(latch.await(10L, TimeUnit.SECONDS)).isTrue()
// Will call `getOrCreate` next - wait a moment to be sure
Thread.sleep(2000L)
val classpath2 = cache.getOrCreate(collection2)
collection1.latch.countDown()
val classpath1 = task.join()
assertThat(classpath1.urLs).isEqualTo(arrayOf(file1.toURI().toURL()))
assertThat(classpath2.urLs).isEqualTo(arrayOf(file2.toURI().toURL()))
} finally {
val remaining = executor.shutdownNow()
assertThat(remaining).isEmpty()
val cache = DefaultClassLoaderCache()
val executor = Executors.newSingleThreadExecutor()
val latch = CountDownLatch(1)
try {
val supplier = Supplier {
latch.countDown()
cache.getOrCreate(collection1)
}
val task = CompletableFuture.supplyAsync(supplier, executor)
@Suppress("UsePropertyAccessSyntax")
assertThat(latch.await(10L, TimeUnit.SECONDS)).isTrue()
// Will call `getOrCreate` next - wait a moment to be sure
Thread.sleep(2000L)
val classpath2 = cache.getOrCreate(collection2)
collection1.latch.countDown()
val classpath1 = task.join()
assertThat(classpath1.urLs).isEqualTo(arrayOf(file1.toURI().toURL()))
assertThat(classpath2.urLs).isEqualTo(arrayOf(file2.toURI().toURL()))
} finally {
val remaining = executor.shutdownNow()
assertThat(remaining).isEmpty()
}
}
}

View File

@@ -1,7 +1,6 @@
package io.gitlab.arturbosch.detekt.report
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.io.File
@@ -9,49 +8,45 @@ private const val TAB = "\t"
class XmlReportMergerSpec {
@Nested
inner class `classpath changes` {
@Test
fun `passes for same files`() {
val file1 = File.createTempFile("detekt1", "xml").apply {
writeText(
"""
<?xml version="1.0" encoding="utf-8"?>
<checkstyle version="4.3">
<file name="Sample1.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_a" />
</file>
</checkstyle>
""".trimIndent()
)
}
val file2 = File.createTempFile("detekt2", "xml").apply {
writeText(
"""
<?xml version="1.0" encoding="utf-8"?>
<checkstyle version="4.3">
<file name="Sample2.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_b" />
</file>
</checkstyle>
""".trimIndent()
)
}
val output = File.createTempFile("output", "xml")
XmlReportMerger.merge(setOf(file1, file2), output)
val expectedText = """
<?xml version="1.0" encoding="UTF-8"?><checkstyle version="4.3">
<file name="Sample1.kt">
<error column="1" line="1" message="TestMessage" severity="warning" source="detekt.id_a"/>
</file>
<file name="Sample2.kt">
<error column="1" line="1" message="TestMessage" severity="warning" source="detekt.id_b"/>
</file>
@Test
fun `passes for same files`() {
val file1 = File.createTempFile("detekt1", "xml").apply {
writeText(
"""
<?xml version="1.0" encoding="utf-8"?>
<checkstyle version="4.3">
<file name="Sample1.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_a" />
</file>
</checkstyle>
""".trimIndent()
assertThat(output.readText()).isEqualToIgnoringNewLines(expectedText)
""".trimIndent()
)
}
val file2 = File.createTempFile("detekt2", "xml").apply {
writeText(
"""
<?xml version="1.0" encoding="utf-8"?>
<checkstyle version="4.3">
<file name="Sample2.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_b" />
</file>
</checkstyle>
""".trimIndent()
)
}
val output = File.createTempFile("output", "xml")
XmlReportMerger.merge(setOf(file1, file2), output)
val expectedText = """
<?xml version="1.0" encoding="UTF-8"?><checkstyle version="4.3">
<file name="Sample1.kt">
<error column="1" line="1" message="TestMessage" severity="warning" source="detekt.id_a"/>
</file>
<file name="Sample2.kt">
<error column="1" line="1" message="TestMessage" severity="warning" source="detekt.id_b"/>
</file>
</checkstyle>
""".trimIndent()
assertThat(output.readText()).isEqualToIgnoringNewLines(expectedText)
}
}

View File

@@ -7,282 +7,278 @@ import org.junit.jupiter.api.Test
class CognitiveComplexitySpec {
@Test
fun `sums seven for sumOfPrimes example`() {
val code = compileContentForTest(
"""
fun sumOfPrimes(max: Int): Int {
var total = 0
next@ for (i in 1..max) {
for (j in 2 until i) {
if (i % j == 0) {
continue@next
}
}
println(i)
total++
}
return total
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(7)
}
@Test
fun `sums one for getWords example for a single when expression`() {
val code = compileContentForTest(
"""
fun getWords(number: Int): String = when (number) {
1 -> "one"
2 -> "a couple"
3 -> "a few"
else -> "lots"
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
@Nested
inner class `cognitive complexity` {
inner class `recursion` {
@Test
fun `sums seven for sumOfPrimes example`() {
fun `adds one for recursion inside class`() {
val code = compileContentForTest(
"""
fun sumOfPrimes(max: Int): Int {
var total = 0
next@ for (i in 1..max) {
for (j in 2 until i) {
if (i % j == 0) {
continue@next
}
}
println(i)
total++
}
return total
class A {
fun factorial(n: Int): Int =
if (n >= 1) n * this.factorial(n - 1) else 1
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(7)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
@Test
fun `sums one for getWords example for a single when expression`() {
fun `adds one for top level recursion`() {
val code = compileContentForTest(
"""
fun getWords(number: Int): String = when (number) {
1 -> "one"
2 -> "a couple"
3 -> "a few"
else -> "lots"
}
fun factorial(n: Int): Int =
if (n >= 1) n * factorial(n - 1) else 1
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
@Test
fun `does not add as it is only the same name`() {
val code = compileContentForTest(
"""
object O { fun factorial(i: Int): Int = i - 1 }
fun factorial(n: Int): Int =
if (n >= 1) n * O.factorial(n - 1) else 1
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
}
@Nested
inner class `recursion` {
@Test
fun `ignores shorthand operators`() {
val code = compileContentForTest(
"""
fun parse(args: Array<String>): Nothing = TODO()
fun main(args: Array<String>) {
args.takeIf { it.size > 3 }?.let(::parse) ?: error("not enough arguments")
}
"""
)
@Test
fun `adds one for recursion inside class`() {
val code = compileContentForTest(
"""
class A {
fun factorial(n: Int): Int =
if (n >= 1) n * this.factorial(n - 1) else 1
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(0)
}
@Test
fun `adds one per catch clause`() {
val code = compileContentForTest(
"""
fun main() {
try {
} catch (e: IllegalArgumentException) {
} catch (e: IllegalStateException) {
} catch (e: Throwable) {}
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(3)
}
@Test
fun `adds extra complexity for nesting`() {
val code = compileContentForTest(
"""
fun main() {
try {
if (true) { // +1
for (i in 0..10) { // +2
while(true) { // +3
// more code
}
}
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
} catch (e: Exception) { // +1
do {} while(true) // +2
}
}
"""
)
@Test
fun `adds one for top level recursion`() {
val code = compileContentForTest(
"""
fun factorial(n: Int): Int =
if (n >= 1) n * factorial(n - 1) else 1
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(9)
}
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
@Test
fun `adds nesting for lambdas but not complexity`() {
val code = compileContentForTest(
"""
fun main() { run { if (true) {} } }
"""
)
@Test
fun `does not add as it is only the same name`() {
val code = compileContentForTest(
"""
object O { fun factorial(i: Int): Int = i - 1 }
fun factorial(n: Int): Int =
if (n >= 1) n * O.factorial(n - 1) else 1
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
}
@Test
fun `adds nesting for nested functions but not complexity`() {
val code = compileContentForTest(
"""
fun main() { fun run() { if (true) {} } }
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
@Nested
inner class `binary expressions` {
@Test
fun `ignores shorthand operators`() {
fun `does not increment on just a condition`() {
val code = compileContentForTest(
"""
fun parse(args: Array<String>): Nothing = TODO()
fun main(args: Array<String>) {
args.takeIf { it.size > 3 }?.let(::parse) ?: error("not enough arguments")
}
fun test(cond_ Boolean) = !cond
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(0)
}
@Test
fun `adds one per catch clause`() {
val code = compileContentForTest(
"""
fun main() {
try {
} catch (e: IllegalArgumentException) {
} catch (e: IllegalStateException) {
} catch (e: Throwable) {}
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(3)
}
@Test
fun `adds extra complexity for nesting`() {
val code = compileContentForTest(
"""
fun main() {
try {
if (true) { // +1
for (i in 0..10) { // +2
while(true) { // +3
// more code
}
}
}
} catch (e: Exception) { // +1
do {} while(true) // +2
}
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(9)
}
@Test
fun `adds nesting for lambdas but not complexity`() {
val code = compileContentForTest(
"""
fun main() { run { if (true) {} } }
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
@Test
fun `adds nesting for nested functions but not complexity`() {
val code = compileContentForTest(
"""
fun main() { fun run() { if (true) {} } }
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
@Nested
inner class `binary expressions` {
inner class `increments for every non-like operator` {
@Test
fun `does not increment on just a condition`() {
fun `adds one for just a &&`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) = !cond
fun test(cond_ Boolean) = !cond && !cond
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(0)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
@Nested
inner class `increments for every non-like operator` {
@Test
fun `adds only one for repeated &&`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) = !cond && !cond && !cond
"""
)
@Test
fun `adds one for just a &&`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) = !cond && !cond
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
@Test
fun `adds one per logical alternate operator`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) = !cond && !cond || cond
"""
)
@Test
fun `adds only one for repeated &&`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) = !cond && !cond && !cond
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(1)
}
@Test
fun `adds one per logical alternate operator with like operators in between`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) {
if ( // +1
!cond
&& !cond && !cond // +1
|| cond || cond // +1
&& cond // +1
) {}
}
"""
)
@Test
fun `adds one per logical alternate operator`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) = !cond && !cond || cond
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(4)
}
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(2)
}
@Test
fun `adds one for negated but similar operators`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) {
if ( // +1
!cond
&& !(cond && cond) // +2
) {}
}
"""
)
@Test
fun `adds one per logical alternate operator with like operators in between`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) {
if ( // +1
!cond
&& !cond && !cond // +1
|| cond || cond // +1
&& cond // +1
) {}
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(3)
}
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(4)
}
@Test
fun `adds only one for a negated chain of similar operators`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) {
if ( // +1
!cond
&& !(cond && cond && cond) // +2
) {}
}
"""
)
@Test
fun `adds one for negated but similar operators`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) {
if ( // +1
!cond
&& !(cond && cond) // +2
) {}
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(3)
}
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(3)
}
@Test
fun `adds one for every negated similar operator chain`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) {
if ( // +1
!cond
&& !(cond && cond && cond) // +2
|| !(cond || cond) // +2
) {}
}
"""
)
@Test
fun `adds only one for a negated chain of similar operators`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) {
if ( // +1
!cond
&& !(cond && cond && cond) // +2
) {}
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(3)
}
@Test
fun `adds one for every negated similar operator chain`() {
val code = compileContentForTest(
"""
fun test(cond_ Boolean) {
if ( // +1
!cond
&& !(cond && cond && cond) // +2
|| !(cond || cond) // +2
) {}
}
"""
)
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(5)
}
assertThat(CognitiveComplexity.calculate(code)).isEqualTo(5)
}
}
}

View File

@@ -17,72 +17,68 @@ import org.junit.jupiter.api.Test
internal class ComplexityReportGeneratorSpec {
private lateinit var detektion: TestDetektion
@BeforeEach
fun setupMocks() {
val finding = mockk<Finding>()
every { finding.id }.returns("test")
detektion = TestDetektion(finding).withTestData()
}
@Nested
inner class `complexity report generator` {
inner class `several complexity metrics` {
private lateinit var detektion: TestDetektion
@Test
fun `successfully generates a complexity report`() {
val expectedContent = listOf(
"1,000 lines of code (loc)",
"6 source lines of code (sloc)",
"5 logical lines of code (lloc)",
"4 comment lines of code (cloc)",
"2 cyclomatic complexity (mcc)",
"2 cognitive complexity",
"1 number of total code smells",
"66% comment source ratio",
"400 mcc per 1,000 lloc",
"200 code smells per 1,000 lloc"
)
@BeforeEach
fun setupMocks() {
val finding = mockk<Finding>()
every { finding.id }.returns("test")
detektion = TestDetektion(finding).withTestData()
assertThat(generateComplexityReport(detektion)).isEqualTo(expectedContent)
}
}
@Nested
inner class `several invalid complexity metrics` {
@Test
fun `returns null for missing mcc`() {
detektion.removeData(complexityKey)
assertThat(generateComplexityReport(detektion)).isNull()
}
@Nested
inner class `several complexity metrics` {
@Test
fun `returns null for missing lloc`() {
detektion.removeData(logicalLinesKey)
assertThat(generateComplexityReport(detektion)).isNull()
@Test
fun `successfully generates a complexity report`() {
val expectedContent = listOf(
"1,000 lines of code (loc)",
"6 source lines of code (sloc)",
"5 logical lines of code (lloc)",
"4 comment lines of code (cloc)",
"2 cyclomatic complexity (mcc)",
"2 cognitive complexity",
"1 number of total code smells",
"66% comment source ratio",
"400 mcc per 1,000 lloc",
"200 code smells per 1,000 lloc"
)
assertThat(generateComplexityReport(detektion)).isEqualTo(expectedContent)
}
detektion.addData(logicalLinesKey, 0)
assertThat(generateComplexityReport(detektion)).isNull()
}
@Nested
inner class `several invalid complexity metrics` {
@Test
fun `returns null for missing sloc`() {
detektion.removeData(sourceLinesKey)
assertThat(generateComplexityReport(detektion)).isNull()
@Test
fun `returns null for missing mcc`() {
detektion.removeData(complexityKey)
assertThat(generateComplexityReport(detektion)).isNull()
}
detektion.addData(sourceLinesKey, 0)
assertThat(generateComplexityReport(detektion)).isNull()
}
@Test
fun `returns null for missing lloc`() {
detektion.removeData(logicalLinesKey)
assertThat(generateComplexityReport(detektion)).isNull()
detektion.addData(logicalLinesKey, 0)
assertThat(generateComplexityReport(detektion)).isNull()
}
@Test
fun `returns null for missing sloc`() {
detektion.removeData(sourceLinesKey)
assertThat(generateComplexityReport(detektion)).isNull()
detektion.addData(sourceLinesKey, 0)
assertThat(generateComplexityReport(detektion)).isNull()
}
@Test
fun `returns null for missing cloc`() {
detektion.removeData(complexityKey)
assertThat(generateComplexityReport(detektion)).isNull()
}
@Test
fun `returns null for missing cloc`() {
detektion.removeData(complexityKey)
assertThat(generateComplexityReport(detektion)).isNull()
}
}
}

View File

@@ -2,21 +2,17 @@ package io.github.detekt.metrics.processors
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class CLOCVisitorSpec {
@Nested
inner class `CLOC` {
@Test
fun `commentCases`() {
val file = compileContentForTest(commentsClass)
val commentLines = with(file) {
accept(CLOCVisitor())
getUserData(commentLinesKey)
}
assertThat(commentLines).isEqualTo(10)
@Test
fun `commentCases`() {
val file = compileContentForTest(commentsClass)
val commentLines = with(file) {
accept(CLOCVisitor())
getUserData(commentLinesKey)
}
assertThat(commentLines).isEqualTo(10)
}
}

View File

@@ -3,39 +3,34 @@ package io.github.detekt.metrics.processors
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.psi.KtFile
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class ClassCountVisitorSpec {
@Nested
inner class `something` {
@Test
fun `twoClassesInSeparateFile`() {
val files = arrayOf(
compileContentForTest(default),
compileContentForTest(classWithFields)
)
val count = getClassCount(files)
assertThat(count).isEqualTo(2)
}
@Test
fun `twoClassesInSeparateFile`() {
val files = arrayOf(
compileContentForTest(default),
compileContentForTest(classWithFields)
)
val count = getClassCount(files)
assertThat(count).isEqualTo(2)
}
@Test
fun `oneClassWithOneNestedClass`() {
val file = compileContentForTest(complexClass)
val count = getClassCount(arrayOf(file))
assertThat(count).isEqualTo(2)
}
@Test
fun `oneClassWithOneNestedClass`() {
val file = compileContentForTest(complexClass)
val count = getClassCount(arrayOf(file))
assertThat(count).isEqualTo(2)
}
@Test
fun `testEnumAndInterface`() {
val files = arrayOf(
compileContentForTest(emptyEnum),
compileContentForTest(emptyInterface)
)
val count = getClassCount(files)
assertThat(count).isEqualTo(2)
}
@Test
fun `testEnumAndInterface`() {
val files = arrayOf(
compileContentForTest(emptyEnum),
compileContentForTest(emptyInterface)
)
val count = getClassCount(files)
assertThat(count).isEqualTo(2)
}
}

View File

@@ -3,22 +3,17 @@ package io.github.detekt.metrics.processors
import io.github.detekt.metrics.CognitiveComplexity
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class CognitiveComplexityProcessorSpec {
@Nested
inner class `CognitiveComplexityProcessor` {
@Test
fun `counts the complexity for the whole file`() {
val file = compileContentForTest(complexClass)
@Test
fun `counts the complexity for the whole file`() {
val file = compileContentForTest(complexClass)
val value = MetricProcessorTester(file)
.test(ProjectCognitiveComplexityProcessor(), CognitiveComplexity.KEY)
val value = MetricProcessorTester(file)
.test(ProjectCognitiveComplexityProcessor(), CognitiveComplexity.KEY)
assertThat(value).isEqualTo(46)
}
assertThat(value).isEqualTo(46)
}
}

View File

@@ -2,26 +2,21 @@ package io.github.detekt.metrics.processors
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class ComplexityVisitorSpec {
@Nested
inner class `something` {
@Test
fun `complexityOfDefaultCaseIsOne`() {
val mcc = calcComplexity(default)
@Test
fun `complexityOfDefaultCaseIsOne`() {
val mcc = calcComplexity(default)
assertThat(mcc).isEqualTo(0)
}
assertThat(mcc).isEqualTo(0)
}
@Test
fun `complexityOfComplexAndNestedClass`() {
val mcc = calcComplexity(complexClass)
@Test
fun `complexityOfComplexAndNestedClass`() {
val mcc = calcComplexity(complexClass)
assertThat(mcc).isEqualTo(44)
}
assertThat(mcc).isEqualTo(44)
}
}

View File

@@ -2,21 +2,17 @@ package io.github.detekt.metrics.processors
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class FieldCountVisitorSpec {
@Nested
inner class `something` {
@Test
fun `defaultFieldCount`() {
val file = compileContentForTest(classWithFields)
val count = with(file) {
accept(PropertyCountVisitor())
getUserData(numberOfFieldsKey)
}
assertThat(count).isEqualTo(2)
@Test
fun `defaultFieldCount`() {
val file = compileContentForTest(classWithFields)
val count = with(file) {
accept(PropertyCountVisitor())
getUserData(numberOfFieldsKey)
}
assertThat(count).isEqualTo(2)
}
}

View File

@@ -3,22 +3,18 @@ package io.github.detekt.metrics.processors
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.psi.KtFile
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class KtFileCountVisitorSpec {
@Nested
inner class `files` {
@Test
fun `twoFiles`() {
val files = arrayOf(
compileContentForTest(default),
compileContentForTest(complexClass)
)
val count = files.sumOf { getData(it) }
assertThat(count).isEqualTo(2)
}
@Test
fun `twoFiles`() {
val files = arrayOf(
compileContentForTest(default),
compileContentForTest(complexClass)
)
val count = files.sumOf { getData(it) }
assertThat(count).isEqualTo(2)
}
}

View File

@@ -2,35 +2,31 @@ package io.github.detekt.metrics.processors
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class LLOCVisitorSpec {
@Nested
inner class `LLOC Visitor` {
@Test
fun `defaultCaseHasOneClassAndAnnotationLine`() {
val file = compileContentForTest(default)
@Test
fun `defaultCaseHasOneClassAndAnnotationLine`() {
val file = compileContentForTest(default)
val lloc = with(file) {
accept(LLOCVisitor())
getUserData(logicalLinesKey)
}
assertThat(lloc).isEqualTo(2)
val lloc = with(file) {
accept(LLOCVisitor())
getUserData(logicalLinesKey)
}
@Test
fun `llocOfComplexClass`() {
val file = compileContentForTest(complexClass)
assertThat(lloc).isEqualTo(2)
}
val lloc = with(file) {
accept(LLOCVisitor())
getUserData(logicalLinesKey)
}
@Test
fun `llocOfComplexClass`() {
val file = compileContentForTest(complexClass)
assertThat(lloc).isEqualTo(85)
val lloc = with(file) {
accept(LLOCVisitor())
getUserData(logicalLinesKey)
}
assertThat(lloc).isEqualTo(85)
}
}

View File

@@ -2,21 +2,17 @@ package io.github.detekt.metrics.processors
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class LOCVisitorSpec {
@Nested
inner class `LOC Visitor` {
@Test
fun `defaultClass`() {
val file = compileContentForTest(default)
val loc = with(file) {
accept(LOCVisitor())
getUserData(linesKey)
}
assertThat(loc).isEqualTo(8)
@Test
fun `defaultClass`() {
val file = compileContentForTest(default)
val loc = with(file) {
accept(LOCVisitor())
getUserData(linesKey)
}
assertThat(loc).isEqualTo(8)
}
}

View File

@@ -3,19 +3,15 @@ package io.github.detekt.metrics.processors
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.psi.KtFile
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class MethodCountVisitorSpec {
@Nested
inner class `Method Count Visitor` {
@Test
fun `defaultMethodCount`() {
val file = compileContentForTest(complexClass)
val count = getMethodCount(file)
assertThat(count).isEqualTo(6)
}
@Test
fun `defaultMethodCount`() {
val file = compileContentForTest(complexClass)
val count = getMethodCount(file)
assertThat(count).isEqualTo(6)
}
}

View File

@@ -3,25 +3,21 @@ package io.github.detekt.metrics.processors
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.psi.KtFile
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class PackageCountVisitorSpec {
@Nested
inner class `Package Count Visitor` {
@Test
fun `twoClassesInSeparatePackage`() {
val files = arrayOf(
compileContentForTest(default),
compileContentForTest(emptyEnum)
)
val count = files
.map { getData(it) }
.distinct()
.count()
assertThat(count).isEqualTo(2)
}
@Test
fun `twoClassesInSeparatePackage`() {
val files = arrayOf(
compileContentForTest(default),
compileContentForTest(emptyEnum)
)
val count = files
.map { getData(it) }
.distinct()
.count()
assertThat(count).isEqualTo(2)
}
}

View File

@@ -2,21 +2,17 @@ package io.github.detekt.metrics.processors
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class SLOCVisitorSpec {
@Nested
inner class `SLOC Visitor` {
@Test
fun `defaultClass`() {
val file = compileContentForTest(default)
val loc = with(file) {
accept(SLOCVisitor())
getUserData(sourceLinesKey)
}
assertThat(loc).isEqualTo(3)
@Test
fun `defaultClass`() {
val file = compileContentForTest(default)
val loc = with(file) {
accept(SLOCVisitor())
getUserData(sourceLinesKey)
}
assertThat(loc).isEqualTo(3)
}
}

View File

@@ -1,30 +1,26 @@
package io.github.detekt.parser
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.io.File
class KotlinEnvironmentUtilsSpec {
@Nested
inner class `retrieved kotlin language version` {
@Test
fun `should match`() {
val expectedVersionString = System.getProperty("kotlinVersion", "")
.splitToSequence('.')
.take(2)
.joinToString(".")
@Test
fun `retrieved kotlin language version should match`() {
val expectedVersionString = System.getProperty("kotlinVersion", "")
.splitToSequence('.')
.take(2)
.joinToString(".")
val classpathFiles = System.getProperty("testClasspath", "")
.splitToSequence(';')
.map(::File)
.filter(File::exists)
.toList()
val classpathFiles = System.getProperty("testClasspath", "")
.splitToSequence(';')
.map(::File)
.filter(File::exists)
.toList()
val languageVersion = classpathFiles.getKotlinLanguageVersion()
assertThat(languageVersion).isNotNull
assertThat(languageVersion?.versionString).isEqualTo(expectedVersionString)
}
val languageVersion = classpathFiles.getKotlinLanguageVersion()
assertThat(languageVersion).isNotNull
assertThat(languageVersion?.versionString).isEqualTo(expectedVersionString)
}
}

View File

@@ -21,162 +21,157 @@ import io.mockk.mockk
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
import org.jetbrains.kotlin.psi.KtElement
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.nio.file.Files
import java.nio.file.Path
class HtmlOutputReportSpec {
@Nested
inner class `HTML output report` {
private val htmlReport = HtmlOutputReport()
private val htmlReport = HtmlOutputReport()
@Test
fun `renders the HTML headers correctly`() {
val result = htmlReport.render(TestDetektion())
@Test
fun `renders the HTML headers correctly`() {
val result = htmlReport.render(TestDetektion())
assertThat(result).startsWith("<!DOCTYPE html>\n<html lang=\"en\">")
assertThat(result).endsWith("</html>\n")
assertThat(result).startsWith("<!DOCTYPE html>\n<html lang=\"en\">")
assertThat(result).endsWith("</html>\n")
assertThat(result).contains("<h2>Metrics</h2>")
assertThat(result).contains("<h2>Complexity Report</h2>")
assertThat(result).contains("<h2>Findings</h2>")
}
assertThat(result).contains("<h2>Metrics</h2>")
assertThat(result).contains("<h2>Complexity Report</h2>")
assertThat(result).contains("<h2>Findings</h2>")
@Test
fun `renders the 'generated with' text correctly`() {
val version = whichDetekt()
val header =
"""generated with <a href="https://detekt.dev/">detekt version $version</a> on """
val result = htmlReport.render(TestDetektion())
assertThat(result).contains(header)
assertThat(result).doesNotContain("@@@date@@@")
}
@Test
fun `contains the total number of findings`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
assertThat(result).contains("Total: 3")
}
@Test
fun `contains no findings`() {
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"EmptyRuleset" to emptyList()
)
}
val result = htmlReport.render(detektion)
assertThat(result).contains("Total: 0")
}
@Test
fun `renders the 'generated with' text correctly`() {
val version = whichDetekt()
val header =
"""generated with <a href="https://detekt.dev/">detekt version $version</a> on """
@Test
fun `renders the right file locations`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
val result = htmlReport.render(TestDetektion())
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample1.kt:11:1</span>")
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample2.kt:22:2</span>")
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample3.kt:33:3</span>")
}
assertThat(result).contains(header)
assertThat(result).doesNotContain("@@@date@@@")
@Test
fun `renders the right file locations for relative paths`() {
val result = htmlReport.render(createTestDetektionFromRelativePath())
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample1.kt:11:1</span>")
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample2.kt:22:2</span>")
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample3.kt:33:3</span>")
}
@Test
fun `renders the right number of issues per rule`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
assertThat(result).contains("<span class=\"rule\">id_a: 2 </span>")
assertThat(result).contains("<span class=\"rule\">id_b: 1 </span>")
}
@Test
fun `renders the right violation messages for the rules`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
assertThat(result).contains("<span class=\"message\">Message finding 1</span>")
assertThat(result).contains("<span class=\"message\">Message finding 2</span>")
assertThat(result).doesNotContain("<span class=\"message\"></span>")
}
@Test
fun `renders the right violation description for the rules`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
assertThat(result).contains("<span class=\"description\">Description id_a</span>")
assertThat(result).contains("<span class=\"description\">Description id_b</span>")
}
@Test
fun `renders a metric report correctly`() {
val detektion = object : TestDetektion() {
override val metrics: Collection<ProjectMetric> = listOf(
ProjectMetric("M1", 10_000),
ProjectMetric("M2", 2)
)
}
val result = htmlReport.render(detektion)
assertThat(result).contains("<li>10,000 M1</li>")
assertThat(result).contains("<li>2 M2</li>")
}
@Test
fun `contains the total number of findings`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
@Test
fun `renders the complexity report correctly`() {
val detektion = TestDetektion()
detektion.addData(complexityKey, 10)
detektion.addData(CognitiveComplexity.KEY, 10)
detektion.addData(sourceLinesKey, 20)
detektion.addData(logicalLinesKey, 10)
detektion.addData(commentLinesKey, 2)
detektion.addData(linesKey, 2222)
val result = htmlReport.render(detektion)
assertThat(result).contains("<li>2,222 lines of code (loc)</li>")
assertThat(result).contains("<li>20 source lines of code (sloc)</li>")
assertThat(result).contains("<li>10 logical lines of code (lloc)</li>")
}
assertThat(result).contains("Total: 3")
}
@Test
fun `renders a blank complexity report correctly`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
assertThat(result).contains("<h2>Complexity Report</h2>\n\n<div>\n <ul></ul>\n</div>")
}
@Test
fun `contains no findings`() {
val detektion = object : TestDetektion() {
override val findings: Map<String, List<Finding>> = mapOf(
"EmptyRuleset" to emptyList()
)
}
val result = htmlReport.render(detektion)
assertThat(result).contains("Total: 0")
}
@Test
fun `asserts that the generated HTML is the same as expected`() {
val expected = resourceAsPath("HtmlOutputFormatTest.html")
var result = htmlReport.render(createTestDetektionWithMultipleSmells())
result = generatedRegex.replace(result, replacement)
@Test
fun `renders the right file locations`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
val actual = createTempFileForTest("actual-report", ".html")
Files.write(actual, result.toByteArray())
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample1.kt:11:1</span>")
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample2.kt:22:2</span>")
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample3.kt:33:3</span>")
}
assertThat(actual).hasSameTextualContentAs(expected)
}
@Test
fun `renders the right file locations for relative paths`() {
val result = htmlReport.render(createTestDetektionFromRelativePath())
@Test
fun `asserts that the generated HTML is the same even if we change the order of the findings`() {
val findings = findings()
val reversedFindings = findings
.reversedArray()
.map { (section, findings) -> section to findings.asReversed() }
.toTypedArray()
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample1.kt:11:1</span>")
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample2.kt:22:2</span>")
assertThat(result).contains("<span class=\"location\">src/main/com/sample/Sample3.kt:33:3</span>")
}
val firstReport = createReportWithFindings(findings)
val secondReport = createReportWithFindings(reversedFindings)
@Test
fun `renders the right number of issues per rule`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
assertThat(result).contains("<span class=\"rule\">id_a: 2 </span>")
assertThat(result).contains("<span class=\"rule\">id_b: 1 </span>")
}
@Test
fun `renders the right violation messages for the rules`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
assertThat(result).contains("<span class=\"message\">Message finding 1</span>")
assertThat(result).contains("<span class=\"message\">Message finding 2</span>")
assertThat(result).doesNotContain("<span class=\"message\"></span>")
}
@Test
fun `renders the right violation description for the rules`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
assertThat(result).contains("<span class=\"description\">Description id_a</span>")
assertThat(result).contains("<span class=\"description\">Description id_b</span>")
}
@Test
fun `renders a metric report correctly`() {
val detektion = object : TestDetektion() {
override val metrics: Collection<ProjectMetric> = listOf(
ProjectMetric("M1", 10_000),
ProjectMetric("M2", 2)
)
}
val result = htmlReport.render(detektion)
assertThat(result).contains("<li>10,000 M1</li>")
assertThat(result).contains("<li>2 M2</li>")
}
@Test
fun `renders the complexity report correctly`() {
val detektion = TestDetektion()
detektion.addData(complexityKey, 10)
detektion.addData(CognitiveComplexity.KEY, 10)
detektion.addData(sourceLinesKey, 20)
detektion.addData(logicalLinesKey, 10)
detektion.addData(commentLinesKey, 2)
detektion.addData(linesKey, 2222)
val result = htmlReport.render(detektion)
assertThat(result).contains("<li>2,222 lines of code (loc)</li>")
assertThat(result).contains("<li>20 source lines of code (sloc)</li>")
assertThat(result).contains("<li>10 logical lines of code (lloc)</li>")
}
@Test
fun `renders a blank complexity report correctly`() {
val result = htmlReport.render(createTestDetektionWithMultipleSmells())
assertThat(result).contains("<h2>Complexity Report</h2>\n\n<div>\n <ul></ul>\n</div>")
}
@Test
fun `asserts that the generated HTML is the same as expected`() {
val expected = resourceAsPath("HtmlOutputFormatTest.html")
var result = htmlReport.render(createTestDetektionWithMultipleSmells())
result = generatedRegex.replace(result, replacement)
val actual = createTempFileForTest("actual-report", ".html")
Files.write(actual, result.toByteArray())
assertThat(actual).hasSameTextualContentAs(expected)
}
@Test
fun `asserts that the generated HTML is the same even if we change the order of the findings`() {
val findings = findings()
val reversedFindings = findings
.reversedArray()
.map { (section, findings) -> section to findings.asReversed() }
.toTypedArray()
val firstReport = createReportWithFindings(findings)
val secondReport = createReportWithFindings(reversedFindings)
assertThat(firstReport).hasSameTextualContentAs(secondReport)
}
assertThat(firstReport).hasSameTextualContentAs(secondReport)
}
}

View File

@@ -4,144 +4,140 @@ import io.gitlab.arturbosch.detekt.api.SourceLocation
import kotlinx.html.div
import kotlinx.html.stream.createHTML
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class HtmlUtilsSpec {
@Nested
inner class `HTML snippet code` {
private val code = """
package cases
// reports 1 - line with just one space
private val code = """
package cases
// reports 1 - line with just one space
// reports 1 - a comment with trailing space
// A comment
// reports 1
class TrailingWhitespacePositive {
// reports 1 - line with just one tab
// reports 1 - a comment with trailing space
// A comment
// reports 1
class TrailingWhitespacePositive {
// reports 1 - line with just one tab
// reports 1
fun myFunction() {
// reports 1 - line with 1 trailing tab
println("A message")
// reports 1
}
fun myFunction() {
// reports 1 - line with 1 trailing tab
println("A message")
// reports 1
}
""".trimIndent().splitToSequence('\n')
}
""".trimIndent().splitToSequence('\n')
@Test
fun `all line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 1), 34)
}
assertThat(snippet).isEqualTo(
"""
<div>
<pre><code><span class="lineno"> 4 </span>// reports 1 - a comment with trailing space
<span class="lineno"> 5 </span>// A comment
<span class="lineno"> 6 </span>// reports 1
<span class="lineno"> 7 </span><span class="error">class TrailingWhitespacePositive {</span>
<span class="lineno"> 8 </span> // reports 1 - line with just one tab
<span class="lineno"> 9 </span>
<span class="lineno"> 10 </span> // reports 1
</code></pre>
</div>
""".trimIndent()
)
@Test
fun `all line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 1), 34)
}
@Test
fun `part of line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 7), 26)
}
assertThat(snippet).isEqualTo(
"""
<div>
<pre><code><span class="lineno"> 4 </span>// reports 1 - a comment with trailing space
<span class="lineno"> 5 </span>// A comment
<span class="lineno"> 6 </span>// reports 1
<span class="lineno"> 7 </span><span class="error">class TrailingWhitespacePositive {</span>
<span class="lineno"> 8 </span> // reports 1 - line with just one tab
<span class="lineno"> 9 </span>
<span class="lineno"> 10 </span> // reports 1
</code></pre>
</div>
assertThat(snippet).isEqualTo(
"""
<div>
<pre><code><span class="lineno"> 4 </span>// reports 1 - a comment with trailing space
<span class="lineno"> 5 </span>// A comment
<span class="lineno"> 6 </span>// reports 1
<span class="lineno"> 7 </span>class <span class="error">TrailingWhitespacePositive</span> {
<span class="lineno"> 8 </span> // reports 1 - line with just one tab
<span class="lineno"> 9 </span>
<span class="lineno"> 10 </span> // reports 1
</code></pre>
</div>
""".trimIndent()
)
}
""".trimIndent()
)
@Test
fun `part of line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 7), 26)
}
@Test
fun `more than one line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 7), 66)
}
assertThat(snippet).isEqualTo(
"""
<div>
<pre><code><span class="lineno"> 4 </span>// reports 1 - a comment with trailing space
<span class="lineno"> 5 </span>// A comment
<span class="lineno"> 6 </span>// reports 1
<span class="lineno"> 7 </span>class <span class="error">TrailingWhitespacePositive</span> {
<span class="lineno"> 8 </span> // reports 1 - line with just one tab
<span class="lineno"> 9 </span>
<span class="lineno"> 10 </span> // reports 1
</code></pre>
</div>
assertThat(snippet).isEqualTo(
"""
<div>
<pre><code><span class="lineno"> 4 </span>// reports 1 - a comment with trailing space
<span class="lineno"> 5 </span>// A comment
<span class="lineno"> 6 </span>// reports 1
<span class="lineno"> 7 </span>class <span class="error">TrailingWhitespacePositive {</span>
<span class="lineno"> 8 </span><span class="error"> // reports 1 - line with just one</span> tab
<span class="lineno"> 9 </span>
<span class="lineno"> 10 </span> // reports 1
</code></pre>
</div>
""".trimIndent()
)
}
""".trimIndent()
)
@Test
fun `more than one line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 7), 66)
}
@Test
fun `first line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(1, 1), 1)
}
assertThat(snippet).isEqualTo(
"""
<div>
<pre><code><span class="lineno"> 4 </span>// reports 1 - a comment with trailing space
<span class="lineno"> 5 </span>// A comment
<span class="lineno"> 6 </span>// reports 1
<span class="lineno"> 7 </span>class <span class="error">TrailingWhitespacePositive {</span>
<span class="lineno"> 8 </span><span class="error"> // reports 1 - line with just one</span> tab
<span class="lineno"> 9 </span>
<span class="lineno"> 10 </span> // reports 1
</code></pre>
</div>
assertThat(snippet).contains((1..4).map { " $it " })
""".trimIndent()
)
}
@Test
fun `first line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(1, 1), 1)
}
@Test
fun `second line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(2, 1), 1)
}
assertThat(snippet).contains((1..4).map { " $it " })
}
assertThat(snippet).contains((1..5).map { " $it " })
@Test
fun `second line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(2, 1), 1)
}
@Test
fun `penultimate line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(15, 1), 1)
}
assertThat(snippet).contains((1..5).map { " $it " })
}
assertThat(snippet).contains((12..16).map { " $it " })
@Test
fun `penultimate line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(15, 1), 1)
}
@Test
fun `last line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(16, 1), 1)
}
assertThat(snippet).contains((12..16).map { " $it " })
}
assertThat(snippet).contains((13..16).map { " $it " })
@Test
fun `last line`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(16, 1), 1)
}
@Test
fun `when we provide an invalid source location the exception div is shown`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 100), 1)
}
assertThat(snippet).contains((13..16).map { " $it " })
}
assertThat(snippet).contains("""<div class="exception">""")
@Test
fun `when we provide an invalid source location the exception div is shown`() {
val snippet = createHTML().div() {
snippetCode("ruleName", code.asSequence(), SourceLocation(7, 100), 1)
}
assertThat(snippet).contains("""<div class="exception">""")
}
}

View File

@@ -13,63 +13,58 @@ import io.gitlab.arturbosch.detekt.test.createEntity
import io.gitlab.arturbosch.detekt.test.createFindingForRelativePath
import io.gitlab.arturbosch.detekt.test.createIssue
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.nio.file.Paths
@OptIn(UnstableApi::class)
class SarifOutputReportSpec {
@Nested
inner class `sarif output report` {
@Test
fun `renders multiple issues`() {
val result = TestDetektion(
createFinding(ruleName = "TestSmellA", severity = SeverityLevel.ERROR),
createFinding(ruleName = "TestSmellB", severity = SeverityLevel.WARNING),
createFinding(ruleName = "TestSmellC", severity = SeverityLevel.INFO)
)
@Test
fun `renders multiple issues`() {
val result = TestDetektion(
createFinding(ruleName = "TestSmellA", severity = SeverityLevel.ERROR),
createFinding(ruleName = "TestSmellB", severity = SeverityLevel.WARNING),
createFinding(ruleName = "TestSmellC", severity = SeverityLevel.INFO)
)
val report = SarifOutputReport()
.apply { init(EmptySetupContext()) }
.render(result)
val report = SarifOutputReport()
.apply { init(EmptySetupContext()) }
.render(result)
assertThat(report).isEqualToIgnoringWhitespace(readResourceContent("vanilla.sarif.json"))
}
assertThat(report).isEqualToIgnoringWhitespace(readResourceContent("vanilla.sarif.json"))
}
@Test
fun `renders multiple issues with relative path`() {
val basePath = "/Users/tester/detekt/"
val result = TestDetektion(
createFindingForRelativePath(ruleName = "TestSmellA", basePath = basePath),
createFindingForRelativePath(ruleName = "TestSmellB", basePath = basePath),
createFindingForRelativePath(ruleName = "TestSmellC", basePath = basePath)
)
@Test
fun `renders multiple issues with relative path`() {
val basePath = "/Users/tester/detekt/"
val result = TestDetektion(
createFindingForRelativePath(ruleName = "TestSmellA", basePath = basePath),
createFindingForRelativePath(ruleName = "TestSmellB", basePath = basePath),
createFindingForRelativePath(ruleName = "TestSmellC", basePath = basePath)
)
val report = SarifOutputReport()
.apply {
init(
EmptySetupContext().apply {
register(DETEKT_OUTPUT_REPORT_BASE_PATH_KEY, Paths.get(basePath))
}
)
}
.render(result)
.stripWhitespace()
val expectedReport = readResourceContent("relative_path.sarif.json")
// Note: Github CI uses D: drive, but it could be any drive for local development
val systemAwareExpectedReport = if (whichOS().startsWith("windows", ignoreCase = true)) {
val winRoot = Paths.get("/").toAbsolutePath().toString().replace("\\", "/")
expectedReport.replace("file:///", "file://$winRoot")
} else {
expectedReport
val report = SarifOutputReport()
.apply {
init(
EmptySetupContext().apply {
register(DETEKT_OUTPUT_REPORT_BASE_PATH_KEY, Paths.get(basePath))
}
)
}
.render(result)
.stripWhitespace()
assertThat(report).isEqualToIgnoringWhitespace(systemAwareExpectedReport)
val expectedReport = readResourceContent("relative_path.sarif.json")
// Note: Github CI uses D: drive, but it could be any drive for local development
val systemAwareExpectedReport = if (whichOS().startsWith("windows", ignoreCase = true)) {
val winRoot = Paths.get("/").toAbsolutePath().toString().replace("\\", "/")
expectedReport.replace("file:///", "file://$winRoot")
} else {
expectedReport
}
assertThat(report).isEqualToIgnoringWhitespace(systemAwareExpectedReport)
}
}

View File

@@ -3,45 +3,40 @@ package io.github.detekt.report.txt
import io.gitlab.arturbosch.detekt.test.TestDetektion
import io.gitlab.arturbosch.detekt.test.createFinding
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class TxtOutputReportSpec {
@Nested
inner class `TXT output report` {
@Test
fun `renders none`() {
val report = TxtOutputReport()
val detektion = TestDetektion()
val renderedText = ""
assertThat(report.render(detektion)).isEqualTo(renderedText)
}
@Test
fun `renders none`() {
val report = TxtOutputReport()
val detektion = TestDetektion()
val renderedText = ""
assertThat(report.render(detektion)).isEqualTo(renderedText)
}
@Test
fun `renders one`() {
val report = TxtOutputReport()
val detektion = TestDetektion(createFinding())
val renderedText = "TestSmell - [TestEntity] at TestFile.kt:1:1 - Signature=TestEntitySignature\n"
assertThat(report.render(detektion)).isEqualTo(renderedText)
}
@Test
fun `renders one`() {
val report = TxtOutputReport()
val detektion = TestDetektion(createFinding())
val renderedText = "TestSmell - [TestEntity] at TestFile.kt:1:1 - Signature=TestEntitySignature\n"
assertThat(report.render(detektion)).isEqualTo(renderedText)
}
@Test
fun `renders multiple`() {
val report = TxtOutputReport()
val detektion = TestDetektion(
createFinding(ruleName = "TestSmellA"),
createFinding(ruleName = "TestSmellB"),
createFinding(ruleName = "TestSmellC")
)
val renderedText = """
TestSmellA - [TestEntity] at TestFile.kt:1:1 - Signature=TestEntitySignature
TestSmellB - [TestEntity] at TestFile.kt:1:1 - Signature=TestEntitySignature
TestSmellC - [TestEntity] at TestFile.kt:1:1 - Signature=TestEntitySignature
@Test
fun `renders multiple`() {
val report = TxtOutputReport()
val detektion = TestDetektion(
createFinding(ruleName = "TestSmellA"),
createFinding(ruleName = "TestSmellB"),
createFinding(ruleName = "TestSmellC")
)
val renderedText = """
TestSmellA - [TestEntity] at TestFile.kt:1:1 - Signature=TestEntitySignature
TestSmellB - [TestEntity] at TestFile.kt:1:1 - Signature=TestEntitySignature
TestSmellC - [TestEntity] at TestFile.kt:1:1 - Signature=TestEntitySignature
""".trimIndent()
assertThat(report.render(detektion)).isEqualTo(renderedText)
}
""".trimIndent()
assertThat(report.render(detektion)).isEqualTo(renderedText)
}
}

View File

@@ -35,7 +35,7 @@ class XmlOutputFormatSpec {
FilePath.fromAbsolute(Paths.get("src/main/com/sample/Sample1.kt"))
)
)
val entity2 = Entity(
private val entity2 = Entity(
"Sample2",
"",
Location(
@@ -46,174 +46,170 @@ class XmlOutputFormatSpec {
)
private val outputFormat = XmlOutputReport()
@Test
fun `renders empty report`() {
val result = outputFormat.render(TestDetektion())
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
</checkstyle>
""".trimIndent()
)
}
@Test
fun `renders one reported issue in single file`() {
val smell = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val result = outputFormat.render(TestDetektion(smell))
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_a" />
</file>
</checkstyle>
""".trimIndent()
)
}
@Test
fun `renders two reported issues in single file`() {
val smell1 = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val smell2 = CodeSmell(Issue("id_b", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val result = outputFormat.render(TestDetektion(smell1, smell2))
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_a" />
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_b" />
</file>
</checkstyle>
""".trimIndent()
)
}
@Test
fun `renders one reported issue across multiple files`() {
val smell1 = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val smell2 = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity2, message = "")
val result = outputFormat.render(TestDetektion(smell1, smell2))
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_a" />
</file>
<file name="src/main/com/sample/Sample2.kt">
$TAB<error line="22" column="2" severity="warning" message="" source="detekt.id_a" />
</file>
</checkstyle>
""".trimIndent()
)
}
@Test
fun `renders issues with relative path`() {
val findingA = createFindingForRelativePath(
ruleName = "id_a",
basePath = "/Users/tester/detekt/",
relativePath = "Sample1.kt"
)
val findingB = createFindingForRelativePath(
ruleName = "id_b",
basePath = "/Users/tester/detekt/",
relativePath = "Sample2.kt"
)
val result = outputFormat.render(TestDetektion(findingA, findingB))
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="Sample1.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_a" />
</file>
<file name="Sample2.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_b" />
</file>
</checkstyle>
""".trimIndent()
)
}
@Test
fun `renders two reported issues across multiple files`() {
val smell1 = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val smell2 = CodeSmell(Issue("id_b", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val smell3 = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity2, message = "")
val smell4 = CodeSmell(Issue("id_b", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity2, message = "")
val result = outputFormat.render(
TestDetektion(
smell1,
smell2,
smell3,
smell4
)
)
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_a" />
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_b" />
</file>
<file name="src/main/com/sample/Sample2.kt">
$TAB<error line="22" column="2" severity="warning" message="" source="detekt.id_a" />
$TAB<error line="22" column="2" severity="warning" message="" source="detekt.id_b" />
</file>
</checkstyle>
""".trimIndent()
)
}
@Nested
inner class `XML output format` {
inner class `severity level conversion` {
@Test
fun `renders empty report`() {
val result = outputFormat.render(TestDetektion())
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
</checkstyle>
""".trimIndent()
)
}
@Test
fun `renders one reported issue in single file`() {
val smell = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val result = outputFormat.render(TestDetektion(smell))
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_a" />
</file>
</checkstyle>
""".trimIndent()
)
}
@Test
fun `renders two reported issues in single file`() {
val smell1 = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val smell2 = CodeSmell(Issue("id_b", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val result = outputFormat.render(TestDetektion(smell1, smell2))
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_a" />
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_b" />
</file>
</checkstyle>
""".trimIndent()
)
}
@Test
fun `renders one reported issue across multiple files`() {
val smell1 = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val smell2 = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity2, message = "")
val result = outputFormat.render(TestDetektion(smell1, smell2))
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_a" />
</file>
<file name="src/main/com/sample/Sample2.kt">
$TAB<error line="22" column="2" severity="warning" message="" source="detekt.id_a" />
</file>
</checkstyle>
""".trimIndent()
)
}
@Test
fun `renders issues with relative path`() {
val findingA = createFindingForRelativePath(
ruleName = "id_a",
basePath = "/Users/tester/detekt/",
relativePath = "Sample1.kt"
)
val findingB = createFindingForRelativePath(
ruleName = "id_b",
basePath = "/Users/tester/detekt/",
relativePath = "Sample2.kt"
)
val result = outputFormat.render(TestDetektion(findingA, findingB))
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="Sample1.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_a" />
</file>
<file name="Sample2.kt">
$TAB<error line="1" column="1" severity="warning" message="TestMessage" source="detekt.id_b" />
</file>
</checkstyle>
""".trimIndent()
)
}
@Test
fun `renders two reported issues across multiple files`() {
val smell1 = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val smell2 = CodeSmell(Issue("id_b", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity1, message = "")
val smell3 = CodeSmell(Issue("id_a", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity2, message = "")
val smell4 = CodeSmell(Issue("id_b", Severity.CodeSmell, "", Debt.TWENTY_MINS), entity2, message = "")
val result = outputFormat.render(
TestDetektion(
smell1,
smell2,
smell3,
smell4
)
)
assertThat(result).isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_a" />
$TAB<error line="11" column="1" severity="warning" message="" source="detekt.id_b" />
</file>
<file name="src/main/com/sample/Sample2.kt">
$TAB<error line="22" column="2" severity="warning" message="" source="detekt.id_a" />
$TAB<error line="22" column="2" severity="warning" message="" source="detekt.id_b" />
</file>
</checkstyle>
""".trimIndent()
)
}
@Nested
inner class `severity level conversion` {
@ParameterizedTest
@EnumSource(SeverityLevel::class)
fun `renders detektion with severity as XML with severity`(severity: SeverityLevel) {
val xmlSeverity = severity.name.toLowerCase(Locale.US)
val finding = object : CodeSmell(
issue = Issue("issue_id", Severity.CodeSmell, "issue description", Debt.FIVE_MINS),
entity = entity1,
message = "message"
) {
override val severity: SeverityLevel
get() = severity
}
val expected = """
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="${finding.location.source.line}" column="${finding.location.source.column}" severity="$xmlSeverity" message="${finding.messageOrDescription()}" source="detekt.${finding.id}" />
</file>
</checkstyle>
"""
val actual = outputFormat.render(TestDetektion(finding))
assertThat(actual).isEqualTo(expected.trimIndent())
@ParameterizedTest
@EnumSource(SeverityLevel::class)
fun `renders detektion with severity as XML with severity`(severity: SeverityLevel) {
val xmlSeverity = severity.name.toLowerCase(Locale.US)
val finding = object : CodeSmell(
issue = Issue("issue_id", Severity.CodeSmell, "issue description", Debt.FIVE_MINS),
entity = entity1,
message = "message"
) {
override val severity: SeverityLevel
get() = severity
}
val expected = """
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="4.3">
<file name="src/main/com/sample/Sample1.kt">
$TAB<error line="${finding.location.source.line}" column="${finding.location.source.column}" severity="$xmlSeverity" message="${finding.messageOrDescription()}" source="detekt.${finding.id}" />
</file>
</checkstyle>
"""
val actual = outputFormat.render(TestDetektion(finding))
assertThat(actual).isEqualTo(expected.trimIndent())
}
}
}

View File

@@ -2,26 +2,21 @@ package io.gitlab.arturbosch.detekt.rules.complexity
import io.gitlab.arturbosch.detekt.test.compileAndLint
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class ComplexConditionSpec {
@Nested
inner class `ComplexCondition rule` {
val code = """
val a = if (5 > 4 && 4 < 6 || (3 < 5 || 2 < 5)) { 42 } else { 24 }
val code = """
val a = if (5 > 4 && 4 < 6 || (3 < 5 || 2 < 5)) { 42 } else { 24 }
fun complexConditions() {
while (5 > 4 && 4 < 6 || (3 < 5 || 2 < 5)) {}
do { } while (5 > 4 && 4 < 6 || (3 < 5 || 2 < 5))
}
"""
@Test
fun `reports some complex conditions`() {
assertThat(ComplexCondition().compileAndLint(code)).hasSize(3)
fun complexConditions() {
while (5 > 4 && 4 < 6 || (3 < 5 || 2 < 5)) {}
do { } while (5 > 4 && 4 < 6 || (3 < 5 || 2 < 5))
}
"""
@Test
fun `reports some complex conditions`() {
assertThat(ComplexCondition().compileAndLint(code)).hasSize(3)
}
}

View File

@@ -17,222 +17,218 @@ class ComplexMethodSpec {
val defaultComplexity = 1
@Nested
inner class `ComplexMethod rule` {
inner class `different complex constructs` {
@Nested
inner class `different complex constructs` {
@Test
fun `counts different loops`() {
val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint(
"""
fun test() {
for (i in 1..10) {}
while (true) {}
do {} while(true)
(1..10).forEach {}
}
"""
)
@Test
fun `counts different loops`() {
val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint(
"""
fun test() {
for (i in 1..10) {}
while (true) {}
do {} while(true)
(1..10).forEach {}
}
"""
)
assertThat(findings.first()).isThresholded().withValue(defaultComplexity + 4)
}
assertThat(findings.first()).isThresholded().withValue(defaultComplexity + 4)
}
@Test
fun `counts catch blocks`() {
val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint(
"""
fun test() {
try {} catch(e: IllegalArgumentException) {} catch(e: Exception) {} finally {}
}
"""
)
@Test
fun `counts catch blocks`() {
val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint(
"""
fun test() {
try {} catch(e: IllegalArgumentException) {} catch(e: Exception) {} finally {}
}
"""
)
assertThat(findings.first()).isThresholded().withValue(defaultComplexity + 2)
}
assertThat(findings.first()).isThresholded().withValue(defaultComplexity + 2)
}
@Test
fun `counts nested conditional statements`() {
val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint(
"""
fun test() {
try {
while (true) {
if (true) {
when ("string") {
"" -> println()
else -> println()
}
@Test
fun `counts nested conditional statements`() {
val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint(
"""
fun test() {
try {
while (true) {
if (true) {
when ("string") {
"" -> println()
else -> println()
}
}
} finally {
// only catches count
}
}
"""
)
assertThat(findings.first()).isThresholded().withValue(defaultComplexity + 4)
}
}
@Nested
inner class `nesting functions` {
val code = """
fun test() {
for (i in 1..10) {}
(1..10).forEach {}
}
"""
@Test
fun `counts three with nesting function 'forEach'`() {
val config = TestConfig(defaultConfigMap.plus("ignoreNestingFunctions" to "false"))
assertExpectedComplexityValue(code, config, expectedValue = 3)
}
@Test
fun `can ignore nesting functions like 'forEach'`() {
val config = TestConfig(defaultConfigMap.plus("ignoreNestingFunctions" to "true"))
assertExpectedComplexityValue(code, config, expectedValue = 2)
}
@Test
fun `skips all if if the nested functions is empty`() {
val config = TestConfig(defaultConfigMap.plus("nestingFunctions" to ""))
assertExpectedComplexityValue(code, config, expectedValue = 2)
}
@Test
fun `skips 'forEach' as it is not specified`() {
val config = TestConfig(defaultConfigMap.plus("nestingFunctions" to "let,apply,also"))
assertExpectedComplexityValue(code, config, expectedValue = 2)
}
@Test
fun `skips 'forEach' as it is not specified list`() {
val config = TestConfig(defaultConfigMap.plus("nestingFunctions" to listOf("let", "apply", "also")))
assertExpectedComplexityValue(code, config, expectedValue = 2)
}
}
@Nested
inner class `several complex methods` {
val path = resourceAsPath("ComplexMethods.kt")
@Test
fun `does not report complex methods with a single when expression`() {
val config = TestConfig(
mapOf(
"threshold" to "4",
"ignoreSingleWhenExpression" to "true"
)
)
val subject = ComplexMethod(config)
assertThat(subject.lint(path)).hasSourceLocations(SourceLocation(43, 5))
}
@Test
fun `reports all complex methods`() {
val config = TestConfig(mapOf("threshold" to "4"))
val subject = ComplexMethod(config)
assertThat(subject.lint(path)).hasSourceLocations(
SourceLocation(6, 5),
SourceLocation(15, 5),
SourceLocation(25, 5),
SourceLocation(35, 5),
SourceLocation(43, 5)
)
}
@Test
fun `does not trip for a reasonable amount of simple when entries when ignoreSimpleWhenEntries is true`() {
val config = TestConfig(mapOf("ignoreSimpleWhenEntries" to "true"))
val subject = ComplexMethod(config)
val code = """
fun f() {
val map = HashMap<Any, String>()
for ((key, value) in map) {
when (key) {
is Int -> print("int")
is String -> print("String")
is Float -> print("Float")
is Double -> print("Double")
is Byte -> print("Byte")
is Short -> print("Short")
is Long -> print("Long")
is Boolean -> print("Boolean")
else -> throw IllegalArgumentException("Unexpected type value")
}
}
}
"""
val findings = subject.compileAndLint(code)
assertThat(findings).isEmpty()
}
}
@Nested
inner class `function containing object literal with many overridden functions` {
val code = """
fun f(): List<Any> {
return object : List<Any> {
override val size: Int get() = TODO("not implemented")
override fun contains(element: Any): Boolean {
TODO("not implemented")
}
override fun containsAll(elements: Collection<Any>): Boolean {
TODO("not implemented")
}
override fun get(index: Int): Any {
TODO("not implemented")
}
override fun indexOf(element: Any): Int {
TODO("not implemented")
}
override fun isEmpty(): Boolean {
TODO("not implemented")
}
override fun iterator(): Iterator<Any> {
TODO("not implemented")
}
override fun lastIndexOf(element: Any): Int {
TODO("not implemented")
}
override fun listIterator(): ListIterator<Any> {
TODO("not implemented")
}
override fun listIterator(index: Int): ListIterator<Any> {
TODO("not implemented")
}
override fun subList(fromIndex: Int, toIndex: Int): List<Any> {
TODO("not implemented")
} finally {
// only catches count
}
}
"""
)
assertThat(findings.first()).isThresholded().withValue(defaultComplexity + 4)
}
}
@Nested
inner class `nesting functions` {
val code = """
fun test() {
for (i in 1..10) {}
(1..10).forEach {}
}
"""
@Test
fun `counts three with nesting function 'forEach'`() {
val config = TestConfig(defaultConfigMap.plus("ignoreNestingFunctions" to "false"))
assertExpectedComplexityValue(code, config, expectedValue = 3)
}
@Test
fun `can ignore nesting functions like 'forEach'`() {
val config = TestConfig(defaultConfigMap.plus("ignoreNestingFunctions" to "true"))
assertExpectedComplexityValue(code, config, expectedValue = 2)
}
@Test
fun `skips all if if the nested functions is empty`() {
val config = TestConfig(defaultConfigMap.plus("nestingFunctions" to ""))
assertExpectedComplexityValue(code, config, expectedValue = 2)
}
@Test
fun `skips 'forEach' as it is not specified`() {
val config = TestConfig(defaultConfigMap.plus("nestingFunctions" to "let,apply,also"))
assertExpectedComplexityValue(code, config, expectedValue = 2)
}
@Test
fun `skips 'forEach' as it is not specified list`() {
val config = TestConfig(defaultConfigMap.plus("nestingFunctions" to listOf("let", "apply", "also")))
assertExpectedComplexityValue(code, config, expectedValue = 2)
}
}
@Nested
inner class `several complex methods` {
val path = resourceAsPath("ComplexMethods.kt")
@Test
fun `does not report complex methods with a single when expression`() {
val config = TestConfig(
mapOf(
"threshold" to "4",
"ignoreSingleWhenExpression" to "true"
)
)
val subject = ComplexMethod(config)
assertThat(subject.lint(path)).hasSourceLocations(SourceLocation(43, 5))
}
@Test
fun `reports all complex methods`() {
val config = TestConfig(mapOf("threshold" to "4"))
val subject = ComplexMethod(config)
assertThat(subject.lint(path)).hasSourceLocations(
SourceLocation(6, 5),
SourceLocation(15, 5),
SourceLocation(25, 5),
SourceLocation(35, 5),
SourceLocation(43, 5)
)
}
@Test
fun `does not trip for a reasonable amount of simple when entries when ignoreSimpleWhenEntries is true`() {
val config = TestConfig(mapOf("ignoreSimpleWhenEntries" to "true"))
val subject = ComplexMethod(config)
val code = """
fun f() {
val map = HashMap<Any, String>()
for ((key, value) in map) {
when (key) {
is Int -> print("int")
is String -> print("String")
is Float -> print("Float")
is Double -> print("Double")
is Byte -> print("Byte")
is Short -> print("Short")
is Long -> print("Long")
is Boolean -> print("Boolean")
else -> throw IllegalArgumentException("Unexpected type value")
}
}
}
}
"""
@Test
fun `should not count these overridden functions to base functions complexity`() {
assertThat(ComplexMethod().compileAndLint(code)).isEmpty()
val findings = subject.compileAndLint(code)
assertThat(findings).isEmpty()
}
}
@Nested
inner class `function containing object literal with many overridden functions` {
val code = """
fun f(): List<Any> {
return object : List<Any> {
override val size: Int get() = TODO("not implemented")
override fun contains(element: Any): Boolean {
TODO("not implemented")
}
override fun containsAll(elements: Collection<Any>): Boolean {
TODO("not implemented")
}
override fun get(index: Int): Any {
TODO("not implemented")
}
override fun indexOf(element: Any): Int {
TODO("not implemented")
}
override fun isEmpty(): Boolean {
TODO("not implemented")
}
override fun iterator(): Iterator<Any> {
TODO("not implemented")
}
override fun lastIndexOf(element: Any): Int {
TODO("not implemented")
}
override fun listIterator(): ListIterator<Any> {
TODO("not implemented")
}
override fun listIterator(index: Int): ListIterator<Any> {
TODO("not implemented")
}
override fun subList(fromIndex: Int, toIndex: Int): List<Any> {
TODO("not implemented")
}
}
}
"""
@Test
fun `should not count these overridden functions to base functions complexity`() {
assertThat(ComplexMethod().compileAndLint(code)).isEmpty()
}
}
}

View File

@@ -3,174 +3,169 @@ package io.gitlab.arturbosch.detekt.rules.complexity
import io.gitlab.arturbosch.detekt.test.TestConfig
import io.gitlab.arturbosch.detekt.test.compileAndLint
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class LabeledExpressionSpec {
private val subject = LabeledExpression()
@Nested
inner class `LabeledExpression rule` {
@Test
fun `reports break and continue labels`() {
val code = """
fun f() {
loop@ for (i in 1..3) {
for (j in 1..3) {
if (j == 4) break@loop
if (j == 5) continue@loop
}
@Test
fun `reports break and continue labels`() {
val code = """
fun f() {
loop@ for (i in 1..3) {
for (j in 1..3) {
if (j == 4) break@loop
if (j == 5) continue@loop
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(3)
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(3)
}
@Test
fun `reports implicit return label`() {
val code = """
fun f(range: IntRange) {
range.forEach {
if (it == 5) return@forEach
println(it)
}
@Test
fun `reports implicit return label`() {
val code = """
fun f(range: IntRange) {
range.forEach {
if (it == 5) return@forEach
println(it)
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports explicit return label`() {
val code = """
fun f(range: IntRange) {
range.forEach label@{
if (it == 5) return@label
println(it)
}
@Test
fun `reports explicit return label`() {
val code = """
fun f(range: IntRange) {
range.forEach label@{
if (it == 5) return@label
println(it)
}
"""
assertThat(subject.compileAndLint(code)).hasSize(2)
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(2)
}
@Test
fun `reports labels referencing inner and outer class`() {
val code = """
class Outer {
inner class Inner {
fun f() {
val i = this@Inner
emptyList<Int>().forEach Outer@{
// references forEach label and not outer class
if (it == 5) return@Outer
println(it)
}
}
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(3)
}
@Test
fun `does not report inner class referencing outer class`() {
val code = """
@Test
fun `reports labels referencing inner and outer class`() {
val code = """
class Outer {
inner class Inner {
fun f() {
print(this@Outer)
val i = this@Inner
emptyList<Int>().forEach Outer@{
// references forEach label and not outer class
if (it == 5) return@Outer
println(it)
}
}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
"""
assertThat(subject.compileAndLint(code)).hasSize(3)
}
@Test
fun `does not report inner class referencing outer class in extension function`() {
val code = """
class Outer {
inner class Inner {
@Test
fun `does not report inner class referencing outer class`() {
val code = """
class Outer {
inner class Inner {
fun f() {
print(this@Outer)
}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report inner class referencing outer class in extension function`() {
val code = """
class Outer {
inner class Inner {
fun Int.f() {
print(this@Inner)
print(this@Outer)
}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report nested class referencing outer class in extension function`() {
val code = """
class Outer {
class Nested {
fun Int.f() {
print(this@Nested)
}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report inner classes referencing outer class in extension function`() {
val code = """
class Outer {
inner class Inner {
inner class InnerInner {
fun f() {
print(this@Outer)
print(this@Inner)
}
fun Int.f() {
print(this@Inner)
print(this@Outer)
print(this@InnerInner)
}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report nested class referencing outer class in extension function`() {
val code = """
class Outer {
class Nested {
fun Int.f() {
print(this@Nested)
}
}
@Test
fun `does not report excluded label`() {
val code = """
fun f() {
loop@ for (i in 1..5) {}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
"""
val config = TestConfig(mapOf("ignoredLabels" to listOf("loop")))
val findings = LabeledExpression(config).compileAndLint(code)
assertThat(findings).isEmpty()
}
@Test
fun `does not report inner classes referencing outer class in extension function`() {
val code = """
class Outer {
inner class Inner {
inner class InnerInner {
fun f() {
print(this@Outer)
print(this@Inner)
}
fun Int.f() {
print(this@Inner)
print(this@InnerInner)
}
}
}
@Test
fun `does not report excluded label config with string`() {
val code = """
fun f() {
loop@ for (i in 1..5) {}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
"""
val config = TestConfig(mapOf("ignoredLabels" to "loop"))
val findings = LabeledExpression(config).compileAndLint(code)
assertThat(findings).isEmpty()
}
@Test
fun `does not report excluded label`() {
val code = """
fun f() {
loop@ for (i in 1..5) {}
}
"""
val config = TestConfig(mapOf("ignoredLabels" to listOf("loop")))
val findings = LabeledExpression(config).compileAndLint(code)
assertThat(findings).isEmpty()
}
@Test
fun `does not report excluded label config with string`() {
val code = """
fun f() {
loop@ for (i in 1..5) {}
}
"""
val config = TestConfig(mapOf("ignoredLabels" to "loop"))
val findings = LabeledExpression(config).compileAndLint(code)
assertThat(findings).isEmpty()
}
@Test
fun `does not report excluded label config with leading and trailing wildcard`() {
val code = """
fun f() {
loop@ for (i in 1..5) {}
}
"""
val config = TestConfig(mapOf("ignoredLabels" to "*loop*,other"))
val findings = LabeledExpression(config).compileAndLint(code)
assertThat(findings).isEmpty()
}
@Test
fun `does not report excluded label config with leading and trailing wildcard`() {
val code = """
fun f() {
loop@ for (i in 1..5) {}
}
"""
val config = TestConfig(mapOf("ignoredLabels" to "*loop*,other"))
val findings = LabeledExpression(config).compileAndLint(code)
assertThat(findings).isEmpty()
}
}

View File

@@ -4,158 +4,153 @@ import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import io.gitlab.arturbosch.detekt.test.TestConfig
import io.gitlab.arturbosch.detekt.test.assertThat
import io.gitlab.arturbosch.detekt.test.compileAndLint
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class LongMethodSpec {
val subject = LongMethod(TestConfig(mapOf("threshold" to 5)))
@Nested
inner class `nested functions can be long` {
@Test
fun `should find two long methods`() {
val code = """
fun longMethod() { // 5 lines
println()
println()
println()
@Test
fun `should find two long methods`() {
val code = """
fun longMethod() { // 5 lines
fun nestedLongMethod() { // 5 lines
println()
println()
println()
fun nestedLongMethod() { // 5 lines
println()
println()
println()
}
}
"""
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(2)
assertThat(findings).hasTextLocations("longMethod", "nestedLongMethod")
}
@Test
fun `should not find too long methods`() {
val code = """
fun methodOk() { // 3 lines
println()
fun localMethodOk() { // 4 lines
println()
println()
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `should not find too long method with params on newlines`() {
val code = """
fun methodWithParams(
param1: String
) { // 4 lines
println()
println()
}
"""
}
"""
val findings = subject.compileAndLint(code)
assertThat(subject.compileAndLint(code)).isEmpty()
}
assertThat(findings).hasSize(2)
assertThat(findings).hasTextLocations("longMethod", "nestedLongMethod")
}
@Test
fun `should find too long method with params on newlines`() {
val code = """
fun longMethodWithParams(
@Test
fun `should not find too long methods`() {
val code = """
fun methodOk() { // 3 lines
println()
fun localMethodOk() { // 4 lines
println()
println()
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `should not find too long method with params on newlines`() {
val code = """
fun methodWithParams(
param1: String
) { // 4 lines
println()
println()
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `should find too long method with params on newlines`() {
val code = """
fun longMethodWithParams(
param1: String
) { // 5 lines
println()
println()
println()
}
"""
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings[0] as ThresholdedCodeSmell).hasValue(5)
}
@Test
fun `should find long method with method call with params on separate lines`() {
val code = """
fun longMethod(
x1: Int,
x2: Int,
y1: Int,
y2: Int
) { // 8 lines
listOf(
x1,
y1,
x2,
y2
)
}
"""
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings[0] as ThresholdedCodeSmell).hasValue(8)
}
@Test
fun `should find two long methods with params on separate lines`() {
val code = """
fun longMethod(
param1: String
) { // 5 lines
println()
println()
println()
fun nestedLongMethod(
param1: String
) { // 5 lines
println()
println()
println()
}
"""
}
"""
val findings = subject.compileAndLint(code)
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings[0] as ThresholdedCodeSmell).hasValue(5)
}
assertThat(findings).hasSize(2)
assertThat(findings).hasTextLocations("longMethod", "nestedLongMethod")
}
@Test
fun `should find long method with method call with params on separate lines`() {
val code = """
fun longMethod(
x1: Int,
x2: Int,
y1: Int,
y2: Int
) { // 8 lines
listOf(
x1,
y1,
x2,
y2
)
}
"""
@Test
fun `should find nested long methods with params on separate lines`() {
val code = """
fun longMethod(
param1: String
) { // 4 lines
println()
println()
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings[0] as ThresholdedCodeSmell).hasValue(8)
}
@Test
fun `should find two long methods with params on separate lines`() {
val code = """
fun longMethod(
fun nestedLongMethod(
param1: String
) { // 5 lines
println()
println()
println()
fun nestedLongMethod(
param1: String
) { // 5 lines
println()
println()
println()
}
}
"""
}
"""
val findings = subject.compileAndLint(code)
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(2)
assertThat(findings).hasTextLocations("longMethod", "nestedLongMethod")
}
@Test
fun `should find nested long methods with params on separate lines`() {
val code = """
fun longMethod(
param1: String
) { // 4 lines
println()
println()
fun nestedLongMethod(
param1: String
) { // 5 lines
println()
println()
println()
}
}
"""
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations("nestedLongMethod")
assertThat(findings[0] as ThresholdedCodeSmell).hasValue(5)
}
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations("nestedLongMethod")
assertThat(findings[0] as ThresholdedCodeSmell).hasValue(5)
}
}

View File

@@ -19,145 +19,141 @@ class LongParameterListSpec {
val subject = LongParameterList(defaultConfig)
val reportMessageForFunction = "The function long(a: Int, b: Int) has too many parameters. " +
"The current threshold is set to $defaultThreshold."
val reportMessageForConstructor = "The constructor(a: Int, b: Int) has too many parameters. " +
"The current threshold is set to $defaultThreshold."
@Test
fun `reports too long parameter list`() {
val code = "fun long(a: Int, b: Int) {}"
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings.first().message).isEqualTo(reportMessageForFunction)
}
@Test
fun `does not report short parameter list`() {
val code = "fun long(a: Int) {}"
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports too long parameter list event for parameters with defaults`() {
val code = "fun long(a: Int, b: Int = 1) {}"
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report long parameter list if parameters with defaults should be ignored`() {
val config = TestConfig(mapOf("ignoreDefaultParameters" to "true"))
val rule = LongParameterList(config)
val code = "fun long(a: Int, b: Int, c: Int = 2) {}"
assertThat(rule.compileAndLint(code)).isEmpty()
}
@Test
fun `reports too long parameter list for primary constructors`() {
val code = "class LongCtor(a: Int, b: Int)"
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings.first().message).isEqualTo(reportMessageForConstructor)
}
@Test
fun `does not report short parameter list for primary constructors`() {
val code = "class LongCtor(a: Int)"
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports too long parameter list for secondary constructors`() {
val code = "class LongCtor() { constructor(a: Int, b: Int) : this() }"
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings.first().message).isEqualTo(reportMessageForConstructor)
}
@Test
fun `does not report short parameter list for secondary constructors`() {
val code = "class LongCtor() { constructor(a: Int) : this() }"
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports long parameter list if custom threshold is set`() {
val config = TestConfig(mapOf("constructorThreshold" to "1"))
val rule = LongParameterList(config)
val code = "class LongCtor(a: Int)"
assertThat(rule.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report long parameter list for constructors of data classes if asked`() {
val config = TestConfig(
mapOf(
"ignoreDataClasses" to "true",
"constructorThreshold" to "1"
)
)
val rule = LongParameterList(config)
val code = "data class Data(val a: Int)"
assertThat(rule.compileAndLint(code)).isEmpty()
}
@Nested
inner class `LongParameterList rule` {
inner class `constructors and functions with ignored annotations` {
val reportMessageForFunction = "The function long(a: Int, b: Int) has too many parameters. " +
"The current threshold is set to $defaultThreshold."
val reportMessageForConstructor = "The constructor(a: Int, b: Int) has too many parameters. " +
"The current threshold is set to $defaultThreshold."
val config =
TestConfig(
mapOf(
"ignoreAnnotatedParameter" to listOf(
"Generated",
"kotlin.Deprecated",
"kotlin.jvm.JvmName",
"kotlin.Suppress"
),
"functionThreshold" to 1,
"constructorThreshold" to 1
)
)
val rule = LongParameterList(config)
@Test
fun `reports too long parameter list`() {
val code = "fun long(a: Int, b: Int) {}"
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings.first().message).isEqualTo(reportMessageForFunction)
}
fun `reports long parameter list for constructors if constructor parameters are annotated with annotation that is not ignored`() {
val code = """
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class CustomAnnotation
@Test
fun `does not report short parameter list`() {
val code = "fun long(a: Int) {}"
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports too long parameter list event for parameters with defaults`() {
val code = "fun long(a: Int, b: Int = 1) {}"
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report long parameter list if parameters with defaults should be ignored`() {
val config = TestConfig(mapOf("ignoreDefaultParameters" to "true"))
val rule = LongParameterList(config)
val code = "fun long(a: Int, b: Int, c: Int = 2) {}"
assertThat(rule.compileAndLint(code)).isEmpty()
}
@Test
fun `reports too long parameter list for primary constructors`() {
val code = "class LongCtor(a: Int, b: Int)"
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings.first().message).isEqualTo(reportMessageForConstructor)
}
@Test
fun `does not report short parameter list for primary constructors`() {
val code = "class LongCtor(a: Int)"
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports too long parameter list for secondary constructors`() {
val code = "class LongCtor() { constructor(a: Int, b: Int) : this() }"
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings.first().message).isEqualTo(reportMessageForConstructor)
}
@Test
fun `does not report short parameter list for secondary constructors`() {
val code = "class LongCtor() { constructor(a: Int) : this() }"
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports long parameter list if custom threshold is set`() {
val config = TestConfig(mapOf("constructorThreshold" to "1"))
val rule = LongParameterList(config)
val code = "class LongCtor(a: Int)"
class Data constructor(@CustomAnnotation val a: Int)
"""
assertThat(rule.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report long parameter list for constructors of data classes if asked`() {
val config = TestConfig(
mapOf(
"ignoreDataClasses" to "true",
"constructorThreshold" to "1"
)
)
val rule = LongParameterList(config)
val code = "data class Data(val a: Int)"
fun `reports long parameter list for functions if enough function parameters are annotated with annotation that is not ignored`() {
val code = """
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class CustomAnnotation
class Data { fun foo(@CustomAnnotation a: Int) {} }
"""
assertThat(rule.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report long parameter list for constructors if enough constructor parameters are annotated with ignored annotation`() {
val code = "class Data constructor(@kotlin.Suppress(\"\") val a: Int)"
assertThat(rule.compileAndLint(code)).isEmpty()
}
@Nested
inner class `constructors and functions with ignored annotations` {
val config =
TestConfig(
mapOf(
"ignoreAnnotatedParameter" to listOf(
"Generated",
"kotlin.Deprecated",
"kotlin.jvm.JvmName",
"kotlin.Suppress"
),
"functionThreshold" to 1,
"constructorThreshold" to 1
)
)
val rule = LongParameterList(config)
@Test
fun `reports long parameter list for constructors if constructor parameters are annotated with annotation that is not ignored`() {
val code = """
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class CustomAnnotation
class Data constructor(@CustomAnnotation val a: Int)
"""
assertThat(rule.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports long parameter list for functions if enough function parameters are annotated with annotation that is not ignored`() {
val code = """
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class CustomAnnotation
class Data { fun foo(@CustomAnnotation a: Int) {} }
"""
assertThat(rule.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report long parameter list for constructors if enough constructor parameters are annotated with ignored annotation`() {
val code = "class Data constructor(@kotlin.Suppress(\"\") val a: Int)"
assertThat(rule.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report long parameter list for functions if enough function parameters are annotated with ignored annotation`() {
val code = """class Data {
fun foo(@kotlin.Suppress("") a: Int) {} }
"""
assertThat(rule.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report long parameter list for functions if enough function parameters are annotated with ignored annotation`() {
val code = """class Data {
fun foo(@kotlin.Suppress("") a: Int) {} }
"""
assertThat(rule.compileAndLint(code)).isEmpty()
}
}
}

View File

@@ -13,266 +13,262 @@ class MethodOverloadingSpec {
val subject = MethodOverloading(defaultConfig)
@Nested
inner class `MethodOverloading rule` {
inner class `several overloaded methods` {
@Nested
inner class `several overloaded methods` {
@Test
fun `reports overloaded methods which exceed the threshold`() {
val code = """
class Test {
fun x() {}
fun x(i: Int) {}
fun x(i: Int, j: Int) {}
}
"""
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings[0].message).isEqualTo("The method 'x' is overloaded 3 times.")
}
@Test
fun `reports overloaded top level methods which exceed the threshold`() {
val code = """
@Test
fun `reports overloaded methods which exceed the threshold`() {
val code = """
class Test {
fun x() {}
fun x(i: Int) {}
fun x(i: Int, j: Int) {}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report overloaded methods which do not exceed the threshold`() {
subject.compileAndLint(
"""
class Test {
fun x() { }
fun x(i: Int) { }
}
"""
)
assertThat(subject.findings.size).isZero()
}
}
@Nested
inner class `several overloaded extensions methods` {
@Test
fun `does not report extension methods with a different receiver`() {
subject.compileAndLint(
"""
fun Boolean.foo() {}
fun Int.foo() {}
fun Long.foo() {}
"""
)
assertThat(subject.findings.size).isZero()
}
@Test
fun `reports extension methods with the same receiver`() {
subject.compileAndLint(
"""
fun Int.foo() {}
fun Int.foo(i: Int) {}
fun Int.foo(i: String) {}
"""
)
assertThat(subject.findings.size).isEqualTo(1)
}
}
@Nested
inner class `several nested overloaded methods` {
@Test
fun `reports nested overloaded methods which exceed the threshold`() {
val code = """
class Outer {
internal class Inner {
fun f() {}
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report nested overloaded methods which do not exceed the threshold`() {
val code = """
class Outer {
fun f() {}
internal class Inner {
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}
@Nested
inner class `several overloaded methods inside objects` {
@Test
fun `reports overloaded methods inside an object which exceed the threshold`() {
val code = """
object Test {
fun f() {}
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report overloaded methods inside an object which do not exceed the threshold`() {
val code = """
object Test {
fun f() {}
fun f(i: Int) {}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports overloaded methods inside a companion object which exceed the threshold`() {
val code = """
class Test {
companion object {
fun f() {}
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report overloaded methods in a companion object that do not exceed the threshold`() {
val code = """
class Test {
companion object {
fun f() {}
fun f(i: Int) {}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report overloaded methods in classes or objects that do not exceed the threshold`() {
val code = """
class Test {
fun f() {}
companion object {
fun f() {}
fun f(i: Int) {}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports overloaded methods inside an anonymous object expression`() {
val code = """
class A {
fun f() {
object : Runnable {
override fun run() {}
fun run(i: Int) {}
fun run(i: Int, j: Int) {}
}
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
}
@Nested
inner class `several overloaded methods inside enum classes` {
@Test
fun `does not report overridden methods inside enum entries`() {
val code = """
enum class Test {
E1 {
override fun f() {}
},
E2 {
override fun f() {}
},
E3 {
override fun f() {}
};
abstract fun f()
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports overloaded methods in enum entry`() {
val code = """
enum class Test {
E {
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
};
fun f() {}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports overloaded methods in enum class`() {
val code = """
enum class Test {
E;
fun f() {}
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
"""
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings[0].message).isEqualTo("The method 'x' is overloaded 3 times.")
}
@Test
fun `does not report a class without a body`() {
val code = "class A"
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report overloaded local functions`() {
fun `reports overloaded top level methods which exceed the threshold`() {
val code = """
fun top() {
fun x() {}
fun x(i: Int) {}
fun x(i: Int, j: Int) {}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report overloaded methods which do not exceed the threshold`() {
subject.compileAndLint(
"""
class Test {
fun x() { }
fun x(i: Int) { }
}
"""
)
assertThat(subject.findings.size).isZero()
}
}
@Nested
inner class `several overloaded extensions methods` {
@Test
fun `does not report extension methods with a different receiver`() {
subject.compileAndLint(
"""
fun Boolean.foo() {}
fun Int.foo() {}
fun Long.foo() {}
"""
)
assertThat(subject.findings.size).isZero()
}
@Test
fun `reports extension methods with the same receiver`() {
subject.compileAndLint(
"""
fun Int.foo() {}
fun Int.foo(i: Int) {}
fun Int.foo(i: String) {}
"""
)
assertThat(subject.findings.size).isEqualTo(1)
}
}
@Nested
inner class `several nested overloaded methods` {
@Test
fun `reports nested overloaded methods which exceed the threshold`() {
val code = """
class Outer {
internal class Inner {
fun f() {}
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report nested overloaded methods which do not exceed the threshold`() {
val code = """
class Outer {
fun f() {}
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
internal class Inner {
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}
@Nested
inner class `several overloaded methods inside objects` {
@Test
fun `reports overloaded methods inside an object which exceed the threshold`() {
val code = """
object Test {
fun f() {}
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report overloaded methods inside an object which do not exceed the threshold`() {
val code = """
object Test {
fun f() {}
fun f(i: Int) {}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports overloaded methods inside a companion object which exceed the threshold`() {
val code = """
class Test {
companion object {
fun f() {}
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report overloaded methods in a companion object that do not exceed the threshold`() {
val code = """
class Test {
companion object {
fun f() {}
fun f(i: Int) {}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report overloaded methods in classes or objects that do not exceed the threshold`() {
val code = """
class Test {
fun f() {}
companion object {
fun f() {}
fun f(i: Int) {}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports overloaded methods inside an anonymous object expression`() {
val code = """
class A {
fun f() {
object : Runnable {
override fun run() {}
fun run(i: Int) {}
fun run(i: Int, j: Int) {}
}
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
}
@Nested
inner class `several overloaded methods inside enum classes` {
@Test
fun `does not report overridden methods inside enum entries`() {
val code = """
enum class Test {
E1 {
override fun f() {}
},
E2 {
override fun f() {}
},
E3 {
override fun f() {}
};
abstract fun f()
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports overloaded methods in enum entry`() {
val code = """
enum class Test {
E {
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
};
fun f() {}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports overloaded methods in enum class`() {
val code = """
enum class Test {
E;
fun f() {}
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
}
@Test
fun `does not report a class without a body`() {
val code = "class A"
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report overloaded local functions`() {
val code = """
fun top() {
fun f() {}
fun f(i: Int) {}
fun f(i: Int, j: Int) {}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}

View File

@@ -14,187 +14,244 @@ class NamedArgumentsSpec(val env: KotlinCoreEnvironment) {
val defaultConfig = TestConfig(mapOf("threshold" to defaultThreshold))
val subject = NamedArguments(defaultConfig)
@Nested
inner class `NameArguments rule` {
@Test
fun `invocation with more than 2 parameters should throw error`() {
val code = """
fun sum(a: Int, b:Int, c:Int) {
println(a + b + c)
}
fun call() {
sum(1, 2, 3)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Test
fun `Function invocation with more than 2 parameters should not throw error if named`() {
val code = """
fun sum(a: Int, b:Int, c:Int) {
println(a + b + c)
}
fun call() {
sum(a = 1, b = 2, c = 3)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `invocation with more than 2 parameters should throw error if even one is not named`() {
val code = """
fun sum(a: Int, b:Int, c:Int) {
println(a + b + c)
}
fun call() {
sum(1, b = 2, c = 3)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Test
fun `invocation with less than 3 parameters should not throw error`() {
val code = """
fun sum(a: Int, b:Int) {
println(a + b)
}
fun call() {
sum(1, 2)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `invocation with less than 3 named parameters should not throw error`() {
val code = """
fun sum(a: Int, b:Int) {
println(a + b)
}
fun call() {
sum(a = 1, b = 2)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `constructor invocation with more than 3 non-named parameters should throw error`() {
val code = """
class C(val a: Int, val b:Int, val c:Int)
val obj = C(1, 2, 3)
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Test
fun `constructor invocation with more than 3 named parameters should not throw error`() {
val code = """
class C(val a: Int, val b:Int, val c:Int)
val obj = C(a = 1, b = 2, c= 3)
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `constructor invocation with less than 3 non-named parameters should not throw error`() {
val code = """
class C(val a: Int, val b:Int)
val obj = C(1, 2)
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `java method invocation should not be flagged`() {
val code = """
import java.time.LocalDateTime
fun test() {
LocalDateTime.of(2020, 3, 13, 14, 0, 0)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `invocation with varargs should not be flagged`() {
val code = """
fun foo(vararg i: Int) {}
fun bar(a: Int, b: Int, c: Int, vararg s: String) {}
fun test() {
foo(1, 2, 3, 4, 5)
bar(1, 2, 3, "a")
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `invocation with spread operator should be flagged`() {
val code = """
fun bar(a: Int, b: Int, c: Int, vararg s: String) {}
fun test() {
bar(1, 2, 3, *arrayOf("a"))
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Nested
inner class `lambda argument` {
@Test
fun `inner lambda argument`() {
val code = """
fun foo(a: Int, b: Int, c: Int, block: ((Int) -> Int)) {}
fun test() {
foo(a = 1, b = 2, c = 3, { it })
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
@Test
fun `invocation with more than 2 parameters should throw error`() {
val code = """
fun sum(a: Int, b:Int, c:Int) {
println(a + b + c)
}
fun call() {
sum(1, 2, 3)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Test
fun `Function invocation with more than 2 parameters should not throw error if named`() {
val code = """
fun sum(a: Int, b:Int, c:Int) {
println(a + b + c)
}
fun call() {
sum(a = 1, b = 2, c = 3)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `invocation with more than 2 parameters should throw error if even one is not named`() {
val code = """
fun sum(a: Int, b:Int, c:Int) {
println(a + b + c)
}
fun call() {
sum(1, b = 2, c = 3)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Test
fun `invocation with less than 3 parameters should not throw error`() {
val code = """
fun sum(a: Int, b:Int) {
println(a + b)
}
fun call() {
sum(1, 2)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `invocation with less than 3 named parameters should not throw error`() {
val code = """
fun sum(a: Int, b:Int) {
println(a + b)
}
fun call() {
sum(a = 1, b = 2)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `constructor invocation with more than 3 non-named parameters should throw error`() {
val code = """
class C(val a: Int, val b:Int, val c:Int)
val obj = C(1, 2, 3)
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Test
fun `constructor invocation with more than 3 named parameters should not throw error`() {
val code = """
class C(val a: Int, val b:Int, val c:Int)
val obj = C(a = 1, b = 2, c= 3)
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `constructor invocation with less than 3 non-named parameters should not throw error`() {
val code = """
class C(val a: Int, val b:Int)
val obj = C(1, 2)
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `java method invocation should not be flagged`() {
val code = """
import java.time.LocalDateTime
fun test() {
LocalDateTime.of(2020, 3, 13, 14, 0, 0)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `invocation with varargs should not be flagged`() {
val code = """
fun foo(vararg i: Int) {}
fun bar(a: Int, b: Int, c: Int, vararg s: String) {}
fun test() {
foo(1, 2, 3, 4, 5)
bar(1, 2, 3, "a")
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `invocation with spread operator should be flagged`() {
val code = """
fun bar(a: Int, b: Int, c: Int, vararg s: String) {}
fun test() {
bar(1, 2, 3, *arrayOf("a"))
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Nested
inner class `lambda argument` {
@Test
fun `inner lambda argument`() {
val code = """
fun foo(a: Int, b: Int, c: Int, block: ((Int) -> Int)) {}
fun test() {
foo(a = 1, b = 2, c = 3, { it })
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Test
fun `outer lambda argument`() {
val code = """
fun foo(a: Int, b: Int, c: Int, block: ((Int) -> Int)) {}
fun test() {
foo(a = 1, b = 2, c = 3) { it }
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `unnamed argument and outer argument`() {
val code = """
fun foo(a: Int, b: Int, c: Int, block: ((Int) -> Int)) {}
fun test() {
foo(a = 1, b = 2, 3) { it }
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
}
@Nested
inner class IgnoreArgumentsMatchingNames {
@Nested
inner class `ignoreArgumentsMatchingNames is true` {
val subject =
NamedArguments(TestConfig(mapOf("threshold" to 2, "ignoreArgumentsMatchingNames" to true)))
@Test
fun `outer lambda argument`() {
fun `all arguments are the same as the parameter names`() {
val code = """
fun foo(a: Int, b: Int, c: Int, block: ((Int) -> Int)) {}
fun test() {
foo(a = 1, b = 2, c = 3) { it }
}
fun foo(a: Int, b: Int, c: Int) {}
fun bar(a: Int, b: Int, c: Int) {
foo(a, b, c)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `unnamed argument and outer argument`() {
fun `some arguments are not the same as the parameter names`() {
val code = """
fun foo(a: Int, b: Int, c: Int, block: ((Int) -> Int)) {}
fun foo(a: Int, b: Int, c: Int) {}
fun bar(a: Int, b: Int, c: Int) {
foo(a, c, b)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
fun test() {
foo(a = 1, b = 2, 3) { it }
}
@Test
fun `all arguments are the same as the parameter names and have a this receiver`() {
val code = """
class Baz {
private var b: Int = 42
fun foo(a: Int, b: Int, c: Int) {}
fun bar(a: Int, c: Int) {
foo(a, this.b, c)
}
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Test
fun `all arguments are the same as the parameter names and have a it receiver`() {
val code = """
data class Baz(val b: Int)
fun foo(a: Int, b: Int, c: Int) {}
fun bar(a: Int, c: Int, baz: Baz?) {
baz?.let { foo(a, it.b, c) }
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
@@ -202,78 +259,17 @@ class NamedArgumentsSpec(val env: KotlinCoreEnvironment) {
}
@Nested
inner class IgnoreArgumentsMatchingNames {
@Nested
inner class `ignoreArgumentsMatchingNames is true` {
val subject =
NamedArguments(TestConfig(mapOf("threshold" to 2, "ignoreArgumentsMatchingNames" to true)))
@Test
fun `all arguments are the same as the parameter names`() {
val code = """
fun foo(a: Int, b: Int, c: Int) {}
fun bar(a: Int, b: Int, c: Int) {
foo(a, b, c)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(0)
}
@Test
fun `some arguments are not the same as the parameter names`() {
val code = """
fun foo(a: Int, b: Int, c: Int) {}
fun bar(a: Int, b: Int, c: Int) {
foo(a, c, b)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Test
fun `all arguments are the same as the parameter names and have a this receiver`() {
val code = """
class Baz {
private var b: Int = 42
fun foo(a: Int, b: Int, c: Int) {}
fun bar(a: Int, c: Int) {
foo(a, this.b, c)
}
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
@Test
fun `all arguments are the same as the parameter names and have a it receiver`() {
val code = """
data class Baz(val b: Int)
fun foo(a: Int, b: Int, c: Int) {}
fun bar(a: Int, c: Int, baz: Baz?) {
baz?.let { foo(a, it.b, c) }
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
}
@Nested
inner class `ignoreArgumentsMatchingNames is false` {
@Test
fun `all arguments are the same as parameter names`() {
val code = """
fun foo(a: Int, b: Int, c: Int) {}
fun bar(a: Int, b: Int, c: Int) {
foo(a, b, c)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
inner class `ignoreArgumentsMatchingNames is false` {
@Test
fun `all arguments are the same as parameter names`() {
val code = """
fun foo(a: Int, b: Int, c: Int) {}
fun bar(a: Int, b: Int, c: Int) {
foo(a, b, c)
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
}
}

View File

@@ -7,7 +7,6 @@ import io.gitlab.arturbosch.detekt.test.assertThat
import io.gitlab.arturbosch.detekt.test.compileAndLint
import io.gitlab.arturbosch.detekt.test.lint
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class NestedBlockDepthSpec {
@@ -16,39 +15,18 @@ class NestedBlockDepthSpec {
val defaultConfig = TestConfig(mapOf("threshold" to defaultThreshold))
val subject = NestedBlockDepth(defaultConfig)
@Nested
inner class `nested classes are also considered` {
@Test
fun `should detect only the nested large class`() {
subject.lint(resourceAsPath("NestedClasses.kt"))
assertThat(subject.findings).hasSize(1)
assertThat((subject.findings[0] as ThresholdedCodeSmell).value).isEqualTo(5)
}
@Test
fun `should detect only the nested large class`() {
subject.lint(resourceAsPath("NestedClasses.kt"))
assertThat(subject.findings).hasSize(1)
assertThat((subject.findings[0] as ThresholdedCodeSmell).value).isEqualTo(5)
}
@Test
fun `should detect too nested block depth`() {
val code = """
fun f() {
if (true) {
if (true) {
if (true) {
if (true) {
}
}
}
}
}
"""
val findings = subject.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(4 to 5)
}
@Test
fun `should not detect valid nested block depth`() {
val code = """
fun f() {
@Test
fun `should detect too nested block depth`() {
val code = """
fun f() {
if (true) {
if (true) {
if (true) {
if (true) {
@@ -56,42 +34,60 @@ class NestedBlockDepthSpec {
}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}
"""
val findings = subject.compileAndLint(code)
@Test
fun `does not report valid nested if else branches`() {
val code = """
fun f() {
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(4 to 5)
}
@Test
fun `should not detect valid nested block depth`() {
val code = """
fun f() {
if (true) {
if (true) {
if (true) {
} else if (true) {
}
}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report valid nested if else branches`() {
val code = """
fun f() {
if (true) {
if (true) {
} else if (true) {
if (true) {
}
}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports deeply nested if else branches`() {
val code = """
fun f() {
if (true) {
if (true) {
} else if (true) {
if (true) {
if (true) {
}
}
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports deeply nested if else branches`() {
val code = """
fun f() {
if (true) {
if (true) {
} else if (true) {
if (true) {
if (true) {
}
}
}
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
}

View File

@@ -4,7 +4,6 @@ import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@KotlinCoreEnvironmentTest
@@ -12,67 +11,63 @@ class ReplaceSafeCallChainWithRunSpec(val env: KotlinCoreEnvironment) {
val subject = ReplaceSafeCallChainWithRun()
@Nested
inner class `ReplaceSafeChainWithRun rule` {
@Test
fun `reports long chain of unnecessary safe qualified expressions`() {
val code = """
val x: String? = "string"
@Test
fun `reports long chain of unnecessary safe qualified expressions`() {
val code = """
val x: String? = "string"
val y = x
?.asSequence()
?.map { it }
?.distinctBy { it }
?.iterator()
?.forEach(::println)
"""
val y = x
?.asSequence()
?.map { it }
?.distinctBy { it }
?.iterator()
?.forEach(::println)
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
@Test
fun `reports short chain of unnecessary safe qualified expressions`() {
val code = """
val x: String? = "string"
@Test
fun `reports short chain of unnecessary safe qualified expressions`() {
val code = """
val x: String? = "string"
val y = x
?.asSequence()
?.map { it }
"""
val y = x
?.asSequence()
?.map { it }
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
@Test
fun `does not report a safe call chain which is too short to benefit`() {
val code = """
val x: String? = "string"
@Test
fun `does not report a safe call chain which is too short to benefit`() {
val code = """
val x: String? = "string"
val y = x
?.asSequence()
"""
val y = x
?.asSequence()
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `does not report a safe call chain on left side of assignment`() {
val code = """
class Something {
var element: Element? = null
}
@Test
fun `does not report a safe call chain on left side of assignment`() {
val code = """
class Something {
var element: Element? = null
}
class Element(var list: List<String>?)
class Element(var list: List<String>?)
val z: Something? = Something()
val z: Something? = Something()
fun modifyList() {
z?.element?.list = listOf("strings")
}
"""
fun modifyList() {
z?.element?.list = listOf("strings")
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
}

View File

@@ -17,145 +17,141 @@ class StringLiteralDuplicationSpec {
val subject = StringLiteralDuplication()
@Nested
inner class `StringLiteralDuplication rule` {
@Nested
inner class `many hardcoded strings` {
@Test
fun `reports 3 equal hardcoded strings`() {
val code = """
class Duplication {
var s1 = "lorem"
fun f(s: String = "lorem") {
s1.equals("lorem")
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report 2 equal hardcoded strings`() {
val code = """val str = "lorem" + "lorem" + "ipsum""""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}
@Nested
inner class `strings in annotations` {
inner class `many hardcoded strings` {
@Test
fun `reports 3 equal hardcoded strings`() {
val code = """
@Suppress("unused")
class A
@Suppress("unused")
class B
@Suppress("unused")
class C
class Duplication {
var s1 = "lorem"
fun f(s: String = "lorem") {
s1.equals("lorem")
}
}
"""
@Test
fun `does not report strings in annotations`() {
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports strings in annotations according to config`() {
val config = TestConfig(mapOf(IGNORE_ANNOTATION to "false"))
assertFindingWithConfig(code, config, 1)
}
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Nested
inner class `strings with less than 5 characters` {
@Test
fun `does not report 2 equal hardcoded strings`() {
val code = """val str = "lorem" + "lorem" + "ipsum""""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}
val code = """val str = "amet" + "amet" + "amet""""
@Nested
inner class `strings in annotations` {
@Test
fun `does not report strings with 4 characters`() {
assertThat(subject.compileAndLint(code)).isEmpty()
}
val code = """
@Suppress("unused")
class A
@Suppress("unused")
class B
@Suppress("unused")
class C
"""
@Test
fun `reports string with 4 characters`() {
val config = TestConfig(mapOf(EXCLUDE_SHORT_STRING to "false"))
assertFindingWithConfig(code, config, 1)
}
@Test
fun `does not report strings in annotations`() {
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Nested
inner class `strings with values to match for the regex` {
@Test
fun `reports strings in annotations according to config`() {
val config = TestConfig(mapOf(IGNORE_ANNOTATION to "false"))
assertFindingWithConfig(code, config, 1)
}
}
val regexTestingCode = """
@Nested
inner class `strings with less than 5 characters` {
val code = """val str = "amet" + "amet" + "amet""""
@Test
fun `does not report strings with 4 characters`() {
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports string with 4 characters`() {
val config = TestConfig(mapOf(EXCLUDE_SHORT_STRING to "false"))
assertFindingWithConfig(code, config, 1)
}
}
@Nested
inner class `strings with values to match for the regex` {
val regexTestingCode = """
val str1 = "lorem" + "lorem" + "lorem"
val str2 = "ipsum" + "ipsum" + "ipsum"
"""
@Test
fun `does not report lorem or ipsum according to config in regex`() {
val code = """
val str1 = "lorem" + "lorem" + "lorem"
val str2 = "ipsum" + "ipsum" + "ipsum"
"""
@Test
fun `does not report lorem or ipsum according to config in regex`() {
val code = """
val str1 = "lorem" + "lorem" + "lorem"
val str2 = "ipsum" + "ipsum" + "ipsum"
"""
val config = TestConfig(mapOf(IGNORE_STRINGS_REGEX to "(lorem|ipsum)"))
assertFindingWithConfig(code, config, 0)
}
@Test
fun `should not fail with invalid regex when disabled`() {
val configValues = mapOf(
"active" to "false",
IGNORE_STRINGS_REGEX to "*lorem"
)
val config = TestConfig(configValues)
assertFindingWithConfig(regexTestingCode, config, 0)
}
@Test
fun `should fail with invalid regex`() {
val config = TestConfig(mapOf(IGNORE_STRINGS_REGEX to "*lorem"))
assertThatExceptionOfType(PatternSyntaxException::class.java).isThrownBy {
StringLiteralDuplication(config).compileAndLint(regexTestingCode)
}
}
val config = TestConfig(mapOf(IGNORE_STRINGS_REGEX to "(lorem|ipsum)"))
assertFindingWithConfig(code, config, 0)
}
@Nested
inner class `saves string literal references` {
@Test
fun `reports 3 locations for 'lorem'`() {
val code = """
class Duplication {
var s1 = "lorem"
fun f(s: String = "lorem") {
s1.equals("lorem")
}
}
"""
val finding = subject.compileAndLint(code)[0]
val locations = finding.references.map { it.location } + finding.entity.location
assertThat(locations).hasSize(3)
}
@Test
fun `should not fail with invalid regex when disabled`() {
val configValues = mapOf(
"active" to "false",
IGNORE_STRINGS_REGEX to "*lorem"
)
val config = TestConfig(configValues)
assertFindingWithConfig(regexTestingCode, config, 0)
}
@Nested
inner class `multiline strings with string interpolation` {
@Test
fun `does not report duplicated parts in multiline strings`() {
val code = """
// does not report because it treats the multiline string parts as one string
val str = ""${'"'}
|
|
|
""${'"'}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
@Test
fun `should fail with invalid regex`() {
val config = TestConfig(mapOf(IGNORE_STRINGS_REGEX to "*lorem"))
assertThatExceptionOfType(PatternSyntaxException::class.java).isThrownBy {
StringLiteralDuplication(config).compileAndLint(regexTestingCode)
}
}
}
@Nested
inner class `saves string literal references` {
@Test
fun `reports 3 locations for 'lorem'`() {
val code = """
class Duplication {
var s1 = "lorem"
fun f(s: String = "lorem") {
s1.equals("lorem")
}
}
"""
val finding = subject.compileAndLint(code)[0]
val locations = finding.references.map { it.location } + finding.entity.location
assertThat(locations).hasSize(3)
}
}
@Nested
inner class `multiline strings with string interpolation` {
@Test
fun `does not report duplicated parts in multiline strings`() {
val code = """
// does not report because it treats the multiline string parts as one string
val str = ""${'"'}
|
|
|
""${'"'}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}
}
private fun assertFindingWithConfig(code: String, config: TestConfig, expected: Int) {

View File

@@ -16,255 +16,251 @@ private const val IGNORE_PRIVATE = "ignorePrivate"
private const val IGNORE_OVERRIDDEN = "ignoreOverridden"
class TooManyFunctionsSpec {
@Nested
inner class `different declarations with one function as threshold` {
val rule =
TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_ENUMS to "1",
THRESHOLD_IN_FILES to "1",
THRESHOLD_IN_INTERFACES to "1",
THRESHOLD_IN_OBJECTS to "1"
)
val rule =
TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_ENUMS to "1",
THRESHOLD_IN_FILES to "1",
THRESHOLD_IN_INTERFACES to "1",
THRESHOLD_IN_OBJECTS to "1"
)
)
)
@Test
fun `finds one function in class`() {
val code = """
class A {
@Test
fun `finds one function in class`() {
val code = """
class A {
fun a() = Unit
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(6 to 7)
}
@Test
fun `finds one function in object`() {
val code = """
object O {
fun o() = Unit
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(7 to 8)
}
@Test
fun `finds one function in interface`() {
val code = """
interface I {
fun i()
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(10 to 11)
}
@Test
fun `finds one function in enum`() {
val code = """
enum class E {
A;
fun e() {}
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(11 to 12)
}
@Test
fun `finds one function in file`() {
val code = "fun f() = Unit"
assertThat(rule.compileAndLint(code)).hasSize(1)
}
@Test
fun `finds one function in file ignoring other declarations`() {
val code = """
fun f1() = Unit
class C
object O
fun f2() = Unit
interface I
enum class E
fun f3() = Unit
"""
assertThat(rule.compileAndLint(code)).hasSize(1)
}
@Test
fun `finds one function in nested class`() {
val code = """
class A {
class B {
fun a() = Unit
}
"""
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(6 to 7)
}
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(20 to 21)
}
@Test
fun `finds one function in object`() {
val code = """
object O {
fun o() = Unit
}
"""
@Nested
inner class `different deprecated functions` {
val code = """
@Deprecated("")
fun f() {
}
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(7 to 8)
}
@Test
fun `finds one function in interface`() {
val code = """
interface I {
fun i()
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(10 to 11)
}
@Test
fun `finds one function in enum`() {
val code = """
enum class E {
A;
fun e() {}
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(11 to 12)
}
@Test
fun `finds one function in file`() {
val code = "fun f() = Unit"
assertThat(rule.compileAndLint(code)).hasSize(1)
}
@Test
fun `finds one function in file ignoring other declarations`() {
val code = """
fun f1() = Unit
class C
object O
fun f2() = Unit
interface I
enum class E
fun f3() = Unit
"""
assertThat(rule.compileAndLint(code)).hasSize(1)
}
@Test
fun `finds one function in nested class`() {
val code = """
class A {
class B {
fun a() = Unit
}
}
"""
val findings = rule.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings).hasTextLocations(20 to 21)
}
@Nested
inner class `different deprecated functions` {
val code = """
class A {
@Deprecated("")
fun f() {
}
}
"""
@Test
fun `finds all deprecated functions per default`() {
assertThat(rule.compileAndLint(code)).hasSize(2)
}
@Test
fun `finds no deprecated functions`() {
val configuredRule = TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_FILES to "1",
IGNORE_DEPRECATED to "true"
)
)
)
assertThat(configuredRule.compileAndLint(code)).isEmpty()
}
}
@Nested
inner class `different private functions` {
val code = """
class A {
private fun f() {}
}
"""
@Test
fun `finds the private function per default`() {
assertThat(rule.compileAndLint(code)).hasSize(1)
}
@Test
fun `finds no private functions`() {
val configuredRule = TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_FILES to "1",
IGNORE_PRIVATE to "true"
)
)
)
assertThat(configuredRule.compileAndLint(code)).isEmpty()
}
}
@Nested
inner class `false negative when private and deprecated functions are ignored - #1439` {
@Test
fun `should not report when file has no public functions`() {
val code = """
class A {
private fun a() = Unit
private fun b() = Unit
@Deprecated("")
fun f() {
}
private fun c() = Unit
}
interface I {
fun a() = Unit
fun b() = Unit
}
class B : I {
override fun a() = Unit
override fun b() = Unit
}
"""
@Test
fun `finds all deprecated functions per default`() {
assertThat(rule.compileAndLint(code)).hasSize(2)
}
@Test
fun `finds no deprecated functions`() {
val configuredRule = TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_FILES to "1",
IGNORE_DEPRECATED to "true"
)
val configuredRule = TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_FILES to "1",
IGNORE_PRIVATE to "true",
IGNORE_DEPRECATED to "true",
IGNORE_OVERRIDDEN to "true"
)
)
assertThat(configuredRule.compileAndLint(code)).isEmpty()
}
)
assertThat(configuredRule.compileAndLint(code)).isEmpty()
}
}
@Nested
inner class `different private functions` {
@Nested
inner class `overridden functions` {
val code = """
class A {
private fun f() {}
val code = """
interface I1 {
fun func1()
fun func2()
}
"""
@Test
fun `finds the private function per default`() {
assertThat(rule.compileAndLint(code)).hasSize(1)
}
class Foo : I1 {
override fun func1() = Unit
override fun func2() = Unit
}
"""
@Test
fun `finds no private functions`() {
val configuredRule = TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_FILES to "1",
IGNORE_PRIVATE to "true"
)
@Test
fun `should not report class with overridden functions, if ignoreOverridden is enabled`() {
val configuredRule = TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_FILES to "1",
IGNORE_OVERRIDDEN to "true"
)
)
assertThat(configuredRule.compileAndLint(code)).isEmpty()
}
)
assertThat(configuredRule.compileAndLint(code)).isEmpty()
}
@Nested
inner class `false negative when private and deprecated functions are ignored - #1439` {
@Test
fun `should not report when file has no public functions`() {
val code = """
class A {
private fun a() = Unit
private fun b() = Unit
@Deprecated("")
private fun c() = Unit
}
interface I {
fun a() = Unit
fun b() = Unit
}
class B : I {
override fun a() = Unit
override fun b() = Unit
}
"""
val configuredRule = TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_FILES to "1",
IGNORE_PRIVATE to "true",
IGNORE_DEPRECATED to "true",
IGNORE_OVERRIDDEN to "true"
)
@Test
fun `should count overridden functions, if ignoreOverridden is disabled`() {
val configuredRule = TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_FILES to "1",
IGNORE_OVERRIDDEN to "false"
)
)
assertThat(configuredRule.compileAndLint(code)).isEmpty()
}
}
@Nested
inner class `overridden functions` {
val code = """
interface I1 {
fun func1()
fun func2()
}
class Foo : I1 {
override fun func1() = Unit
override fun func2() = Unit
}
"""
@Test
fun `should not report class with overridden functions, if ignoreOverridden is enabled`() {
val configuredRule = TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_FILES to "1",
IGNORE_OVERRIDDEN to "true"
)
)
)
assertThat(configuredRule.compileAndLint(code)).isEmpty()
}
@Test
fun `should count overridden functions, if ignoreOverridden is disabled`() {
val configuredRule = TooManyFunctions(
TestConfig(
mapOf(
THRESHOLD_IN_CLASSES to "1",
THRESHOLD_IN_FILES to "1",
IGNORE_OVERRIDDEN to "false"
)
)
)
assertThat(configuredRule.compileAndLint(code)).hasSize(1)
}
)
assertThat(configuredRule.compileAndLint(code)).hasSize(1)
}
}
}

View File

@@ -4,74 +4,69 @@ import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.test.assertThat
import io.gitlab.arturbosch.detekt.test.compileAndLint
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class GlobalCoroutineUsageSpec {
val subject = GlobalCoroutineUsage(Config.empty)
@Nested
inner class `GlobalCoroutineUsage rule` {
@Test
@DisplayName("should report GlobalScope.launch")
fun reportGlobalScopeLaunch() {
val code = """
import kotlinx.coroutines.delay
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@Test
@DisplayName("should report GlobalScope.launch")
fun reportGlobalScopeLaunch() {
val code = """
import kotlinx.coroutines.delay
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
fun foo() {
GlobalScope.launch { delay(1_000L) }
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
fun foo() {
GlobalScope.launch { delay(1_000L) }
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
@DisplayName("should report GlobalScope.async")
fun reportGlobalScopeAsync() {
val code = """
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.GlobalScope
@Test
@DisplayName("should report GlobalScope.async")
fun reportGlobalScopeAsync() {
val code = """
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.GlobalScope
fun foo() {
GlobalScope.async { delay(1_000L) }
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
fun foo() {
GlobalScope.async { delay(1_000L) }
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `should not report bar(GlobalScope)`() {
val code = """
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
@Test
fun `should not report bar(GlobalScope)`() {
val code = """
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
fun bar(scope: CoroutineScope) = Unit
fun bar(scope: CoroutineScope) = Unit
fun foo() {
bar(GlobalScope)
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
fun foo() {
bar(GlobalScope)
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `should not report 'val scope = GlobalScope'`() {
val code = """
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
@Test
fun `should not report 'val scope = GlobalScope'`() {
val code = """
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
fun bar(scope: CoroutineScope) = Unit
fun bar(scope: CoroutineScope) = Unit
fun foo() {
val scope = GlobalScope
bar(scope)
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
fun foo() {
val scope = GlobalScope
bar(scope)
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}

View File

@@ -5,7 +5,6 @@ import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@KotlinCoreEnvironmentTest
@@ -13,120 +12,116 @@ class RedundantSuspendModifierSpec(val env: KotlinCoreEnvironment) {
val subject = RedundantSuspendModifier(Config.empty)
@Nested
inner class `RedundantSuspendModifier` {
@Test
fun `reports when public function returns expression of platform type`() {
val code = """
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@Test
fun `reports when public function returns expression of platform type`() {
val code = """
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
suspend fun suspendCoroutine() = suspendCoroutine { continuation: Continuation<String> ->
continuation.resume("string")
}
suspend fun suspendCoroutine() = suspendCoroutine { continuation: Continuation<String> ->
continuation.resume("string")
class RedundantSuspend {
suspend fun redundantSuspend() {
println("hello world")
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
class RedundantSuspend {
suspend fun redundantSuspend() {
println("hello world")
@Test
fun `does not report when private`() {
val code = """
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
suspend fun suspendCoroutine() = suspendCoroutine { continuation: Continuation<String> ->
continuation.resume("string")
}
suspend fun doesSuspend() {
suspendCoroutine()
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `does not report when public function returns expression of platform type`() {
val code = """
class RedundantClass {
open suspend fun redundantSuspend() {
println("hello world")
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `does not report suspend function without body`() {
val code = """
interface SuspendInterface {
suspend fun empty()
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `does not report overridden suspend function`() {
val code = """
interface SuspendInterface {
suspend fun empty()
}
class SuspendClass : SuspendInterface {
override suspend fun empty() {
println("hello world")
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `ignores when iterator is suspending`() {
val code = """
class SuspendingIterator {
suspend operator fun iterator(): Iterator<Any> = iterator { yield("value") }
}
suspend fun bar() {
for (x in SuspendingIterator()) {
println(x)
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `ignores when suspending function used in property delegate`() {
val code = """
class SuspendingIterator {
suspend operator fun iterator(): Iterator<Any> = iterator { yield("value") }
}
fun coroutine(block: suspend () -> Unit) {}
suspend fun bar() {
val lazyValue: String by lazy {
coroutine {
SuspendingIterator().iterator()
}
"Hello"
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
@Test
fun `does not report when private`() {
val code = """
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
suspend fun suspendCoroutine() = suspendCoroutine { continuation: Continuation<String> ->
continuation.resume("string")
}
suspend fun doesSuspend() {
suspendCoroutine()
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `does not report when public function returns expression of platform type`() {
val code = """
class RedundantClass {
open suspend fun redundantSuspend() {
println("hello world")
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `does not report suspend function without body`() {
val code = """
interface SuspendInterface {
suspend fun empty()
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `does not report overridden suspend function`() {
val code = """
interface SuspendInterface {
suspend fun empty()
}
class SuspendClass : SuspendInterface {
override suspend fun empty() {
println("hello world")
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `ignores when iterator is suspending`() {
val code = """
class SuspendingIterator {
suspend operator fun iterator(): Iterator<Any> = iterator { yield("value") }
}
suspend fun bar() {
for (x in SuspendingIterator()) {
println(x)
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `ignores when suspending function used in property delegate`() {
val code = """
class SuspendingIterator {
suspend operator fun iterator(): Iterator<Any> = iterator { yield("value") }
}
fun coroutine(block: suspend () -> Unit) {}
suspend fun bar() {
val lazyValue: String by lazy {
coroutine {
SuspendingIterator().iterator()
}
"Hello"
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
}

View File

@@ -6,7 +6,6 @@ import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@Suppress("BlockingMethodInNonBlockingContext", "RedundantSuspendModifier")
@@ -15,64 +14,61 @@ class SleepInsteadOfDelaySpec(val env: KotlinCoreEnvironment) {
val subject = SleepInsteadOfDelay(Config.empty)
@Nested
inner class `SleepInsteadOfDelay rule` {
@Test
fun `should report no issue for delay() in suspend functions`() {
val code = """
import kotlinx.coroutines.delay
@Test
fun `should report no issue for delay() in suspend functions`() {
val code = """
import kotlinx.coroutines.delay
suspend fun foo() {
delay(1000L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(0)
}
suspend fun foo() {
delay(1000L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(0)
}
@Test
@DisplayName("should report Thread.sleep() in suspend functions")
fun reportThreadSleepInSuspendFunctions() {
val code = """
suspend fun foo() {
@Test
@DisplayName("should report Thread.sleep() in suspend functions")
fun reportThreadSleepInSuspendFunctions() {
val code = """
suspend fun foo() {
Thread.sleep(1000L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
@Test
@DisplayName("should report Thread.sleep() in CoroutineScope.launch()")
fun reportThreadSleepInCoroutineScopeLaunch() {
val code = """
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
fun foo() {
CoroutineScope(Dispatchers.IO).launch {
Thread.sleep(1000L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
@Test
@DisplayName("should report Thread.sleep() in CoroutineScope.launch()")
fun reportThreadSleepInCoroutineScopeLaunch() {
val code = """
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Test
@DisplayName("should report Thread.sleep() in CoroutineScope.async()")
fun reportThreadSleepInCoroutineScopeAsync() {
@Suppress("DeferredResultUnused")
val code = """
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
fun foo() {
CoroutineScope(Dispatchers.IO).launch {
Thread.sleep(1000L)
}
fun foo() {
CoroutineScope(Dispatchers.IO).async {
Thread.sleep(1000L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
@Test
@DisplayName("should report Thread.sleep() in CoroutineScope.async()")
fun reportThreadSleepInCoroutineScopeAsync() {
@Suppress("DeferredResultUnused")
val code = """
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
fun foo() {
CoroutineScope(Dispatchers.IO).async {
Thread.sleep(1000L)
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(1)
}
}

View File

@@ -5,7 +5,6 @@ import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@KotlinCoreEnvironmentTest
@@ -13,18 +12,118 @@ class SuspendFunWithFlowReturnTypeSpec(val env: KotlinCoreEnvironment) {
val subject = SuspendFunWithFlowReturnType(Config.empty)
@Nested
inner class `SuspendFunWithFlowReturn` {
@Test
fun `reports when top-level suspend function has explicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.yield
@Test
fun `reports when top-level suspend function has explicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.yield
suspend fun flowValues(): Flow<Long> {
yield()
return flowOf(1L, 2L, 3L)
}
suspend fun stateFlowValues(): StateFlow<Long> {
yield()
return MutableStateFlow(value = 1L)
}
suspend fun mutableStateFlowValues(): MutableStateFlow<Long> {
yield()
return MutableStateFlow(value = 1L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
@Test
fun `reports when top-level suspend function has explicit Flow return type and star import used`() {
val code = """
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.yield
suspend fun flowValues(): Flow<Long> {
yield()
return flowOf(1L, 2L, 3L)
}
suspend fun stateFlowValues(): StateFlow<Long> {
yield()
return MutableStateFlow(value = 1L)
}
suspend fun mutableStateFlowValues(): MutableStateFlow<Long> {
yield()
return MutableStateFlow(value = 1L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
@Test
fun `reports when top-level suspend function has explicit FQN Flow return type`() {
val code = """
import kotlinx.coroutines.yield
suspend fun flowValues(): kotlinx.coroutines.flow.Flow<Long> {
yield()
return kotlinx.coroutines.flow.flowOf(1L, 2L, 3L)
}
suspend fun stateFlowValues(): kotlinx.coroutines.flow.StateFlow<Long> {
yield()
return kotlinx.coroutines.flow.MutableStateFlow(value = 1L)
}
suspend fun mutableStateFlowValues(): kotlinx.coroutines.flow.MutableStateFlow<Long> {
yield()
return kotlinx.coroutines.flow.MutableStateFlow(value = 1L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
@Test
fun `reports when top-level suspend function has implicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow
suspend fun flowValues() = flowOf(1L, 2L, 3L)
suspend fun mutableStateFlowValues() = MutableStateFlow(value = 1L)
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(2)
}
@Test
fun `reports when interface suspend function has explicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
interface ValuesRepository {
suspend fun flowValues(): Flow<Long>
suspend fun stateFlowValues(): StateFlow<Long>
suspend fun mutableStateFlowValues(): MutableStateFlow<Long>
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
@Test
fun `reports when class suspend function has explicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.yield
class ValuesRepository {
suspend fun flowValues(): Flow<Long> {
yield()
return flowOf(1L, 2L, 3L)
@@ -39,246 +138,142 @@ class SuspendFunWithFlowReturnTypeSpec(val env: KotlinCoreEnvironment) {
yield()
return MutableStateFlow(value = 1L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
@Test
fun `reports when top-level suspend function has explicit Flow return type and star import used`() {
val code = """
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.yield
suspend fun flowValues(): Flow<Long> {
yield()
return flowOf(1L, 2L, 3L)
}
suspend fun stateFlowValues(): StateFlow<Long> {
yield()
return MutableStateFlow(value = 1L)
}
suspend fun mutableStateFlowValues(): MutableStateFlow<Long> {
yield()
return MutableStateFlow(value = 1L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
@Test
fun `reports when top-level suspend function has explicit FQN Flow return type`() {
val code = """
import kotlinx.coroutines.yield
suspend fun flowValues(): kotlinx.coroutines.flow.Flow<Long> {
yield()
return kotlinx.coroutines.flow.flowOf(1L, 2L, 3L)
}
suspend fun stateFlowValues(): kotlinx.coroutines.flow.StateFlow<Long> {
yield()
return kotlinx.coroutines.flow.MutableStateFlow(value = 1L)
}
suspend fun mutableStateFlowValues(): kotlinx.coroutines.flow.MutableStateFlow<Long> {
yield()
return kotlinx.coroutines.flow.MutableStateFlow(value = 1L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
@Test
fun `reports when top-level suspend function has implicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow
@Test
fun `reports when class suspend function has implicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow
class ValuesRepository {
suspend fun flowValues() = flowOf(1L, 2L, 3L)
suspend fun mutableStateFlowValues() = MutableStateFlow(value = 1L)
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(2)
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(2)
}
@Test
fun `reports when interface suspend function has explicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@Test
fun `reports when suspend extension function has explicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.yield
interface ValuesRepository {
suspend fun flowValues(): Flow<Long>
suspend fun stateFlowValues(): StateFlow<Long>
suspend fun mutableStateFlowValues(): MutableStateFlow<Long>
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
suspend fun Long.flowValues(): Flow<Long> {
yield()
return (0..this).asFlow()
}
@Test
fun `reports when class suspend function has explicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.yield
suspend fun Long.stateFlowValues(): StateFlow<Long> {
yield()
return MutableStateFlow(value = this)
}
class ValuesRepository {
suspend fun flowValues(): Flow<Long> {
yield()
return flowOf(1L, 2L, 3L)
}
suspend fun Long.mutableStateFlowValues(): MutableStateFlow<Long> {
yield()
return MutableStateFlow(value = this)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
suspend fun stateFlowValues(): StateFlow<Long> {
yield()
return MutableStateFlow(value = 1L)
}
@Test
fun `reports when suspend extension function has implicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.MutableStateFlow
suspend fun mutableStateFlowValues(): MutableStateFlow<Long> {
yield()
return MutableStateFlow(value = 1L)
}
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
suspend fun Long.flowValues() = (0..this).asFlow()
suspend fun Long.mutableStateFlowValues() = MutableStateFlow(value = this)
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(2)
}
@Test
fun `reports when class suspend function has implicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow
@Test
fun `does not report when suspend lambda has Flow return type`() {
val code = """
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class ValuesRepository {
suspend fun flowValues() = flowOf(1L, 2L, 3L)
suspend fun mutableStateFlowValues() = MutableStateFlow(value = 1L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(2)
}
fun doSomething1(block: suspend () -> Flow<Long>) {
TODO()
}
fun doSomething2(block: suspend () -> StateFlow<Long>) {
TODO()
}
@Test
fun `reports when suspend extension function has explicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.yield
fun doSomething3(block: suspend () -> MutableStateFlow<Long>) {
TODO()
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
suspend fun Long.flowValues(): Flow<Long> {
yield()
return (0..this).asFlow()
}
@Test
fun `does not report when suspend functions have non-Flow return types`() {
val code = """
import kotlinx.coroutines.delay
suspend fun Long.stateFlowValues(): StateFlow<Long> {
yield()
return MutableStateFlow(value = this)
}
suspend fun delayValue(value: Long): Long {
delay(1_000L)
return value
}
suspend fun Long.mutableStateFlowValues(): MutableStateFlow<Long> {
yield()
return MutableStateFlow(value = this)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(3)
}
suspend fun delayValue2(value: Long) = value.apply { delay(1_000L) }
@Test
fun `reports when suspend extension function has implicit Flow return type`() {
val code = """
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.MutableStateFlow
suspend fun Long.delayValue(): Long {
delay(1_000L)
return this
}
suspend fun Long.flowValues() = (0..this).asFlow()
suspend fun Long.mutableStateFlowValues() = MutableStateFlow(value = this)
"""
assertThat(subject.compileAndLintWithContext(env, code)).hasSize(2)
}
suspend fun Long.delayValue2() = this.apply { delay(1_000L) }
@Test
fun `does not report when suspend lambda has Flow return type`() {
val code = """
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
interface ValueRepository {
suspend fun getValue(): Long
}
fun doSomething1(block: suspend () -> Flow<Long>) {
TODO()
}
fun doSomething2(block: suspend () -> StateFlow<Long>) {
TODO()
}
fun doSomething3(block: suspend () -> MutableStateFlow<Long>) {
TODO()
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `does not report when suspend functions have non-Flow return types`() {
val code = """
import kotlinx.coroutines.delay
suspend fun delayValue(value: Long): Long {
class ValueRepository2 {
suspend fun getValue(): Long {
delay(1_000L)
return value
return 5L
}
}
suspend fun delayValue2(value: Long) = value.apply { delay(1_000L) }
class ValueRepository3 {
suspend fun getValue() = 5L.apply { delay(1_000L) }
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
suspend fun Long.delayValue(): Long {
delay(1_000L)
return this
}
@Test
fun `does not report when non-suspend functions have Flow return types`() {
val code = """
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
suspend fun Long.delayValue2() = this.apply { delay(1_000L) }
fun flowValues(): Flow<Long> {
return flowOf(1L, 2L, 3L)
}
interface ValueRepository {
suspend fun getValue(): Long
}
fun stateFlowValues(): StateFlow<Long> {
return MutableStateFlow(value = 1L)
}
class ValueRepository2 {
suspend fun getValue(): Long {
delay(1_000L)
return 5L
}
}
class ValueRepository3 {
suspend fun getValue() = 5L.apply { delay(1_000L) }
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
@Test
fun `does not report when non-suspend functions have Flow return types`() {
val code = """
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
fun flowValues(): Flow<Long> {
return flowOf(1L, 2L, 3L)
}
fun stateFlowValues(): StateFlow<Long> {
return MutableStateFlow(value = 1L)
}
fun mutableStateFlowValues(): MutableStateFlow<Long> {
return MutableStateFlow(value = 1L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
fun mutableStateFlowValues(): MutableStateFlow<Long> {
return MutableStateFlow(value = 1L)
}
"""
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}
}

View File

@@ -18,180 +18,164 @@ import java.net.URI
class AbsentOrWrongFileLicenseSpec {
@Test
fun `file with correct license header reports nothing`() {
val findings = checkLicence(
"""
/* LICENSE */
package cases
"""
)
assertThat(findings).isEmpty()
}
@Test
fun `file with incorrect license header reports missed license header`() {
val findings = checkLicence(
"""
/* WRONG LICENSE */
package cases
"""
)
assertThat(findings).hasSize(1)
}
@Test
fun `file with absent license header reports missed license header`() {
val findings = checkLicence(
"""
package cases
"""
)
assertThat(findings).hasSize(1)
}
@Nested
inner class `AbsentOrWrongFileLicense rule` {
inner class `file with correct license header using regex matching` {
@Nested
inner class `file with correct license header` {
@Test
fun `reports nothing for 2016`() {
val findings = checkLicence(
"""
//
// Copyright 2016 Artur Bosch & Contributors
// http://www.apache.org/licenses/LICENSE-2.0
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cases
""".trimIndent(),
isRegexLicense = true
)
@Test
fun `reports nothing`() {
val findings = checkLicence(
"""
/* LICENSE */
package cases
"""
)
assertThat(findings).isEmpty()
}
assertThat(findings).isEmpty()
}
@Nested
inner class `file with incorrect license header` {
@Test
fun `reports nothing for 2021`() {
val findings = checkLicence(
"""
//
// Copyright 2021 Artur Bosch & Contributors
// http://www.apache.org/licenses/LICENSE-2.0
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cases
""".trimIndent(),
isRegexLicense = true
)
@Test
fun `reports missed license header`() {
val findings = checkLicence(
"""
/* WRONG LICENSE */
package cases
"""
)
assertThat(findings).isEmpty()
}
}
assertThat(findings).hasSize(1)
}
@Nested
inner class `file with incorrect license header using regex matching` {
@Test
fun `file with missing license header`() {
val findings = checkLicence(
"""
package cases
""".trimIndent(),
isRegexLicense = true
)
assertThat(findings).hasSize(1)
}
@Nested
inner class `file with absent license header` {
@Test
fun `file with license header not on the first line`() {
val findings = checkLicence(
"""
package cases
//
// Copyright 2021 Artur Bosch & Contributors
// http://www.apache.org/licenses/LICENSE-2.0
// See the License for the specific language governing permissions and
// limitations under the License.
//
""".trimIndent(),
isRegexLicense = true
)
@Test
fun `reports missed license header`() {
val findings = checkLicence(
"""
package cases
"""
)
assertThat(findings).hasSize(1)
}
assertThat(findings).hasSize(1)
}
@Nested
inner class `file with correct license header using regex matching` {
@Test
fun `file with incomplete license header`() {
val findings = checkLicence(
"""
//
// Copyright 2021 Artur Bosch & Contributors
//
package cases
""".trimIndent(),
isRegexLicense = true
)
@Test
fun `reports nothing for 2016`() {
val findings = checkLicence(
"""
//
// Copyright 2016 Artur Bosch & Contributors
// http://www.apache.org/licenses/LICENSE-2.0
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cases
""".trimIndent(),
isRegexLicense = true
)
assertThat(findings).isEmpty()
}
@Test
fun `reports nothing for 2021`() {
val findings = checkLicence(
"""
//
// Copyright 2021 Artur Bosch & Contributors
// http://www.apache.org/licenses/LICENSE-2.0
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cases
""".trimIndent(),
isRegexLicense = true
)
assertThat(findings).isEmpty()
}
assertThat(findings).hasSize(1)
}
@Nested
inner class `file with incorrect license header using regex matching` {
@Test
fun `file with too many empty likes in license header`() {
val findings = checkLicence(
"""
//
//
// Copyright 2021 Artur Bosch & Contributors
// http://www.apache.org/licenses/LICENSE-2.0
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cases
""".trimIndent(),
isRegexLicense = true
)
@Test
fun `file with missing license header`() {
val findings = checkLicence(
"""
package cases
""".trimIndent(),
isRegexLicense = true
)
assertThat(findings).hasSize(1)
}
assertThat(findings).hasSize(1)
}
@Test
fun `file with incorrect year in license header`() {
val findings = checkLicence(
"""
//
// Copyright 202 Artur Bosch & Contributors
// http://www.apache.org/licenses/LICENSE-2.0
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cases
""".trimIndent(),
isRegexLicense = true
)
@Test
fun `file with license header not on the first line`() {
val findings = checkLicence(
"""
package cases
//
// Copyright 2021 Artur Bosch & Contributors
// http://www.apache.org/licenses/LICENSE-2.0
// See the License for the specific language governing permissions and
// limitations under the License.
//
""".trimIndent(),
isRegexLicense = true
)
assertThat(findings).hasSize(1)
}
@Test
fun `file with incomplete license header`() {
val findings = checkLicence(
"""
//
// Copyright 2021 Artur Bosch & Contributors
//
package cases
""".trimIndent(),
isRegexLicense = true
)
assertThat(findings).hasSize(1)
}
@Test
fun `file with too many empty likes in license header`() {
val findings = checkLicence(
"""
//
//
// Copyright 2021 Artur Bosch & Contributors
// http://www.apache.org/licenses/LICENSE-2.0
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cases
""".trimIndent(),
isRegexLicense = true
)
assertThat(findings).hasSize(1)
}
@Test
fun `file with incorrect year in license header`() {
val findings = checkLicence(
"""
//
// Copyright 202 Artur Bosch & Contributors
// http://www.apache.org/licenses/LICENSE-2.0
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cases
""".trimIndent(),
isRegexLicense = true
)
assertThat(findings).hasSize(1)
}
assertThat(findings).hasSize(1)
}
}
}

View File

@@ -2,50 +2,45 @@ package io.gitlab.arturbosch.detekt.rules.documentation
import io.gitlab.arturbosch.detekt.test.compileAndLint
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class CommentOverPrivateMethodSpec {
val subject = CommentOverPrivateFunction()
@Nested
inner class `CommentOverPrivateFunction rule` {
@Test
fun `reports private method with a comment`() {
val code = """
class Test {
/**
* asdf
*/
private fun f() {}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports private method with a comment`() {
val code = """
class Test {
/**
* asdf
*/
private fun f() {}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report public method with a comment`() {
val code = """
/**
* asdf
*/
fun f() {}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report public method with a comment`() {
val code = """
@Test
fun `does not report public method in a class with a comment`() {
val code = """
class Test {
/**
* asdf
*/
fun f() {}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report public method in a class with a comment`() {
val code = """
class Test {
/**
* asdf
*/
fun f() {}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}

View File

@@ -2,61 +2,56 @@ package io.gitlab.arturbosch.detekt.rules.documentation
import io.gitlab.arturbosch.detekt.test.compileAndLint
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class CommentOverPrivatePropertiesSpec {
val subject = CommentOverPrivateProperty()
private val subject = CommentOverPrivateProperty()
@Nested
inner class `CommentOverPrivateProperty rule` {
@Test
fun `reports private property with a comment`() {
val code = """
/**
* asdf
*/
private val v = 1
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports private property with a comment`() {
val code = """
@Test
fun `does not report public property with a comment`() {
val code = """
/**
* asdf
*/
val v = 1
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports private property in class with a comment`() {
val code = """
class Test {
/**
* asdf
*/
private val v = 1
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report public property with a comment`() {
val code = """
@Test
fun `does not report public property in class with a comment`() {
val code = """
class Test {
/**
* asdf
*/
val v = 1
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `reports private property in class with a comment`() {
val code = """
class Test {
/**
* asdf
*/
private val v = 1
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not report public property in class with a comment`() {
val code = """
class Test {
/**
* asdf
*/
val v = 1
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}

View File

@@ -8,151 +8,148 @@ import org.junit.jupiter.api.Test
class DeprecatedBlockTagSpec {
val subject = DeprecatedBlockTag()
@Test
fun `does not report regular kdoc block`() {
val code = """
/**
* This is just a regular kdoc block.
*
* Nothing to see here...
*/
val v = 2
"""
assertThat(subject.compileAndLint(code)).hasSize(0)
}
@Nested
inner class `DeprecatedBlockTag rule` {
inner class `reporting deprecation tag on kdoc block` {
val code = """
/**
* I am a KDoc block
*
* @deprecated oh no, this should not be here
*/
fun ohNo() { }
"""
@Test
fun `does not report regular kdoc block`() {
val code = """
/**
* This is just a regular kdoc block.
*
* Nothing to see here...
*/
val v = 2
"""
assertThat(subject.compileAndLint(code)).hasSize(0)
fun `has found something`() {
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Nested
inner class `reporting deprecation tag on kdoc block` {
@Test
fun `correct message`() {
assertThat(subject.compileAndLint(code)[0]).hasMessage(
"@deprecated tag block does not properly report " +
"deprecation in Kotlin, use @Deprecated annotation instead"
)
}
}
@Nested
inner class `reporting deprecation tag wherever @Deprecated is available` {
@Test
fun `report deprecation tag on class`() {
val code = """
/**
* I am a KDoc block
*
* @deprecated oh no, this should not be here
*/
fun ohNo() { }
/**
* Hello there
*
* @deprecated This thing is deprecated
*/
class Thing { }
"""
@Test
fun `has found something`() {
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `correct message`() {
assertThat(subject.compileAndLint(code)[0]).hasMessage(
"@deprecated tag block does not properly report " +
"deprecation in Kotlin, use @Deprecated annotation instead"
)
}
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Nested
inner class `reporting deprecation tag wherever @Deprecated is available` {
@Test
fun `report deprecation tag on class`() {
val code = """
@Test
fun `report deprecation tag on property`() {
val code = """
class Thing {
/**
* Hello there
* A thing you should not use
*
* @deprecated This thing is deprecated
* @deprecated Do not use that
*/
class Thing { }
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
val doNotUseMe = 0
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `report deprecation tag on property`() {
val code = """
@Test
fun `report deprecation tag on annotation class`() {
val code = """
/**
* An annotation you should not use
*
* @deprecated Do not use that
*/
annotation class Thing()
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `report deprecation tag on constructor`() {
val code = """
class Thing {
/**
* A thing you should not use
* A constructor you should not use
*
* @deprecated Do not use that
*/
val doNotUseMe = 0
constructor(something: String)
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `report deprecation tag on annotation class`() {
val code = """
/**
* An annotation you should not use
*
* @deprecated Do not use that
*/
annotation class Thing()
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `report deprecation tag on constructor`() {
val code = """
class Thing {
@Test
fun `report deprecation tag on property setter`() {
val code = """
class Thing {
var someProperty: Int
get() = 10
/**
* A constructor you should not use
* Do not use this setter
*
* @deprecated Do not use that
* @deprecated Do not use it
*/
constructor(something: String)
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
set(value) { println(value) }
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `report deprecation tag on property setter`() {
val code = """
class Thing {
var someProperty: Int
get() = 10
/**
* Do not use this setter
*
* @deprecated Do not use it
*/
set(value) { println(value) }
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `report deprecation tag on property getter`() {
val code = """
class Thing {
var someProperty: Int
/**
* Do not use this getter
*
* @deprecated Do not use it
*/
get() = 10
set(value) { println(value) }
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `report deprecation tag on property getter`() {
val code = """
class Thing {
var someProperty: Int
/**
* Do not use this getter
*
* @deprecated Do not use it
*/
get() = 10
set(value) { println(value) }
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `report deprecation tag on typealias`() {
val code = """
/**
* This alias is pointless, do not use it
*
* @deprecated Do not use this typealias
*/
typealias VeryString = String
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `report deprecation tag on typealias`() {
val code = """
/**
* This alias is pointless, do not use it
*
* @deprecated Do not use this typealias
*/
typealias VeryString = String
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
}
}

View File

@@ -2,209 +2,204 @@ package io.gitlab.arturbosch.detekt.rules.documentation
import io.gitlab.arturbosch.detekt.test.compileAndLint
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class EndOfSentenceFormatSpec {
val subject = EndOfSentenceFormat()
@Nested
inner class `KDocStyle rule` {
@Test
fun `reports invalid KDoc endings on classes`() {
val code = """
/** Some doc */
class Test {
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports invalid KDoc endings on classes`() {
val code = """
@Test
fun `reports invalid KDoc endings on function with expression body`() {
val code = """
/** Some doc */
class Test {
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
fun f(x: Int, y: Int, z: Int) =
if (x == 0) y + z else x + y
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports invalid KDoc endings on function with expression body`() {
val code = """
/** Some doc */
fun f(x: Int, y: Int, z: Int) =
if (x == 0) y + z else x + y
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
@Test
fun `reports invalid KDoc endings on properties`() {
val code = """
class Test {
/** Some doc */
val test = 3
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports invalid KDoc endings on properties`() {
val code = """
class Test {
/** Some doc */
val test = 3
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports invalid KDoc endings on top-level functions`() {
val code = """
/** Some doc */
fun test() = 3
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports invalid KDoc endings on top-level functions`() {
val code = """
@Test
fun `reports invalid KDoc endings on functions`() {
val code = """
class Test {
/** Some doc */
fun test() = 3
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports invalid KDoc endings`() {
val code = """
class Test {
/** Some doc-- */
fun test() = 3
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports invalid KDoc endings in block`() {
val code = """
/**
* Something off abc@@
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not validate first sentence KDoc endings in a multi sentence comment`() {
val code = """
/**
* This sentence is correct.
*
* This sentence doesn't matter
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc which doesn't contain any real sentence`() {
val code = """
/**
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc which doesn't contain any real sentence but many tags`() {
val code = """
/**
* @configuration this - just an example (default: `150`)
*
* @active since v1.0.0
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc which doesn't contain any real sentence but html tags`() {
val code = """
/**
*
* <noncompliant>
* fun foo(): Unit { }
* </noncompliant>
*
* <compliant>
* fun foo() { }
* </compliant>
*
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc ending with periods`() {
val code = """
/**
* Something correct.
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc ending with questionmarks`() {
val code = """
/**
* Something correct?
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc ending with exclamation marks`() {
val code = """
/**
* Something correct!
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc ending with colon`() {
val code = """
/**
* Something correct:
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report URLs in comments`() {
val code = """
/** http://www.google.com */
class Test1 {
}
@Test
fun `reports invalid KDoc endings on functions`() {
val code = """
class Test {
/** Some doc */
fun test() = 3
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports invalid KDoc endings`() {
val code = """
class Test {
/** Some doc-- */
fun test() = 3
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `reports invalid KDoc endings in block`() {
val code = """
/**
* Something off abc@@
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).hasSize(1)
}
@Test
fun `does not validate first sentence KDoc endings in a multi sentence comment`() {
val code = """
/**
* This sentence is correct.
*
* This sentence doesn't matter
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc which doesn't contain any real sentence`() {
val code = """
/**
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc which doesn't contain any real sentence but many tags`() {
val code = """
/**
* @configuration this - just an example (default: `150`)
*
* @active since v1.0.0
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc which doesn't contain any real sentence but html tags`() {
val code = """
/**
*
* <noncompliant>
* fun foo(): Unit { }
* </noncompliant>
*
* <compliant>
* fun foo() { }
* </compliant>
*
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc ending with periods`() {
val code = """
/**
* Something correct.
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc ending with questionmarks`() {
val code = """
/**
* Something correct?
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc ending with exclamation marks`() {
val code = """
/**
* Something correct!
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report KDoc ending with colon`() {
val code = """
/**
* Something correct:
*/
class Test {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
@Test
fun `does not report URLs in comments`() {
val code = """
/** http://www.google.com */
class Test1 {
}
/** Look here
http://google.com */
class Test2 {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
/** Look here
http://google.com */
class Test2 {
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}
}

Some files were not shown because too many files have changed in this diff Show More