Skip to content

Commit

Permalink
Enhancements for tests (#1644)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrey Shcheglov <andrewbass+saveourtool@gmail.com>
  • Loading branch information
nulls and 0x6675636b796f75676974687562 authored Mar 27, 2023
1 parent ae84613 commit e4e6465
Show file tree
Hide file tree
Showing 12 changed files with 465 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org.cqfn.diktat.plugin.maven

import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider
import org.cqfn.diktat.ruleset.utils.isKotlinCodeOrScript

import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.LintError
Expand Down Expand Up @@ -207,7 +208,7 @@ abstract class DiktatBaseMojo : AbstractMojo() {
directory
.walk()
.filter { file ->
file.isDirectory || file.extension.let { it == "kt" || it == "kts" }
file.isDirectory || file.toPath().isKotlinCodeOrScript()
}
.filter { it.isFile }
.filterNot { file -> file in excludedFiles || excludedDirs.any { file.startsWith(it) } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class IndentationRule(configRules: List<RulesConfig>) : DiktatRule(
::KdocIndentationChecker,
::CustomGettersAndSettersChecker,
::ArrowInWhenChecker
).map { it.invoke(configuration) }
).map { it(configuration) }

if (checkIsIndentedWithSpaces(node)) {
checkIndentation(node)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@

package org.cqfn.diktat.ruleset.utils

import java.nio.file.Path
import kotlin.io.path.extension

internal const val SRC_DIRECTORY_NAME = "src"
private const val KOTLIN_EXTENSION = "kt"
private const val KOTLIN_SCRIPT_EXTENSION = KOTLIN_EXTENSION + "s"

/**
* Splits [this] string by file path separator.
Expand All @@ -17,11 +22,25 @@ fun String.splitPathToDirs(): List<String> =
.split("/")

/**
* Checks if [this] String is a name of a kotlin script file by checking whether file extension equals 'kts'
* Checks if [this] [String] is a name of a kotlin script file by checking whether file extension equals 'kts'
*
* @return true if this is a kotlin script file name, false otherwise
*/
fun String.isKotlinScript() = endsWith(".$KOTLIN_SCRIPT_EXTENSION")

/**
* Check if [this] [Path] is a kotlin script by checking whether an extension equals to 'kts'
*
* @return true if this is a kotlin script file name, false otherwise
*/
fun String.isKotlinScript() = endsWith(".kts")
fun Path.isKotlinScript() = this.extension.lowercase() == KOTLIN_SCRIPT_EXTENSION

/**
* Check if [this] [Path] is a kotlin code or script by checking whether an extension equals to `kt` or 'kts'
*
* @return true if this is a kotlin code or script file name, false otherwise
*/
fun Path.isKotlinCodeOrScript() = this.extension.lowercase() in setOf(KOTLIN_EXTENSION, KOTLIN_SCRIPT_EXTENSION)

/**
* Checks if [this] String is a name of a gradle kotlin script file by checking whether file extension equals 'gradle.kts'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ class HeaderCommentRuleTest : LintTestBase(::HeaderCommentRule) {
lintMethod(
"""
/*
* Copyright (c) 2023 My Company, Ltd. All rights reserved.
* Copyright (c) $curYear My Company, Ltd. All rights reserved.
*/
/**
* Very useful description, why this file has two classes
Expand All @@ -244,7 +244,7 @@ class HeaderCommentRuleTest : LintTestBase(::HeaderCommentRule) {
lintMethod(
"""
/*
* Copyright (c) My Company, Ltd. 2012-2023. All rights reserved.
* Copyright (c) My Company, Ltd. 2012-$curYear. All rights reserved.
*/
/**
* Very useful description, why this file has two classes
Expand All @@ -267,7 +267,7 @@ class HeaderCommentRuleTest : LintTestBase(::HeaderCommentRule) {
lintMethod(
"""
/*
Copyright (c) My Company, Ltd. 2021-2023. All rights reserved.
Copyright (c) My Company, Ltd. 2021-$curYear. All rights reserved.
*/
/**
* Very useful description, why this file has two classes
Expand All @@ -290,7 +290,7 @@ class HeaderCommentRuleTest : LintTestBase(::HeaderCommentRule) {
lintMethod(
"""
/*
* Copyright (c) My Company, Ltd. 2002-2023. All rights reserved.
* Copyright (c) My Company, Ltd. 2002-$curYear. All rights reserved.
*/
/**
* Very useful description, why this file has two classes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.cqfn.diktat.ruleset.junit

import org.cqfn.diktat.test.framework.util.resetPermissions
import org.cqfn.diktat.test.framework.util.tryToDeleteOnExit
import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource
import java.io.IOException
import java.nio.file.DirectoryNotEmptyException
Expand All @@ -15,6 +17,7 @@ import kotlin.io.path.absolute
import kotlin.io.path.deleteExisting
import kotlin.io.path.isDirectory
import kotlin.io.path.notExists
import kotlin.io.path.relativeToOrSelf

/**
* @property directory the temporary directory (will be recursively deleted once
Expand Down Expand Up @@ -102,48 +105,16 @@ data class CloseablePath(val directory: Path) : CloseableResource {
@Suppress("WRONG_NEWLINES") // False positives, see #1495.
val joinedPaths = keys
.asSequence()
.map(Path::tryToDeleteOnExit)
.map { path ->
path.tryToDeleteOnExit()
}.map { path ->
path.relativizeSafely()
}.map(Any::toString)
path.relativeToOrSelf(directory)
}
.map(Any::toString)
.joinToString()

return IOException("Failed to delete temp directory ${directory.absolute()}. " +
"The following paths could not be deleted (see suppressed exceptions for details): $joinedPaths").apply {
values.forEach(this::addSuppressed)
}
}

private fun Path.tryToDeleteOnExit(): Path {
try {
toFile().deleteOnExit()
} catch (_: UnsupportedOperationException) {
/*
* Ignore.
*/
}

return this
}

private fun Path.relativizeSafely(): Path =
try {
directory.relativize(this)
} catch (_: IllegalArgumentException) {
this
}

private companion object {
private fun Path.resetPermissions() {
toFile().apply {
setReadable(true)
setWritable(true)

if (isDirectory) {
setExecutable(true)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.isLeaf
import com.pinterest.ktlint.core.ast.nextCodeSibling
import com.pinterest.ktlint.core.ast.nextSibling
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType
Expand Down Expand Up @@ -807,7 +808,7 @@ private class PrettyPrintingVisitor(private val elementType: IElementType,
companion object {
fun assertStringRepr(
elementType: IElementType,
code: String,
@Language("kotlin") code: String,
level: Int = 0,
maxLevel: Int = -1,
expected: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.common.config.rules.RulesConfigReader
import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider
import org.cqfn.diktat.ruleset.rules.OrderedRuleSet.Companion.delegatee
import org.cqfn.diktat.test.framework.util.filterContentMatches

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.RuleSet
Expand All @@ -21,7 +22,12 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test

import java.io.File
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
import kotlin.io.path.nameWithoutExtension
import kotlin.io.path.walk

/**
* simple class for emulating RuleSetProvider to inject .yml rule configuration and mock this part of code
Expand All @@ -38,28 +44,19 @@ class DiktatRuleSetProvider4Test(private val ruleSupplier: (rulesConfigList: Lis
}

class DiktatRuleSetProviderTest {
@OptIn(ExperimentalPathApi::class)
@Suppress("UnsafeCallOnNullableType")
@Test
fun `check DiktatRuleSetProviderTest contain all rules`() {
val path = "${System.getProperty("user.dir")}/src/main/kotlin/org/cqfn/diktat/ruleset/rules"
val filesName = File(path)
val fileNames = Path(path)
.walk()
.filter { it.isFile }
.filter { file ->
/*
* Include only those files which contain `Rule` or `DiktatRule`
* descendants (any of the 1st 150 lines contains a superclass
* constructor call).
*/
val constructorCall = Regex(""":\s*(?:Diktat)?Rule\s*\(""")
file.bufferedReader().lineSequence().take(150)
.any { line ->
line.contains(constructorCall)
}
}
.map { it.nameWithoutExtension }
.filterNot { it in ignoreFile }
val rulesName = DiktatRuleSetProvider().get()
.filter(Path::isRegularFile)
.filterContentMatches(linesToRead = 150, Regex(""":\s*(?:Diktat)?Rule\s*\("""))
.map(Path::nameWithoutExtension)
.filterNot { it in ignoredFileNames }
.toList()
val ruleNames = DiktatRuleSetProvider().get()
.asSequence()
.onEachIndexed { index, rule ->
if (index != 0) {
Expand All @@ -70,16 +67,22 @@ class DiktatRuleSetProviderTest {
}
}
.map { it.delegatee() }
.map { it::class.simpleName!! }
.filterNot { it == "DummyWarning" }
.map { it::class.simpleName }
.filterNotNull()
.filterNot { it in ignoredRuleNames }
.toList()
assertThat(rulesName.sorted()).containsExactlyElementsOf(filesName.sorted().toList())
assertThat(fileNames).isNotEmpty
assertThat(ruleNames).isNotEmpty
assertThat(ruleNames.sorted()).containsExactlyElementsOf(fileNames.sorted())
}

companion object {
private val ignoreFile = listOf(
private val ignoredFileNames = listOf(
"DiktatRule",
"OrderedRuleSet",
)
private val ignoredRuleNames = listOf(
"DummyWarning",
)
}
}
75 changes: 72 additions & 3 deletions diktat-rules/src/test/kotlin/org/cqfn/diktat/util/TestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,90 @@ import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.RuleSet
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.fail
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

import java.io.Reader
import java.util.concurrent.atomic.AtomicInteger
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract

internal const val TEST_FILE_NAME = "TestFileName.kt"

private val debuggerPromptPrefixes: Array<out String> = arrayOf(
"Listening for transport dt_socket at address: ",
"Listening for transport dt_shmem at address: ",
)

/**
* Casts a nullable value to a non-`null` one, similarly to the `!!`
* operator.
*
* @param lazyFailureMessage the message to evaluate in case of a failure.
* @return a non-`null` value.
*/
internal fun <T> T?.assertNotNull(lazyFailureMessage: () -> String = { "Expecting actual not to be null" }): T =
this ?: fail(lazyFailureMessage())
@OptIn(ExperimentalContracts::class)
internal fun <T : Any> T?.assertNotNull(lazyFailureMessage: () -> String = { "Expecting actual not to be null" }): T {
contract {
returns() implies (this@assertNotNull != null)
}

return this ?: fail(lazyFailureMessage())
}

/**
* Calls the [block] callback giving it a sequence of all the lines in this file
* and closes the reader once the processing is complete.
*
* If [filterDebuggerPrompt] is `true`, the JVM debugger prompt is filtered out
* from the sequence of lines before it is consumed by [block].
*
* If [filterDebuggerPrompt] is `false`, this function behaves exactly as the
* overloaded function from the standard library.
*
* @param filterDebuggerPrompt whether the JVM debugger prompt should be
* filtered out.
* @param block the callback which consumes the lines produced by this [Reader].
* @return the value returned by [block].
*/
@OptIn(ExperimentalContracts::class)
internal fun <T> Reader.useLines(
filterDebuggerPrompt: Boolean,
block: (Sequence<String>) -> T,
): T {
contract {
callsInPlace(block, EXACTLY_ONCE)
}

return when {
filterDebuggerPrompt -> {
/*
* Transform the line consumer.
*/
{ lines ->
lines.filterNot(String::isDebuggerPrompt).let(block)
}
}

else -> block
}.let(this::useLines)
}

private fun String.isDebuggerPrompt(printIfTrue: Boolean = true): Boolean {
val isDebuggerPrompt = debuggerPromptPrefixes.any { prefix ->
this.startsWith(prefix)
}
if (isDebuggerPrompt && printIfTrue) {
/*
* Print the prompt to the standard out,
* so that the IDE can attach to the debugger.
*/
@Suppress("DEBUG_PRINT")
println(this)
}
return isDebuggerPrompt
}

/**
* This utility function lets you run arbitrary code on every node of given [code].
Expand All @@ -41,7 +110,7 @@ internal fun <T> T?.assertNotNull(lazyFailureMessage: () -> String = { "Expectin
* @param applyToNode Function to be called on each AST node, should increment counter if assert is called
*/
@Suppress("TYPE_ALIAS")
internal fun applyToCode(code: String,
internal fun applyToCode(@Language("kotlin") code: String,
expectedAsserts: Int,
applyToNode: (node: ASTNode, counter: AtomicInteger) -> Unit
) {
Expand Down
Loading

0 comments on commit e4e6465

Please sign in to comment.