Add diagnostic spec tests generator and validator, helper functions and classes

This commit is contained in:
victor.petukhov
2018-08-10 17:48:09 +03:00
parent e989cf2bf4
commit 0a58898817
13 changed files with 623 additions and 2 deletions
@@ -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)
+2
View File
@@ -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
@@ -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()
}
}
}
@@ -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(", ")}")
}
}
}