Skip to content

Commit

Permalink
Add unit tests for mis-indented expression body functions
Browse files Browse the repository at this point in the history
### What's done:

 * Added unit tests for mis-indented expression body functions.
 * See #1330.
  • Loading branch information
0x6675636b796f75676974687562 committed Jun 7, 2022
1 parent 0cb2ca6 commit e13686f
Show file tree
Hide file tree
Showing 2 changed files with 372 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,23 @@ import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_INDENTATION
import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID
import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationRule
import org.cqfn.diktat.ruleset.utils.indentation.IndentationConfig
import org.cqfn.diktat.util.LintTestBase

import com.pinterest.ktlint.core.LintError
import generated.WarningNames
import org.assertj.core.api.AbstractSoftAssertions
import org.assertj.core.api.AssertionErrorCollector
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions.assertSoftly
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.opentest4j.MultipleFailuresError
import java.util.function.Consumer

import java.lang.Boolean.getBoolean as getBooleanProperty

@Suppress("LargeClass")
class IndentationRuleWarnTest : LintTestBase(::IndentationRule) {
Expand Down Expand Up @@ -697,5 +708,340 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule) {
)
}

/**
* See [#1330](https://github.com/saveourtool/diktat/issues/1330).
*
* @see expressionBodyFunctionsContinuationIndent
*/
@Language("kotlin")
private val expressionBodyFunctionsSingleIndent: Array<String> = arrayOf(
"""
|@Test
|fun `checking that suppression with ignore everything works`() {
| val code =
| ""${'"'}
| @Suppress("diktat")
| fun foo() {
| val a = 1
| }
| ""${'"'}.trimIndent()
| lintMethod(code)
|}
""".trimMargin(),

"""
|val currentTime: Time
| get() =
| with(currentDateTime) {
| Time(hour = hour, minute = minute, second = second)
| }
""".trimMargin(),

"""
|fun formatDateByPattern(date: String, pattern: String = "ddMMyy"): String =
| DateTimeFormatter.ofPattern(pattern).format(LocalDate.parse(date))
""".trimMargin(),

"""
|private fun createLayoutParams(): WindowManager.LayoutParams =
| WindowManager.LayoutParams().apply { /* ... */ }
""".trimMargin(),

"""
|val offsetDelta =
| if (shimmerAnimationType != ShimmerAnimationType.FADE) translateAnim.dp
| else 2000.dp
""".trimMargin(),

"""
|private fun lerp(start: Float, stop: Float, fraction: Float): Float =
| (1 - fraction) * start + fraction * stop
""".trimMargin(),
)

/**
* See [#1330](https://github.com/saveourtool/diktat/issues/1330).
*
* @see expressionBodyFunctionsSingleIndent
*/
@Language("kotlin")
private val expressionBodyFunctionsContinuationIndent: Array<String> = arrayOf(
"""
|@Test
|fun `checking that suppression with ignore everything works`() {
| val code =
| ""${'"'}
| @Suppress("diktat")
| fun foo() {
| val a = 1
| }
| ""${'"'}.trimIndent()
| lintMethod(code)
|}
""".trimMargin(),

"""
|val currentTime: Time
| get() =
| with(currentDateTime) {
| Time(hour = hour, minute = minute, second = second)
| }
""".trimMargin(),

"""
|fun formatDateByPattern(date: String, pattern: String = "ddMMyy"): String =
| DateTimeFormatter.ofPattern(pattern).format(LocalDate.parse(date))
""".trimMargin(),

"""
|private fun createLayoutParams(): WindowManager.LayoutParams =
| WindowManager.LayoutParams().apply { /* ... */ }
""".trimMargin(),

"""
|val offsetDelta =
| if (shimmerAnimationType != ShimmerAnimationType.FADE) translateAnim.dp
| else 2000.dp
""".trimMargin(),

"""
|private fun lerp(start: Float, stop: Float, fraction: Float): Float =
| (1 - fraction) * start + fraction * stop
""".trimMargin(),
)

/**
* See [#1330](https://github.com/saveourtool/diktat/issues/1330).
*/
@Test
fun `expression body functions should be properly indented (extendedIndentAfterOperators = true)`() {
assumeTrue(true || testsCanBeMuted) {
"Skipping a known-to-fail test"
}

val defaultConfig = IndentationConfig("newlineAtEnd" to false)
val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to true)

lintMultipleMethods(
expressionBodyFunctionsContinuationIndent,
lintErrors = emptyArray(),
customConfig.asRulesConfigList()
)
}

/**
* See [#1330](https://github.com/saveourtool/diktat/issues/1330).
*/
@Test
fun `expression body functions should be properly indented (extendedIndentAfterOperators = false)`() {
assumeTrue(true || testsCanBeMuted) {
"Skipping a known-to-fail test"
}

val defaultConfig = IndentationConfig("newlineAtEnd" to false)
val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to false)

lintMultipleMethods(
expressionBodyFunctionsSingleIndent,
lintErrors = emptyArray(),
customConfig.asRulesConfigList()
)
}

/**
* See [#1330](https://github.com/saveourtool/diktat/issues/1330).
*/
@Test
fun `expression body functions should be reported if mis-indented (extendedIndentAfterOperators = true)`() {
assumeTrue(true || testsCanBeMuted) {
"Skipping a known-to-fail test"
}

val defaultConfig = IndentationConfig("newlineAtEnd" to false)
val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to true)

