Implement feature interaction tests generator based on templates
This commit is contained in:
@@ -16,14 +16,10 @@ projectTest {
|
||||
workingDir = rootDir
|
||||
}
|
||||
|
||||
val generateTests by generator("org.jetbrains.kotlin.generators.tests.GenerateCompilerSpecTestsKt")
|
||||
val generateSpecTests by generator("org.jetbrains.kotlin.spec.tasks.GenerateSpecTestsKt")
|
||||
|
||||
val printSpecTestsStatistic by smartJavaExec {
|
||||
classpath = javaPluginConvention().sourceSets.getByName("test").runtimeClasspath
|
||||
main = "org.jetbrains.kotlin.spec.tasks.PrintSpecTestsStatisticKt"
|
||||
}
|
||||
val generateFeatureInteractionSpecTestData by generator("org.jetbrains.kotlin.spec.tasks.GenerateFeatureInteractionSpecTestDataKt")
|
||||
|
||||
val generateJsonTestsMap by smartJavaExec {
|
||||
classpath = javaPluginConvention().sourceSets.getByName("test").runtimeClasspath
|
||||
main = "org.jetbrains.kotlin.spec.tasks.GenerateJsonTestsMapKt"
|
||||
}
|
||||
val printSpecTestsStatistic by generator("org.jetbrains.kotlin.spec.tasks.PrintSpecTestsStatisticKt")
|
||||
|
||||
val generateJsonTestsMap by generator("org.jetbrains.kotlin.spec.tasks.GenerateJsonTestsMapKt")
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.generators.featureinteraction
|
||||
|
||||
enum class Feature(val config: FeatureTemplatesConfig) {
|
||||
IDENTIFIERS(
|
||||
FeatureTemplatesConfig(
|
||||
FeatureTemplatesType.AS_FILE,
|
||||
templatesPath = "identifiers"
|
||||
)
|
||||
),
|
||||
BOOLEAN_LITERALS(
|
||||
FeatureTemplatesConfig(
|
||||
FeatureTemplatesType.AS_STRING,
|
||||
templates = mapOf("true" to "true", "false" to "false")
|
||||
)
|
||||
),
|
||||
BOOLEAN_LITERALS_IN_BACKTICKS(
|
||||
FeatureTemplatesConfig(
|
||||
FeatureTemplatesType.AS_STRING,
|
||||
templates = mapOf("trueWithBacktick" to "`true`", "falseWithBacktick" to "`false`"),
|
||||
validationTransformer = TemplateValidationTransformerType.TRIM_BACKTICKS
|
||||
)
|
||||
)
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.generators.featureinteraction
|
||||
|
||||
import org.jetbrains.kotlin.spec.utils.GeneralConfiguration.TESTDATA_PATH
|
||||
import java.io.File
|
||||
import java.util.regex.Pattern
|
||||
|
||||
enum class SubstitutionPassType { FIRST, SECOND }
|
||||
|
||||
data class SubstitutionRule(
|
||||
val tag: SubstitutionTag,
|
||||
val filename: String,
|
||||
val testNumber: Int,
|
||||
val varNumber: Int? = null
|
||||
)
|
||||
|
||||
class FeatureInteractionTestDataGenerator(private val configuration: GenerationSpecTestDataConfig) {
|
||||
companion object {
|
||||
const val TEMPLATES_PATH = "templates"
|
||||
|
||||
private const val PARAMETER_REGEXP = """(?:".*?"|.*?)"""
|
||||
|
||||
private fun getVariablePattern(varRegex: String = ".*?", afterContent: String = "") =
|
||||
Pattern.compile("""<!(?<varName>$varRegex)(?:\((?<parameters>$PARAMETER_REGEXP(?:,\s*$PARAMETER_REGEXP)*)\))?!>$afterContent""")
|
||||
|
||||
private fun String.extractDirectives(): Pair<String, String> {
|
||||
val matcher = getVariablePattern(
|
||||
varRegex = SubstitutionTag.DIRECTIVES.name,
|
||||
afterContent = System.lineSeparator().repeat(2)
|
||||
).matcher(this)
|
||||
|
||||
if (!matcher.find())
|
||||
return Pair("", this)
|
||||
|
||||
val parameters = parseParameters(matcher.group("parameters"))
|
||||
val directives = parameters.joinToString { "// $it${System.lineSeparator()}" } + System.lineSeparator()
|
||||
val template = StringBuffer(this.length).let {
|
||||
matcher.appendReplacement(it, "").appendTail(it).toString()
|
||||
}
|
||||
|
||||
return Pair(directives, template)
|
||||
}
|
||||
|
||||
private fun parseParameters(rawParameters: String) =
|
||||
rawParameters.split(Regex(""",\s*""")).map { it.trim('"') }
|
||||
}
|
||||
|
||||
private fun String.substitute(
|
||||
filename: String,
|
||||
testNumber: Int,
|
||||
passType: SubstitutionPassType = SubstitutionPassType.FIRST
|
||||
): String {
|
||||
val buffer = StringBuffer(this.length)
|
||||
val matcher = getVariablePattern().matcher(this)
|
||||
while (matcher.find()) {
|
||||
val varName = matcher.group("varName")
|
||||
val rawParameters = matcher.group("parameters")
|
||||
val varNumber = if (rawParameters != null) parseParameters(rawParameters)[0].toInt() else null
|
||||
val tag = SubstitutionTag.valueOf(varName)
|
||||
|
||||
if (tag.passType != passType)
|
||||
continue
|
||||
|
||||
matcher.appendReplacement(
|
||||
buffer,
|
||||
configuration.substitutions[tag]?.invoke(
|
||||
SubstitutionRule(tag, filename, testNumber, varNumber)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return matcher.appendTail(buffer).toString()
|
||||
}
|
||||
|
||||
fun generate() {
|
||||
var testNumber = 1
|
||||
val testsPartPath = "$TESTDATA_PATH/${configuration.getTestsPartPath()}"
|
||||
val layoutTemplate = File("$TESTDATA_PATH/${configuration.getLayoutPath()}").readText()
|
||||
|
||||
File(testsPartPath).parentFile.mkdirs()
|
||||
|
||||
for ((filename, template) in configuration.prepareAndGetFirstFeatureTemplates()) {
|
||||
val (directives, templateWithoutDirectives) = template.extractDirectives()
|
||||
val code = templateWithoutDirectives
|
||||
.substitute(filename, testNumber, SubstitutionPassType.FIRST)
|
||||
.substitute(filename, testNumber, SubstitutionPassType.SECOND)
|
||||
val layout = layoutTemplate.substitute(filename, testNumber)
|
||||
val testFilePath = "$testsPartPath$testNumber.kt"
|
||||
|
||||
File(testFilePath).writeText(directives + layout + System.lineSeparator().repeat(2) + code)
|
||||
testNumber++
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.generators.featureinteraction
|
||||
|
||||
import org.jetbrains.kotlin.spec.utils.GeneralConfiguration.TESTDATA_PATH
|
||||
import org.jetbrains.kotlin.spec.validators.TestArea
|
||||
import java.io.File
|
||||
|
||||
enum class FeatureTemplatesType {
|
||||
AS_FILE,
|
||||
AS_STRING
|
||||
}
|
||||
|
||||
class FeatureTemplatesConfig(
|
||||
private val featureTemplatesType: FeatureTemplatesType,
|
||||
private val templatesPath: String? = null,
|
||||
private val templates: Map<String, String>? = null,
|
||||
val validationTransformer: TemplateValidationTransformerType? = null
|
||||
) {
|
||||
lateinit var testArea: TestArea
|
||||
var currentTemplatesIterator = getTemplatesIterator()
|
||||
|
||||
private fun getTemplatesPath(testArea: TestArea) = buildString {
|
||||
append(TESTDATA_PATH)
|
||||
append("/${testArea.testDataPath}")
|
||||
append("/${FeatureInteractionTestDataGenerator.TEMPLATES_PATH}")
|
||||
append("/$templatesPath")
|
||||
}
|
||||
|
||||
private fun getTemplateFiles(testArea: TestArea) =
|
||||
File(getTemplatesPath(testArea)).walkTopDown().filter { it.extension == "kt" }
|
||||
.associate { Pair(it.nameWithoutExtension, it.readText()) }
|
||||
|
||||
private fun getTemplates(testArea: TestArea) = lazy {
|
||||
when (featureTemplatesType) {
|
||||
FeatureTemplatesType.AS_STRING -> templates!!
|
||||
FeatureTemplatesType.AS_FILE -> getTemplateFiles(testArea)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTemplatesIterator() = lazy { getTemplates(testArea).value.iterator() }
|
||||
|
||||
fun resetTemplatesIterator() { currentTemplatesIterator = getTemplatesIterator() }
|
||||
|
||||
fun getNextWithRepeat() = let {
|
||||
takeUnless { it.currentTemplatesIterator.value.hasNext() }?.resetTemplatesIterator()
|
||||
currentTemplatesIterator.value.next().value
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.generators.featureinteraction
|
||||
|
||||
import org.jetbrains.kotlin.spec.validators.SpecTestLinkedType
|
||||
import org.jetbrains.kotlin.spec.validators.TestArea
|
||||
import org.jetbrains.kotlin.spec.validators.TestType
|
||||
|
||||
enum class SubstitutionTag(val passType: SubstitutionPassType = SubstitutionPassType.FIRST) {
|
||||
DIRECTIVES,
|
||||
|
||||
// Test meta info tags
|
||||
TEST_TYPE,
|
||||
SECTIONS,
|
||||
CATEGORIES,
|
||||
PARAGRAPH_NUMBER,
|
||||
SENTENCE_NUMBER,
|
||||
SENTENCE,
|
||||
TEST_NUMBER,
|
||||
TEST_DESCRIPTION,
|
||||
|
||||
// Test data tags
|
||||
ELEMENT,
|
||||
ELEMENT_VALIDATION(SubstitutionPassType.SECOND),
|
||||
CLASS_OF_FILE
|
||||
}
|
||||
|
||||
typealias TemplatesIterator = Iterator<Map.Entry<String, String>>
|
||||
|
||||
abstract class GenerationSpecTestDataConfig {
|
||||
private val repeatableElements = mutableMapOf<Int, String>()
|
||||
|
||||
lateinit var testType: TestType
|
||||
lateinit var testDescription: String
|
||||
lateinit var firstFeature: Feature
|
||||
lateinit var secondFeature: Feature
|
||||
lateinit var testArea: TestArea
|
||||
|
||||
protected val baseSubstitutions = mapOf<SubstitutionTag, (SubstitutionRule) -> String>(
|
||||
SubstitutionTag.TEST_TYPE to { _ -> testType.toString() },
|
||||
SubstitutionTag.TEST_NUMBER to { rule -> rule.testNumber.toString() },
|
||||
SubstitutionTag.TEST_DESCRIPTION to { rule -> testDescription.format(rule.filename) },
|
||||
SubstitutionTag.ELEMENT to { rule ->
|
||||
val isRepeatableVar = rule.varNumber != null
|
||||
|
||||
when {
|
||||
isRepeatableVar && repeatableElements.contains(rule.varNumber) ->
|
||||
repeatableElements[rule.varNumber]!!
|
||||
else -> {
|
||||
val element = secondFeature.config.getNextWithRepeat()
|
||||
if (isRepeatableVar)
|
||||
repeatableElements[rule.varNumber!!] = element
|
||||
element
|
||||
}
|
||||
}
|
||||
},
|
||||
SubstitutionTag.ELEMENT_VALIDATION to { rule ->
|
||||
val validationFunction = secondFeature.config.validationTransformer
|
||||
val element = repeatableElements[rule.varNumber]!!
|
||||
|
||||
when (validationFunction) {
|
||||
null -> element
|
||||
else -> templateValidationTransformers[validationFunction]!!(element)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
private fun buildTemplatesIterator(originalIterator: TemplatesIterator) =
|
||||
object : TemplatesIterator {
|
||||
override fun next() = run {
|
||||
repeatableElements.clear()
|
||||
secondFeature.config.resetTemplatesIterator()
|
||||
originalIterator.next()
|
||||
}
|
||||
|
||||
override fun hasNext() = originalIterator.hasNext()
|
||||
}
|
||||
|
||||
fun getLayoutPath() = "${testArea.testDataPath}/templates/_layout/$layoutFilename"
|
||||
|
||||
fun prepareAndGetFirstFeatureTemplates(): TemplatesIterator {
|
||||
secondFeature.config.testArea = testArea
|
||||
firstFeature.config.testArea = testArea
|
||||
|
||||
return firstFeature.config.run {
|
||||
resetTemplatesIterator()
|
||||
buildTemplatesIterator(currentTemplatesIterator.value)
|
||||
}
|
||||
}
|
||||
|
||||
abstract val layoutFilename: String
|
||||
abstract val substitutions: MutableMap<SubstitutionTag, (SubstitutionRule) -> String>
|
||||
abstract fun getTestsPartPath(): String
|
||||
}
|
||||
|
||||
class GenerationLinkedSpecTestDataConfig : GenerationSpecTestDataConfig() {
|
||||
var paragraphNumber: Int = 0
|
||||
var sentenceNumber: Int = 0
|
||||
lateinit var sentence: String
|
||||
lateinit var sections: List<String>
|
||||
|
||||
override val layoutFilename = "linkedTestsLayout.kt"
|
||||
override val substitutions = mutableMapOf<SubstitutionTag, (SubstitutionRule) -> String>(
|
||||
SubstitutionTag.SECTIONS to { _ -> sections.joinToString(", ") },
|
||||
SubstitutionTag.PARAGRAPH_NUMBER to { _ -> paragraphNumber.toString() },
|
||||
SubstitutionTag.SENTENCE_NUMBER to { _ -> sentenceNumber.toString() },
|
||||
SubstitutionTag.SENTENCE to { _ -> sentence },
|
||||
SubstitutionTag.CLASS_OF_FILE to { rule -> "_${sentenceNumber}_${rule.testNumber}Kt" }
|
||||
).apply { putAll(baseSubstitutions) }
|
||||
|
||||
override fun getTestsPartPath() =
|
||||
"${testArea.testDataPath}/${SpecTestLinkedType.LINKED.testDataPath}/${sections.joinToString("/")}/p-$paragraphNumber/${testType.type}/$sentenceNumber."
|
||||
}
|
||||
|
||||
class GenerationNotLinkedSpecTestDataConfig : GenerationSpecTestDataConfig() {
|
||||
lateinit var categories: List<String>
|
||||
|
||||
override val layoutFilename = "notLinkedTestsLayout.kt"
|
||||
override val substitutions = mutableMapOf<SubstitutionTag, (SubstitutionRule) -> String>(
|
||||
SubstitutionTag.CATEGORIES to { _ -> categories.joinToString(", ") },
|
||||
SubstitutionTag.CLASS_OF_FILE to { rule -> "_${rule.testNumber}Kt" }
|
||||
).apply { putAll(baseSubstitutions) }
|
||||
|
||||
override fun getTestsPartPath() =
|
||||
"${testArea.testDataPath}/${SpecTestLinkedType.NOT_LINKED.testDataPath}/${categories.joinToString("/")}/${testType.type}/"
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.generators.featureinteraction
|
||||
|
||||
import org.jetbrains.kotlin.spec.tasks.generateTests
|
||||
|
||||
fun generationSpecTestDataConfigGroup(regenerateTests: Boolean = false, body: () -> Unit) {
|
||||
body()
|
||||
if (regenerateTests) generateTests()
|
||||
}
|
||||
|
||||
fun generationLinkedSpecTestDataConfig(body: GenerationLinkedSpecTestDataConfig.() -> Unit) =
|
||||
GenerationLinkedSpecTestDataConfig().also {
|
||||
body(it)
|
||||
FeatureInteractionTestDataGenerator(it).generate()
|
||||
}
|
||||
|
||||
fun generationNotLinkedSpecTestDataConfig(body: GenerationNotLinkedSpecTestDataConfig.() -> Unit) =
|
||||
GenerationNotLinkedSpecTestDataConfig().also {
|
||||
body(it)
|
||||
FeatureInteractionTestDataGenerator(it).generate()
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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.generators.featureinteraction
|
||||
|
||||
enum class TemplateValidationTransformerType {
|
||||
TRIM_BACKTICKS
|
||||
}
|
||||
|
||||
val templateValidationTransformers = mapOf<TemplateValidationTransformerType, (String) -> String>(
|
||||
TemplateValidationTransformerType.TRIM_BACKTICKS to { element -> element.trim('`') }
|
||||
)
|
||||
+50
@@ -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.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
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
generationSpecTestDataConfigGroup(regenerateTests = true) {
|
||||
generationLinkedSpecTestDataConfig {
|
||||
testArea = TestArea.PSI
|
||||
testType = TestType.NEGATIVE
|
||||
sections = listOf("constant-literals", "boolean-literals")
|
||||
paragraphNumber = 1
|
||||
sentenceNumber = 2
|
||||
sentence = "These are strong keywords which cannot be used as identifiers unless escaped."
|
||||
testDescription = "The use of Boolean literals as the identifier (without backtick) in the %s."
|
||||
firstFeature = Feature.IDENTIFIERS
|
||||
secondFeature = Feature.BOOLEAN_LITERALS
|
||||
}
|
||||
generationLinkedSpecTestDataConfig {
|
||||
testArea = TestArea.PSI
|
||||
testType = TestType.POSITIVE
|
||||
sections = listOf("constant-literals", "boolean-literals")
|
||||
paragraphNumber = 1
|
||||
sentenceNumber = 2
|
||||
sentence = "These are strong keywords which cannot be used as identifiers unless escaped."
|
||||
testDescription = "The use of Boolean literals as the identifier (with backtick) in the %s."
|
||||
firstFeature = Feature.IDENTIFIERS
|
||||
secondFeature = Feature.BOOLEAN_LITERALS_IN_BACKTICKS
|
||||
}
|
||||
generationLinkedSpecTestDataConfig {
|
||||
testArea = TestArea.CODEGEN_BOX
|
||||
testType = TestType.POSITIVE
|
||||
sections = listOf("constant-literals", "boolean-literals")
|
||||
paragraphNumber = 1
|
||||
sentenceNumber = 2
|
||||
sentence = "These are strong keywords which cannot be used as identifiers unless escaped."
|
||||
testDescription = "The use of Boolean literals as the identifier (with backtick) in the %s."
|
||||
firstFeature = Feature.IDENTIFIERS
|
||||
secondFeature = Feature.BOOLEAN_LITERALS_IN_BACKTICKS
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,19 +6,20 @@
|
||||
package org.jetbrains.kotlin.spec.tasks
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import org.jetbrains.kotlin.spec.TestsJsonMapBuilder
|
||||
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 TEST_DATA_DIR = "./testData"
|
||||
private const val OUT_DIR = "./out"
|
||||
private const val OUT_DIR = "out"
|
||||
private const val OUT_FILENAME = "testsMap.json"
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val testsMap = JsonObject()
|
||||
|
||||
File(TEST_DATA_DIR).walkTopDown().forEach {
|
||||
File(TESTDATA_PATH).walkTopDown().forEach {
|
||||
val specTestValidator = LinkedSpecTestValidator(it)
|
||||
|
||||
try {
|
||||
@@ -30,6 +31,8 @@ fun main(args: Array<String>) {
|
||||
TestsJsonMapBuilder.buildJsonElement(specTestValidator.testInfo, testsMap)
|
||||
}
|
||||
|
||||
File(OUT_DIR).mkdir()
|
||||
File("$OUT_DIR/$OUT_FILENAME").writeText(testsMap.toString())
|
||||
val outDir = "$MODULE_PATH/$OUT_DIR"
|
||||
|
||||
File(outDir).mkdir()
|
||||
File("$outDir/$OUT_FILENAME").writeText(testsMap.toString())
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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.utils
|
||||
|
||||
object GeneralConfiguration {
|
||||
const val MODULE_PATH = "compiler/tests-spec"
|
||||
const val TESTDATA_PATH = "$MODULE_PATH/testData"
|
||||
const val TEST_PATH = "$MODULE_PATH/tests"
|
||||
}
|
||||
Reference in New Issue
Block a user