Reorganize spec tests infrastructure code

- Add the tests mute system for the diagnostic tests
- Move the code for the test info parsing to the separate package `parsers`
- Unification of the `linked` and `not linked` spec tests
- Package structure is refactored
- Change the multiline comment format with a test information
- Actualize `PrintSpecTestsStatistic`
- Other different code improvements
This commit is contained in:
victor.petukhov
2018-10-31 12:56:27 +03:00
parent 2af9efa4a0
commit 64f531fc93
348 changed files with 4722 additions and 3802 deletions
@@ -9,10 +9,16 @@ 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.models.AbstractSpecTest
import org.jetbrains.kotlin.spec.SpecTestLinkedType
import org.jetbrains.kotlin.spec.parsers.CommonParser
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.ls
import org.jetbrains.kotlin.spec.validators.*
import org.jetbrains.kotlin.test.*
import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
import org.junit.Assert
import java.io.File
import java.util.regex.Pattern
abstract class AbstractDiagnosticsTestSpec : AbstractDiagnosticsTest() {
companion object {
@@ -35,18 +41,22 @@ abstract class AbstractDiagnosticsTestSpec : AbstractDiagnosticsTest() {
private const val MODULE_PATH = "compiler/tests-spec"
private const val DIAGNOSTICS_TESTDATA_PATH = "$MODULE_PATH/testData/diagnostics"
private const val HELPERS_PATH = "$DIAGNOSTICS_TESTDATA_PATH/helpers"
private val exceptionPattern =
Pattern.compile("""Exception while analyzing expression at \((?<lineNumber>\d+),(?<symbolNumber>\d+)\) in /(?<filename>.*?)$""")
}
private lateinit var testValidator: AbstractSpecTestValidator<out AbstractSpecTest>
lateinit var specTest: AbstractSpecTest
lateinit var testLinkedType: SpecTestLinkedType
private var skipDescriptors = true
private fun checkDirective(directive: String, testFiles: List<TestFile>) =
testFiles.any { it.directives.contains(directive) }
private fun enableDescriptorsGenerationIfNeeded(testDataFile: File) {
private fun enableDescriptorsGenerationIfNeeded(testFilePath: String) {
skipDescriptors = withoutDescriptorsTestGroups.any {
val testGroupAbsolutePath = File("$DIAGNOSTICS_TESTDATA_PATH/$it").absolutePath
testDataFile.absolutePath.startsWith(testGroupAbsolutePath)
testFilePath.startsWith(testGroupAbsolutePath)
}
}
@@ -70,19 +80,37 @@ abstract class AbstractDiagnosticsTestSpec : AbstractDiagnosticsTest() {
}
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
enableDescriptorsGenerationIfNeeded(testDataFile)
val testFilePath = testDataFile.canonicalPath
testValidator = AbstractSpecTestValidator.getInstanceByType(testDataFile)
enableDescriptorsGenerationIfNeeded(testFilePath)
try {
testValidator.parseTestInfo()
} catch (e: SpecTestValidationException) {
Assert.fail(e.description)
CommonParser.parseSpecTest(testFilePath, files.associate { Pair(it.ktFile!!.name, it.clearText) }).apply {
specTest = first
testLinkedType = second
}
testValidator.printTestInfo()
println(specTest)
super.analyzeAndCheck(testDataFile, files)
try {
super.analyzeAndCheck(testDataFile, files)
} catch (e: KotlinExceptionWithAttachments) {
val matches = exceptionPattern.matcher(e.message)
if (!matches.find())
Assert.fail(SpecTestValidationFailedReason.UNKNOWN_FRONTEND_EXCEPTION.description)
val lineNumber = matches.group("lineNumber").toInt()
val symbolNumber = matches.group("symbolNumber").toInt()
val filename = matches.group("filename")
val fileContent = files.find { it.ktFile?.name == filename }!!.clearText
val exceptionPosition =
fileContent.lines().subList(0, lineNumber).joinToString("\n").length + symbolNumber
val testCases = specTest.cases.byRanges[filename]
val isExpectedException = testCases!!.floorEntry(exceptionPosition).value.all { it.value.unexpectedBehavior }
if (!isExpectedException)
Assert.fail()
}
}
override fun performAdditionalChecksAfterDiagnostics(
@@ -93,11 +121,16 @@ abstract class AbstractDiagnosticsTestSpec : AbstractDiagnosticsTest() {
moduleBindings: Map<TestModule?, BindingContext>,
languageVersionSettingsByModule: Map<TestModule?, LanguageVersionSettings>
) {
if (testValidator.testInfo.unexpectedBehavior!!) return
val diagnosticValidator = try {
DiagnosticTestTypeValidator(testFiles, testDataFile, specTest)
} catch (e: SpecTestValidationException) {
Assert.fail(e.description)
return
}
val diagnosticValidator = DiagnosticTestTypeValidator(testFiles)
try {
testValidator.validateTestType(computedTestType = diagnosticValidator.computeTestType())
diagnosticValidator.validatePathConsistency(testLinkedType)
diagnosticValidator.validateTestType()
} catch (e: SpecTestValidationException) {
Assert.fail(e.description)
} finally {
@@ -1226,6 +1226,11 @@ public class DiagnosticsTestSpecGenerated extends AbstractDiagnosticsTestSpec {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/13.kt");
}
@TestMetadata("14.kt")
public void test14() throws Exception {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/14.kt");
}
@TestMetadata("15.kt")
public void test15() throws Exception {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/15.kt");
@@ -1450,6 +1455,11 @@ public class DiagnosticsTestSpecGenerated extends AbstractDiagnosticsTestSpec {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/neg/17.kt");
}
@TestMetadata("19.kt")
public void test19() throws Exception {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/neg/19.kt");
}
@TestMetadata("2.kt")
public void test2() throws Exception {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/neg/2.kt");
@@ -1513,16 +1523,6 @@ public class DiagnosticsTestSpecGenerated extends AbstractDiagnosticsTestSpec {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos/2.kt");
}
@TestMetadata("3.kt")
public void test3() throws Exception {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos/3.kt");
}
@TestMetadata("4.kt")
public void test4() throws Exception {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos/4.kt");
}
public void testAllFilesPresentInPos() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@@ -1785,11 +1785,6 @@ public class DiagnosticsTestSpecGenerated extends AbstractDiagnosticsTestSpec {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractFunction/pos/1.kt");
}
@TestMetadata("2.kt")
public void test2() throws Exception {
runTest("compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractFunction/pos/2.kt");
}
public void testAllFilesPresentInPos() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractFunction/pos"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@@ -5,13 +5,12 @@
package org.jetbrains.kotlin.codegen
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.spec.parsers.CommonParser
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.packagePattern
import org.jetbrains.kotlin.spec.utils.GeneralConfiguration.TESTDATA_PATH
import java.io.File
import org.jetbrains.kotlin.spec.validators.AbstractSpecTestValidator
import org.jetbrains.kotlin.spec.validators.SpecTestValidationException
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.junit.Assert
import java.util.regex.Pattern
import java.io.File
abstract class AbstractBlackBoxCodegenTestSpec : AbstractBlackBoxCodegenTest() {
companion object {
@@ -24,15 +23,13 @@ abstract class AbstractBlackBoxCodegenTestSpec : AbstractBlackBoxCodegenTest() {
private val helperDirectives = mapOf(
"REFLECT" to "reflect.kt"
)
private val packagePattern =
Pattern.compile("""(?:^|${AbstractSpecTestValidator.lineSeparator})package (?<packageName>.*?)(?:;|${AbstractSpecTestValidator.lineSeparator})""")
}
private fun addPackageDirectiveToHelperFile(helperContent: String, packageName: String?) =
helperContent.replace(HELPERS_PACKAGE_VARIABLE, if (packageName == null) "" else "package $packageName")
private fun includeHelpers(wholeFile: File, files: MutableList<TestFile>) {
val fileContent = wholeFile.readText()
val fileContent = FileUtil.loadFile(wholeFile, true)
val helpersSpecified = InTextDirectivesUtils.findListWithPrefixes(fileContent, HELPERS_DIRECTIVE)
val packageName = packagePattern.matcher(fileContent).let {
if (it.find()) it.group("packageName") else null
@@ -40,7 +37,7 @@ abstract class AbstractBlackBoxCodegenTestSpec : AbstractBlackBoxCodegenTest() {
helpersSpecified.forEach {
if (helperDirectives.contains(it)) {
val helperContent = File("$HELPERS_PATH/${helperDirectives[it]}").readText()
val helperContent = FileUtil.loadFile(File("$HELPERS_PATH/${helperDirectives[it]}"), true)
files.add(
TestFile(helperDirectives[it]!!, addPackageDirectiveToHelperFile(helperContent, packageName))
)
@@ -49,15 +46,12 @@ abstract class AbstractBlackBoxCodegenTestSpec : AbstractBlackBoxCodegenTest() {
}
override fun doMultiFileTest(wholeFile: File, files: MutableList<TestFile>, javaFilesDir: File?) {
val testValidator = AbstractSpecTestValidator.getInstanceByType(wholeFile)
val (specTest, _) = CommonParser.parseSpecTest(
wholeFile.canonicalPath,
mapOf("main.kt" to FileUtil.loadFile(wholeFile, true))
)
try {
testValidator.parseTestInfo()
} catch (e: SpecTestValidationException) {
Assert.fail(e.description)
}
testValidator.printTestInfo()
println(specTest)
includeHelpers(wholeFile, files)
@@ -5,29 +5,28 @@
package org.jetbrains.kotlin.parsing
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.spec.parsers.CommonParser
import org.jetbrains.kotlin.spec.validators.*
import org.junit.Assert
import java.io.File
abstract class AbstractParsingTestSpec : AbstractParsingTest() {
private lateinit var testValidator: AbstractSpecTestValidator<out AbstractSpecTest>
override fun doParsingTest(filePath: String) {
testValidator = AbstractSpecTestValidator.getInstanceByType(filePath)
val (specTest, testLinkedType) = CommonParser.parseSpecTest(
filePath,
mapOf("main.kt" to FileUtil.loadFile(File(filePath), true))
)
println(specTest)
super.doParsingTest(filePath, CommonParser::testInfoFilter)
val psiTestValidator = ParsingTestTypeValidator(myFile, File(filePath), specTest)
try {
testValidator.parseTestInfo()
} catch (e: SpecTestValidationException) {
Assert.fail(e.description)
}
testValidator.printTestInfo()
super.doParsingTest(filePath, testValidator::testInfoFilter)
if (testValidator.testInfo.unexpectedBehavior!!) return
try {
testValidator.validateTestType(computedTestType = ParsingTestTypeValidator.computeTestType(myFile))
psiTestValidator.validatePathConsistency(testLinkedType)
psiTestValidator.validateTestType()
} catch (e: SpecTestValidationException) {
Assert.fail(e.description)
}
@@ -228,6 +228,16 @@ public class ParsingTestSpecGenerated extends AbstractParsingTestSpec {
KotlinTestUtils.runTest(this::doParsingTest, TargetBackend.ANY, testDataFilePath);
}
@TestMetadata("1.1.kt")
public void test1_1() throws Exception {
runTest("compiler/tests-spec/testData/psi/linked/constant-literals/boolean-literals/p-1/pos/1.1.kt");
}
@TestMetadata("1.2.kt")
public void test1_2() throws Exception {
runTest("compiler/tests-spec/testData/psi/linked/constant-literals/boolean-literals/p-1/pos/1.2.kt");
}
@TestMetadata("2.1.kt")
public void test2_1() throws Exception {
runTest("compiler/tests-spec/testData/psi/linked/constant-literals/boolean-literals/p-1/pos/2.1.kt");
@@ -0,0 +1,83 @@
/*
* 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.spec.models.LinkedSpecTestFileInfoElementType
import org.jetbrains.kotlin.spec.models.NotLinkedSpecTestFileInfoElementType
import org.jetbrains.kotlin.spec.parsers.BasePatterns
import org.jetbrains.kotlin.spec.parsers.LinkedSpecTestPatterns
import org.jetbrains.kotlin.spec.parsers.NotLinkedSpecTestPatterns
import org.jetbrains.kotlin.spec.parsers.CommonParser.withSpaces
import java.util.*
import java.util.regex.Matcher
import java.util.regex.Pattern
typealias TestFiles = Map<String, String>
typealias TestCasesByNumbers = MutableMap<Int, SpecTestCase>
typealias TestCasesByFiles = MutableMap<String, TestCasesByNumbers>
typealias TestCasesByRanges = MutableMap<String, NavigableMap<Int, TestCasesByNumbers>>
enum class TestType(val type: String) {
POSITIVE("pos"),
NEGATIVE("neg");
companion object {
private val map = values().associateBy(TestType::type)
val joinedValues = values().joinToString("|").withSpaces()
fun fromValue(type: String) = map[type]
}
}
enum class TestArea(val testDataPath: String) {
PSI("psi"),
DIAGNOSTICS("diagnostics"),
CODEGEN_BOX("codegen/box");
companion object {
val joinedValues = values().joinToString("|").withSpaces()
}
}
enum class SpecTestLinkedType(
val testDataPath: String,
val patterns: Lazy<BasePatterns>,
val infoElements: Lazy<Array<out SpecTestInfoElementType>>
) {
LINKED(
"linked",
lazy { LinkedSpecTestPatterns },
lazy { LinkedSpecTestFileInfoElementType.values() }
),
NOT_LINKED(
"notLinked",
lazy { NotLinkedSpecTestPatterns },
lazy { NotLinkedSpecTestFileInfoElementType.values() }
)
}
interface SpecTestInfoElementType {
val valuePattern: Pattern?
val required: Boolean
}
data class SpecTestInfoElementContent(
val content: String,
val additionalMatcher: Matcher? = null
)
data class SpecTestCase(
var code: String,
var ranges: MutableList<IntRange>,
var unexpectedBehavior: Boolean,
val issues: MutableList<String>?
)
data class SpecTestCasesSet(
val byFiles: TestCasesByFiles,
val byRanges: TestCasesByRanges,
val byNumbers: TestCasesByNumbers
)
@@ -3,7 +3,7 @@
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.spec.generators.featureinteraction
package org.jetbrains.kotlin.spec.generators.templates
enum class Feature(val config: FeatureTemplatesConfig) {
IDENTIFIERS(
@@ -3,7 +3,7 @@
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.spec.generators.featureinteraction
package org.jetbrains.kotlin.spec.generators.templates
import org.jetbrains.kotlin.spec.utils.GeneralConfiguration.TESTDATA_PATH
import java.io.File
@@ -3,10 +3,10 @@
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.spec.generators.featureinteraction
package org.jetbrains.kotlin.spec.generators.templates
import org.jetbrains.kotlin.spec.TestArea
import org.jetbrains.kotlin.spec.utils.GeneralConfiguration.TESTDATA_PATH
import org.jetbrains.kotlin.spec.validators.TestArea
import java.io.File
enum class FeatureTemplatesType {
@@ -3,11 +3,11 @@
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.spec.generators.featureinteraction
package org.jetbrains.kotlin.spec.generators.templates
import org.jetbrains.kotlin.spec.validators.SpecTestLinkedType
import org.jetbrains.kotlin.spec.validators.TestArea
import org.jetbrains.kotlin.spec.validators.TestType
import org.jetbrains.kotlin.spec.SpecTestLinkedType
import org.jetbrains.kotlin.spec.TestArea
import org.jetbrains.kotlin.spec.TestType
enum class SubstitutionTag(val passType: SubstitutionPassType = SubstitutionPassType.FIRST) {
DIRECTIVES,
@@ -3,7 +3,7 @@
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.spec.generators.featureinteraction
package org.jetbrains.kotlin.spec.generators.templates
import org.jetbrains.kotlin.spec.tasks.generateTests
@@ -3,7 +3,7 @@
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.spec.generators.featureinteraction
package org.jetbrains.kotlin.spec.generators.templates
enum class TemplateValidationTransformerType {
TRIM_BACKTICKS
@@ -0,0 +1,79 @@
/*
* 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.models
import org.jetbrains.kotlin.spec.*
import org.jetbrains.kotlin.spec.parsers.CommonPatterns
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.issuesPattern
import org.jetbrains.kotlin.spec.parsers.TestCasePatterns.testCaseNumberPattern
import java.util.regex.Matcher
import java.util.regex.Pattern
typealias SpecTestInfoElements<T> = Map<T, SpecTestInfoElementContent>
enum class CommonInfoElementType(
override val valuePattern: Pattern? = null,
override val required: Boolean = false
) : SpecTestInfoElementType {
UNEXPECTED_BEHAVIOUR,
ISSUES(valuePattern = issuesPattern),
DISCUSSION,
NOTE
}
enum class CommonSpecTestFileInfoElementType(
override val valuePattern: Pattern? = null,
override val required: Boolean = false
) : SpecTestInfoElementType {
SECTIONS(valuePattern = CommonPatterns.sectionsInFilePattern, required = true),
NUMBER(required = true),
DESCRIPTION(required = true)
}
enum class SpecTestCaseInfoElementType(
override val valuePattern: Pattern? = null,
override val required: Boolean = false
) : SpecTestInfoElementType {
TESTCASE_NUMBER(valuePattern = testCaseNumberPattern, required = true)
}
abstract class AbstractSpecTest(
val testArea: TestArea,
val testType: TestType,
val sections: List<String>,
val testNumber: Int,
val description: String,
val cases: SpecTestCasesSet,
val unexpectedBehavior: Boolean,
val issues: Set<String>
) {
companion object {
private fun issuesToString(issues: Set<String>) = issues.joinToString(", ") { CommonPatterns.ISSUE_TRACKER + it }
}
abstract fun checkPathConsistency(pathMatcher: Matcher): Boolean
protected fun getIssuesText(): String? {
val testCaseIssues = cases.byNumbers.flatMap { it.value.issues!! }
return if (issues.isNotEmpty() || testCaseIssues.isNotEmpty()) {
"LINKED ISSUES: ${issuesToString(issues + testCaseIssues)}"
} else null
}
protected fun getUnexpectedBehaviourText(): String? {
val separatedTestCasesUnexpectedBehaviorNumber = cases.byNumbers.count { it.value.unexpectedBehavior }
val testCasesUnexpectedBehaviorNumber = when {
unexpectedBehavior -> cases.byNumbers.size
separatedTestCasesUnexpectedBehaviorNumber != 0 -> separatedTestCasesUnexpectedBehaviorNumber
else -> 0
}
return if (testCasesUnexpectedBehaviorNumber != 0) {
"!!! HAS UNEXPECTED BEHAVIOUR (in $testCasesUnexpectedBehaviorNumber cases) !!!"
} else null
}
}
@@ -0,0 +1,60 @@
/*
* 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.models
import org.jetbrains.kotlin.spec.SpecTestCasesSet
import org.jetbrains.kotlin.spec.SpecTestInfoElementType
import org.jetbrains.kotlin.spec.TestArea
import org.jetbrains.kotlin.spec.TestType
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.ls
import org.jetbrains.kotlin.spec.parsers.CommonParser.withUnderscores
import org.jetbrains.kotlin.spec.parsers.CommonParser.splitByPathSeparator
import org.jetbrains.kotlin.spec.parsers.CommonParser.withSpaces
import org.jetbrains.kotlin.spec.parsers.LinkedSpecTestPatterns.sentencePattern
import java.util.regex.Matcher
import java.util.regex.Pattern
enum class LinkedSpecTestFileInfoElementType(
override val valuePattern: Pattern? = null,
override val required: Boolean = false
) : SpecTestInfoElementType {
PARAGRAPH(required = true),
SENTENCE(valuePattern = sentencePattern, required = true)
}
class LinkedSpecTest(
testArea: TestArea,
testType: TestType,
sections: List<String>,
val paragraphNumber: Int,
val sentenceNumber: Int,
private val sentence: String,
testNumber: Int,
description: String,
cases: SpecTestCasesSet,
unexpectedBehavior: Boolean,
issues: Set<String>
) : AbstractSpecTest(testArea, testType, sections, testNumber, description, cases, unexpectedBehavior, issues) {
override fun checkPathConsistency(pathMatcher: Matcher) =
testArea == TestArea.valueOf(pathMatcher.group("testArea").withUnderscores())
&& testType == TestType.fromValue(pathMatcher.group("testType"))!!
&& sections == pathMatcher.group("sections").splitByPathSeparator()
&& paragraphNumber == pathMatcher.group("paragraphNumber").toInt()
&& sentenceNumber == pathMatcher.group("sentenceNumber").toInt()
&& testNumber == pathMatcher.group("testNumber").toInt()
override fun toString() = buildString {
append("--------------------------------------------------$ls")
super.getUnexpectedBehaviourText()?.let { append(it + ls) }
append("${testArea.name.withSpaces()} $testType SPEC TEST (${testType.toString().withSpaces()})$ls")
append("SECTIONS: ${sections.joinToString()} (paragraph: $paragraphNumber)$ls")
append("SENTENCE $sentenceNumber: $sentence$ls")
append("NUMBER: $testNumber$ls")
append("TEST CASES: ${cases.byNumbers.size.coerceAtLeast(1)}$ls")
append("DESCRIPTION: $description$ls")
super.getIssuesText()?.let { append(it + ls) }
}
}
@@ -0,0 +1,50 @@
/*
* 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.models
import org.jetbrains.kotlin.spec.SpecTestCasesSet
import org.jetbrains.kotlin.spec.SpecTestInfoElementType
import org.jetbrains.kotlin.spec.TestArea
import org.jetbrains.kotlin.spec.TestType
import org.jetbrains.kotlin.spec.parsers.CommonParser.splitByPathSeparator
import org.jetbrains.kotlin.spec.parsers.CommonParser.withUnderscores
import org.jetbrains.kotlin.spec.parsers.CommonParser.withSpaces
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.ls
import java.util.regex.Matcher
import java.util.regex.Pattern
enum class NotLinkedSpecTestFileInfoElementType(
override val valuePattern: Pattern? = null,
override val required: Boolean = false
) : SpecTestInfoElementType
class NotLinkedSpecTest(
testArea: TestArea,
testType: TestType,
sections: List<String>,
testNumber: Int,
description: String,
cases: SpecTestCasesSet,
unexpectedBehavior: Boolean,
issues: Set<String>
) : AbstractSpecTest(testArea, testType, sections, testNumber, description, cases, unexpectedBehavior, issues) {
override fun checkPathConsistency(pathMatcher: Matcher) =
testArea == TestArea.valueOf(pathMatcher.group("testArea").withUnderscores())
&& testType == TestType.fromValue(pathMatcher.group("testType"))!!
&& sections == pathMatcher.group("sections").splitByPathSeparator()
&& testNumber == pathMatcher.group("testNumber").toInt()
override fun toString() = buildString {
append("--------------------------------------------------$ls")
super.getUnexpectedBehaviourText()?.let { append(it + ls) }
append("${testArea.name.withSpaces()} $testType SPEC TEST (${testType.type.withSpaces()})$ls")
append("SECTIONS: ${sections.joinToString()}$ls")
append("NUMBER: $testNumber$ls")
append("TEST CASES: ${cases.byNumbers.size.coerceAtLeast(1)}$ls")
append("DESCRIPTION: $description$ls")
super.getIssuesText()?.let { append(it + ls) }
}
}
@@ -0,0 +1,121 @@
/*
* 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.parsers
import org.jetbrains.kotlin.spec.*
import org.jetbrains.kotlin.spec.SpecTestInfoElementContent
import org.jetbrains.kotlin.spec.SpecTestLinkedType
import org.jetbrains.kotlin.spec.models.LinkedSpecTest
import org.jetbrains.kotlin.spec.models.LinkedSpecTestFileInfoElementType
import org.jetbrains.kotlin.spec.models.NotLinkedSpecTest
import org.jetbrains.kotlin.spec.models.SpecTestInfoElements
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.testInfoElementPattern
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.testPathBaseRegexTemplate
import org.jetbrains.kotlin.spec.parsers.LinkedSpecTestPatterns.testInfoPattern
import org.jetbrains.kotlin.spec.parsers.TestCasePatterns.testCaseInfoPattern
import org.jetbrains.kotlin.spec.validators.*
import java.io.File
import java.util.regex.Pattern
object CommonParser {
fun String.withUnderscores() = replace(" ", "_").toUpperCase()
fun String.splitByComma() = split(Regex(""",\s*"""))
fun String.splitByPathSeparator() = split(File.separator)
fun String.withSpaces() = replace("_", " ")
private fun isPathMatched(pathPartRegex: String, testFilePath: String) =
Pattern.compile(testPathBaseRegexTemplate.format(pathPartRegex)).matcher(testFilePath).find()
fun parseSpecTest(testFilePath: String, files: TestFiles) = when {
isPathMatched(LinkedSpecTestPatterns.pathPartRegex, testFilePath) ->
Pair(parseLinkedSpecTest(testFilePath, files), SpecTestLinkedType.LINKED)
isPathMatched(NotLinkedSpecTestPatterns.pathPartRegex, testFilePath) ->
Pair(parseNotLinkedSpecTest(testFilePath, files), SpecTestLinkedType.NOT_LINKED)
else ->
throw SpecTestValidationException(SpecTestValidationFailedReason.FILENAME_NOT_VALID)
}
private fun parseLinkedSpecTest(testFilePath: String, testFiles: TestFiles): LinkedSpecTest {
val parsedTestFile = parseTestInfo(testFilePath, testFiles, SpecTestLinkedType.LINKED)
val testInfoElements = parsedTestFile.testInfoElements
val sentenceMatcher = testInfoElements[LinkedSpecTestFileInfoElementType.SENTENCE]!!.additionalMatcher!!
return LinkedSpecTest(
parsedTestFile.testArea,
parsedTestFile.testType,
parsedTestFile.sections,
testInfoElements[LinkedSpecTestFileInfoElementType.PARAGRAPH]!!.content.toInt(),
sentenceMatcher.group("number").toInt(),
sentenceMatcher.group("text"),
parsedTestFile.testNumber,
parsedTestFile.testDescription,
parsedTestFile.testCasesSet,
parsedTestFile.unexpectedBehavior,
parsedTestFile.issues
)
}
private fun parseNotLinkedSpecTest(testFilePath: String, testFiles: TestFiles): NotLinkedSpecTest {
val parsedTestFile = parseTestInfo(testFilePath, testFiles, SpecTestLinkedType.NOT_LINKED)
return NotLinkedSpecTest(
parsedTestFile.testArea,
parsedTestFile.testType,
parsedTestFile.sections,
parsedTestFile.testNumber,
parsedTestFile.testDescription,
parsedTestFile.testCasesSet,
parsedTestFile.unexpectedBehavior,
parsedTestFile.issues
)
}
fun parseIssues(issues: SpecTestInfoElementContent?) = issues?.content?.splitByComma()?.toSet().orEmpty()
fun parseTestInfoElements(rules: Array<SpecTestInfoElementType>, rawElements: String):
SpecTestInfoElements<SpecTestInfoElementType> {
val testInfoElementsMap = mutableMapOf<SpecTestInfoElementType, SpecTestInfoElementContent>()
val testInfoElementMatcher = testInfoElementPattern.matcher(rawElements)
while (testInfoElementMatcher.find()) {
val testInfoOriginalElementName = testInfoElementMatcher.group("name")
val testInfoElementName = rules.find {
it as Enum<*>
it.name == testInfoOriginalElementName.withUnderscores()
} ?: 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)
}
rules.forEach {
if (it.required && !testInfoElementsMap.contains(it)) {
throw SpecTestValidationException(
SpecTestValidationFailedReason.TESTINFO_NOT_VALID,
"$it in case or test info is required."
)
}
}
return testInfoElementsMap
}
fun testInfoFilter(fileContent: String) =
testInfoPattern.matcher(fileContent).replaceAll("").let {
testCaseInfoPattern.matcher(it).replaceAll("")
}
}
@@ -0,0 +1,94 @@
/*
* 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.parsers
import org.jetbrains.kotlin.spec.models.SpecTestCaseInfoElementType
import org.jetbrains.kotlin.spec.SpecTestLinkedType
import org.jetbrains.kotlin.spec.TestArea
import org.jetbrains.kotlin.spec.TestType
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.INTEGER_REGEX
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.SINGLE_LINE_COMMENT_REGEX
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.ASTERISK_REGEX
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.directiveRegex
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.multilineCommentRegex
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.ps
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.testAreaRegex
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.testPathRegexTemplate
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.testTypeRegex
import org.jetbrains.kotlin.spec.parsers.CommonParser.withSpaces
import org.jetbrains.kotlin.spec.parsers.CommonPatterns.sectionsInPathRegex
import java.io.File
import java.util.regex.Pattern
object CommonPatterns {
const val ISSUE_TRACKER = "https://youtrack.jetbrains.com/issue/"
const val INTEGER_REGEX = """[1-9]\d*"""
const val SINGLE_LINE_COMMENT_REGEX = """\/\/\s*%s"""
const val ASTERISK_REGEX = """\*"""
val ls: String = System.lineSeparator()
val ps: String = Pattern.quote(File.separator)
val multilineCommentRegex = """\/\*\s+?%s\s+\*\/(?:\n)*"""
val directiveRegex =
"""${SINGLE_LINE_COMMENT_REGEX.format("""[\w\s]+:""")}|${multilineCommentRegex.format(""" $ASTERISK_REGEX [\w\s]+:[\s\S]*?""")}"""
val testAreaRegex = """(?<testArea>${TestArea.joinedValues})"""
val testTypeRegex = """(?<testType>${TestType.joinedValues})"""
val testInfoElementPattern: Pattern = Pattern.compile("""(?: \* )?(?<name>[A-Z ]+?)(?::\s*(?<value>.*?))?\n""")
val testPathBaseRegexTemplate = """^.*?$ps(?<testArea>diagnostics|psi|(?:codegen${ps}box))$ps%s"""
val testPathRegexTemplate = """$testPathBaseRegexTemplate$ps(?<testType>pos|neg)$ps%s$"""
val issuesPattern: Pattern = Pattern.compile("""(KT-[1-9]\d*)(,\s*KT-[1-9]\d*)*""")
val sectionsInFilePattern: Pattern = Pattern.compile("""\w+(,\s+\w+)*""")
val sectionsInPathRegex = """(?<sections>(?:[\w-]+)(?:$ps[\w-]+)*?)"""
val packagePattern: Pattern = Pattern.compile("""(?:^|\n)package (?<packageName>.*?)(?:;|\ns)""")
}
interface BasePatterns {
val pathPartRegex: String
val testPathPattern: Pattern
val testInfoPattern: Pattern
}
object NotLinkedSpecTestPatterns : BasePatterns {
private const val FILENAME_REGEX = """(?<testNumber>$INTEGER_REGEX)\.kt"""
override val pathPartRegex = SpecTestLinkedType.NOT_LINKED.testDataPath + ps + sectionsInPathRegex
override val testPathPattern: Pattern =
Pattern.compile(testPathRegexTemplate.format(pathPartRegex, FILENAME_REGEX))
override val testInfoPattern: Pattern =
Pattern.compile(multilineCommentRegex.format(""" $ASTERISK_REGEX KOTLIN $testAreaRegex NOT LINKED SPEC TEST \($testTypeRegex\)\n(?<infoElements>[\s\S]*?\n)"""))
}
object LinkedSpecTestPatterns : BasePatterns {
private const val FILENAME_REGEX = """(?<sentenceNumber>$INTEGER_REGEX)\.(?<testNumber>$INTEGER_REGEX)\.kt"""
override val pathPartRegex =
"""${SpecTestLinkedType.LINKED.testDataPath}$ps$sectionsInPathRegex${ps}p-(?<paragraphNumber>$INTEGER_REGEX)"""
override val testPathPattern: Pattern =
Pattern.compile(testPathRegexTemplate.format(pathPartRegex, FILENAME_REGEX))
override val testInfoPattern: Pattern =
Pattern.compile(multilineCommentRegex.format(""" $ASTERISK_REGEX KOTLIN $testAreaRegex SPEC TEST \($testTypeRegex\)\n(?<infoElements>[\s\S]*?\n)"""))
val sentencePattern: Pattern = Pattern.compile("""^\[(?<number>$INTEGER_REGEX)\]\s*(?<text>.*?)$""")
}
object TestCasePatterns {
private const val TEST_CASE_CODE_REGEX = """(?<%s>[\s\S]*?)"""
private val testCaseInfoElementsRegex = """(?<%s>%s${SpecTestCaseInfoElementType.TESTCASE_NUMBER.name.withSpaces()}:%s*?\n)"""
private val testCaseInfoRegex = """$TEST_CASE_CODE_REGEX(?<%s>(?:$directiveRegex)|$)"""
private val testCaseInfoSingleLineRegex =
SINGLE_LINE_COMMENT_REGEX.format(
testCaseInfoElementsRegex.format("infoElementsSL", "", """\s*.""")
) + testCaseInfoRegex.format("codeSL", "nextDirectiveSL")
private val testCaseInfoMultilineRegex =
multilineCommentRegex.format(
testCaseInfoElementsRegex.format("infoElementsML", """ $ASTERISK_REGEX """, """[\s\S]""")
) + testCaseInfoRegex.format("codeML", "nextDirectiveML")
val testCaseInfoPattern: Pattern = Pattern.compile("(?:$testCaseInfoSingleLineRegex)|(?:$testCaseInfoMultilineRegex)")
val testCaseNumberPattern: Pattern = Pattern.compile("""([1-9]\d*)(,\s*[1-9]\d*)*""")
}
@@ -0,0 +1,83 @@
/*
* 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.parsers
import org.jetbrains.kotlin.spec.*
import org.jetbrains.kotlin.spec.models.CommonInfoElementType
import org.jetbrains.kotlin.spec.models.SpecTestCaseInfoElementType
import org.jetbrains.kotlin.spec.models.SpecTestInfoElements
import org.jetbrains.kotlin.spec.parsers.CommonParser.splitByComma
import org.jetbrains.kotlin.spec.parsers.TestCasePatterns.testCaseInfoPattern
import java.util.*
private operator fun SpecTestCase.plusAssign(addTestCase: SpecTestCase) {
this.code += addTestCase.code
this.unexpectedBehavior = this.unexpectedBehavior or addTestCase.unexpectedBehavior
this.issues?.addAll(addTestCase.issues!!)
this.ranges.addAll(addTestCase.ranges)
}
private fun SpecTestCase.save(
testCasesByNumbers: TestCasesByNumbers,
testCasesOfFile: TestCasesByNumbers,
testCasesByRangesOfFile: NavigableMap<Int, TestCasesByNumbers>,
caseInfoElements: SpecTestInfoElements<SpecTestInfoElementType>
) {
val testCaseNumbers =
caseInfoElements[SpecTestCaseInfoElementType.TESTCASE_NUMBER]!!.content.splitByComma().map { it.trim().toInt() }
val startPosition = this.ranges[0].first
testCaseNumbers.forEach { testCaseNumber ->
if (testCasesOfFile[testCaseNumber] != null) {
testCasesOfFile[testCaseNumber]!! += this
testCasesByNumbers[testCaseNumber]!! += this
} else {
testCasesOfFile[testCaseNumber] = this
testCasesByNumbers[testCaseNumber] = this
}
testCasesByRangesOfFile.putIfAbsent(startPosition, mutableMapOf())
testCasesByRangesOfFile[startPosition]!![testCaseNumber] = this
}
}
fun parseTestCases(testFiles: TestFiles): SpecTestCasesSet {
val testCasesSet = SpecTestCasesSet(mutableMapOf(), mutableMapOf(), mutableMapOf())
var rangeOffset = 0
for ((filename, fileContent) in testFiles) {
val matcher = testCaseInfoPattern.matcher(fileContent)
var startFind = 0
if (!testCasesSet.byFiles.contains(filename)) {
testCasesSet.byFiles[filename] = mutableMapOf()
testCasesSet.byRanges[filename] = TreeMap()
}
val testCasesOfFile = testCasesSet.byFiles[filename]!!
val testCasesByRangesOfFile = testCasesSet.byRanges[filename]!!
while (matcher.find(startFind)) {
val caseInfoElements = CommonParser.parseTestInfoElements(
arrayOf(*CommonInfoElementType.values(), *SpecTestCaseInfoElementType.values()),
matcher.group("infoElementsSL") ?: matcher.group("infoElementsML")
)
val nextDirective = matcher.group("nextDirectiveSL") ?: matcher.group("nextDirectiveML")
val range = matcher.start()..matcher.end() - nextDirective.length
SpecTestCase(
code = matcher.group("codeSL") ?: matcher.group("codeML"),
ranges = mutableListOf(range),
unexpectedBehavior = caseInfoElements.contains(CommonInfoElementType.UNEXPECTED_BEHAVIOUR),
issues = CommonParser.parseIssues(caseInfoElements[CommonInfoElementType.ISSUES]).toMutableList()
).save(testCasesSet.byNumbers, testCasesOfFile, testCasesByRangesOfFile, caseInfoElements)
startFind = matcher.end() - nextDirective.length
}
rangeOffset += fileContent.length
}
return testCasesSet
}
@@ -0,0 +1,58 @@
/*
* 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.parsers
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.spec.*
import org.jetbrains.kotlin.spec.models.CommonInfoElementType
import org.jetbrains.kotlin.spec.models.CommonSpecTestFileInfoElementType
import org.jetbrains.kotlin.spec.models.SpecTestInfoElements
import org.jetbrains.kotlin.spec.parsers.CommonParser.withUnderscores
import org.jetbrains.kotlin.spec.parsers.CommonParser.splitByComma
import org.jetbrains.kotlin.spec.validators.*
import java.io.File
data class ParsedTestFile(
val testArea: TestArea,
val testType: TestType,
val sections: List<String>,
val testNumber: Int,
val testDescription: String,
val testInfoElements: SpecTestInfoElements<SpecTestInfoElementType>,
val testCasesSet: SpecTestCasesSet,
val unexpectedBehavior: Boolean,
val issues: Set<String>
)
fun parseTestInfo(testFilePath: String, testFiles: TestFiles, linkedTestType: SpecTestLinkedType): ParsedTestFile {
val patterns = linkedTestType.patterns.value
val testInfoByFilenameMatcher = patterns.testPathPattern.matcher(testFilePath)
if (!testInfoByFilenameMatcher.find())
throw SpecTestValidationException(SpecTestValidationFailedReason.FILENAME_NOT_VALID)
val testInfoByContentMatcher = patterns.testInfoPattern.matcher(FileUtil.loadFile(File(testFilePath), true))
if (!testInfoByContentMatcher.find())
throw SpecTestValidationException(SpecTestValidationFailedReason.TESTINFO_NOT_VALID)
val testInfoElements = CommonParser.parseTestInfoElements(
arrayOf(*CommonInfoElementType.values(), *CommonSpecTestFileInfoElementType.values(), *linkedTestType.infoElements.value),
testInfoByContentMatcher.group("infoElements")
)
return ParsedTestFile(
testArea = TestArea.valueOf(testInfoByContentMatcher.group("testArea").withUnderscores()),
testType = TestType.valueOf(testInfoByContentMatcher.group("testType")),
sections = testInfoElements[CommonSpecTestFileInfoElementType.SECTIONS]!!.content.splitByComma(),
testNumber = testInfoElements[CommonSpecTestFileInfoElementType.NUMBER]!!.content.toInt(),
testDescription = testInfoElements[CommonSpecTestFileInfoElementType.DESCRIPTION]!!.content,
testInfoElements = testInfoElements,
testCasesSet = parseTestCases(testFiles),
unexpectedBehavior = testInfoElements.contains(CommonInfoElementType.UNEXPECTED_BEHAVIOUR),
issues = CommonParser.parseIssues(testInfoElements[CommonInfoElementType.ISSUES])
)
}
@@ -5,11 +5,11 @@
package org.jetbrains.kotlin.spec.tasks
import org.jetbrains.kotlin.spec.generators.featureinteraction.Feature
import org.jetbrains.kotlin.spec.generators.featureinteraction.generationLinkedSpecTestDataConfig
import org.jetbrains.kotlin.spec.generators.featureinteraction.generationSpecTestDataConfigGroup
import org.jetbrains.kotlin.spec.validators.TestArea
import org.jetbrains.kotlin.spec.validators.TestType
import org.jetbrains.kotlin.spec.TestArea
import org.jetbrains.kotlin.spec.TestType
import org.jetbrains.kotlin.spec.generators.templates.Feature
import org.jetbrains.kotlin.spec.generators.templates.generationLinkedSpecTestDataConfig
import org.jetbrains.kotlin.spec.generators.templates.generationSpecTestDataConfigGroup
fun main(args: Array<String>) {
generationSpecTestDataConfigGroup(regenerateTests = true) {
@@ -6,11 +6,11 @@
package org.jetbrains.kotlin.spec.tasks
import com.google.gson.JsonObject
import org.jetbrains.kotlin.spec.models.LinkedSpecTest
import org.jetbrains.kotlin.spec.parsers.CommonParser
import org.jetbrains.kotlin.spec.utils.GeneralConfiguration.MODULE_PATH
import org.jetbrains.kotlin.spec.utils.GeneralConfiguration.TESTDATA_PATH
import org.jetbrains.kotlin.spec.utils.TestsJsonMapBuilder
import org.jetbrains.kotlin.spec.validators.LinkedSpecTestValidator
import org.jetbrains.kotlin.spec.validators.SpecTestValidationException
import java.io.File
private const val OUT_DIR = "out"
@@ -20,15 +20,10 @@ fun main(args: Array<String>) {
val testsMap = JsonObject()
File(TESTDATA_PATH).walkTopDown().forEach {
val specTestValidator = LinkedSpecTestValidator(it)
val (specTest, _) = CommonParser.parseSpecTest(it.canonicalPath, mapOf("main.kt" to it.readText()))
try {
specTestValidator.parseTestInfo()
} catch (e: SpecTestValidationException) {
return@forEach
}
TestsJsonMapBuilder.buildJsonElement(specTestValidator.testInfo, testsMap)
if (specTest is LinkedSpecTest)
TestsJsonMapBuilder.buildJsonElement(specTest, testsMap)
}
val outDir = "$MODULE_PATH/$OUT_DIR"
@@ -5,15 +5,15 @@
package org.jetbrains.kotlin.spec.tasks
import org.jetbrains.kotlin.spec.SpecTestLinkedType
import org.jetbrains.kotlin.spec.utils.SpecTestsStatElement
import org.jetbrains.kotlin.spec.utils.SpecTestsStatElementType
import org.jetbrains.kotlin.spec.utils.TestsStatisticCollector
import org.jetbrains.kotlin.spec.validators.SpecTestLinkedType
const val PRINT_BASE_INDENT = " "
fun linkedSpecTestsPrint() {
println("SPEC TESTS STATISTIC")
println("LINKED SPEC TESTS STATISTIC")
println("--------------------------------------------------")
val statistic = TestsStatisticCollector.collect(SpecTestLinkedType.LINKED)
@@ -21,14 +21,9 @@ fun linkedSpecTestsPrint() {
for ((areaName, areaElement) in statistic) {
println("$areaName: ${areaElement.number} tests")
for ((sectionName, sectionElement) in areaElement.elements) {
println(" $sectionName: ${sectionElement.number} tests")
for ((paragraphName, paragraphElement) in sectionElement.elements) {
val testsStatByType = mutableListOf<String>()
for ((typeName, typeElement) in paragraphElement.elements)
testsStatByType.add(" [ $typeName: ${typeElement.number} ]")
print(PRINT_BASE_INDENT.repeat(2))
println("PARAGRAPH $paragraphName: ${paragraphElement.number} tests${testsStatByType.joinToString("")}")
}
print(" $sectionName: ${sectionElement.number} tests")
notLinkedSpecTestsCategoriesPrint(sectionElement.elements)
println()
}
}
}
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.spec.utils
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import org.jetbrains.kotlin.spec.validators.*
import org.jetbrains.kotlin.spec.models.LinkedSpecTest
object TestsJsonMapBuilder {
private val stringListType = object : TypeToken<List<String>>() {}.type
@@ -19,7 +19,7 @@ object TestsJsonMapBuilder {
}
fun buildJsonElement(testInfo: LinkedSpecTest, testsMap: JsonObject) {
val sectionElement = addJsonIfNotExist(testsMap, testInfo.section)
val sectionElement = addJsonIfNotExist(testsMap, testInfo.sections[0])
val paragraphElement = addJsonIfNotExist(sectionElement, testInfo.paragraphNumber)
val sentenceElement = addJsonIfNotExist(paragraphElement, testInfo.sentenceNumber)
val testAreaElement = addJsonIfNotExist(sentenceElement, testInfo.testArea.name.toLowerCase())
@@ -27,12 +27,12 @@ object TestsJsonMapBuilder {
val testNumberElement = addJsonIfNotExist(testTypeElement, testInfo.testNumber)
testNumberElement.addProperty("description", testInfo.description)
testNumberElement.addProperty("caseNumber", testInfo.cases!!.size)
testNumberElement.addProperty("caseNumber", testInfo.cases.byFiles.size)
if (testInfo.unexpectedBehavior!!)
if (testInfo.unexpectedBehavior)
testNumberElement.addProperty("unexpectedBehavior", testInfo.unexpectedBehavior)
if (testInfo.issues!!.isNotEmpty())
if (testInfo.issues.isNotEmpty())
testNumberElement.add("issues", Gson().toJsonTree(testInfo.issues, stringListType))
}
}
@@ -5,8 +5,11 @@
package org.jetbrains.kotlin.spec.utils
import org.jetbrains.kotlin.spec.SpecTestLinkedType
import org.jetbrains.kotlin.spec.TestArea
import org.jetbrains.kotlin.spec.models.AbstractSpecTest
import org.jetbrains.kotlin.spec.parsers.CommonParser
import org.jetbrains.kotlin.spec.utils.GeneralConfiguration.TESTDATA_PATH
import org.jetbrains.kotlin.spec.validators.*
import java.io.File
open class SpecTestsStatElement(val type: SpecTestsStatElementType) {
@@ -42,27 +45,18 @@ object TestsStatisticCollector {
val statistic = mutableMapOf<TestArea, SpecTestsStatElement>()
for (specTestArea in TestArea.values()) {
val specTestsPath = "$TESTDATA_PATH/${specTestArea.name.toLowerCase()}/${testLinkedType.testDataPath}"
val specTestsPath = "$TESTDATA_PATH/${specTestArea.name.toLowerCase().replace("_", "/")}/${testLinkedType.testDataPath}"
statistic[specTestArea] = SpecTestsStatElement(SpecTestsStatElementType.AREA)
File(specTestsPath).walkTopDown().forEach areaTests@{
if (!it.isFile || it.extension != "kt") return@areaTests
val specTestsValidator = AbstractSpecTestValidator.getInstanceByType(it)
try {
specTestsValidator.parseTestInfo()
} catch (e: SpecTestValidationException) {
return@areaTests
}
val (specTest, _) = CommonParser.parseSpecTest(it.canonicalPath, mapOf("main.kt" to it.readText()))
incrementStatCounters(
statistic[specTestArea]!!,
when (testLinkedType) {
SpecTestLinkedType.LINKED -> getStatElementsByLinkedTests(specTestsValidator.testInfo as LinkedSpecTest)
SpecTestLinkedType.NOT_LINKED -> getStatElementsByNotLinkedTests(specTestsValidator.testInfo as NotLinkedSpecTest)
}
getStatElements(specTest)
)
}
}
@@ -70,15 +64,9 @@ object TestsStatisticCollector {
return statistic
}
private fun getStatElementsByLinkedTests(testInfo: LinkedSpecTest) = listOf(
SpecTestsStatElementType.SECTION to testInfo.section,
SpecTestsStatElementType.PARAGRAPH to testInfo.paragraphNumber,
SpecTestsStatElementType.TYPE to testInfo.testType.type
)
private fun getStatElementsByNotLinkedTests(testInfo: NotLinkedSpecTest) =
mutableListOf(SpecTestsStatElementType.SECTION to testInfo.section).apply {
addAll(testInfo.categories.map { SpecTestsStatElementType.CATEGORY to it })
private fun getStatElements(testInfo: AbstractSpecTest) =
mutableListOf(SpecTestsStatElementType.SECTION to testInfo.sections[0]).apply {
addAll(testInfo.sections.map { SpecTestsStatElementType.CATEGORY to it })
add(SpecTestsStatElementType.TYPE to testInfo.testType.type)
}
}
@@ -1,288 +0,0 @@
/*
* 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.validators
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(val testDataPath: String) {
PSI("psi"),
DIAGNOSTICS("diagnostics"),
CODEGEN_BOX("codegen/box")
}
enum class SpecTestLinkedType(val testDataPath: String) {
LINKED("linked"),
NOT_LINKED("notLinked")
}
interface SpecTestInfoElementType {
val valuePattern: Pattern?
val required: Boolean
}
enum class SpecTestCaseInfoElementType(
override val valuePattern: Pattern? = null,
override val required: Boolean = false
) : SpecTestInfoElementType {
CASE_DESCRIPTION(required = true),
ISSUES(valuePattern = LinkedSpecTestFileInfoElementType.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>?
)
enum class SpecTestValidationFailedReason(val description: String) {
FILENAME_NOT_VALID("Incorrect test filename or folder name."),
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>
interface SpecTestValidatorHelperObject {
val pathPartRegex: String
val filenameRegex: String
fun getPathPattern(): Pattern
}
abstract class AbstractSpecTest(
val testArea: TestArea,
val testType: TestType,
val section: String,
val testNumber: Int,
val description: String? = null,
val cases: List<SpecTestCase>? = null,
val unexpectedBehavior: Boolean? = null,
val issues: Set<String>? = null
) {
abstract fun checkConsistency(other: AbstractSpecTest): Boolean
}
abstract class AbstractSpecTestValidator<T : AbstractSpecTest>(private val testDataFile: File) {
val testInfo by lazy { testInfoByContent }
protected lateinit var testInfoByFilename: T
protected lateinit var testInfoByContent: T
abstract val testPathPattern: Pattern
abstract val testInfoPattern: Pattern
companion object {
const val ISSUE_TRACKER = "https://youtrack.jetbrains.com/issue/"
const val INTEGER_REGEX = """[1-9]\d*"""
val pathSeparator: String = Pattern.quote(File.separator)
val lineSeparator: String = System.lineSeparator()
val testAreaRegex = """(?<testArea>${TestArea.values().joinToString("|").replace("_", " ")})"""
val testTypeRegex = """(?<testType>${TestType.values().joinToString("|")})"""
val multilineCommentRegex = """\/\*\s*%s\s+\*\/(?:$lineSeparator)*"""
private val singlelineCommentRegex = """\/\/\s*%s(?:$lineSeparator)*"""
private val testInfoElementPattern: Pattern = Pattern.compile("""\s*(?<name>[A-Z ]+?)(?::\s*(?<value>.*?))?$lineSeparator""")
private val testCaseInfoRegex = """(?<infoElements>CASE DESCRIPTION:[\s\S]*?$lineSeparator)(?:$lineSeparator)*"""
private val testPathBaseRegexTemplate =
"""^.*?$pathSeparator(?<testArea>diagnostics|psi|(?:codegen${pathSeparator}box))$pathSeparator%s"""
val testPathRegexTemplate = """$testPathBaseRegexTemplate$pathSeparator(?<testType>pos|neg)$pathSeparator%s$"""
val testCaseInfoSingleLinePattern: Pattern = Pattern.compile(singlelineCommentRegex.format(testCaseInfoRegex))
val testCaseInfoMultilinePattern: Pattern = Pattern.compile(multilineCommentRegex.format(testCaseInfoRegex))
fun getInstanceByType(testFile: File) = when {
Pattern.compile(testPathBaseRegexTemplate.format(LinkedSpecTestValidator.pathPartRegex)).matcher(testFile.canonicalPath).find() ->
LinkedSpecTestValidator(testFile)
Pattern.compile(testPathBaseRegexTemplate.format(NotLinkedSpecTestValidator.pathPartRegex)).matcher(testFile.canonicalPath).find() ->
NotLinkedSpecTestValidator(testFile)
else -> throw SpecTestValidationException(SpecTestValidationFailedReason.FILENAME_NOT_VALID)
}
fun getInstanceByType(testPath: String) = getInstanceByType(File(testPath))
private fun getTestInfoElements(
testInfoElementRules: Array<out SpecTestInfoElementType>,
testInfoElements: String
): SpecTestInfoElements<SpecTestInfoElementType> {
val testInfoElementsMap = mutableMapOf<SpecTestInfoElementType, SpecTestInfoElementContent>()
val testInfoElementMatcher = testInfoElementPattern.matcher(testInfoElements)
while (testInfoElementMatcher.find()) {
val testInfoOriginalElementName = testInfoElementMatcher.group("name")
val testInfoElementName = testInfoElementRules.find {
it as Enum<*>
it.name == testInfoOriginalElementName.replace(" ", "_")
} ?: 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)
}
testInfoElementRules.forEach {
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 parseIssues(issues: SpecTestInfoElementContent?) = issues?.content?.split(",")
}
private fun getTestCasesInfo(
testCaseInfoMatcher: Matcher,
infoElements: SpecTestInfoElements<SpecTestInfoElementType>
): List<SpecTestCase> {
val testCases = mutableListOf<SpecTestCase>()
var testCasesCounter = 1
while (testCaseInfoMatcher.find()) {
val caseInfoElements = getTestInfoElements(
SpecTestCaseInfoElementType.values(),
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
}
abstract fun getSingleTestCase(testInfoElements: SpecTestInfoElements<SpecTestInfoElementType>) : SpecTestCase
fun testInfoFilter(fileContent: String): String =
testInfoPattern.matcher(fileContent).replaceAll("").let {
testCaseInfoSingleLinePattern.matcher(it).replaceAll("").let {
testCaseInfoMultilinePattern.matcher(it).replaceAll("")
}
}
abstract fun getTestInfo(
testInfoMatcher: Matcher,
testInfoElements: SpecTestInfoElements<SpecTestInfoElementType>,
testCases: List<SpecTestCase>,
unexpectedBehavior: Boolean = false,
issues: Set<String>? = null
): T
abstract fun getTestInfo(testInfoMatcher: Matcher): T
abstract fun parseTestInfo()
abstract fun printTestInfo()
fun parseTestInfo(testInfoElementsRules: Array<out SpecTestInfoElementType>) {
val testInfoByFilenameMatcher = testPathPattern.matcher(testDataFile.canonicalPath)
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(
testInfoElementsRules,
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(LinkedSpecTestFileInfoElementType.UNEXPECTED_BEHAVIOUR) || testCases.any { it.unexpectedBehavior },
issues = getIssues(
testCases,
parseIssues(
testInfoElements[LinkedSpecTestFileInfoElementType.ISSUES]
)
)
)
if (!testInfoByFilename.checkConsistency(testInfoByContent))
throw SpecTestValidationException(SpecTestValidationFailedReason.FILEPATH_AND_TESTINFO_IN_FILE_NOT_CONSISTENCY)
}
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)
}
}
}
@@ -0,0 +1,60 @@
/*
* 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.validators
import org.jetbrains.kotlin.spec.models.AbstractSpecTest
import org.jetbrains.kotlin.spec.SpecTestLinkedType
import org.jetbrains.kotlin.spec.TestType
import org.jetbrains.kotlin.spec.parsers.CommonPatterns
import java.io.File
enum class SpecTestValidationFailedReason(val description: String) {
FILENAME_NOT_VALID("Incorrect test filename or folder name."),
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)."),
INVALID_TEST_CASES_STRUCTURE(
"All code in the test file must be divided and marked as a 'test case' label.${CommonPatterns.ls}Example:${CommonPatterns.ls.repeat(2)}// TESTCASE NUMBER: 1${CommonPatterns.ls}fun main() { println(\"Hello, Kotlin!\") }${CommonPatterns.ls.repeat(2)}"
),
UNKNOWN_FRONTEND_EXCEPTION("Unknown frontend exception. Manual analysis is required."),
UNMATCHED_FRONTEND_EXCEPTION("Unmatched frontend exception. Manual analysis is required."),
UNKNOWN("Unknown validation error.")
}
class SpecTestValidationException(reason: SpecTestValidationFailedReason, details: String = "") : Exception() {
val description = "${reason.description} $details"
}
abstract class AbstractTestValidator(private val testInfo: AbstractSpecTest, private val testDataFile: File) {
fun validatePathConsistency(testLinkedType: SpecTestLinkedType) {
val matcher = testLinkedType.patterns.value.testPathPattern.matcher(testDataFile.canonicalPath).apply { find() }
if (!testInfo.checkPathConsistency(matcher))
throw SpecTestValidationException(SpecTestValidationFailedReason.FILEPATH_AND_TESTINFO_IN_FILE_NOT_CONSISTENCY)
}
abstract fun computeTestTypes(): Map<Int, TestType>
fun validateTestType() {
val computedTestTypes = computeTestTypes()
for ((caseNumber, case) in testInfo.cases.byNumbers) {
val testType = computedTestTypes[caseNumber] ?: TestType.POSITIVE
if (testType != testInfo.testType && !testInfo.unexpectedBehavior && !case.unexpectedBehavior) {
val isNotNegative = testType == TestType.POSITIVE && testInfo.testType == TestType.NEGATIVE
val isNotPositive = testType == 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, details = "TESTCASE: $caseNumber")
}
}
}
}
@@ -8,20 +8,44 @@ package org.jetbrains.kotlin.spec.validators
import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.spec.models.AbstractSpecTest
import org.jetbrains.kotlin.spec.TestCasesByNumbers
import org.jetbrains.kotlin.spec.TestType
import java.io.File
class DiagnosticTestTypeValidator(testFiles: List<BaseDiagnosticsTest.TestFile>) {
class DiagnosticTestTypeValidator(
testFiles: List<BaseDiagnosticsTest.TestFile>,
testDataFile: File,
private val testInfo: AbstractSpecTest
) : AbstractTestValidator(testInfo, testDataFile) {
private val diagnostics = mutableListOf<Diagnostic>()
private val diagnosticStats = mutableMapOf<String, Int>()
private val diagnosticSeverityStats = mutableMapOf<Severity, Int>()
private val diagnosticSeverityStats = mutableMapOf<Int, MutableMap<Severity, Int>>()
init {
collectDiagnostics(testFiles)
}
private fun findTestCases(diagnostic: Diagnostic): TestCasesByNumbers {
val ranges = diagnostic.textRanges
val filename = diagnostic.psiFile.name
val foundTestCases = testInfo.cases.byRanges[filename]!!.floorEntry(ranges[0].startOffset)
if (foundTestCases != null)
return foundTestCases.value
throw SpecTestValidationException(SpecTestValidationFailedReason.INVALID_TEST_CASES_STRUCTURE)
}
private fun collectDiagnosticStatistic() {
diagnostics.forEach {
val testCases = findTestCases(it)
val severity = it.factory.severity
diagnosticSeverityStats.run { put(severity, getOrDefault(severity, 0) + 1) }
for ((caseNumber, _) in testCases) {
diagnosticSeverityStats.putIfAbsent(caseNumber, mutableMapOf())
diagnosticSeverityStats[caseNumber]!!.run { put(severity, getOrDefault(severity, 0) + 1) }
}
}
}
@@ -36,8 +60,9 @@ class DiagnosticTestTypeValidator(testFiles: List<BaseDiagnosticsTest.TestFile>)
collectDiagnosticStatistic()
}
fun computeTestType() =
if (Severity.ERROR in diagnosticSeverityStats) TestType.NEGATIVE else TestType.POSITIVE
override fun computeTestTypes() = diagnosticSeverityStats.mapValues {
if (Severity.ERROR in it.value) TestType.NEGATIVE else TestType.POSITIVE
}
fun printDiagnosticStatistic() {
val diagnostics = if (diagnosticStats.isNotEmpty()) "$diagnosticSeverityStats | $diagnosticStats" else "does not contain"
@@ -1,125 +0,0 @@
/*
* 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.validators
import java.io.File
import java.util.regex.Matcher
import java.util.regex.Pattern
enum class LinkedSpecTestFileInfoElementType(
override val valuePattern: Pattern? = null,
override val required: Boolean = false
) : SpecTestInfoElementType {
SECTIONS(
Pattern.compile("""\w+(,\s+\w+)*"""),
required = true
),
PARAGRAPH(required = true),
SENTENCE(
valuePattern = Pattern.compile("""\[(?<number>${AbstractSpecTestValidator.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
}
class LinkedSpecTest(
testArea: TestArea,
testType: TestType,
val sections: List<String>,
val paragraphNumber: Int,
val sentenceNumber: Int,
val sentence: String? = null,
testNumber: Int,
description: String? = null,
cases: List<SpecTestCase>? = null,
unexpectedBehavior: Boolean? = null,
issues: Set<String>? = null
) : AbstractSpecTest(testArea, testType, sections[0], testNumber, description, cases, unexpectedBehavior, issues) {
override fun checkConsistency(other: AbstractSpecTest) =
other is LinkedSpecTest
&& this.sections == other.sections
&& this.testArea == other.testArea
&& this.testType == other.testType
&& this.testNumber == other.testNumber
&& this.paragraphNumber == other.paragraphNumber
&& this.sentenceNumber == other.sentenceNumber
}
class LinkedSpecTestValidator(testDataFile: File) : AbstractSpecTestValidator<LinkedSpecTest>(testDataFile) {
override val testPathPattern = getPathPattern()
override val testInfoPattern: Pattern =
Pattern.compile(multilineCommentRegex.format("""KOTLIN $testAreaRegex SPEC TEST \($testTypeRegex\)$lineSeparator(?<infoElements>[\s\S]*?$lineSeparator)"""))
companion object : SpecTestValidatorHelperObject {
override val pathPartRegex =
"""${SpecTestLinkedType.LINKED.testDataPath}$pathSeparator(?<sections>(?:[\w-]+)(?:$pathSeparator[\w-]+)*?)${pathSeparator}p-(?<paragraphNumber>$INTEGER_REGEX)"""
override val filenameRegex = """(?<sentenceNumber>$INTEGER_REGEX)\.(?<testNumber>$INTEGER_REGEX)\.kt"""
override fun getPathPattern(): Pattern = Pattern.compile(testPathRegexTemplate.format(pathPartRegex, filenameRegex))
}
override fun getTestInfo(
testInfoMatcher: Matcher,
testInfoElements: SpecTestInfoElements<SpecTestInfoElementType>,
testCases: List<SpecTestCase>,
unexpectedBehavior: Boolean,
issues: Set<String>?
): LinkedSpecTest {
val sentenceMatcher = testInfoElements[LinkedSpecTestFileInfoElementType.SENTENCE]!!.additionalMatcher!!
return LinkedSpecTest(
TestArea.valueOf(testInfoMatcher.group("testArea").replace(" ", "_").toUpperCase()),
TestType.valueOf(testInfoMatcher.group("testType")),
testInfoElements[LinkedSpecTestFileInfoElementType.SECTIONS]!!.content.split(Regex(""",\s*""")),
testInfoElements[LinkedSpecTestFileInfoElementType.PARAGRAPH]!!.content.toInt(),
sentenceMatcher.group("number").toInt(),
sentenceMatcher.group("text"),
testInfoElements[LinkedSpecTestFileInfoElementType.NUMBER]!!.content.toInt(),
testInfoElements[LinkedSpecTestFileInfoElementType.DESCRIPTION]!!.content,
testCases,
unexpectedBehavior,
issues
)
}
override fun getTestInfo(testInfoMatcher: Matcher) =
LinkedSpecTest(
TestArea.valueOf(testInfoMatcher.group("testArea").replace(File.separator, "_").toUpperCase()),
TestType.fromValue(testInfoMatcher.group("testType"))!!,
testInfoMatcher.group("sections").split(File.separator),
testInfoMatcher.group("paragraphNumber").toInt(),
testInfoMatcher.group("sentenceNumber").toInt(),
testNumber = testInfoMatcher.group("testNumber").toInt()
)
override fun parseTestInfo() = parseTestInfo(LinkedSpecTestFileInfoElementType.values())
override fun printTestInfo() {
println("--------------------------------------------------")
if (testInfoByContent.unexpectedBehavior!!)
println("(!!!) HAS UNEXPECTED BEHAVIOUR (!!!)")
println("${testInfoByFilename.testArea.name.replace("_", " ")} ${testInfoByFilename.testType} SPEC TEST")
println("SECTIONS: ${testInfoByContent.sections} (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 { "${ISSUE_TRACKER + it}," }}")
}
override fun getSingleTestCase(testInfoElements: SpecTestInfoElements<SpecTestInfoElementType>) =
SpecTestCase(
1,
description = testInfoElements[LinkedSpecTestFileInfoElementType.DESCRIPTION]!!.content,
unexpectedBehavior = testInfoElements.contains(LinkedSpecTestFileInfoElementType.UNEXPECTED_BEHAVIOUR),
issues = parseIssues(testInfoElements[LinkedSpecTestFileInfoElementType.ISSUES])
)
}
@@ -1,112 +0,0 @@
/*
* 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.validators
import java.io.File
import java.util.regex.Matcher
import java.util.regex.Pattern
enum class NotLinkedSpecTestFileInfoElementType(
override val valuePattern: Pattern? = null,
override val required: Boolean = false
) : SpecTestInfoElementType {
SECTION(required = true),
CATEGORIES(
valuePattern = Pattern.compile("""\w+(,\s+\w+)*"""),
required = true
),
NUMBER(required = true),
DESCRIPTION(required = true),
ISSUES(valuePattern = LinkedSpecTestFileInfoElementType.ISSUES.valuePattern),
UNEXPECTED_BEHAVIOUR,
DISCUSSION,
NOTE
}
class NotLinkedSpecTest(
testArea: TestArea,
testType: TestType,
section: String,
val categories: List<String>,
testNumber: Int,
description: String? = null,
cases: List<SpecTestCase>? = null,
unexpectedBehavior: Boolean? = null,
issues: Set<String>? = null
) : AbstractSpecTest(testArea, testType, section, testNumber, description, cases, unexpectedBehavior, issues) {
override fun checkConsistency(other: AbstractSpecTest) =
other is NotLinkedSpecTest
&& this.section == other.section
&& this.testArea == other.testArea
&& this.testType == other.testType
&& this.categories.joinToString(",") == other.categories.joinToString(",")
&& this.testNumber == other.testNumber
}
class NotLinkedSpecTestValidator(testDataFile: File) : AbstractSpecTestValidator<NotLinkedSpecTest>(testDataFile) {
override val testPathPattern = getPathPattern()
override val testInfoPattern: Pattern =
Pattern.compile(multilineCommentRegex.format("""KOTLIN $testAreaRegex NOT LINKED SPEC TEST \($testTypeRegex\)$lineSeparator(?<infoElements>[\s\S]*?$lineSeparator)"""))
companion object : SpecTestValidatorHelperObject {
override val pathPartRegex =
"""${SpecTestLinkedType.NOT_LINKED.testDataPath}$pathSeparator(?<sections>[\w-]+)$pathSeparator(?<categories>(?:[\w-]+)(?:$pathSeparator[\w-]+)*?)"""
override val filenameRegex = """(?<testNumber>$INTEGER_REGEX)\.kt"""
override fun getPathPattern(): Pattern = Pattern.compile(testPathRegexTemplate.format(pathPartRegex, filenameRegex))
}
override fun getTestInfo(
testInfoMatcher: Matcher,
testInfoElements: SpecTestInfoElements<SpecTestInfoElementType>,
testCases: List<SpecTestCase>,
unexpectedBehavior: Boolean,
issues: Set<String>?
) =
NotLinkedSpecTest(
TestArea.valueOf(testInfoMatcher.group("testArea").replace(" ", "_").toUpperCase()),
TestType.valueOf(testInfoMatcher.group("testType")),
testInfoElements[NotLinkedSpecTestFileInfoElementType.SECTION]!!.content,
testInfoElements[NotLinkedSpecTestFileInfoElementType.CATEGORIES]!!.content.split(Regex(""",\s*""")),
testInfoElements[NotLinkedSpecTestFileInfoElementType.NUMBER]!!.content.toInt(),
testInfoElements[NotLinkedSpecTestFileInfoElementType.DESCRIPTION]!!.content,
testCases,
unexpectedBehavior,
issues
)
override fun getTestInfo(testInfoMatcher: Matcher) =
NotLinkedSpecTest(
TestArea.valueOf(testInfoMatcher.group("testArea").toUpperCase()),
TestType.fromValue(testInfoMatcher.group("testType"))!!,
testInfoMatcher.group("sections"),
testInfoMatcher.group("categories").split(File.separator),
testNumber = testInfoMatcher.group("testNumber").toInt()
)
override fun parseTestInfo() = parseTestInfo(NotLinkedSpecTestFileInfoElementType.values())
override fun printTestInfo() {
println("--------------------------------------------------")
if (testInfoByContent.unexpectedBehavior!!)
println("(!!!) HAS UNEXPECTED BEHAVIOUR (!!!)")
println("${testInfoByFilename.testArea} ${testInfoByFilename.testType} NOT LINKED SPEC TEST")
println("SECTIONS: ${testInfoByContent.section}")
println("CATEGORIES: ${testInfoByContent.categories.joinToString(", ")}")
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!!.map { ISSUE_TRACKER + it }.joinToString(", ")}")
}
override fun getSingleTestCase(testInfoElements: SpecTestInfoElements<SpecTestInfoElementType>) =
SpecTestCase(
1,
description = testInfoElements[NotLinkedSpecTestFileInfoElementType.DESCRIPTION]!!.content,
unexpectedBehavior = testInfoElements.contains(NotLinkedSpecTestFileInfoElementType.UNEXPECTED_BEHAVIOUR),
issues = parseIssues(testInfoElements[NotLinkedSpecTestFileInfoElementType.ISSUES])
)
}
@@ -8,11 +8,17 @@ package org.jetbrains.kotlin.spec.validators
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.spec.TestType
import org.jetbrains.kotlin.spec.models.AbstractSpecTest
import java.io.File
object ParsingTestTypeValidator {
class ParsingTestTypeValidator(
private val psiFile: PsiFile,
testDataFile: File,
testInfo: AbstractSpecTest
) : AbstractTestValidator(testInfo, testDataFile) {
private fun checkErrorElement(psi: PsiElement): Boolean =
psi.children.any { it is PsiErrorElement || checkErrorElement(it) }
fun computeTestType(psiFile: PsiFile) =
if (checkErrorElement(psiFile)) TestType.NEGATIVE else TestType.POSITIVE
override fun computeTestTypes() = mapOf(1 to if (checkErrorElement(psiFile)) TestType.NEGATIVE else TestType.POSITIVE)
}