assertSoftly { softly ->
expressionBodyFunctionsSingleIndent.forEach { code ->
softly.assertThat(lintResult(code, customConfig.asRulesConfigList()))
.describedAs("lint result for \"$code\"")
.isNotEmpty
.hasSizeBetween(1, 3).allSatisfy(Consumer { lintError ->
assertThat(lintError.ruleId).describedAs("ruleId").isEqualTo(ruleId)
assertThat(lintError.canBeAutoCorrected).describedAs("canBeAutoCorrected").isTrue
assertThat(lintError.detail).matches(WARN_TEXT_REGEX)
})
}
}
}

/**
* See [#1330](https://github.com/saveourtool/diktat/issues/1330).
*/
@Test
fun `expression body functions should be reported if mis-indented (extendedIndentAfterOperators = false)`() {
assumeTrue(true || testsCanBeMuted) {
"Skipping a known-to-fail test"
}

val defaultConfig = IndentationConfig("newlineAtEnd" to false)
val customConfig = defaultConfig.withCustomParameters("extendedIndentAfterOperators" to false)

assertSoftly { softly ->
expressionBodyFunctionsContinuationIndent.forEach { code ->
softly.assertThat(lintResult(code, customConfig.asRulesConfigList()))
.describedAs("lint result for \"$code\"")
.isNotEmpty
.hasSizeBetween(1, 3).allSatisfy(Consumer { lintError ->
assertThat(lintError.ruleId).describedAs("ruleId").isEqualTo(ruleId)
assertThat(lintError.canBeAutoCorrected).describedAs("canBeAutoCorrected").isTrue
assertThat(lintError.detail).matches(WARN_TEXT_REGEX)
})
}
}
}

/**
* @see WARN_TEXT_REGEX
*/
private fun warnText(expected: Int, actual: Int) = "${WRONG_INDENTATION.warnText()} expected $expected but was $actual"

/**
* Creates an `IndentationConfig` from zero or more
* [config entries][configEntries]. Invoke without arguments to create a
* default `IndentationConfig`.
*
* @see [IndentationConfig]
*/
@Suppress("TestFunctionName")
private fun IndentationConfig(vararg configEntries: Pair<String, Any>): IndentationConfig =
IndentationConfig(mapOf(*configEntries).mapValues(Any::toString))

private fun IndentationConfig.withCustomParameters(vararg configEntries: Pair<String, Any>): Map<String, String> =
mutableMapOf(
"alignedParameters" to "$alignedParameters",
"indentationSize" to "$indentationSize",
"newlineAtEnd" to "$newlineAtEnd",
"extendedIndentOfParameters" to "$extendedIndentOfParameters",
"extendedIndentAfterOperators" to "$extendedIndentAfterOperators",
"extendedIndentBeforeDot" to "$extendedIndentBeforeDot",
).apply {
configEntries.forEach { (key, value) ->
this[key] = value.toString()
}
}

/**
* Converts this map to a list containing a single [RulesConfig].
*/
private fun Map<String, String>.asRulesConfigList(): List<RulesConfig> {
return listOf(
RulesConfig(
name = WRONG_INDENTATION.name,
enabled = true,
configuration = this
)
)
}

/**
* When within a scope of an `AbstractSoftAssertions`, collects failures
* thrown by [block], correctly accumulating multiple failures from nested
* soft assertions (if any).
*
* @see AssertionErrorCollector.collectAssertionError
*/
private fun AbstractSoftAssertions.collectAssertionErrors(block: () -> Unit) =
try {
block()
} catch (mfe: MultipleFailuresError) {
mfe.failures.forEach { failure ->
when (failure) {
is AssertionError -> collectAssertionError(failure)
else -> fail(failure.toString(), failure)
}
}
} catch (ae: AssertionError) {
collectAssertionError(ae)
} catch (t: Throwable) {
fail(t.toString(), t)
}

/**
* Similar to [lintMethod], but can be invoked from a scope of
* `AbstractSoftAssertions` in order to accumulate test results from linting
* _multiple_ code fragments.
*
* @param rulesConfigList the list of rules which can optionally override
* the [default value][LintTestBase.rulesConfigList].
* @see lintMethod
*/
private fun AbstractSoftAssertions.lintMethodSoftly(
@Language("kotlin") code: String,
vararg lintErrors: LintError,
rulesConfigList: List<RulesConfig>? = null,
fileName: String? = null
) {
require(code.isNotBlank()) {
"code is blank"
}

collectAssertionErrors {
lintMethod(code, lintErrors = lintErrors, rulesConfigList, fileName)
}
}

/**
* Tests multiple code [fragments] using the same
* [rule configuration][rulesConfigList].
*
* All code fragments get concatenated together and the resulting, bigger
* fragment gets tested, too.
*
* @param rulesConfigList the list of rules which can optionally override
* the [default value][LintTestBase.rulesConfigList].
* @see lintMethod
*/
private fun lintMultipleMethods(
@Language("kotlin") fragments: Array<String>,
vararg lintErrors: LintError,
rulesConfigList: List<RulesConfig>? = null,
fileName: String? = null
) {
require(fragments.isNotEmpty()) {
"code fragments is an empty array"
}

assertSoftly { softly ->
sequence {
yieldAll(fragments.asSequence())

/*
* All fragments concatenated.
*/
yield(fragments.joinToString(separator = "\n\n"))
}.forEach { fragment ->
softly.lintMethodSoftly(
fragment,
lintErrors = lintErrors,
rulesConfigList,
fileName
)
}
}
}

/**
* @return `true` if known-to-fail unit tests can be muted on the CI server.
*/
private val testsCanBeMuted: Boolean
get() =
getBooleanProperty("tests.can.be.muted")

companion object {
/**
* @see warnText
*/
@Language("RegExp")
private val WARN_TEXT_REGEX = "^\\Q${WRONG_INDENTATION.warnText()}\\E expected \\d+ but was \\d+$"
}
}
Loading

0 comments on commit e13686f

Please sign in to comment.