Add diagnostic spec tests generator and validator, helper functions and classes
This commit is contained in:
@@ -85,7 +85,7 @@ abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava<TestModule, Tes
|
||||
|
||||
protected abstract fun analyzeAndCheck(testDataFile: File, files: List<TestFile>)
|
||||
|
||||
protected fun getKtFiles(testFiles: List<TestFile>, includeExtras: Boolean): List<KtFile> {
|
||||
protected open fun getKtFiles(testFiles: List<TestFile>, includeExtras: Boolean): List<KtFile> {
|
||||
var declareFlexibleType = false
|
||||
var declareCheckType = false
|
||||
val ktFiles = arrayListOf<KtFile>()
|
||||
@@ -123,9 +123,10 @@ abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava<TestModule, Tes
|
||||
val module: TestModule?,
|
||||
fileName: String,
|
||||
textWithMarkers: String,
|
||||
directives: Map<String, String>
|
||||
val directives: Map<String, String>
|
||||
) {
|
||||
private val diagnosedRanges: List<CheckerTestUtil.DiagnosedRange> = ArrayList()
|
||||
val actualDiagnostics: MutableList<ActualDiagnostic> = ArrayList()
|
||||
val expectedText: String
|
||||
private val clearText: String
|
||||
private val createKtFile: Lazy<KtFile?>
|
||||
@@ -240,6 +241,8 @@ abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava<TestModule, Tes
|
||||
{ whatDiagnosticsToConsider.value(it.diagnostic) }
|
||||
)
|
||||
|
||||
actualDiagnostics.addAll(diagnostics)
|
||||
|
||||
val uncheckedDiagnostics = mutableListOf<PositionalTextDiagnostic>()
|
||||
val inferenceCompatibilityOfTest = asInferenceCompatibility(withNewInference)
|
||||
val invertedInferenceCompatibilityOfTest = asInferenceCompatibility(!withNewInference)
|
||||
|
||||
@@ -15,3 +15,5 @@ sourceSets {
|
||||
projectTest {
|
||||
workingDir = rootDir
|
||||
}
|
||||
|
||||
val generateTests by generator("org.jetbrains.kotlin.generators.tests.GenerateCompilerSpecTestsKt")
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
fun getInt(arg: Any = Any()) = arg.hashCode()
|
||||
fun getShort(arg: Any = Any()) = arg.hashCode().toShort()
|
||||
fun getLong(arg: Any = Any()) = arg.hashCode().toLong()
|
||||
fun getFloat(arg: Any = Any()) = arg.hashCode().toFloat()
|
||||
fun getDouble(arg: Any = Any()) = arg.hashCode().toDouble()
|
||||
fun getByte(arg: Any = Any()) = arg.hashCode().toByte()
|
||||
fun getChar(arg: Any = Any()) = arg.hashCode().toChar()
|
||||
fun getString(arg: Any = Any()) = arg.hashCode().toString()
|
||||
fun getBoolean(arg: Any = Any()) = arg.hashCode() % 2 == 0
|
||||
fun getNothing(): Nothing = throw Exception()
|
||||
fun getUnit() = {}
|
||||
fun getAny() = Any()
|
||||
fun getList() = mutableListOf<Int>()
|
||||
|
||||
class _BasicTypesProvider {
|
||||
fun getInt(arg: Any = Any()) = arg.hashCode()
|
||||
fun getShort(arg: Any = Any()) = arg.hashCode().toShort()
|
||||
fun getLong(arg: Any = Any()) = arg.hashCode().toLong()
|
||||
fun getFloat(arg: Any = Any()) = arg.hashCode().toFloat()
|
||||
fun getDouble(arg: Any = Any()) = arg.hashCode().toDouble()
|
||||
fun getByte(arg: Any = Any()) = arg.hashCode().toByte()
|
||||
fun getChar(arg: Any = Any()) = arg.hashCode().toChar()
|
||||
fun getString(arg: Any = Any()) = arg.hashCode().toString()
|
||||
fun getBoolean(arg: Any = Any()) = arg.hashCode() % 2 == 0
|
||||
fun getNothing(): Nothing = throw Exception()
|
||||
fun getUnit() = {}
|
||||
fun getAny() = Any()
|
||||
fun getList() = mutableListOf<Int>()
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
class _Class {
|
||||
val prop_1 = 1
|
||||
val prop_2 = 2
|
||||
val prop_3 = 3
|
||||
|
||||
fun fun_1(): (Int) -> (Int) -> Int = {number: Int -> { number * 5 }}
|
||||
fun fun_2(value: Int): Int = value * 2
|
||||
fun fun_3(value1: Int): (Int) -> Int = fun(value2: Int): Int = value1 * value2 * 2
|
||||
|
||||
operator fun contains(a: Int): Boolean = a > 30
|
||||
operator fun contains(a: Long): Boolean = a > 30L
|
||||
operator fun contains(a: Char): Boolean = a > 30.toChar()
|
||||
|
||||
fun getIntArray(value: Int): IntArray = intArrayOf(1, 2, 3, value, 91923, 14, 123124)
|
||||
fun getLongArray(value: Long): LongArray = longArrayOf(1L, 2L, 3L, value, 9192323244L, 14L, 123124L)
|
||||
fun getCharArray(value: Char): CharArray = charArrayOf(1.toChar(), 2.toChar(), 3.toChar(), value)
|
||||
|
||||
class _NestedClass {
|
||||
val prop_4 = 4
|
||||
val prop_5 = 5
|
||||
}
|
||||
}
|
||||
|
||||
class _EmptyClass {}
|
||||
|
||||
class _ClassWithCompanionObject {
|
||||
companion object {}
|
||||
}
|
||||
|
||||
open class _ClassLevel1 {}
|
||||
open class _ClassLevel2: _ClassLevel1() {}
|
||||
open class _ClassLevel3: _ClassLevel2() {}
|
||||
open class _ClassLevel4: _ClassLevel3() {}
|
||||
open class _ClassLevel5: _ClassLevel4() {}
|
||||
class _ClassLevel6: _ClassLevel5() {}
|
||||
@@ -0,0 +1,9 @@
|
||||
enum class _EnumClass {
|
||||
NORTH, SOUTH, WEST, EAST
|
||||
}
|
||||
|
||||
enum class _EnumClassSingle {
|
||||
EVERYTHING
|
||||
}
|
||||
|
||||
enum class _EnumClassEmpty
|
||||
@@ -0,0 +1,3 @@
|
||||
fun _funWithoutArgs(): Int {
|
||||
return Any().hashCode().toInt()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
object _EmptyObject {}
|
||||
@@ -0,0 +1,36 @@
|
||||
sealed class _SealedClass
|
||||
data class _SealedChild1(val number: Int) : _SealedClass()
|
||||
data class _SealedChild2(val e1: Int, val e2: Int) : _SealedClass()
|
||||
data class _SealedChild3(val m1: Int, val m2: Int) : _SealedClass()
|
||||
|
||||
sealed class _SealedClassWithObjects
|
||||
object _SealedWithObjectsChild1 : _SealedClassWithObjects()
|
||||
object _SealedWithObjectsChild2 : _SealedClassWithObjects()
|
||||
object _SealedWithObjectsChild3 : _SealedClassWithObjects()
|
||||
|
||||
sealed class _SealedClassSingle
|
||||
data class _SealedSingleChild1(val number: Int) : _SealedClassSingle()
|
||||
|
||||
sealed class _SealedClassSingleWithObject
|
||||
object _SealedSingleWithObjectChild1: Expr3() {}
|
||||
|
||||
sealed class _SealedClassEmpty
|
||||
|
||||
sealed class _SealedClassWithMethods
|
||||
class _SealedWithMethodsChild1() : _SealedClassWithMethods() {
|
||||
fun m1() = this.hashCode().toString()
|
||||
}
|
||||
class _SealedWithMethodsChild2() : _SealedClassWithMethods() {
|
||||
fun m2() = this.hashCode().toString()
|
||||
}
|
||||
class _SealedWithMethodsChild3() : _SealedClassWithMethods() {
|
||||
fun m3() = this.hashCode().toString()
|
||||
}
|
||||
|
||||
sealed class _SealedClassMixed
|
||||
data class _SealedMixedChild1(val number: Int) : _SealedClassMixed()
|
||||
data class _SealedMixedChild2(val e1: Int, val e2: Int) : _SealedClassMixed()
|
||||
data class _SealedMixedChild3(val m1: Int, val m2: Int) : _SealedClassMixed()
|
||||
object _SealedMixedChildObject1 : _SealedClassMixed()
|
||||
object _SealedMixedChildObject2 : _SealedClassMixed()
|
||||
object _SealedMixedChildObject3 : _SealedClassMixed()
|
||||
@@ -0,0 +1,4 @@
|
||||
typealias _TypeAliasAny = Any
|
||||
typealias _TypeAliasUnit = Unit
|
||||
typealias _TypeAliasNothing = Nothing
|
||||
typealias _TypeAliasInt = Int
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.checkers
|
||||
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.spec.DiagnosticSpecTestValidator
|
||||
import org.jetbrains.kotlin.spec.SpecTestValidationException
|
||||
import org.jetbrains.kotlin.test.*
|
||||
import org.junit.Assert
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractDiagnosticsTestSpec : AbstractDiagnosticsTest() {
|
||||
companion object {
|
||||
// map of pairs: source helper filename - target helper filename
|
||||
private val directives = mapOf(
|
||||
"WITH_BASIC_TYPES" to Pair("basicTypes.kt", "BASIC_TYPES.kt"),
|
||||
"WITH_CLASSES" to Pair("classes.kt", "CLASSES.kt"),
|
||||
"WITH_ENUM_CLASSES" to Pair("enumClasses.kt", "ENUM_CLASSES.kt"),
|
||||
"WITH_SEALED_CLASSES" to Pair("sealedClasses.kt", "SEALED_CLASSES.kt"),
|
||||
"WITH_FUNS" to Pair("funs.kt", "FUNS.kt"),
|
||||
"WITH_OBJECTS" to Pair("objects.kt", "OBJECTS.kt"),
|
||||
"WITH_TYPEALIASES" to Pair("typeAliases.kt", "TYPE_ALIASES.kt")
|
||||
)
|
||||
|
||||
private const val MODULE_PATH = "./compiler/tests-spec"
|
||||
private const val HELPERS_PATH = "$MODULE_PATH/testData/helpers/diagnostics"
|
||||
}
|
||||
|
||||
private lateinit var testValidator: DiagnosticSpecTestValidator
|
||||
|
||||
private fun checkDirective(directive: String, testFiles: List<TestFile>) =
|
||||
testFiles.any { it.directives.contains(directive) }
|
||||
|
||||
override fun getConfigurationKind() = ConfigurationKind.ALL
|
||||
|
||||
override fun skipDescriptorsValidation(): Boolean = true
|
||||
|
||||
override fun getKtFiles(testFiles: List<TestFile>, includeExtras: Boolean): List<KtFile> {
|
||||
val ktFiles = super.getKtFiles(testFiles, includeExtras) as ArrayList
|
||||
|
||||
if (includeExtras) {
|
||||
for ((name, filenames) in directives) {
|
||||
if (checkDirective(name, testFiles)) {
|
||||
val (sourceFilename, targetFilename) = filenames
|
||||
val declarations = File("$HELPERS_PATH/$sourceFilename").readText()
|
||||
|
||||
ktFiles.add(KotlinTestUtils.createFile(targetFilename, declarations, project))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ktFiles
|
||||
}
|
||||
|
||||
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
|
||||
testValidator = DiagnosticSpecTestValidator(testDataFile)
|
||||
|
||||
try {
|
||||
testValidator.parseTestInfo()
|
||||
} catch (e: SpecTestValidationException) {
|
||||
Assert.fail(e.description)
|
||||
}
|
||||
|
||||
testValidator.printTestInfo()
|
||||
|
||||
super.analyzeAndCheck(testDataFile, files)
|
||||
}
|
||||
|
||||
override fun performAdditionalChecksAfterDiagnostics(
|
||||
testDataFile: File,
|
||||
testFiles: List<TestFile>,
|
||||
moduleFiles: Map<TestModule?, List<TestFile>>,
|
||||
moduleDescriptors: Map<TestModule?, ModuleDescriptorImpl>,
|
||||
moduleBindings: Map<TestModule?, BindingContext>,
|
||||
languageVersionSettingsByModule: Map<TestModule?, LanguageVersionSettings>
|
||||
) {
|
||||
try {
|
||||
testValidator.validateTestType(testFiles)
|
||||
} catch (e: SpecTestValidationException) {
|
||||
Assert.fail(e.description)
|
||||
} finally {
|
||||
testValidator.printDiagnosticStatistic()
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.generators.tests
|
||||
|
||||
import org.jetbrains.kotlin.generators.tests.generator.testGroup
|
||||
import org.jetbrains.kotlin.checkers.AbstractDiagnosticsTestSpec
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
testGroup("compiler/tests-spec/tests", "compiler/tests-spec/testData") {
|
||||
testClass<AbstractDiagnosticsTestSpec> {
|
||||
model("diagnostics")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.spec
|
||||
|
||||
import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import java.io.File
|
||||
|
||||
class DiagnosticSpecTestValidator(testDataFile: File) : SpecTestValidator(testDataFile, TestArea.DIAGNOSTICS) {
|
||||
private lateinit var diagnostics: MutableList<Diagnostic>
|
||||
private lateinit var diagnosticStats: MutableMap<String, Int>
|
||||
private lateinit var diagnosticSeverityStats: MutableMap<Severity, Int>
|
||||
|
||||
private fun collectDiagnosticStatistic() {
|
||||
diagnosticSeverityStats = mutableMapOf()
|
||||
|
||||
diagnostics.forEach {
|
||||
val severity = it.factory.severity
|
||||
|
||||
if (diagnosticSeverityStats.contains(severity)) {
|
||||
diagnosticSeverityStats[severity] = diagnosticSeverityStats[severity]!! + 1
|
||||
} else {
|
||||
diagnosticSeverityStats[severity] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeTestType(): TestType {
|
||||
return if (Severity.ERROR in diagnosticSeverityStats) TestType.NEGATIVE else TestType.POSITIVE
|
||||
}
|
||||
|
||||
private fun collectDiagnostics(files: List<BaseDiagnosticsTest.TestFile>) {
|
||||
diagnostics = mutableListOf()
|
||||
diagnosticStats = mutableMapOf()
|
||||
|
||||
files.forEach {
|
||||
it.actualDiagnostics.forEach {
|
||||
val diagnosticName = it.diagnostic.factory.name
|
||||
|
||||
if (diagnosticStats.contains(diagnosticName)) {
|
||||
diagnosticStats[diagnosticName] = diagnosticStats[diagnosticName]!! + 1
|
||||
} else {
|
||||
diagnosticStats[diagnosticName] = 1
|
||||
}
|
||||
|
||||
diagnostics.add(it.diagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
collectDiagnosticStatistic()
|
||||
}
|
||||
|
||||
fun validateTestType(files: List<BaseDiagnosticsTest.TestFile>) {
|
||||
if (!this::diagnostics.isInitialized) this.collectDiagnostics(files)
|
||||
|
||||
validateTestType(computeTestType())
|
||||
}
|
||||
|
||||
fun printDiagnosticStatistic() {
|
||||
val diagnostics = if (diagnosticStats.isNotEmpty()) "$diagnosticSeverityStats | $diagnosticStats" else "does not contain"
|
||||
|
||||
println("DIAGNOSTICS: $diagnostics")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.spec
|
||||
|
||||
import java.io.File
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
enum class TestType(val type: String) {
|
||||
POSITIVE("pos"),
|
||||
NEGATIVE("neg");
|
||||
|
||||
companion object {
|
||||
private val map = TestType.values().associateBy(TestType::type)
|
||||
fun fromValue(type: String) = map[type]
|
||||
}
|
||||
}
|
||||
|
||||
enum class TestArea {
|
||||
PSI,
|
||||
DIAGNOSTICS,
|
||||
CODEGEN
|
||||
}
|
||||
|
||||
interface SpecTestInfoElementType {
|
||||
val valuePattern: Pattern?
|
||||
val required: Boolean
|
||||
}
|
||||
|
||||
enum class SpecTestFileInfoElementType(
|
||||
override val valuePattern: Pattern? = null,
|
||||
override val required: Boolean = false
|
||||
) : SpecTestInfoElementType {
|
||||
SECTION(
|
||||
valuePattern = Pattern.compile("""(?<number>(?:${SpecTestValidator.INTEGER_REGEX})(?:\.${SpecTestValidator.INTEGER_REGEX})*)(?<name>.*?)"""),
|
||||
required = true
|
||||
),
|
||||
PARAGRAPH(required = true),
|
||||
SENTENCE(
|
||||
valuePattern = Pattern.compile("""\[(?<number>${SpecTestValidator.INTEGER_REGEX})\](?<text>.*?)"""),
|
||||
required = true
|
||||
),
|
||||
NUMBER(required = true),
|
||||
DESCRIPTION(required = true),
|
||||
ISSUES(valuePattern = Pattern.compile("""(KT-[1-9]\d*)(,\s*KT-[1-9]\d*)*""")),
|
||||
UNEXPECTED_BEHAVIOUR,
|
||||
DISCUSSION,
|
||||
NOTE
|
||||
}
|
||||
|
||||
enum class SpecTestCaseInfoElementType(
|
||||
override val valuePattern: Pattern? = null,
|
||||
override val required: Boolean = false
|
||||
) : SpecTestInfoElementType {
|
||||
CASE_DESCRIPTION(required = true),
|
||||
ISSUES(valuePattern = SpecTestFileInfoElementType.ISSUES.valuePattern),
|
||||
UNEXPECTED_BEHAVIOUR,
|
||||
DISCUSSION,
|
||||
NOTE
|
||||
}
|
||||
|
||||
data class SpecTestInfoElementContent(
|
||||
val content: String,
|
||||
val additionalMatcher: Matcher? = null
|
||||
)
|
||||
|
||||
data class SpecTestCase(
|
||||
val number: Int,
|
||||
val description: String,
|
||||
val unexpectedBehavior: Boolean,
|
||||
val issues: List<String>?
|
||||
)
|
||||
|
||||
class SpecTest(
|
||||
val testArea: TestArea,
|
||||
val testType: TestType,
|
||||
val sectionNumber: String,
|
||||
val sectionName: String,
|
||||
val paragraphNumber: Int,
|
||||
val sentenceNumber: Int,
|
||||
val sentence: String? = null,
|
||||
val testNumber: Int,
|
||||
val description: String? = null,
|
||||
val cases: List<SpecTestCase>? = null,
|
||||
val unexpectedBehavior: Boolean? = null,
|
||||
val issues: Set<String>? = null
|
||||
) {
|
||||
fun checkConsistency(other: SpecTest): Boolean {
|
||||
return this.testArea == other.testArea
|
||||
&& this.testType == other.testType
|
||||
&& this.sectionNumber == other.sectionNumber
|
||||
&& this.testNumber == other.testNumber
|
||||
&& this.paragraphNumber == other.paragraphNumber
|
||||
&& this.sentenceNumber == other.sentenceNumber
|
||||
}
|
||||
}
|
||||
|
||||
enum class SpecTestValidationFailedReason(val description: String) {
|
||||
FILENAME_NOT_VALID(
|
||||
"Incorrect test filename or folder name.${SpecTestValidator.lineSeparator}" +
|
||||
"It must match the following path pattern: " +
|
||||
"testsData/<diagnostic|psi|codegen>/s<sectionNumber>_<sectionName>/p-<paragraph>/<pos|neg>/<sentence>_<testNumber>.kt " +
|
||||
"(example: testsData/diagnostic/s-16.30_when-expression/p-3/pos/1.3.kt)"
|
||||
),
|
||||
TESTINFO_NOT_VALID("Test info is incorrect."),
|
||||
FILEPATH_AND_TESTINFO_IN_FILE_NOT_CONSISTENCY("Test info from filepath and file content is not consistency"),
|
||||
TEST_IS_NOT_POSITIVE("Test is not positive because it contains error elements (PsiErrorElement or diagnostic with error severity)."),
|
||||
TEST_IS_NOT_NEGATIVE("Test is not negative because it not contains error type elements (PsiErrorElement or diagnostic with error severity)."),
|
||||
UNKNOWN("Unknown validation error.")
|
||||
}
|
||||
|
||||
class SpecTestValidationException(reason: SpecTestValidationFailedReason, details: String = "") : Exception() {
|
||||
val description = "${reason.description} $details"
|
||||
}
|
||||
|
||||
typealias SpecTestInfoElements<T> = Map<T, SpecTestInfoElementContent>
|
||||
|
||||
abstract class SpecTestValidator(private val testDataFile: File, private val testArea: TestArea) {
|
||||
private lateinit var testInfoByFilename: SpecTest
|
||||
private lateinit var testInfoByContent: SpecTest
|
||||
private val testInfo by lazy { testInfoByContent }
|
||||
|
||||
companion object {
|
||||
const val INTEGER_REGEX = """[1-9]\d*"""
|
||||
private const val SINGLELINE_COMMENT_REGEX = """\/\/\s*%s"""
|
||||
private const val MULTILINE_COMMENT_REGEX = """\/\*\s*%s\s+\*\/"""
|
||||
|
||||
val lineSeparator: String = System.lineSeparator()
|
||||
private val testCaseInfoRegex = """(?<infoElements>CASE DESCRIPTION:[\s\S]*?$lineSeparator)"""
|
||||
private val testAreaRegex = """(?<testArea>${TestArea.values().joinToString("|")})"""
|
||||
private val testTypeRegex = """(?<testType>${TestType.values().joinToString("|")})"""
|
||||
private val testInfoElementPattern: Pattern = Pattern.compile("""\s*(?<name>[A-Z ]*?):\s*(?<value>.*?)$lineSeparator""")
|
||||
private val pathSeparator = Pattern.quote(File.separator)
|
||||
|
||||
val testPathPattern: Pattern =
|
||||
Pattern.compile("""^.*?$pathSeparator(?<testArea>diagnostics|psi|codegen)${pathSeparator}s-(?<sectionNumber>(?:$INTEGER_REGEX)(?:\.$INTEGER_REGEX)*)_(?<sectionName>[\w-]+)${pathSeparator}p-(?<paragraphNumber>$INTEGER_REGEX)$pathSeparator(?<testType>pos|neg)$pathSeparator(?<sentenceNumber>$INTEGER_REGEX)\.(?<testNumber>$INTEGER_REGEX)\.kt$""")
|
||||
val testInfoPattern: Pattern =
|
||||
Pattern.compile(MULTILINE_COMMENT_REGEX.format("""KOTLIN $testAreaRegex SPEC TEST \($testTypeRegex\)$lineSeparator(?<infoElements>[\s\S]*?$lineSeparator)"""))
|
||||
val testCaseInfoSinglelinePattern: Pattern = Pattern.compile(SINGLELINE_COMMENT_REGEX.format(testCaseInfoRegex))
|
||||
val testCaseInfoMultilinePattern: Pattern = Pattern.compile(MULTILINE_COMMENT_REGEX.format(testCaseInfoRegex))
|
||||
|
||||
private fun getTestInfo(
|
||||
testInfoMatcher: Matcher,
|
||||
testInfoElements: SpecTestInfoElements<SpecTestInfoElementType>,
|
||||
testCases: List<SpecTestCase>,
|
||||
unexpectedBehavior: Boolean = false,
|
||||
issues: Set<String>? = null
|
||||
): SpecTest {
|
||||
val sectionMatcher = testInfoElements[SpecTestFileInfoElementType.SECTION]!!.additionalMatcher!!
|
||||
val sentenceMatcher = testInfoElements[SpecTestFileInfoElementType.SENTENCE]!!.additionalMatcher!!
|
||||
|
||||
return SpecTest(
|
||||
TestArea.valueOf(testInfoMatcher.group("testArea").toUpperCase()),
|
||||
TestType.valueOf(testInfoMatcher.group("testType")),
|
||||
sectionMatcher.group("number"),
|
||||
sectionMatcher.group("name"),
|
||||
testInfoElements[SpecTestFileInfoElementType.PARAGRAPH]!!.content.toInt(),
|
||||
sentenceMatcher.group("number").toInt(),
|
||||
sentenceMatcher.group("text"),
|
||||
testInfoElements[SpecTestFileInfoElementType.NUMBER]!!.content.toInt(),
|
||||
testInfoElements[SpecTestFileInfoElementType.DESCRIPTION]!!.content,
|
||||
testCases,
|
||||
unexpectedBehavior,
|
||||
issues
|
||||
)
|
||||
}
|
||||
|
||||
private fun getTestInfo(testInfoMatcher: Matcher): SpecTest {
|
||||
return SpecTest(
|
||||
TestArea.valueOf(testInfoMatcher.group("testArea").toUpperCase()),
|
||||
TestType.fromValue(testInfoMatcher.group("testType"))!!,
|
||||
testInfoMatcher.group("sectionNumber"),
|
||||
testInfoMatcher.group("sectionName"),
|
||||
testInfoMatcher.group("paragraphNumber").toInt(),
|
||||
testInfoMatcher.group("sentenceNumber").toInt(),
|
||||
testNumber = testInfoMatcher.group("testNumber").toInt()
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseIssues(issues: SpecTestInfoElementContent?) = issues?.content?.split(",")
|
||||
|
||||
private fun getSingleTestCase(testInfoElements: SpecTestInfoElements<SpecTestInfoElementType>) = SpecTestCase(
|
||||
1,
|
||||
description = testInfoElements[SpecTestFileInfoElementType.DESCRIPTION]!!.content,
|
||||
unexpectedBehavior = testInfoElements.contains(SpecTestFileInfoElementType.UNEXPECTED_BEHAVIOUR),
|
||||
issues = parseIssues(testInfoElements[SpecTestFileInfoElementType.ISSUES])
|
||||
)
|
||||
|
||||
private fun getTestCasesInfo(testCaseInfoMatcher: Matcher, infoElements: SpecTestInfoElements<SpecTestInfoElementType>): List<SpecTestCase> {
|
||||
val testCases = mutableListOf<SpecTestCase>()
|
||||
var testCasesCounter = 1
|
||||
|
||||
while (testCaseInfoMatcher.find()) {
|
||||
val caseInfoElements = getTestInfoElements<SpecTestCaseInfoElementType>(testCaseInfoMatcher.group("infoElements"))
|
||||
|
||||
testCases.add(
|
||||
SpecTestCase(
|
||||
testCasesCounter++,
|
||||
caseInfoElements[SpecTestCaseInfoElementType.CASE_DESCRIPTION]!!.content,
|
||||
caseInfoElements.contains(SpecTestCaseInfoElementType.UNEXPECTED_BEHAVIOUR),
|
||||
parseIssues(caseInfoElements[SpecTestCaseInfoElementType.ISSUES])
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (testCases.isEmpty()) testCases.add(getSingleTestCase(infoElements))
|
||||
|
||||
return testCases
|
||||
}
|
||||
|
||||
private inline fun <reified T : Enum<T>> getTestInfoElements(testInfoElements: String): SpecTestInfoElements<SpecTestInfoElementType> {
|
||||
val testInfoElementsMap = mutableMapOf<SpecTestInfoElementType, SpecTestInfoElementContent>()
|
||||
val testInfoElementMatcher = testInfoElementPattern.matcher(testInfoElements)
|
||||
|
||||
while (testInfoElementMatcher.find()) {
|
||||
val testInfoOriginalElementName = testInfoElementMatcher.group("name")
|
||||
val testInfoElementName: SpecTestInfoElementType
|
||||
try {
|
||||
testInfoElementName = enumValueOf<T>(testInfoOriginalElementName.replace(" ", "_")) as SpecTestInfoElementType
|
||||
} catch (e: IllegalArgumentException) {
|
||||
throw SpecTestValidationException(
|
||||
SpecTestValidationFailedReason.TESTINFO_NOT_VALID,
|
||||
"Unknown '$testInfoOriginalElementName' test info element name."
|
||||
)
|
||||
}
|
||||
val testInfoElementValue = testInfoElementMatcher.group("value")
|
||||
val testInfoElementValueMatcher = testInfoElementName.valuePattern?.matcher(testInfoElementValue)
|
||||
|
||||
if (testInfoElementValueMatcher != null && !testInfoElementValueMatcher.find())
|
||||
throw SpecTestValidationException(
|
||||
SpecTestValidationFailedReason.TESTINFO_NOT_VALID,
|
||||
"'$testInfoElementValue' in '$testInfoElementName' is not parsed."
|
||||
)
|
||||
|
||||
testInfoElementsMap[testInfoElementName] = SpecTestInfoElementContent(testInfoElementValue, testInfoElementValueMatcher)
|
||||
}
|
||||
|
||||
enumValues<T>().forEach {
|
||||
it as SpecTestInfoElementType
|
||||
if (it.required && !testInfoElementsMap.contains(it)) {
|
||||
throw SpecTestValidationException(
|
||||
SpecTestValidationFailedReason.TESTINFO_NOT_VALID,
|
||||
"$it in case or test info is required."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return testInfoElementsMap
|
||||
}
|
||||
|
||||
private fun getIssues(testCases: List<SpecTestCase>, testIssues: List<String>?): Set<String> {
|
||||
val issues = mutableSetOf<String>()
|
||||
|
||||
testCases.forEach {
|
||||
if (it.issues != null) issues.addAll(it.issues)
|
||||
}
|
||||
|
||||
if (testIssues != null) issues.addAll(testIssues)
|
||||
|
||||
return issues
|
||||
}
|
||||
}
|
||||
|
||||
fun parseTestInfo() {
|
||||
val testInfoByFilenameMatcher = testPathPattern.matcher(testDataFile.path)
|
||||
|
||||
if (!testInfoByFilenameMatcher.find())
|
||||
throw SpecTestValidationException(SpecTestValidationFailedReason.FILENAME_NOT_VALID)
|
||||
|
||||
val fileContent = testDataFile.readText()
|
||||
val testInfoByContentMatcher = testInfoPattern.matcher(fileContent)
|
||||
|
||||
if (!testInfoByContentMatcher.find())
|
||||
throw SpecTestValidationException(SpecTestValidationFailedReason.TESTINFO_NOT_VALID)
|
||||
|
||||
val testInfoElements = getTestInfoElements<SpecTestFileInfoElementType>(testInfoByContentMatcher.group("infoElements"))
|
||||
val testCases = getTestCasesInfo(testCaseInfoSinglelinePattern.matcher(fileContent), testInfoElements) +
|
||||
getTestCasesInfo(testCaseInfoMultilinePattern.matcher(fileContent), testInfoElements)
|
||||
|
||||
testInfoByFilename = getTestInfo(testInfoByFilenameMatcher)
|
||||
testInfoByContent = getTestInfo(
|
||||
testInfoByContentMatcher,
|
||||
testInfoElements,
|
||||
testCases = testCases,
|
||||
unexpectedBehavior = testInfoElements.contains(SpecTestFileInfoElementType.UNEXPECTED_BEHAVIOUR) || testCases.any { it.unexpectedBehavior },
|
||||
issues = getIssues(testCases, parseIssues(testInfoElements[SpecTestFileInfoElementType.ISSUES]))
|
||||
)
|
||||
|
||||
if (!testInfoByFilename.checkConsistency(testInfoByContent))
|
||||
throw SpecTestValidationException(SpecTestValidationFailedReason.FILEPATH_AND_TESTINFO_IN_FILE_NOT_CONSISTENCY)
|
||||
}
|
||||
|
||||
protected fun validateTestType(computedTestType: TestType) {
|
||||
if (computedTestType != testInfo.testType) {
|
||||
val isNotNegative = computedTestType == TestType.POSITIVE && testInfo.testType == TestType.NEGATIVE
|
||||
val isNotPositive = computedTestType == TestType.NEGATIVE && testInfo.testType == TestType.POSITIVE
|
||||
val reason = when {
|
||||
isNotNegative -> SpecTestValidationFailedReason.TEST_IS_NOT_NEGATIVE
|
||||
isNotPositive -> SpecTestValidationFailedReason.TEST_IS_NOT_POSITIVE
|
||||
else -> SpecTestValidationFailedReason.UNKNOWN
|
||||
}
|
||||
throw SpecTestValidationException(reason)
|
||||
}
|
||||
}
|
||||
|
||||
fun printTestInfo() {
|
||||
println("--------------------------------------------------")
|
||||
if (testInfoByContent.unexpectedBehavior!!)
|
||||
println("(!!!) HAS UNEXPECTED BEHAVIOUR (!!!)")
|
||||
println("$testArea ${testInfoByFilename.testType} SPEC TEST")
|
||||
println("SECTION: ${testInfoByFilename.sectionNumber} ${testInfoByContent.sectionName} (paragraph: ${testInfoByFilename.paragraphNumber})")
|
||||
println("SENTENCE ${testInfoByContent.sentenceNumber}: ${testInfoByContent.sentence}")
|
||||
println("TEST NUMBER: ${testInfoByContent.testNumber}")
|
||||
println("NUMBER OF TEST CASES: ${testInfoByContent.cases!!.size}")
|
||||
println("DESCRIPTION: ${testInfoByContent.description}")
|
||||
if (testInfoByContent.issues!!.isNotEmpty()) {
|
||||
println("LINKED ISSUES: ${testInfoByContent.issues!!.joinToString(", ")}")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user