diff --git a/compiler/tests-spec/build.gradle.kts b/compiler/tests-spec/build.gradle.kts index 6baa25b7f00..dcbf2846665 100644 --- a/compiler/tests-spec/build.gradle.kts +++ b/compiler/tests-spec/build.gradle.kts @@ -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") diff --git a/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/Feature.kt b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/Feature.kt new file mode 100644 index 00000000000..973096b330a --- /dev/null +++ b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/Feature.kt @@ -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 + ) + ) +} diff --git a/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/FeatureInteractionTestDataGenerator.kt b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/FeatureInteractionTestDataGenerator.kt new file mode 100644 index 00000000000..3f8781a9b5d --- /dev/null +++ b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/FeatureInteractionTestDataGenerator.kt @@ -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("""$varRegex)(?:\((?$PARAMETER_REGEXP(?:,\s*$PARAMETER_REGEXP)*)\))?!>$afterContent""") + + private fun String.extractDirectives(): Pair { + 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++ + } + } +} diff --git a/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/FeatureTemplatesConfig.kt b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/FeatureTemplatesConfig.kt new file mode 100644 index 00000000000..cfd4734bc46 --- /dev/null +++ b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/FeatureTemplatesConfig.kt @@ -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? = 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 + } +} \ No newline at end of file diff --git a/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/GenerationConfig.kt b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/GenerationConfig.kt new file mode 100644 index 00000000000..5f2462ac0a0 --- /dev/null +++ b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/GenerationConfig.kt @@ -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> + +abstract class GenerationSpecTestDataConfig { + private val repeatableElements = mutableMapOf() + + lateinit var testType: TestType + lateinit var testDescription: String + lateinit var firstFeature: Feature + lateinit var secondFeature: Feature + lateinit var testArea: TestArea + + protected val baseSubstitutions = mapOf 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 String> + abstract fun getTestsPartPath(): String +} + +class GenerationLinkedSpecTestDataConfig : GenerationSpecTestDataConfig() { + var paragraphNumber: Int = 0 + var sentenceNumber: Int = 0 + lateinit var sentence: String + lateinit var sections: List + + override val layoutFilename = "linkedTestsLayout.kt" + override val substitutions = mutableMapOf 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 + + override val layoutFilename = "notLinkedTestsLayout.kt" + override val substitutions = mutableMapOf 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}/" +} \ No newline at end of file diff --git a/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/GenerationUtils.kt b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/GenerationUtils.kt new file mode 100644 index 00000000000..981bf90097d --- /dev/null +++ b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/GenerationUtils.kt @@ -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() + } diff --git a/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/TemplateSubstitutionTransformers.kt b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/TemplateSubstitutionTransformers.kt new file mode 100644 index 00000000000..6ffe3086f39 --- /dev/null +++ b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/generators/featureinteraction/TemplateSubstitutionTransformers.kt @@ -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 String>( + TemplateValidationTransformerType.TRIM_BACKTICKS to { element -> element.trim('`') } +) diff --git a/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/tasks/GenerateFeatureInteractionSpecTestData.kt b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/tasks/GenerateFeatureInteractionSpecTestData.kt new file mode 100644 index 00000000000..aacf1725719 --- /dev/null +++ b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/tasks/GenerateFeatureInteractionSpecTestData.kt @@ -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) { + 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 + } + } +} diff --git a/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/tasks/GenerateJsonTestsMap.kt b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/tasks/GenerateJsonTestsMap.kt index 479974e2ae1..1dfa3aaeeda 100644 --- a/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/tasks/GenerateJsonTestsMap.kt +++ b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/tasks/GenerateJsonTestsMap.kt @@ -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) { 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) { 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()) } \ No newline at end of file diff --git a/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/utils/GeneralConfiguration.kt b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/utils/GeneralConfiguration.kt new file mode 100644 index 00000000000..4cb939ed342 --- /dev/null +++ b/compiler/tests-spec/tests/org/jetbrains/kotlin/spec/utils/GeneralConfiguration.kt @@ -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" +}