[Native][tests] Remove external tests from old test infra
This commit is contained in:
@@ -39,7 +39,6 @@ apply plugin: 'kotlin'
|
||||
configurations {
|
||||
cli_bc
|
||||
update_tests
|
||||
update_stdlib_tests
|
||||
|
||||
nopPluginApi
|
||||
api.extendsFrom nopPluginApi
|
||||
@@ -50,10 +49,6 @@ dependencies {
|
||||
cli_bc project(path: ':kotlin-native:backend.native', configuration: 'cli_bcApiElements')
|
||||
}
|
||||
ext.testOutputRoot = rootProject.file("test.output").absolutePath
|
||||
ext.externalTestsDir = project.file("build/external")
|
||||
ext.externalStdlibTestsDir = project.file("build/stdlib_external/stdlib")
|
||||
externalTestsDir.mkdirs()
|
||||
externalStdlibTestsDir.mkdirs()
|
||||
|
||||
ext.platformManager = project.project(":kotlin-native").platformManager
|
||||
ext.target = platformManager.targetManager(project.testTarget).target
|
||||
@@ -89,11 +84,6 @@ if (cacheTesting != null) {
|
||||
dependsOn cacheTesting.buildCacheTask
|
||||
extraOpts cacheTesting.compilerArgs
|
||||
}
|
||||
|
||||
tasks.withType(RunExternalTestGroup.class).configureEach {
|
||||
dependsOn cacheTesting.buildCacheTask
|
||||
flags = (flags ?: []) + cacheTesting.compilerArgs
|
||||
}
|
||||
}
|
||||
|
||||
// Enable two-stage test compilation if the test_two_stage property is set.
|
||||
@@ -103,10 +93,6 @@ tasks.withType(KonanCompileNativeBinary.class).configureEach {
|
||||
enableTwoStageCompilation = twoStageEnabled
|
||||
}
|
||||
|
||||
tasks.withType(RunExternalTestGroup.class).configureEach {
|
||||
enableTwoStageCompilation = twoStageEnabled
|
||||
}
|
||||
|
||||
ext.isExperimentalMM = project.globalTestArgs.contains("-memory-model") && project.globalTestArgs.contains("experimental")
|
||||
ext.isNoopGC = project.globalTestArgs.contains("-Xgc=noop")
|
||||
|
||||
@@ -115,10 +101,6 @@ if (project.globalTestArgs.contains("-g")) {
|
||||
tasks.withType(KonanCompileNativeBinary.class).configureEach {
|
||||
extraOpts "-Xbinary=runtimeAssertionsMode=panic"
|
||||
}
|
||||
|
||||
tasks.withType(RunExternalTestGroup.class).configureEach {
|
||||
flags = (flags ?: []) + "-Xbinary=runtimeAssertionsMode=panic"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -221,18 +203,13 @@ task sanity {
|
||||
// Collect reports in one json.
|
||||
task resultsTask() {
|
||||
doLast {
|
||||
List<KonanTestGroupReport> reports = []
|
||||
def statistics = new Statistics()
|
||||
tasks.withType(RunExternalTestGroup).matching { it.state.executed }.each {
|
||||
reports += new KonanTestGroupReport(it.name, it.testGroupReporter.suiteReports)
|
||||
statistics.add(it.testGroupReporter.statistics)
|
||||
}
|
||||
|
||||
tasks.withType(KonanGTest).matching { it.state.executed }.each {
|
||||
statistics.add(it.statistics)
|
||||
}
|
||||
|
||||
ExternalReportUtilsKt.saveReport("$testOutputExternal/reports.json", statistics, reports)
|
||||
ExternalReportUtilsKt.saveReport("$testOutputExternal/reports.json", statistics)
|
||||
use(KonanTestSuiteReportKt) {
|
||||
project.logger.quiet("DONE.\n\n" +
|
||||
"TOTAL: $statistics.total\n" +
|
||||
@@ -344,38 +321,6 @@ Task dynamicTest(String name, Closure<KonanDynamicTest> configureClosure) {
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, List<String>> ignoredTests = [:]
|
||||
|
||||
task run_external () {
|
||||
outputs.upToDateWhen{ false }
|
||||
|
||||
rootProject.files(
|
||||
'compiler/testData/codegen/box',
|
||||
'compiler/testData/codegen/boxInline'
|
||||
).asFileTree.visit { file ->
|
||||
if (!file.isDirectory() || !file.file.list().any{ it.endsWith(".kt")})
|
||||
return
|
||||
def taskPath = rootProject.relativePath(file.file)
|
||||
def fileSeparator = System.properties["file.separator"]
|
||||
def taskName = taskPath.split("\\$fileSeparator").join('_')
|
||||
project.tasks.register(taskName, RunExternalTestGroup) {
|
||||
it.groupDirectory = taskPath
|
||||
it.finalizedBy resultsTask
|
||||
it.ignoredTests = ignoredTests[taskName]
|
||||
}
|
||||
}
|
||||
|
||||
// Set up dependencies.
|
||||
dependsOn(tasksOf(RunExternalTestGroup))
|
||||
dependsOn("stdlibTest")
|
||||
dependsOn("stdlibTestInWorker")
|
||||
}
|
||||
|
||||
task daily() {
|
||||
dependsOn(run_external)
|
||||
}
|
||||
|
||||
|
||||
task slackReport(type:Reporter) {
|
||||
reportHome = rootProject.file("test.output").absoluteFile
|
||||
}
|
||||
@@ -5850,51 +5795,6 @@ tasks.register("llvm_variant_dev", KonanDriverTest) {
|
||||
outputChecker = { out -> !out.contains(message) }
|
||||
}
|
||||
|
||||
createStdlibTest('stdlibTest', /* inWorker = */ false)
|
||||
createStdlibTest('stdlibTestInWorker', /*inWorker = */ true)
|
||||
|
||||
/**
|
||||
* Creates tasks to build and execute stdlib tests.
|
||||
*/
|
||||
private void createStdlibTest(String name, boolean inWorker) {
|
||||
KotlinNativeTestKt.createTest(project, name, KonanGTest) { task ->
|
||||
configureStdlibTest(task, inWorker)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures tasks to build and execute stdlib tests.
|
||||
*/
|
||||
private void configureStdlibTest(KonanGTest task, boolean inWorker) {
|
||||
def sources = UtilsKt.getFilesToCompile(project,
|
||||
[ '../../../libraries/stdlib/common/test',
|
||||
'../../../libraries/stdlib/test',
|
||||
'../../../libraries/kotlin.test/common/src/test/kotlin',
|
||||
'stdlib_external/utils.kt',
|
||||
'stdlib_external/jsCollectionFactoriesActuals.kt',
|
||||
'stdlib_external/text'],
|
||||
[ /*'build/stdlib_external/stdlib/test/internalAnnotations.kt'*/ ])
|
||||
|
||||
konanArtifacts {
|
||||
program(task.name, targets: [target.name]) {
|
||||
srcFiles sources
|
||||
baseDir "$testOutputStdlib/$task.name"
|
||||
enableMultiplatform true
|
||||
extraOpts inWorker ? '-trw' : '-tr',
|
||||
'-Xverify-ir',
|
||||
'-opt-in=kotlin.RequiresOptIn,kotlin.ExperimentalStdlibApi',
|
||||
"-friend-modules", project.rootProject.file("${UtilsKt.getKotlinNativeDist(project)}/klib/common/stdlib").absolutePath
|
||||
extraOpts project.globalTestArgs
|
||||
}
|
||||
}
|
||||
// Exclude test providing args to the test executable
|
||||
task.arguments = [ "--ktest_negative_regex_filter=test.collections.CollectionTest.abstractCollectionToArray" ]
|
||||
task.useFilter = false
|
||||
task.testLogger = KonanTest.Logger.GTEST
|
||||
task.finalizedBy("resultsTask")
|
||||
task.enabled = (project.testTarget != 'wasm32') // Uses exceptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds tests into a single binary with TestRunner
|
||||
*/
|
||||
@@ -5951,13 +5851,6 @@ sourceSets {
|
||||
srcDir 'debugger/src/test/kotlin'
|
||||
}
|
||||
}
|
||||
update_stdlib_tests{
|
||||
kotlin {
|
||||
srcDir '../../../libraries/stdlib/common/test'
|
||||
srcDir '../../../libraries/stdlib/test'
|
||||
srcDir '../../../libraries/kotlin.test/common/src/test/kotlin'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileNopPluginKotlin {
|
||||
|
||||
@@ -1,646 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin
|
||||
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.JavaExec
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.process.ExecResult
|
||||
import org.jetbrains.kotlin.utils.DFS
|
||||
import org.jetbrains.kotlin.*
|
||||
|
||||
import java.nio.file.Paths
|
||||
import java.util.function.Function
|
||||
import java.util.function.UnaryOperator
|
||||
import java.util.regex.Pattern
|
||||
import java.util.stream.Collectors
|
||||
import java.io.File
|
||||
|
||||
class RunExternalTestGroup extends JavaExec implements CompilerRunner {
|
||||
private def platformManager = project.project(":kotlin-native").platformManager
|
||||
private def target = platformManager.targetManager(project.testTarget).target
|
||||
|
||||
@Internal
|
||||
def dist = UtilsKt.getKotlinNativeDist(project)
|
||||
|
||||
@Input
|
||||
def enableKonanAssertions = true
|
||||
|
||||
@Input
|
||||
def verifyIr = true
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
String outputDirectory = null
|
||||
@Input
|
||||
@Optional
|
||||
String goldValue = null
|
||||
|
||||
@Internal
|
||||
// Checks test's output against gold value and returns true if the output matches the expectation
|
||||
Function<String, Boolean> outputChecker = { str -> (goldValue == null || goldValue == str) }
|
||||
|
||||
@Input
|
||||
boolean printOutput = true
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
String testData = null
|
||||
|
||||
@Input
|
||||
int expectedExitStatus = 0
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
List<String> arguments = null
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
List<String> flags = null
|
||||
|
||||
@Input
|
||||
boolean multiRuns = false
|
||||
@Input
|
||||
@Optional
|
||||
List<List<String>> multiArguments = null
|
||||
|
||||
@Input
|
||||
boolean expectedFail = false
|
||||
|
||||
@Input
|
||||
boolean compilerMessages = false
|
||||
|
||||
// Uses directory defined in $outputSourceSetName source set
|
||||
void createOutputDirectory() {
|
||||
if (outputDirectory != null) {
|
||||
return
|
||||
}
|
||||
def outputSourceSet = UtilsKt.getTestOutputExternal(project)
|
||||
outputDirectory = Paths.get(outputSourceSet, name).toString()
|
||||
project.file(outputDirectory).mkdirs()
|
||||
}
|
||||
|
||||
RunExternalTestGroup() {
|
||||
// We don't build the compiler if a custom dist path is specified.
|
||||
UtilsKt.dependsOnDist(this)
|
||||
main = 'org.jetbrains.kotlin.cli.bc.K2NativeKt'
|
||||
}
|
||||
|
||||
@Override
|
||||
void exec() {
|
||||
// Perhaps later we will return this exec() back but for now rest of infrastructure expects
|
||||
// compilation begins on runCompiler call, to emulate this behaviour we call super.exec() after
|
||||
// configuration part at runCompiler.
|
||||
}
|
||||
|
||||
@Override
|
||||
void runCompiler(List<String> filesToCompile, String output, List<String> moreArgs) {
|
||||
def log = new ByteArrayOutputStream()
|
||||
try {
|
||||
classpath = project.fileTree("$dist.canonicalPath/konan/lib/") {
|
||||
include '*.jar'
|
||||
}
|
||||
jvmArgs "-Xmx4G"
|
||||
jvmArgs "-Dkonan.home=${UtilsKt.getKotlinNativeDist(project)}"
|
||||
enableAssertions = true
|
||||
def sources = File.createTempFile(name, ".lst")
|
||||
sources.deleteOnExit()
|
||||
def sourcesWriter = sources.newWriter()
|
||||
filesToCompile.each { f ->
|
||||
sourcesWriter.write(f.chars().any { Character.isWhitespace(it) }
|
||||
? "\"${f.replace("\\", "\\\\")}\"\n" // escape file name
|
||||
: "$f\n")
|
||||
}
|
||||
sourcesWriter.close()
|
||||
args = ["-output", output,
|
||||
"@${sources.absolutePath}",
|
||||
*moreArgs,
|
||||
*project.globalTestArgs]
|
||||
if (project.testTarget) {
|
||||
args "-target", target.visibleName
|
||||
}
|
||||
if (enableKonanAssertions) {
|
||||
args "-ea"
|
||||
}
|
||||
if (verifyIr) {
|
||||
args "-Xverify-ir"
|
||||
}
|
||||
if (project.hasProperty("test_verbose")) {
|
||||
println("Files to compile: $filesToCompile")
|
||||
println(args)
|
||||
}
|
||||
standardOutput = log
|
||||
errorOutput = log
|
||||
super.exec()
|
||||
} finally {
|
||||
def logString = log.toString("UTF-8")
|
||||
project.file("${output}.compilation.log").write(logString)
|
||||
println(logString)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: output directory here changes and hence this is not a property
|
||||
String executablePath() { return "$outputDirectory/program.tr" }
|
||||
|
||||
@Internal
|
||||
OutputStream out
|
||||
|
||||
void runExecutable() {
|
||||
if (!enabled) {
|
||||
println "Test is disabled: $name"
|
||||
return
|
||||
}
|
||||
def program = executablePath()
|
||||
def suffix = target.family.exeSuffix
|
||||
def exe = "$program.$suffix"
|
||||
|
||||
println "execution: $exe"
|
||||
|
||||
def compilerMessagesText = compilerMessages ? project.file("${program}.compilation.log").getText('UTF-8') : ""
|
||||
|
||||
out = new ByteArrayOutputStream()
|
||||
//TODO Add test timeout
|
||||
|
||||
def times = multiRuns ? multiArguments.size() : 1
|
||||
|
||||
def exitCodeMismatch = false
|
||||
for (int i = 0; i < times; i++) {
|
||||
ExecResult execResult = project.extensions.executor.execute {
|
||||
|
||||
commandLine exe
|
||||
|
||||
if (arguments != null) {
|
||||
args arguments
|
||||
}
|
||||
if (multiRuns && multiArguments[i] != null) {
|
||||
args multiArguments[i]
|
||||
}
|
||||
if (testData != null) {
|
||||
standardInput = new ByteArrayInputStream(testData.bytes)
|
||||
}
|
||||
standardOutput = out
|
||||
|
||||
ignoreExitValue = true
|
||||
}
|
||||
|
||||
exitCodeMismatch |= execResult.exitValue != expectedExitStatus
|
||||
if (exitCodeMismatch) {
|
||||
def message = "Expected exit status: $expectedExitStatus, actual: ${execResult.exitValue}"
|
||||
if (this.expectedFail) {
|
||||
println("Expected failure. $message")
|
||||
} else {
|
||||
throw new TestFailedException("Test failed on iteration $i. $message\n ${out.toString("UTF-8")}")
|
||||
}
|
||||
}
|
||||
}
|
||||
def result = compilerMessagesText + out.toString("UTF-8")
|
||||
if (printOutput) {
|
||||
println(result)
|
||||
}
|
||||
result = result.replace(System.lineSeparator(), "\n")
|
||||
def goldValueMismatch = !outputChecker.apply(result)
|
||||
if (goldValueMismatch) {
|
||||
def message
|
||||
if (goldValue != null) {
|
||||
message = "Expected output: $goldValue, actual output: $result"
|
||||
} else {
|
||||
message = "Actual output doesn't match output checker: $result"
|
||||
}
|
||||
if (this.expectedFail) {
|
||||
println("Expected failure. $message")
|
||||
} else {
|
||||
throw new TestFailedException("Test failed. $message")
|
||||
}
|
||||
}
|
||||
|
||||
if (!exitCodeMismatch && !goldValueMismatch && this.expectedFail) println("Unexpected pass")
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, the test executable will be built in two stages:
|
||||
* 1. Build a klibrary from sources.
|
||||
* 2. Build a final executable from this klibrary.
|
||||
*/
|
||||
@Input
|
||||
def enableTwoStageCompilation = false
|
||||
|
||||
@Input
|
||||
def groupDirectory = "."
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
List<String> ignoredTests = null
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
String filter = project.findProperty("filter")
|
||||
|
||||
@Internal
|
||||
def testGroupReporter = new KonanTestGroupReportEnvironment(project)
|
||||
|
||||
void parseLanguageFlags(String src) {
|
||||
def text = project.rootProject.file(src).text
|
||||
def languageSettings = findLinesWithPrefixesRemoved(text, "// !LANGUAGE: ") +
|
||||
findLinesWithPrefixesRemoved(text, '// LANGUAGE: ')
|
||||
if (languageSettings.size() != 0) {
|
||||
languageSettings.forEach { line ->
|
||||
line.split(" ").toList().forEach {
|
||||
if (it != "+NewInference") // It is on already by default, but passing it explicitly turns on a special "compatibility mode" in FE which is not desirable.
|
||||
flags.add("-XXLanguage:$it")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def experimentalSettings = findLinesWithPrefixesRemoved(text, "// !OPT_IN: ")
|
||||
if (experimentalSettings.size() != 0) {
|
||||
experimentalSettings.forEach { line ->
|
||||
line.split(" ").toList().forEach { flags.add("-opt-in=$it") }
|
||||
}
|
||||
}
|
||||
def expectActualLinker = findLinesWithPrefixesRemoved(text, "// EXPECT_ACTUAL_LINKER")
|
||||
if (expectActualLinker.size() != 0) {
|
||||
flags.add("-Xexpect-actual-linker")
|
||||
}
|
||||
}
|
||||
|
||||
static String markMutableObjects(String text) {
|
||||
def lines = text.readLines()
|
||||
def result = new ArrayList<String>(lines.size())
|
||||
lines.forEach { line ->
|
||||
// FIXME: find only those who has vars inside
|
||||
// Find object declarations and companion objects
|
||||
if (line.matches("\\s*(private|public|internal)?\\s*object [a-zA-Z_][a-zA-Z0-9_]*\\s*.*")
|
||||
|| line.matches("\\s*(private|public|internal)?\\s*companion object.*")) {
|
||||
result += "@kotlin.native.ThreadLocal"
|
||||
}
|
||||
result += line
|
||||
}
|
||||
return result.join(System.lineSeparator())
|
||||
}
|
||||
|
||||
static String insertInTextAfter(String text, String insert, String after) {
|
||||
def begin = text.indexOf(after)
|
||||
if (begin != -1) {
|
||||
def end = text.indexOf("\n", begin)
|
||||
text = text.substring(0, end) + insert + text.substring(end)
|
||||
} else {
|
||||
text = insert + text
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
List<TestFile> createTestFiles(String src, TestModule defaultModule) {
|
||||
def identifier = /[a-zA-Z_][a-zA-Z0-9_]/
|
||||
def fullQualified = /[a-zA-Z_][a-zA-Z0-9_.]/
|
||||
def importRegex = /(?m)^\s*import\s+/
|
||||
|
||||
def packagePattern = ~/(?m)^\s*package\s+(${fullQualified}*)/
|
||||
def boxPattern = ~/(?m)fun\s+box\s*\(\s*\)/
|
||||
def classPattern = ~/.*(class|object|enum|interface)\s+(${identifier}*).*/
|
||||
|
||||
def sourceName = "_" + normalize(project.rootProject.file(src).name)
|
||||
def packages = new LinkedHashSet<String>()
|
||||
def imports = []
|
||||
def classes = []
|
||||
def vars = new HashSet<String>() // variables that has the same name as a package
|
||||
TestModule mainModule = defaultModule
|
||||
def testFiles = TestDirectivesKt.buildCompileList(
|
||||
project.rootProject.file(src).toPath(),
|
||||
"$outputDirectory/${project.rootProject.file(src).name}",
|
||||
mainModule
|
||||
)
|
||||
for (TestFile testFile : testFiles) {
|
||||
def text = testFile.text
|
||||
def filePath = testFile.path
|
||||
if (text.contains('COROUTINES_PACKAGE')) {
|
||||
text = text.replace('COROUTINES_PACKAGE', 'kotlin.coroutines')
|
||||
}
|
||||
def pkg
|
||||
if (text =~ packagePattern) {
|
||||
pkg = (text =~ packagePattern)[0][1]
|
||||
if (!pkg.startsWith("kotlin")) {
|
||||
packages.add(pkg)
|
||||
pkg = "$sourceName.$pkg"
|
||||
text = text.replaceFirst(packagePattern, "package $pkg")
|
||||
}
|
||||
} else {
|
||||
pkg = sourceName
|
||||
text = insertInTextAfter(text, "\npackage $pkg\n", "@file:")
|
||||
}
|
||||
if (text =~ boxPattern) {
|
||||
imports.add("${pkg}.*")
|
||||
mainModule = testFile.module
|
||||
}
|
||||
|
||||
// Find mutable objects that should be marked as ThreadLocal
|
||||
if (filePath != "$outputDirectory/${project.rootProject.file(src).name}/helpers.kt") {
|
||||
text = markMutableObjects(text)
|
||||
}
|
||||
testFile.text = text
|
||||
}
|
||||
for (TestFile testFile : testFiles) {
|
||||
def text = testFile.text
|
||||
// Find if there are any imports in the file
|
||||
def matcher = (text =~ ~/${importRegex}(${fullQualified}*)/)
|
||||
if (matcher) {
|
||||
// Prepend package name to found imports
|
||||
for (int i = 0; i < matcher.count; i++) {
|
||||
String importStatement = matcher[i][1]
|
||||
def subImport = importStatement.with {
|
||||
int dotIdx = indexOf('.')
|
||||
dotIdx > 0 ? substring(0, dotIdx) : it
|
||||
}
|
||||
if (packages.contains(subImport) || classes.contains(subImport)) {
|
||||
// add only to those who import packages or import classes from the test files
|
||||
text = text.replaceFirst(~/${importRegex}${Pattern.quote(importStatement)}/,
|
||||
"import $sourceName.$importStatement")
|
||||
} else if (text =~ classPattern) {
|
||||
// special case for import from the local class
|
||||
def clsMatcher = (text =~ classPattern)
|
||||
for (int j = 0; j < clsMatcher.count; j++) {
|
||||
def cl = (text =~ classPattern)[j][2]
|
||||
classes.add(cl)
|
||||
if (subImport == cl) {
|
||||
text = text.replaceFirst(~/${importRegex}${Pattern.quote(importStatement)}/,
|
||||
"import $sourceName.$importStatement")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (packages.empty) {
|
||||
// Add import statement after package
|
||||
def pkg = null
|
||||
if (text =~ packagePattern) {
|
||||
pkg = 'package ' + (text =~ packagePattern)[0][1]
|
||||
text = text.replaceFirst(packagePattern, '')
|
||||
}
|
||||
text = insertInTextAfter(text, (pkg ? "\n$pkg\n" : "") + "import $sourceName.*\n", "@file:")
|
||||
}
|
||||
// now replace all package usages in full qualified names
|
||||
def res = "" // filesToCompile
|
||||
text.eachLine { line ->
|
||||
packages.each { pkg ->
|
||||
// line contains val or var declaration or function parameter declaration
|
||||
if ((line =~ ~/va(l|r) *$pkg*( *: *$fullQualified*)?( *get\(\))? *\=.*/) ||
|
||||
(line =~ ~/fun .*\(\n?\s*$pkg:.*/)) {
|
||||
vars.add(pkg)
|
||||
}
|
||||
if (line.contains("$pkg.") && !(line =~ packagePattern || line =~ importRegex)
|
||||
&& !vars.contains(pkg)) {
|
||||
def idx = 0
|
||||
while ((idx = line.indexOf(pkg, idx)) >= 0) {
|
||||
if (!Character.isJavaIdentifierPart(line.charAt(idx - 1))) {
|
||||
line = line.substring(0, idx) + "$sourceName.$pkg" + line.substring(idx + pkg.length())
|
||||
idx += sourceName.length() + pkg.length() + 1
|
||||
} else {
|
||||
idx += pkg.length()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res += "$line\n"
|
||||
}
|
||||
testFile.text = res
|
||||
}
|
||||
def launcherText = createLauncherFileText(src, imports)
|
||||
testFiles.add(
|
||||
new TestFile(
|
||||
"_launcher.kt",
|
||||
"$outputDirectory/$src/_launcher.kt".toString(),
|
||||
launcherText,
|
||||
mainModule
|
||||
)
|
||||
)
|
||||
return testFiles
|
||||
}
|
||||
|
||||
String normalize(String name) {
|
||||
return name.replace('.kt', '')
|
||||
.replace('-', '_')
|
||||
.replace('.', '_')
|
||||
}
|
||||
|
||||
/**
|
||||
* There are tests that require non-trivial 'package foo' in test launcher.
|
||||
*/
|
||||
String createLauncherFileText(String src, List<String> imports) {
|
||||
StringBuilder text = new StringBuilder()
|
||||
def pack = normalize(project.file(src).name)
|
||||
text.append("package _$pack\n")
|
||||
for (v in imports) {
|
||||
text.append("import $v\n")
|
||||
}
|
||||
text.append(
|
||||
"""
|
||||
import kotlin.test.Test
|
||||
|
||||
@Test
|
||||
fun runTest() {
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val result = box()
|
||||
if (result != "OK") throw AssertionError("Test failed with: " + result)
|
||||
}
|
||||
""" )
|
||||
return text.toString()
|
||||
}
|
||||
|
||||
List<String> findLinesWithPrefixesRemoved(String text, String prefix) {
|
||||
def result = []
|
||||
text.eachLine {
|
||||
if (it.startsWith(prefix)) {
|
||||
result.add(it - prefix)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static def excludeList = [
|
||||
"compiler/testData/codegen/boxInline/multiplatform/defaultArguments/receiversAndParametersInLambda.kt", // KT-36880
|
||||
"compiler/testData/codegen/box/compileKotlinAgainstKotlin/specialBridgesInDependencies.kt", // KT-42723
|
||||
"compiler/testData/codegen/box/collections/kt41123.kt", // KT-42723
|
||||
"compiler/testData/codegen/box/multiplatform/multiModule/expectActualTypealiasLink.kt", // KT-40137
|
||||
"compiler/testData/codegen/box/multiplatform/multiModule/expectActualMemberLink.kt", // KT-33091
|
||||
"compiler/testData/codegen/box/multiplatform/multiModule/expectActualLink.kt", // KT-41901
|
||||
"compiler/testData/codegen/box/coroutines/multiModule/", // KT-40121
|
||||
"compiler/testData/codegen/box/compileKotlinAgainstKotlin/clashingFakeOverrideSignatures.kt" // KT-42020
|
||||
]
|
||||
|
||||
boolean isEnabledForNativeBackend(String fileName) {
|
||||
def testFile = project.rootProject.file(fileName)
|
||||
def text = testFile.text
|
||||
|
||||
if (testFile.name in ignoredTests)
|
||||
return false
|
||||
|
||||
if (excludeList.any { fileName.replace(File.separator, "/").contains(it) }) return false
|
||||
|
||||
def languageSettings = findLinesWithPrefixesRemoved(text, '// !LANGUAGE: ') +
|
||||
findLinesWithPrefixesRemoved(text, '// LANGUAGE: ')
|
||||
if (!languageSettings.empty) {
|
||||
def settings = languageSettings.first()
|
||||
if (settings.contains('-ProperIeee754Comparisons') || // K/N supports only proper IEEE754 comparisons
|
||||
settings.contains('-ReleaseCoroutines') || // only release coroutines
|
||||
settings.contains('-DataClassInheritance') || // old behavior is not supported
|
||||
settings.contains('-ProhibitAssigningSingleElementsToVarargsInNamedForm') || // Prohibit these assignments
|
||||
settings.contains('-ProhibitDataClassesOverridingCopy') || // Prohibit as no longer supported
|
||||
settings.contains('-ProhibitOperatorMod') || // Prohibit as no longer supported
|
||||
settings.contains('-UseBuilderInferenceOnlyIfNeeded') || // Run only default one
|
||||
settings.contains('-UseCorrectExecutionOrderForVarargArguments')) { // Run only correct one
|
||||
return false
|
||||
}
|
||||
}
|
||||
def diagnostics = findLinesWithPrefixesRemoved(text, '// !DIAGNOSTICS')
|
||||
if (!diagnostics.empty) {
|
||||
return false
|
||||
}
|
||||
|
||||
def version = findLinesWithPrefixesRemoved(text, '// LANGUAGE_VERSION: ')
|
||||
if (version.size() != 0 && (!version.contains("1.3") || !version.contains("1.4"))) {
|
||||
// Support tests for 1.3 and exclude 1.2
|
||||
return false
|
||||
}
|
||||
|
||||
def apiVersion = findLinesWithPrefixesRemoved(text, '// !API_VERSION: ')
|
||||
if (apiVersion.size() != 0 && !apiVersion.contains("1.4")) {
|
||||
return false
|
||||
}
|
||||
|
||||
def targetBackend = findLinesWithPrefixesRemoved(text, "// TARGET_BACKEND")
|
||||
if (targetBackend.size() != 0) {
|
||||
// There is some target backend. Check if it is NATIVE or not.
|
||||
for (String s : targetBackend) {
|
||||
if (s.contains("NATIVE")){ return true }
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
// No target backend. Check if NATIVE backend is ignored.
|
||||
def ignoredBackends = findLinesWithPrefixesRemoved(text, "// IGNORE_BACKEND: ") +
|
||||
findLinesWithPrefixesRemoved(text, "// DONT_TARGET_EXACT_BACKEND: ")
|
||||
for (String s : ignoredBackends) {
|
||||
if (s.contains("NATIVE")) { return false }
|
||||
}
|
||||
// No ignored backends. Check if test is targeted to FULL_JDK or has JVM_TARGET set
|
||||
if (!findLinesWithPrefixesRemoved(text, "// FULL_JDK").isEmpty()) { return false }
|
||||
if (!findLinesWithPrefixesRemoved(text, "// JVM_TARGET:").isEmpty()) { return false }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void executeTest() {
|
||||
createOutputDirectory()
|
||||
// Form the test list.
|
||||
def ktFiles = project.rootProject.projectDir.toPath().resolve(groupDirectory).toFile()
|
||||
.listFiles({
|
||||
it.isFile() && it.name.endsWith(".kt")
|
||||
} as FileFilter)
|
||||
if (filter != null) {
|
||||
def pattern = ~filter
|
||||
ktFiles = ktFiles.findAll {
|
||||
it.name =~ pattern
|
||||
}
|
||||
}
|
||||
|
||||
def defaultModule = TestModule.default()
|
||||
|
||||
testGroupReporter.suite(name) { suite ->
|
||||
// Build tests in the group
|
||||
flags = (flags ?: []) + "-tr"
|
||||
List<TestFile> compileList = []
|
||||
ktFiles.each {
|
||||
def src = project.rootProject.relativePath(it)
|
||||
if (isEnabledForNativeBackend(src)) {
|
||||
// Create separate output directory for each test in the group.
|
||||
parseLanguageFlags(src)
|
||||
compileList.addAll(createTestFiles(src, defaultModule))
|
||||
}
|
||||
}
|
||||
if (compileList.any { it.module.dependencies.contains("support") }) {
|
||||
def supportModule = TestModule.support()
|
||||
compileList.add(new TestFile("helpers.kt", "$outputDirectory/helpers.kt",
|
||||
CoroutineTestUtilKt.createTextForHelpers(), supportModule))
|
||||
}
|
||||
compileList*.writeTextToFile()
|
||||
try {
|
||||
if (enableTwoStageCompilation) {
|
||||
// Two-stage compilation.
|
||||
def klibPath = "${executablePath()}.klib"
|
||||
def files = compileList.stream()
|
||||
.map { it.path }
|
||||
.collect(Collectors.toList())
|
||||
if (!files.empty) {
|
||||
runCompiler(files, klibPath, flags + ["-p", "library"])
|
||||
runCompiler([], executablePath(), flags + ["-Xinclude=$klibPath"])
|
||||
}
|
||||
} else {
|
||||
// Regular compilation with modules.
|
||||
Map<String, TestModule> modules = compileList.stream()
|
||||
.map {
|
||||
println(it.module)
|
||||
it.module }
|
||||
.distinct()
|
||||
.collect(Collectors.toMap({ it.name }, UnaryOperator.identity()))
|
||||
|
||||
List<TestModule> orderedModules = DFS.INSTANCE.topologicalOrder(modules.values()) { module ->
|
||||
module.dependencies.collect { modules[it] }.findAll { it != null }
|
||||
}
|
||||
def compiler = new MultiModuleCompilerInvocations(this, outputDirectory, executablePath(), modules, flags)
|
||||
|
||||
orderedModules.reverse().each { module ->
|
||||
println("${module.name} ${module.dependencies}")
|
||||
if (!module.isDefaultModule()) {
|
||||
compiler.produceLibrary(module)
|
||||
}
|
||||
}
|
||||
|
||||
compiler.produceProgram(compileList)
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
project.logger.quiet("ERROR: Compilation failed for test suite: $name with exception", ex)
|
||||
project.logger.quiet("The following files were unable to compile:")
|
||||
ktFiles.each { project.logger.quiet(it.name) }
|
||||
suite.abort("Compilation failed for test suite: $name", ex, ktFiles.collect { it.name })
|
||||
return
|
||||
}
|
||||
|
||||
// Run the tests.
|
||||
arguments = (arguments ?: []) + "--ktest_logger=SILENT"
|
||||
ktFiles.each { file ->
|
||||
def src = project.rootProject.relativePath(file)
|
||||
def savedArgs = arguments
|
||||
arguments += "--ktest_filter=_${normalize(file.name)}.*"
|
||||
use(KonanTestSuiteReportKt) {
|
||||
project.logger.quiet("TEST: $file.name " +
|
||||
"(done: $testGroupReporter.statistics.total/${ktFiles.size()}, " +
|
||||
"passed: $testGroupReporter.statistics.passed, " +
|
||||
"skipped: $testGroupReporter.statistics.skipped)")
|
||||
}
|
||||
if (isEnabledForNativeBackend(src)) {
|
||||
suite.executeTest(file.name) {
|
||||
project.logger.quiet(src)
|
||||
runExecutable()
|
||||
}
|
||||
} else {
|
||||
suite.skipTest(file.name)
|
||||
}
|
||||
arguments = savedArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -11,11 +11,11 @@ import java.io.PrintWriter
|
||||
|
||||
data class ExternalTestReport(@Expose val statistics: Statistics, @Expose val groups: List<KonanTestGroupReport>)
|
||||
|
||||
fun saveReport(reportFileName: String, statistics: Statistics, groups:List<KonanTestGroupReport>){
|
||||
fun saveReport(reportFileName: String, statistics: Statistics){
|
||||
File(reportFileName).apply {
|
||||
parentFile.mkdirs()
|
||||
PrintWriter(this).use {
|
||||
it.append(gson.toJson(ExternalTestReport(statistics, groups)))
|
||||
it.append(gson.toJson(ExternalTestReport(statistics, emptyList())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package org.jetbrains.kotlin
|
||||
|
||||
interface CompilerRunner {
|
||||
fun runCompiler(filesToCompile: List<String>, output: String, moreArgs: List<String>)
|
||||
}
|
||||
|
||||
// This class knows how to run the compiler for multi-module external tests.
|
||||
//
|
||||
// In addition it knows how to deal with multi-versioned modules.
|
||||
// The versioned modules are used for binary compatibility testing.
|
||||
// For such modules we produce two klibs:
|
||||
// `version1/foo.klib` and `version2/foo.klib`.
|
||||
// For un-versioned modules we still produce just `bar.klib`
|
||||
// We link all klibs against `version1/foo.klib` and `bar.klib`
|
||||
// We link the final executable against `version2/foo.klib` and `bar.klib`.
|
||||
|
||||
class MultiModuleCompilerInvocations(
|
||||
val konanTest: CompilerRunner,
|
||||
val outputDirectory: String,
|
||||
val executablePath: String,
|
||||
val modules: Map<String, TestModule>,
|
||||
val flags: List<String>
|
||||
) {
|
||||
val libs = HashSet<String>()
|
||||
|
||||
fun moduleToKlibName(moduleName: String, version: Int): String {
|
||||
val module = modules[moduleName] ?: error("Could not find module $moduleName")
|
||||
|
||||
return if (module.hasVersions) {
|
||||
val versionSuffix = "_version$version"
|
||||
"$executablePath$versionSuffix/$moduleName.klib"
|
||||
} else {
|
||||
"$executablePath.$moduleName.klib"
|
||||
}
|
||||
}
|
||||
|
||||
private fun produceLibrary(module: TestModule, moduleVersion: Int) {
|
||||
val klibModulePath = moduleToKlibName(module.name, moduleVersion)
|
||||
libs.addAll(module.dependencies)
|
||||
|
||||
val dependencyVersion = moduleVersion
|
||||
|
||||
val klibs = module.dependencies.flatMap { listOf("-l", moduleToKlibName(it, dependencyVersion)) }
|
||||
val repos = listOf("-r", "${executablePath}_version$dependencyVersion", "-r", outputDirectory)
|
||||
|
||||
val friends = module.friends.flatMap {
|
||||
listOf("-friend-modules", "$executablePath.$it.klib")
|
||||
}
|
||||
konanTest.runCompiler(module.versionFiles(moduleVersion).map { it.path },
|
||||
klibModulePath, flags + listOf("-p", "library") + repos + klibs + friends)
|
||||
}
|
||||
|
||||
fun produceLibrary(module: TestModule) {
|
||||
if (!module.hasVersions) {
|
||||
produceLibrary(module, 1)
|
||||
} else {
|
||||
produceLibrary(module, 1)
|
||||
produceLibrary(module, 2)
|
||||
}
|
||||
}
|
||||
|
||||
fun produceProgram(compileList: List<TestFile>) {
|
||||
val compileMain = compileList.filter {
|
||||
it.module.isDefaultModule() && !it.module.isSupportModule()
|
||||
}
|
||||
compileMain.forEach { f ->
|
||||
libs.addAll(f.module.dependencies)
|
||||
}
|
||||
|
||||
// When producing an executable link them against version 2 of multiversioned klibs.
|
||||
val dependencyVersion = 2
|
||||
|
||||
val friends = compileMain.flatMap { it.module.friends }.toSet()
|
||||
val repos = listOf("-r", "${executablePath}_version$dependencyVersion", "-r", outputDirectory)
|
||||
|
||||
if (compileMain.isNotEmpty()) {
|
||||
konanTest.runCompiler(compileMain.map { it.path }, executablePath, flags + repos +
|
||||
libs.flatMap { listOf("-l", moduleToKlibName(it, dependencyVersion)) } +
|
||||
friends.flatMap { listOf("-friend-modules", "${executablePath}.${it}.klib") }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +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
|
||||
|
||||
// This is almost a full copy of kotlin/compiler/tests-common/tests/org/jetbrains/kotlin/coroutineTestUtil.kt
|
||||
// TODO: get it automatically as a dependency
|
||||
fun createTextForHelpers(): String = """
|
||||
package helpers
|
||||
import kotlin.coroutines.*
|
||||
|
||||
fun <T> handleResultContinuation(x: (T) -> Unit): Continuation<T> = object: Continuation<T> {
|
||||
override val context = EmptyCoroutineContext
|
||||
|
||||
override fun resumeWith(result: Result<T>) {
|
||||
x(result.getOrThrow())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation<Any?> = object: Continuation<Any?> {
|
||||
override val context = EmptyCoroutineContext
|
||||
|
||||
override fun resumeWith(result: Result<Any?>) {
|
||||
result.exceptionOrNull()?.let(x)
|
||||
}
|
||||
}
|
||||
|
||||
open class EmptyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<Any?> {
|
||||
companion object : EmptyContinuation()
|
||||
|
||||
override fun resumeWith(result: Result<Any?>) {
|
||||
result.getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ContinuationAdapter<in T> : Continuation<T> {
|
||||
override val context: CoroutineContext = EmptyCoroutineContext
|
||||
|
||||
override fun resumeWith(result: Result<T>) {
|
||||
if (result.isSuccess) {
|
||||
resume(result.getOrThrow())
|
||||
} else {
|
||||
resumeWithException(result.exceptionOrNull()!!)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun resumeWithException(exception: Throwable)
|
||||
abstract fun resume(value: T)
|
||||
}
|
||||
|
||||
class StateMachineCheckerClass {
|
||||
private var counter = 0
|
||||
var finished = false
|
||||
|
||||
var proceed: () -> Unit = {}
|
||||
|
||||
fun reset() {
|
||||
counter = 0
|
||||
finished = false
|
||||
proceed = {}
|
||||
}
|
||||
|
||||
suspend fun suspendHere() = suspendCoroutine<Unit> { c ->
|
||||
counter++
|
||||
proceed = { c.resume(Unit) }
|
||||
}
|
||||
|
||||
fun check(numberOfSuspensions: Int, checkFinished: Boolean = true) {
|
||||
for (i in 1..numberOfSuspensions) {
|
||||
if (counter != i) error("Wrong state-machine generated: suspendHere called should be called exactly once in one state. Expected " + i + ", got " + counter)
|
||||
proceed()
|
||||
}
|
||||
if (counter != numberOfSuspensions)
|
||||
error("Wrong state-machine generated: suspendHere called should be called exactly once in one state. Expected " + numberOfSuspensions + ", got " + counter)
|
||||
if (finished) error("Wrong state-machine generated: it is finished early")
|
||||
proceed()
|
||||
if (checkFinished && !finished) error("Wrong state-machine generated: it is not finished yet")
|
||||
}
|
||||
}
|
||||
val StateMachineChecker = StateMachineCheckerClass()
|
||||
object CheckStateMachineContinuation: Continuation<Unit> {
|
||||
override val context: CoroutineContext
|
||||
get() = EmptyCoroutineContext
|
||||
|
||||
override fun resumeWith(result: Result<Unit>) {
|
||||
result.getOrThrow()
|
||||
StateMachineChecker.proceed = {
|
||||
StateMachineChecker.finished = true
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Reference in New Issue
Block a user