Move code conformance tests from compiler tests
#KTI-962
This commit is contained in:
committed by
Space Team
parent
dec0946896
commit
080b034950
@@ -0,0 +1,27 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation(intellijCore())
|
||||
testImplementation(projectTests(":compiler:tests-common"))
|
||||
|
||||
testImplementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.7")
|
||||
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.7")
|
||||
testImplementation("com.fasterxml.woodstox:woodstox-core:6.2.4")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" {}
|
||||
"test" {
|
||||
projectDefault()
|
||||
}
|
||||
}
|
||||
|
||||
projectTest() {
|
||||
dependsOn(":dist")
|
||||
workingDir = rootDir
|
||||
}
|
||||
|
||||
testsJar()
|
||||
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.code
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
class CodeConformanceTest : TestCase() {
|
||||
companion object {
|
||||
private val JAVA_FILE_PATTERN = Pattern.compile(".+\\.java")
|
||||
private val SOURCES_FILE_PATTERN = Pattern.compile(".+\\.(java|kt|js)")
|
||||
private const val MAX_STEPS_COUNT = 100
|
||||
private val nonSourcesMatcher = FileMatcher(
|
||||
File("."),
|
||||
listOf(
|
||||
".git",
|
||||
".idea",
|
||||
"build/js",
|
||||
"build/tmp",
|
||||
"buildSrc",
|
||||
"compiler/build",
|
||||
"compiler/fir/lightTree/testData",
|
||||
"compiler/testData/psi/kdoc",
|
||||
"compiler/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt",
|
||||
"compiler/util/src/org/jetbrains/kotlin/config/MavenComparableVersion.java",
|
||||
"dependencies",
|
||||
"dependencies/protobuf/protobuf-relocated/build",
|
||||
"dist",
|
||||
"idea/testData/codeInsight/renderingKDoc",
|
||||
"js/js.tests/.gradle",
|
||||
"js/js.tests/build",
|
||||
"js/js.translator/qunit/qunit.js",
|
||||
"js/js.translator/testData/node_modules",
|
||||
"local",
|
||||
"libraries/kotlin.test/js/it/.gradle",
|
||||
"libraries/kotlin.test/js/it/node_modules",
|
||||
"libraries/reflect/api/src/java9/java/kotlin/reflect/jvm/internal/impl",
|
||||
"libraries/reflect/build",
|
||||
"libraries/stdlib/jdk8/moduleTest/NonExportedPackagesTest.kt",
|
||||
"libraries/stdlib/js-ir/.gradle",
|
||||
"libraries/stdlib/js-ir/build",
|
||||
"libraries/stdlib/js-ir-minimal-for-test/.gradle",
|
||||
"libraries/stdlib/js-ir-minimal-for-test/build",
|
||||
"libraries/stdlib/js-v1/.gradle",
|
||||
"libraries/stdlib/js-v1/build",
|
||||
"libraries/tools/binary-compatibility-validator/src/main/kotlin/org.jetbrains.kotlin.tools",
|
||||
"libraries/tools/kotlin-gradle-plugin-core/gradle_api_jar/build/tmp",
|
||||
"libraries/tools/kotlin-gradle-plugin-integration-tests/out",
|
||||
"libraries/tools/kotlin-js-tests/src/test/web/qunit.js",
|
||||
"libraries/tools/kotlin-maven-plugin/target",
|
||||
"libraries/tools/kotlin-source-map-loader/.gradle",
|
||||
"libraries/tools/kotlin-test-js-runner/.gradle",
|
||||
"libraries/tools/kotlin-test-js-runner/lib",
|
||||
"libraries/tools/kotlin-test-js-runner/node_modules",
|
||||
"libraries/tools/kotlin-test-nodejs-runner/.gradle",
|
||||
"libraries/tools/kotlin-test-nodejs-runner/node_modules",
|
||||
"libraries/tools/kotlinp/src",
|
||||
"libraries/tools/new-project-wizard/new-project-wizard-cli/build",
|
||||
"out",
|
||||
"kotlin-native/build",
|
||||
"kotlin-native/performance",
|
||||
"kotlin-native/samples"
|
||||
)
|
||||
)
|
||||
|
||||
private val COPYRIGHT_EXCLUDED_FILES_AND_DIRS_MATCHER = FileMatcher(
|
||||
File("."),
|
||||
listOf(
|
||||
"build",
|
||||
"buildSrc/build/generated-sources",
|
||||
"buildSrc/prepare-deps/build",
|
||||
"compiler/ir/serialization.js/build/fullRuntime",
|
||||
"compiler/ir/serialization.js/build/reducedRuntime/src/libraries/stdlib/js-ir/runtime/longjs.kt",
|
||||
"compiler/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt",
|
||||
"dependencies",
|
||||
"dependencies/android-sdk/build",
|
||||
"dependencies/protobuf/protobuf-relocated/build",
|
||||
"dist",
|
||||
"idea/idea-jvm/src/org/jetbrains/kotlin/idea/copyright",
|
||||
"js/js.tests/.gradle",
|
||||
"js/js.tests/build",
|
||||
"js/js.translator/testData/node_modules",
|
||||
"libraries/kotlin.test/js/it/.gradle",
|
||||
"libraries/kotlin.test/js/it/node_modules",
|
||||
"libraries/stdlib/common/build",
|
||||
"libraries/stdlib/js-ir/.gradle",
|
||||
"libraries/stdlib/js-ir/build",
|
||||
"libraries/stdlib/js-ir/build/",
|
||||
"libraries/stdlib/js-ir/runtime/longjs.kt",
|
||||
"libraries/stdlib/js-ir-minimal-for-test/.gradle",
|
||||
"libraries/stdlib/js-ir-minimal-for-test/build",
|
||||
"libraries/stdlib/js-v1/.gradle",
|
||||
"libraries/stdlib/js-v1/build",
|
||||
"libraries/stdlib/js-v1/node_modules",
|
||||
"libraries/stdlib/wasm/build",
|
||||
"libraries/tools/kotlin-gradle-plugin-integration-tests/build",
|
||||
"libraries/tools/kotlin-gradle-plugin-integration-tests/.testKitDir",
|
||||
"libraries/tools/kotlin-gradle-plugin-integration-tests/out",
|
||||
"libraries/tools/kotlin-maven-plugin-test/target",
|
||||
"libraries/tools/kotlin-test-js-runner/.gradle",
|
||||
"libraries/tools/kotlin-test-js-runner/lib",
|
||||
"libraries/tools/kotlin-test-js-runner/node_modules",
|
||||
"libraries/tools/kotlin-test-nodejs-runner/.gradle",
|
||||
"libraries/tools/kotlin-test-nodejs-runner/node_modules",
|
||||
"libraries/tools/kotlin-source-map-loader/.gradle",
|
||||
"kotlin-native", "libraries/stdlib/native-wasm", // Have a separate licences manager
|
||||
"out",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun testParserCode() {
|
||||
val pattern = Pattern.compile("assert.*?\\b[^_]at.*?$", Pattern.MULTILINE)
|
||||
|
||||
for (sourceFile in FileUtil.findFilesByMask(JAVA_FILE_PATTERN, File("compiler/frontend/src/org/jetbrains/kotlin/parsing"))) {
|
||||
val matcher = pattern.matcher(sourceFile.readText())
|
||||
if (matcher.find()) {
|
||||
fail("An at-method with side-effects is used inside assert: ${matcher.group()}\nin file: $sourceFile")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCorrectExtension(filename: String, extensions: Set<String>): Boolean {
|
||||
val additionalExtensions = listOf(
|
||||
"after", "new", "before", "expected",
|
||||
"todo", "delete", "touch", "prefix", "postfix", "map",
|
||||
"fragment", "after2", "result", "log", "messages", "conflicts", "match", "imports", "txt", "xml"
|
||||
)
|
||||
val possibleAdditionalExtensions = extensions.plus(additionalExtensions)
|
||||
val fileExtensions = filename.split("\\.").drop(1)
|
||||
if (fileExtensions.size < 2) {
|
||||
return true
|
||||
}
|
||||
val extension = fileExtensions.last()
|
||||
|
||||
return !((extension !in possibleAdditionalExtensions && (extension.toIntOrNull() ?: MAX_STEPS_COUNT) >= MAX_STEPS_COUNT))
|
||||
}
|
||||
|
||||
fun testForgottenBunchDirectivesAndFiles() {
|
||||
val sourceBunchFilePattern = Pattern.compile("(.+\\.java|.+\\.kt|.+\\.js)(\\.\\w+)?")
|
||||
val root = nonSourcesMatcher.root
|
||||
val extensions = File(root, ".bunch").readLines().map { it.split("_") }.flatten().toSet()
|
||||
val failBuilder = mutableListOf<String>()
|
||||
nonSourcesMatcher.excludeWalkTopDown(sourceBunchFilePattern).forEach { sourceFile ->
|
||||
val matches = Regex("BUNCH (\\w+)")
|
||||
.findAll(sourceFile.readText())
|
||||
.map { it.groupValues[1] }
|
||||
.toSet()
|
||||
.filterNot { it in extensions }
|
||||
for (bunch in matches) {
|
||||
val filename = FileUtil.toSystemIndependentName(sourceFile.toRelativeString(root))
|
||||
failBuilder.add("$filename has unregistered $bunch bunch directive")
|
||||
}
|
||||
|
||||
if (!isCorrectExtension(sourceFile.name, extensions)) {
|
||||
val filename = FileUtil.toSystemIndependentName(sourceFile.toRelativeString(root))
|
||||
failBuilder.add("$filename has unknown bunch extension")
|
||||
}
|
||||
}
|
||||
|
||||
if (failBuilder.isNotEmpty()) {
|
||||
fail("\n" + failBuilder.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
fun testNoBadSubstringsInProjectCode() {
|
||||
class TestData(val message: String, val filter: (File, String) -> Boolean) {
|
||||
val result: MutableList<File> = ArrayList()
|
||||
}
|
||||
|
||||
val atAuthorPattern = Pattern.compile("/\\*.+@author.+\\*/", Pattern.DOTALL)
|
||||
|
||||
val tests = listOf(
|
||||
TestData(
|
||||
"%d source files contain @author javadoc tag.\nPlease remove them or exclude in this test:\n%s"
|
||||
) { _, source ->
|
||||
// substring check is an optimization
|
||||
"@author" in source && atAuthorPattern.matcher(source).find() &&
|
||||
"ASM: a very small and fast Java bytecode manipulation framework" !in source &&
|
||||
"package org.jetbrains.kotlin.tools.projectWizard.settings.version.maven" !in source
|
||||
},
|
||||
TestData(
|
||||
"%d source files use something from com.beust.jcommander.internal package.\n" +
|
||||
"This code won't work when there's no TestNG in the classpath of our IDEA plugin, " +
|
||||
"because there's only an optional dependency on testng.jar.\n" +
|
||||
"Most probably you meant to use Guava's Lists, Maps or Sets instead. " +
|
||||
"Please change references in these files to com.google.common.collect:\n%s"
|
||||
) { _, source ->
|
||||
"com.beust.jcommander.internal" in source
|
||||
},
|
||||
TestData(
|
||||
"%d source files contain references to package org.jetbrains.jet.\n" +
|
||||
"Package org.jetbrains.jet is deprecated now in favor of org.jetbrains.kotlin. " +
|
||||
"Please consider changing the package in these files:\n%s"
|
||||
) { _, source ->
|
||||
"org.jetbrains.jet" in source
|
||||
},
|
||||
TestData(
|
||||
"%d source files contain references to package kotlin.reflect.jvm.internal.impl.\n" +
|
||||
"This package contains internal reflection implementation and is a result of a " +
|
||||
"post-processing of kotlin-reflect.jar by jarjar.\n" +
|
||||
"Most probably you meant to use classes from org.jetbrains.kotlin.**.\n" +
|
||||
"Please change references in these files or exclude them in this test:\n%s"
|
||||
) { _, source ->
|
||||
"kotlin.reflect.jvm.internal.impl" in source
|
||||
},
|
||||
TestData(
|
||||
"%d source files contain references to package org.objectweb.asm.\n" +
|
||||
"Package org.jetbrains.org.objectweb.asm should be used instead to avoid troubles with different asm versions in classpath. " +
|
||||
"Please consider changing the package in these files:\n%s"
|
||||
) { _, source ->
|
||||
" org.objectweb.asm" in source
|
||||
}
|
||||
)
|
||||
|
||||
nonSourcesMatcher.excludeWalkTopDown(SOURCES_FILE_PATTERN).forEach { sourceFile ->
|
||||
val source = sourceFile.readText()
|
||||
for (test in tests) {
|
||||
if (test.filter(sourceFile, source)) test.result.add(sourceFile)
|
||||
}
|
||||
}
|
||||
|
||||
if (tests.flatMap { it.result }.isNotEmpty()) {
|
||||
fail(buildString {
|
||||
for (test in tests) {
|
||||
if (test.result.isNotEmpty()) {
|
||||
append(test.message.format(test.result.size, test.result.joinToString("\n")))
|
||||
appendLine()
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun testThirdPartyCopyrights() {
|
||||
val filesWithUnlistedCopyrights = mutableListOf<String>()
|
||||
val knownThirdPartyCode = loadKnownThirdPartyCodeList()
|
||||
val copyrightRegex = Regex("""\bCopyright\b""")
|
||||
val root = COPYRIGHT_EXCLUDED_FILES_AND_DIRS_MATCHER.root
|
||||
|
||||
COPYRIGHT_EXCLUDED_FILES_AND_DIRS_MATCHER.excludeWalkTopDown(SOURCES_FILE_PATTERN)
|
||||
.filter { sourceFile ->
|
||||
val relativePath = FileUtil.toSystemIndependentName(sourceFile.toRelativeString(root))
|
||||
!knownThirdPartyCode.any { relativePath.startsWith(it) }
|
||||
}
|
||||
.forEach { sourceFile ->
|
||||
sourceFile.useLines { lineSequence ->
|
||||
for (line in lineSequence) {
|
||||
if (copyrightRegex in line && "JetBrains" !in line) {
|
||||
val relativePath = FileUtil.toSystemIndependentName(sourceFile.toRelativeString(root))
|
||||
filesWithUnlistedCopyrights.add("$relativePath: $line")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filesWithUnlistedCopyrights.isNotEmpty()) {
|
||||
fail(
|
||||
"The following files contain third-party copyrights and no license information. " +
|
||||
"Please update license/README.md accordingly:\n${filesWithUnlistedCopyrights.joinToString("\n")}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class FileMatcher(val root: File, paths: Collection<String>) {
|
||||
private val files = paths.map { File(it) }
|
||||
private val paths = files.mapTo(HashSet()) { it.invariantSeparatorsPath }
|
||||
private val relativePaths = files.filterTo(ArrayList()) { it.isDirectory }.mapTo(HashSet()) { it.invariantSeparatorsPath + "/" }
|
||||
|
||||
fun matchExact(file: File): Boolean {
|
||||
return file.relativeTo(root).invariantSeparatorsPath in paths
|
||||
}
|
||||
|
||||
fun matchWithContains(file: File): Boolean {
|
||||
if (matchExact(file)) return true
|
||||
val relativePath = file.relativeTo(root).invariantSeparatorsPath
|
||||
return relativePaths.any { relativePath.startsWith(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun FileMatcher.excludeWalkTopDown(filePattern: Pattern): Sequence<File> {
|
||||
return root.walkTopDown()
|
||||
.onEnter { dir ->
|
||||
!matchExact(dir) // Don't enter to ignored dirs
|
||||
}
|
||||
.filter { file -> !matchExact(file) } // filter ignored files
|
||||
.filter { file -> filePattern.matcher(file.name).matches() }
|
||||
.filter { file -> file.isFile }
|
||||
}
|
||||
|
||||
fun testRepositoriesAbuse() {
|
||||
class RepoAllowList(val repo: String, root: File, allowList: Set<String>, val exclude: String? = null) {
|
||||
val matcher = FileMatcher(root, allowList)
|
||||
}
|
||||
|
||||
val root = nonSourcesMatcher.root
|
||||
|
||||
val repoCheckers = listOf(
|
||||
RepoAllowList(
|
||||
// Please use cache-redirector for importing in tests
|
||||
"https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev", root,
|
||||
setOf("repo/scripts/cache-redirector.settings.gradle.kts")
|
||||
),
|
||||
RepoAllowList(
|
||||
"https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev", root,
|
||||
setOf("repo/scripts/cache-redirector.settings.gradle.kts")
|
||||
),
|
||||
RepoAllowList(
|
||||
// Please use cache-redirector for importing in tests
|
||||
"https://maven.pkg.jetbrains.space/kotlin/p/kotlin/eap", root,
|
||||
setOf("repo/scripts/cache-redirector.settings.gradle.kts")
|
||||
),
|
||||
RepoAllowList(
|
||||
"https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev", root,
|
||||
setOf("repo/scripts/cache-redirector.settings.gradle.kts")
|
||||
),
|
||||
RepoAllowList(
|
||||
"kotlin/ktor", root,
|
||||
setOf("repo/scripts/cache-redirector.settings.gradle.kts")
|
||||
),
|
||||
RepoAllowList(
|
||||
"bintray.com", root,
|
||||
setOf("repo/scripts/cache-redirector.settings.gradle.kts"),
|
||||
exclude = "jcenter.bintray.com"
|
||||
)
|
||||
)
|
||||
|
||||
data class RepoOccurance(val repo: String, val file: File)
|
||||
data class RepoOccurrences(val repo: String, val files: Collection<File>)
|
||||
|
||||
val extensionsPattern = Pattern.compile(".+\\.(java|kt|gradle|kts|xml)(\\.\\w+)?")
|
||||
val repoOccurrences: List<RepoOccurrences> = nonSourcesMatcher.excludeWalkTopDown(extensionsPattern)
|
||||
.flatMap { file ->
|
||||
val checkers = repoCheckers.filter { checker ->
|
||||
!checker.matcher.matchWithContains(file)
|
||||
}
|
||||
|
||||
if (checkers.isNotEmpty()) {
|
||||
val occurrences = ArrayList<RepoOccurance>()
|
||||
file.useLines { lines ->
|
||||
for (line in lines) {
|
||||
for (checker in checkers) {
|
||||
if (line.contains(checker.repo) && (checker.exclude == null || !line.contains(checker.exclude))) {
|
||||
occurrences.add(RepoOccurance(checker.repo, file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
occurrences
|
||||
} else {
|
||||
listOf()
|
||||
}
|
||||
}
|
||||
.groupBy { it.repo }
|
||||
.map { (repo, occurrences) -> RepoOccurrences(repo, occurrences.mapTo(HashSet()) { it.file }) }
|
||||
|
||||
if (repoOccurrences.isNotEmpty()) {
|
||||
val repoOccurrencesStableOrder = repoOccurrences
|
||||
.map { occurrence -> RepoOccurrences(occurrence.repo, occurrence.files.sortedBy { file -> file.path }) }
|
||||
.sortedBy { it.repo }
|
||||
fail(
|
||||
buildString {
|
||||
appendLine("The following files use repositories and not listed in the correspondent allow lists")
|
||||
for ((repo, files) in repoOccurrencesStableOrder) {
|
||||
appendLine(repo)
|
||||
for (file in files) {
|
||||
appendLine(" ${file.relativeTo(root).invariantSeparatorsPath}")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadKnownThirdPartyCodeList(): List<String> {
|
||||
File("license/README.md").useLines { lineSequence ->
|
||||
return lineSequence
|
||||
.filter { it.startsWith(" - Path: ") }
|
||||
.map { it.removePrefix(" - Path: ").trim().ensureFileOrEndsWithSlash() }
|
||||
.toList()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun testLanguageFeatureOrder() {
|
||||
val values = enumValues<LanguageFeature>()
|
||||
val enabledFeatures = values.filter { it.sinceVersion != null }
|
||||
|
||||
if (enabledFeatures.sortedBy { it.sinceVersion!! } != enabledFeatures) {
|
||||
val (a, b) = enabledFeatures.zipWithNext().first { (a, b) -> a.sinceVersion!! > b.sinceVersion!! }
|
||||
fail(
|
||||
"Please make sure LanguageFeature entries are sorted by sinceVersion to improve readability & reduce confusion.\n" +
|
||||
"The feature $b is out of order; its sinceVersion is ${b.sinceVersion}, yet it comes after $a, whose " +
|
||||
"sinceVersion is ${a.sinceVersion}.\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.ensureFileOrEndsWithSlash() =
|
||||
if (endsWith("/") || "." in substringAfterLast('/')) this else "$this/"
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.code
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
|
||||
class DepsVerificationMetadataTest {
|
||||
@JacksonXmlRootElement(localName = "verification-metadata")
|
||||
private data class VerificationMetadata(
|
||||
@field:JacksonXmlElementWrapper(localName = "components")
|
||||
val components: List<Component> = listOf()
|
||||
)
|
||||
|
||||
private data class Component(
|
||||
@field:JacksonXmlProperty(isAttribute = true)
|
||||
val group: String,
|
||||
@field:JacksonXmlProperty(isAttribute = true)
|
||||
val name: String,
|
||||
@field:JacksonXmlProperty(isAttribute = true)
|
||||
val version: String,
|
||||
@field:JacksonXmlElementWrapper(useWrapping = false)
|
||||
val artifact: List<Artifact>,
|
||||
)
|
||||
|
||||
private data class Artifact(
|
||||
@field:JacksonXmlProperty(isAttribute = true)
|
||||
val name: String,
|
||||
val md5: Hash?,
|
||||
val sha256: Hash?
|
||||
)
|
||||
|
||||
private data class Hash(@field:JacksonXmlProperty(isAttribute = true) val value: String)
|
||||
|
||||
@Test
|
||||
fun dependenciesHasValidHashes() {
|
||||
val mapper = XmlMapper()
|
||||
.apply {
|
||||
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
registerKotlinModule()
|
||||
}
|
||||
val verificationMetadata = mapper.readValue<VerificationMetadata>(File("gradle/verification-metadata.xml"))
|
||||
val fails = mutableListOf<String>()
|
||||
|
||||
verificationMetadata.components.forEach { component ->
|
||||
component.artifact.forEach { artifact ->
|
||||
if (artifact.md5 == null) {
|
||||
fails.add("${artifact.name} - md5 is missing")
|
||||
}
|
||||
if (artifact.sha256 == null) {
|
||||
fails.add("${artifact.name} - sha256 is missing")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KtUsefulTestCase.assertEmpty(fails)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.code
|
||||
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Modifier
|
||||
import kotlin.reflect.jvm.javaField
|
||||
|
||||
class ReflectionCodeSanityTest : TestCase() {
|
||||
private lateinit var classLoader: ClassLoader
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
classLoader = ForTestCompileRuntime.runtimeAndReflectJarClassLoader()
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
ReflectionCodeSanityTest::classLoader.javaField!!.set(this, null)
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
private fun loadClass(name: String): Class<*> =
|
||||
classLoader.loadClass("kotlin.reflect.jvm.internal.$name")
|
||||
|
||||
private fun collectClassesWithSupers(vararg names: String): Set<Class<*>> {
|
||||
val result = linkedSetOf<Class<*>>()
|
||||
fun addClassToCheck(klass: Class<*>) {
|
||||
if (result.add(klass)) {
|
||||
klass.superclass?.let(::addClassToCheck)
|
||||
}
|
||||
}
|
||||
|
||||
for (name in names) {
|
||||
addClassToCheck(loadClass(name))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun testNoDelegatedPropertiesInKClassAndKProperties() {
|
||||
val badFields = linkedSetOf<Field>()
|
||||
for (klass in collectClassesWithSupers(
|
||||
"KClassImpl",
|
||||
"KMutableProperty0Impl",
|
||||
"KMutableProperty1Impl",
|
||||
"KMutableProperty2Impl"
|
||||
)) {
|
||||
badFields.addAll(klass.declaredFields.filter { it.name.endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX) })
|
||||
}
|
||||
|
||||
if (badFields.isNotEmpty()) {
|
||||
fail("The fields listed below appear to be delegates for properties.\n" +
|
||||
"It's highly not recommended to use property delegates in reflection.jvm because a KProperty instance\n" +
|
||||
"is created for each delegated property and that makes the initialization sequence of reflection\n" +
|
||||
"implementation classes unpredictable and leads to a deadlock or ExceptionInInitializerError.\n\n" +
|
||||
"Please un-delegate the corresponding properties:\n\n" +
|
||||
badFields.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
fun testMaxAllowedFields() {
|
||||
// The following classes are instantiated a lot in Kotlin applications and thus they should be optimized as best as possible.
|
||||
// This test checks that these classes have not more fields than a predefined small number, which can usually be calculated as
|
||||
// the number of constructor parameters (number of objects needed to initialize an instance) + 1 for 'data', the reflection cache.
|
||||
val classesWithMaxAllowedFields = linkedMapOf(
|
||||
"KClassImpl" to 2, // jClass, data
|
||||
"KPackageImpl" to 3 // jClass, moduleName, data
|
||||
)
|
||||
|
||||
val badClasses = linkedMapOf<Class<*>, Collection<Field>>()
|
||||
for ((className, maxAllowedFields) in classesWithMaxAllowedFields) {
|
||||
val klass = loadClass(className)
|
||||
val fields = generateSequence(klass) { it.superclass }
|
||||
.flatMap { it.declaredFields.asSequence() }
|
||||
.filterNot { Modifier.isStatic(it.modifiers) }
|
||||
.toList()
|
||||
if (fields.size > maxAllowedFields) {
|
||||
badClasses[klass] = fields
|
||||
}
|
||||
}
|
||||
|
||||
if (badClasses.isNotEmpty()) {
|
||||
fail("Some classes in reflection.jvm contain more fields than it is allowed. Please optimize storage in these classes:\n\n" +
|
||||
badClasses.entries.joinToString("\n") { entry ->
|
||||
val (klass, fields) = entry
|
||||
"$klass has ${fields.size} fields but max allowed = ${classesWithMaxAllowedFields[klass.simpleName]}:\n" +
|
||||
fields.joinToString("\n") { " $it" }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user