Test module support for external tests. Code cleanup for external tests runner class.
This commit is contained in:
committed by
Pavel Punegov
parent
2da889ad1a
commit
c15d9ee81a
@@ -106,12 +106,12 @@ final CacheTesting cacheTesting = CacheTestingKt.configureCacheTesting(project)
|
||||
if (cacheTesting != null) {
|
||||
// Note: can't do this in [CacheTesting.configure] since task classes aren't accessible there.
|
||||
|
||||
tasks.withType(KonanCompileNativeBinary.class) {
|
||||
tasks.withType(KonanCompileNativeBinary.class).configureEach {
|
||||
dependsOn cacheTesting.buildCacheTask
|
||||
extraOpts cacheTesting.compilerArgs
|
||||
}
|
||||
|
||||
tasks.withType(RunExternalTestGroup.class) {
|
||||
tasks.withType(RunExternalTestGroup.class).configureEach {
|
||||
dependsOn cacheTesting.buildCacheTask
|
||||
flags = (flags ?: []) + cacheTesting.compilerArgs
|
||||
}
|
||||
@@ -120,11 +120,11 @@ if (cacheTesting != null) {
|
||||
// Enable two-stage test compilation if the test_two_stage property is set.
|
||||
ext.twoStageEnabled = project.hasProperty("test_two_stage")
|
||||
|
||||
tasks.withType(KonanCompileNativeBinary.class) {
|
||||
tasks.withType(KonanCompileNativeBinary.class).configureEach {
|
||||
enableTwoStageCompilation = twoStageEnabled
|
||||
}
|
||||
|
||||
tasks.withType(RunExternalTestGroup.class) {
|
||||
tasks.withType(RunExternalTestGroup.class).configureEach {
|
||||
enableTwoStageCompilation = twoStageEnabled
|
||||
}
|
||||
|
||||
@@ -290,22 +290,20 @@ boolean isExcluded(String dir) {
|
||||
return result
|
||||
}
|
||||
|
||||
def createTestTasks(File testRoot, Class<Task> taskType, Closure taskConfiguration) {
|
||||
def createTestTasks(File testRoot, Class<Task> taskType) {
|
||||
testRoot.eachDirRecurse {
|
||||
// Skip build directory and directories without *.kt files.
|
||||
if (it.name == "build" || it.listFiles().count { it.name.endsWith(".kt") && it.isFile() } == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
def taskDirectory = project.relativePath(it)
|
||||
def taskDirectory = project.relativePath(it).substring("build/".length())
|
||||
if (!isExcluded(taskDirectory)) {
|
||||
def taskName = taskDirectory.replaceAll("[-/\\\\]", '_')
|
||||
|
||||
def task = project.task(taskName, type: taskType) {
|
||||
groupDirectory = taskDirectory
|
||||
finalizedBy resultsTask
|
||||
project.tasks.register(taskName, taskType) {
|
||||
it.groupDirectory = taskDirectory
|
||||
it.finalizedBy resultsTask
|
||||
}
|
||||
taskConfiguration(task)
|
||||
} else {
|
||||
println("$taskDirectory is excluded")
|
||||
}
|
||||
@@ -404,7 +402,7 @@ task run_external () {
|
||||
}
|
||||
|
||||
// Create tasks for external tests.
|
||||
createTestTasks(externalTestsDir, RunExternalTestGroup) { }
|
||||
createTestTasks(externalTestsDir, RunExternalTestGroup)
|
||||
|
||||
// Set up dependencies.
|
||||
dependsOn(tasksOf(RunExternalTestGroup))
|
||||
@@ -4399,7 +4397,7 @@ task buildKonanTests { t ->
|
||||
// These tests should not be built into the TestRunner's test executable
|
||||
def excludeList = [ "codegen/inline/returnLocalClassFromBlock.kt" ]
|
||||
project.tasks
|
||||
.matching { it instanceof KonanStandaloneTest }
|
||||
.withType(KonanStandaloneTest.class)
|
||||
.each {
|
||||
// add library and source files
|
||||
if (it instanceof KonanLinkTest) {
|
||||
@@ -4426,7 +4424,8 @@ task buildKonanTests { t ->
|
||||
|
||||
// Local tests build into a single binary should depend on this task
|
||||
project.tasks
|
||||
.matching { !(it instanceof KonanStandaloneTest) && it instanceof KonanLocalTest }
|
||||
.withType(KonanLocalTest.class)
|
||||
.matching { !(it instanceof KonanStandaloneTest) }
|
||||
.forEach { it.dependsOn(t) }
|
||||
}
|
||||
|
||||
@@ -4478,12 +4477,13 @@ dependencies {
|
||||
// Configure build for iOS device targets.
|
||||
if (target.family == Family.IOS && (target.architecture == ARM32 || target.architecture == ARM64)) {
|
||||
project.tasks
|
||||
.matching { it instanceof KonanTestExecutable }
|
||||
.withType(KonanTestExecutable.class)
|
||||
.forEach {
|
||||
ExecutorServiceKt.configureXcodeBuild((KonanTestExecutable) it)
|
||||
}
|
||||
// Exclude tasks that can not be run on device
|
||||
// Exclude tasks that cannot be run on device
|
||||
project.tasks
|
||||
.withType(KonanTest.class)
|
||||
.matching { (it instanceof KonanLocalTest && ((KonanLocalTest) it).testData != null) ||
|
||||
// Filter out tests that depend on libs. TODO: copy them too
|
||||
it instanceof KonanInteropTest ||
|
||||
@@ -4491,4 +4491,4 @@ if (target.family == Family.IOS && (target.architecture == ARM32 || target.archi
|
||||
it instanceof KonanLinkTest
|
||||
}
|
||||
.forEach { it.enabled = false }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +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 file.
|
||||
*/
|
||||
|
||||
package helpers
|
||||
@@ -1,26 +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 file.
|
||||
*/
|
||||
|
||||
package kotlin.test
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
public fun assertTypeEquals(expected: Any?, actual: Any?) {
|
||||
//TODO: find analogue
|
||||
//assertEquals(expected?.javaClass, actual?.javaClass)
|
||||
}
|
||||
|
||||
fun <T> Iterable<T>.assertSorted(isInOrder: (T, T) -> Boolean): Unit { this.iterator().assertSorted(isInOrder) }
|
||||
fun <T> Iterator<T>.assertSorted(isInOrder: (T, T) -> Boolean) {
|
||||
if (!hasNext()) return
|
||||
var index = 0
|
||||
var prev = next()
|
||||
while (hasNext()) {
|
||||
index += 1
|
||||
val next = next()
|
||||
assertTrue(isInOrder(prev, next), "Not in order at position $index, element[${index-1}]: $prev, element[$index]: $next")
|
||||
prev = next
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -42,9 +42,13 @@ repositories {
|
||||
maven("https://kotlin.bintray.com/kotlinx")
|
||||
}
|
||||
|
||||
val kotlinCompilerJar by configurations.creating
|
||||
|
||||
dependencies {
|
||||
compileOnly(gradleApi())
|
||||
|
||||
kotlinCompilerJar("org.jetbrains.kotlin:kotlin-compiler:${kotlinVersion}@jar")
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
|
||||
@@ -92,6 +96,7 @@ val compileGroovy: GroovyCompile by tasks
|
||||
// Add Kotlin classes to a classpath for the Groovy compiler
|
||||
compileGroovy.apply {
|
||||
classpath += project.files(compileKotlin.destinationDir)
|
||||
classpath += kotlinCompilerJar
|
||||
dependsOn(compileKotlin)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,24 +20,19 @@ import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.JavaExec
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.process.ExecResult
|
||||
import org.jetbrains.kotlin.utils.DFS
|
||||
|
||||
import javax.inject.Inject
|
||||
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
|
||||
|
||||
abstract class OldKonanTest extends JavaExec {
|
||||
public boolean inDevelopersRun = false
|
||||
|
||||
public String source
|
||||
class RunExternalTestGroup extends JavaExec {
|
||||
def platformManager = project.rootProject.platformManager
|
||||
def target = platformManager.targetManager(project.testTarget).target
|
||||
def dist = project.rootProject.file(project.findProperty("org.jetbrains.kotlin.native.home") ?:
|
||||
project.findProperty("konan.home") ?: "dist")
|
||||
def dependenciesDir = project.rootProject.dependenciesDir
|
||||
def konancDriver = project.isWindows() ? "konanc.bat" : "konanc"
|
||||
def konanc = new File("${dist.canonicalPath}/bin/$konancDriver").absolutePath
|
||||
def outputSourceSetName = "testOutputLocal"
|
||||
def dist = UtilsKt.getKotlinNativeDist(project)
|
||||
|
||||
def enableKonanAssertions = true
|
||||
String outputDirectory = null
|
||||
String goldValue = null
|
||||
@@ -52,44 +47,22 @@ abstract class OldKonanTest extends JavaExec {
|
||||
boolean multiRuns = false
|
||||
List<List<String>> multiArguments = null
|
||||
|
||||
boolean enabled = true
|
||||
boolean expectedFail = false
|
||||
boolean run = true
|
||||
boolean compilerMessages = false
|
||||
|
||||
void setDisabled(boolean value) {
|
||||
this.enabled = !value
|
||||
}
|
||||
|
||||
void setExpectedFail(boolean value) {
|
||||
this.expectedFail = value
|
||||
}
|
||||
|
||||
// Uses directory defined in $outputSourceSetName source set.
|
||||
// If such source set doesn't exist, uses temporary directory.
|
||||
// Uses directory defined in $outputSourceSetName source set
|
||||
void createOutputDirectory() {
|
||||
if (outputDirectory != null) {
|
||||
return
|
||||
}
|
||||
def outputSourceSet = project.findProperty(getOutputSourceSetName())
|
||||
if (outputSourceSet != null) {
|
||||
outputDirectory = outputSourceSet.absolutePath + "/$name"
|
||||
project.file(outputDirectory).mkdirs()
|
||||
} else {
|
||||
outputDirectory = getTemporaryDir().absolutePath
|
||||
}
|
||||
def outputSourceSet = UtilsKt.getTestOutputExternal(project)
|
||||
outputDirectory = Paths.get(outputSourceSet, name).toString()
|
||||
project.file(outputDirectory).mkdirs()
|
||||
}
|
||||
|
||||
OldKonanTest() {
|
||||
RunExternalTestGroup() {
|
||||
// We don't build the compiler if a custom dist path is specified.
|
||||
if (!project.ext.useCustomDist) {
|
||||
dependsOn(project.rootProject.tasks['dist'])
|
||||
if (project.testTarget) {
|
||||
// if a test_target property is set then tests should depend on a crossDist
|
||||
// otherwise runtime components would not be build for a target
|
||||
dependsOn(project.rootProject.tasks["${target}CrossDist"])
|
||||
}
|
||||
}
|
||||
UtilsKt.dependsOnDist(this)
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,8 +72,6 @@ abstract class OldKonanTest extends JavaExec {
|
||||
// configuration part at runCompiler.
|
||||
}
|
||||
|
||||
abstract void compileTest(List<String> filesToCompile, String exe)
|
||||
|
||||
protected void runCompiler(List<String> filesToCompile, String output, List<String> moreArgs) {
|
||||
def log = new ByteArrayOutputStream()
|
||||
try {
|
||||
@@ -144,91 +115,20 @@ abstract class OldKonanTest extends JavaExec {
|
||||
}
|
||||
}
|
||||
|
||||
protected void runCompiler(String source, String output, List<String> moreArgs) {
|
||||
runCompiler([source], output, moreArgs)
|
||||
}
|
||||
|
||||
String buildExePath() {
|
||||
def exeName = project.file(source).name.replace(".kt", "")
|
||||
return "$outputDirectory/$exeName"
|
||||
}
|
||||
|
||||
protected String removeDiagnostics(String str) {
|
||||
return str.replaceAll(~/<!.*?!>(.*?)<!>/) { all, text -> text }
|
||||
}
|
||||
|
||||
protected List<String> registerKtFile(List<String> sourceFiles, String newFilePath, String newFileContent) {
|
||||
createFile(newFilePath, newFileContent)
|
||||
if (newFilePath.endsWith(".kt")) {
|
||||
sourceFiles.add(newFilePath)
|
||||
}
|
||||
return sourceFiles
|
||||
}
|
||||
|
||||
// TODO refactor
|
||||
List<String> buildCompileList() {
|
||||
def result = []
|
||||
def filePattern = ~/(?m)\/\/\s*FILE:\s*(.*)$/
|
||||
def srcFile = project.file(source)
|
||||
def srcText = removeDiagnostics(srcFile.text)
|
||||
def matcher = filePattern.matcher(srcText)
|
||||
|
||||
if (srcText.contains('// WITH_COROUTINES')) {
|
||||
def coroutineHelpersFileName = "$outputDirectory/helpers.kt"
|
||||
createFile(coroutineHelpersFileName, CoroutineTestUtilKt.createTextForHelpers(true))
|
||||
result.add(coroutineHelpersFileName)
|
||||
}
|
||||
|
||||
if (!matcher.find()) {
|
||||
// There is only one file in the input
|
||||
def filePath = "$outputDirectory/${srcFile.name}"
|
||||
registerKtFile(result, filePath, srcText)
|
||||
} else {
|
||||
// There are several files
|
||||
def processedChars = 0
|
||||
while (true) {
|
||||
def filePath = "$outputDirectory/${matcher.group(1)}"
|
||||
def start = processedChars
|
||||
def nextFileExists = matcher.find()
|
||||
def end = nextFileExists ? matcher.start() : srcText.length()
|
||||
def fileText = srcText.substring(start, end)
|
||||
processedChars = end
|
||||
registerKtFile(result, filePath, fileText)
|
||||
if (!nextFileExists) break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
void createFile(String file, String text) {
|
||||
Paths.get(file).with {
|
||||
getParent().toFile().with {
|
||||
if (!exists()) { mkdirs() }
|
||||
}
|
||||
write(text)
|
||||
}
|
||||
}
|
||||
// FIXME: output directory here changes and hence this is not a property
|
||||
String executablePath() { return "$outputDirectory/program.tr" }
|
||||
|
||||
OutputStream out
|
||||
|
||||
@TaskAction
|
||||
void executeTest() {
|
||||
void runExecutable() {
|
||||
if (!enabled) {
|
||||
println "Test is disabled: $name"
|
||||
return
|
||||
}
|
||||
|
||||
createOutputDirectory()
|
||||
def program = buildExePath()
|
||||
def program = executablePath()
|
||||
def suffix = target.family.exeSuffix
|
||||
def exe = "$program.$suffix"
|
||||
|
||||
compileTest(buildCompileList(), program)
|
||||
|
||||
if (!run) {
|
||||
println "to be executed manually: $exe"
|
||||
return
|
||||
}
|
||||
println "execution: $exe"
|
||||
|
||||
def compilerMessagesText = compilerMessages ? project.file("${program}.compilation.log").getText('UTF-8') : ""
|
||||
@@ -290,13 +190,6 @@ abstract class OldKonanTest extends JavaExec {
|
||||
|
||||
if (!exitCodeMismatch && !goldValueMismatch && this.expectedFail) println("Unexpected pass")
|
||||
}
|
||||
}
|
||||
|
||||
class RunExternalTestGroup extends OldKonanTest {
|
||||
/**
|
||||
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
|
||||
*/
|
||||
public def inDevelopersRun = false
|
||||
|
||||
/**
|
||||
* If true, the test executable will be built in two stages:
|
||||
@@ -306,22 +199,15 @@ class RunExternalTestGroup extends OldKonanTest {
|
||||
@Input
|
||||
public def enableTwoStageCompilation = false
|
||||
|
||||
@Input
|
||||
def groupDirectory = "."
|
||||
def outputSourceSetName = "testOutputExternal"
|
||||
|
||||
String filter = project.findProperty("filter")
|
||||
|
||||
def testGroupReporter = new KonanTestGroupReportEnvironment(project)
|
||||
|
||||
RunExternalTestGroup() {
|
||||
}
|
||||
|
||||
@Override
|
||||
List<String> buildCompileList() {
|
||||
// Already build by the previous step
|
||||
return null
|
||||
}
|
||||
|
||||
void parseLanguageFlags() {
|
||||
def text = project.file(source).text
|
||||
void parseLanguageFlags(String src) {
|
||||
def text = project.buildDir.toPath().resolve(src).text
|
||||
def languageSettings = findLinesWithPrefixesRemoved(text, "// !LANGUAGE: ")
|
||||
if (languageSettings.size() != 0) {
|
||||
languageSettings.forEach { line ->
|
||||
@@ -352,7 +238,7 @@ class RunExternalTestGroup extends OldKonanTest {
|
||||
return result.join(System.lineSeparator())
|
||||
}
|
||||
|
||||
String insertInTextAfter(String text, String insert, String after) {
|
||||
static String insertInTextAfter(String text, String insert, String after) {
|
||||
def begin = text.indexOf(after)
|
||||
if (begin != -1) {
|
||||
def end = text.indexOf("\n", begin)
|
||||
@@ -363,7 +249,7 @@ class RunExternalTestGroup extends OldKonanTest {
|
||||
return text
|
||||
}
|
||||
|
||||
List<String> createTestFiles() {
|
||||
List<TestFile> createTestFiles(String src) {
|
||||
def identifier = /[a-zA-Z_][a-zA-Z0-9_]/
|
||||
def fullQualified = /[a-zA-Z_][a-zA-Z0-9_.]/
|
||||
def importRegex = /(?m)^\s*import\s+/
|
||||
@@ -372,18 +258,20 @@ class RunExternalTestGroup extends OldKonanTest {
|
||||
def boxPattern = ~/(?m)fun\s+box\s*\(\s*\)/
|
||||
def classPattern = ~/.*(class|object|enum|interface)\s+(${identifier}*).*/
|
||||
|
||||
def sourceName = "_" + normalize(project.file(source).name)
|
||||
def sourceName = "_" + normalize(project.buildDir.toPath().resolve(src).toFile().name)
|
||||
def packages = new LinkedHashSet<String>()
|
||||
def imports = []
|
||||
def classes = []
|
||||
TestModule mainModule = null
|
||||
|
||||
def result = super.buildCompileList()
|
||||
for (String filePath : result) {
|
||||
def text = project.file(filePath).text
|
||||
def testFiles = TestDirectivesKt.buildCompileList(project.file("build/$src").toPath(), "$outputDirectory/$src")
|
||||
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 = null
|
||||
def pkg
|
||||
if (text =~ packagePattern) {
|
||||
pkg = (text =~ packagePattern)[0][1]
|
||||
packages.add(pkg)
|
||||
@@ -395,18 +283,17 @@ class RunExternalTestGroup extends OldKonanTest {
|
||||
}
|
||||
if (text =~ boxPattern) {
|
||||
imports.add("${pkg}.*")
|
||||
mainModule = testFile.module
|
||||
}
|
||||
|
||||
// Find mutable objects that should be marked as ThreadLocal
|
||||
if (filePath != "$outputDirectory/helpers.kt") {
|
||||
if (filePath != "$outputDirectory/$src/helpers.kt") {
|
||||
text = markMutableObjects(text)
|
||||
}
|
||||
|
||||
createFile(filePath, text)
|
||||
testFile.text = text
|
||||
}
|
||||
// TODO: optimize files writes
|
||||
for (String filePath : result) {
|
||||
def text = project.file(filePath).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) {
|
||||
@@ -444,7 +331,7 @@ class RunExternalTestGroup extends OldKonanTest {
|
||||
text = insertInTextAfter(text, (pkg ? "\n$pkg\n" : "") + "import $sourceName.*\n", "@file:Suppress")
|
||||
}
|
||||
// now replace all package usages in full qualified names
|
||||
def res = "" // result
|
||||
def res = "" // filesToCompile
|
||||
def vars = new HashSet<String>() // variables that has the same name as a package
|
||||
text.eachLine { line ->
|
||||
packages.each { pkg ->
|
||||
@@ -452,8 +339,8 @@ class RunExternalTestGroup extends OldKonanTest {
|
||||
if ((line =~ ~/va(l|r) *$pkg *\=/) || (line =~ ~/fun .*\(\n?\s*$pkg:.*/)) {
|
||||
vars.add(pkg)
|
||||
}
|
||||
if (line.contains("$pkg.") && ! (line =~ packagePattern || line =~ importRegex)
|
||||
&& ! vars.contains(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))) {
|
||||
@@ -467,11 +354,12 @@ class RunExternalTestGroup extends OldKonanTest {
|
||||
}
|
||||
res += "$line\n"
|
||||
}
|
||||
createFile(filePath, res)
|
||||
testFile.text = res
|
||||
}
|
||||
createLauncherFile("$outputDirectory/_launcher.kt", imports)
|
||||
result.add("$outputDirectory/_launcher.kt")
|
||||
return result
|
||||
def launcherText = createLauncherFileText(src, imports)
|
||||
testFiles.add(new TestFile("_launcher.kt", "$outputDirectory/$src/_launcher.kt".toString(),
|
||||
launcherText, mainModule != null ? mainModule : TestModule.default))
|
||||
return testFiles
|
||||
}
|
||||
|
||||
String normalize(String name) {
|
||||
@@ -483,14 +371,13 @@ class RunExternalTestGroup extends OldKonanTest {
|
||||
/**
|
||||
* There are tests that require non-trivial 'package foo' in test launcher.
|
||||
*/
|
||||
void createLauncherFile(String file, List<String> imports) {
|
||||
String createLauncherFileText(String src, List<String> imports) {
|
||||
StringBuilder text = new StringBuilder()
|
||||
def pack = normalize(project.file(source).name)
|
||||
def pack = normalize(project.file(src).name)
|
||||
text.append("package _$pack\n")
|
||||
for (v in imports) {
|
||||
text.append("import ").append(v).append('\n')
|
||||
text.append("import $v\n")
|
||||
}
|
||||
|
||||
text.append(
|
||||
"""
|
||||
import kotlin.test.Test
|
||||
@@ -500,10 +387,9 @@ fun runTest() {
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val result = box()
|
||||
if (result != "OK") throw AssertionError("Test failed with: " + result)
|
||||
print(result)
|
||||
}
|
||||
""" )
|
||||
createFile(file, text.toString())
|
||||
return text.toString()
|
||||
}
|
||||
|
||||
List<String> findLinesWithPrefixesRemoved(String text, String prefix) {
|
||||
@@ -522,7 +408,7 @@ fun runTest() {
|
||||
]
|
||||
|
||||
boolean isEnabledForNativeBackend(String fileName) {
|
||||
def text = project.file(fileName).text
|
||||
def text = project.buildDir.toPath().resolve(fileName).text
|
||||
|
||||
if (excludeList.contains(fileName.replace(File.separator, "/"))) return false
|
||||
|
||||
@@ -570,31 +456,11 @@ fun runTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void compileTest(List<String> filesToCompile, String exe) {
|
||||
// An executable should be already compiled
|
||||
}
|
||||
|
||||
@Override
|
||||
String buildExePath() {
|
||||
def outputDir
|
||||
def outputSourceSet = project.findProperty(getOutputSourceSetName())
|
||||
if (outputSourceSet != null) {
|
||||
outputDir = outputSourceSet.absolutePath+ "/$name"
|
||||
} else {
|
||||
outputDir = getTemporaryDir().absolutePath
|
||||
}
|
||||
return "$outputDir/program.tr"
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
@Override
|
||||
void executeTest() {
|
||||
createOutputDirectory()
|
||||
def outputRootDirectory = outputDirectory
|
||||
|
||||
// Form the test list.
|
||||
List<File> ktFiles = project.file(groupDirectory)
|
||||
List<File> ktFiles = project.buildDir.toPath().resolve(groupDirectory).toFile()
|
||||
.listFiles({
|
||||
it.isFile() && it.name.endsWith(".kt")
|
||||
} as FileFilter)
|
||||
@@ -608,29 +474,53 @@ fun runTest() {
|
||||
testGroupReporter.suite(name) { suite ->
|
||||
// Build tests in the group
|
||||
flags = (flags ?: []) + "-tr"
|
||||
def compileList = []
|
||||
List<TestFile> compileList = []
|
||||
ktFiles.each {
|
||||
source = project.relativePath(it)
|
||||
if (isEnabledForNativeBackend(source)) {
|
||||
def src = project.buildDir.relativePath(it)
|
||||
if (isEnabledForNativeBackend(src)) {
|
||||
// Create separate output directory for each test in the group.
|
||||
outputDirectory = outputRootDirectory + "/${it.name}"
|
||||
project.file(outputDirectory).mkdirs()
|
||||
parseLanguageFlags()
|
||||
compileList.addAll(createTestFiles())
|
||||
project.file("$outputDirectory/${it.name}").mkdirs()
|
||||
parseLanguageFlags(src)
|
||||
compileList.addAll(createTestFiles(src))
|
||||
}
|
||||
}
|
||||
compileList.add(project.file("testUtils.kt").absolutePath)
|
||||
compileList.add(project.file("helpers.kt").absolutePath)
|
||||
compileList*.writeTextToFile()
|
||||
try {
|
||||
def exePath = buildExePath()
|
||||
if (enableTwoStageCompilation) {
|
||||
// Two-stage compilation.
|
||||
def klibPath = "${exePath}.klib"
|
||||
runCompiler(compileList, klibPath, flags + ["-p", "library"])
|
||||
runCompiler([], exePath, flags + ["-Xinclude=$klibPath"])
|
||||
def klibPath = "${executablePath()}.klib"
|
||||
def files = compileList.stream()
|
||||
.map { it.path }
|
||||
.collect(Collectors.toList())
|
||||
runCompiler(files, klibPath, flags + ["-p", "library"])
|
||||
runCompiler([], executablePath(), flags + ["-Xinclude=$klibPath"])
|
||||
} else {
|
||||
// Regular compilation.
|
||||
runCompiler(compileList, exePath, flags)
|
||||
// Regular compilation with modules.
|
||||
Map<String, TestModule> modules = compileList.stream()
|
||||
.map { it.module }
|
||||
.distinct()
|
||||
.collect(Collectors.toMap({ it.name }, UnaryOperator.identity() ))
|
||||
|
||||
List<TestModule> orderedModules = DFS.topologicalOrder(modules.values()) { module ->
|
||||
module.dependencies.collect { modules[it] }.findAll { it != null }
|
||||
}
|
||||
def libsFlags = []
|
||||
orderedModules.reverse().each { module ->
|
||||
if (!module.isDefaultModule()) {
|
||||
def klibModulePath = "${executablePath()}.${module.name}.klib"
|
||||
libsFlags += module.linkDependencies(executablePath())
|
||||
runCompiler(compileList.findAll { it.module == module }.collect { it.path },
|
||||
klibModulePath, flags + ["-p", "library"] + libsFlags)
|
||||
}
|
||||
}
|
||||
|
||||
def compileMain = compileList.findAll {
|
||||
it.module.isDefaultModule() || it.module == TestModule.support
|
||||
}
|
||||
compileMain.forEach { f ->
|
||||
libsFlags.addAll(f.module.linkDependencies(executablePath()))
|
||||
}
|
||||
if (!compileMain.empty) runCompiler(compileMain.collect { it.path }, executablePath(), flags + libsFlags)
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
project.logger.quiet("ERROR: Compilation failed for test suite: $name with exception", ex)
|
||||
@@ -641,20 +531,21 @@ fun runTest() {
|
||||
}
|
||||
|
||||
// Run the tests.
|
||||
def currentResult = null
|
||||
outputDirectory = outputRootDirectory
|
||||
arguments = (arguments ?: []) + "--ktest_logger=SILENT"
|
||||
ktFiles.each { file ->
|
||||
source = project.relativePath(file)
|
||||
def src = project.buildDir.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)")
|
||||
project.logger.quiet("TEST: $file.name " +
|
||||
"(done: $testGroupReporter.statistics.total/${ktFiles.size()}, " +
|
||||
"passed: $testGroupReporter.statistics.passed, " +
|
||||
"skipped: $testGroupReporter.statistics.skipped)")
|
||||
}
|
||||
if (isEnabledForNativeBackend(source)) {
|
||||
if (isEnabledForNativeBackend(src)) {
|
||||
suite.executeTest(file.name) {
|
||||
project.logger.quiet(source)
|
||||
super.executeTest()
|
||||
project.logger.quiet(src)
|
||||
runExecutable()
|
||||
}
|
||||
} else {
|
||||
suite.skipTest(file.name)
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.gradle.api.Action
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
@@ -334,7 +335,11 @@ open class KonanStandaloneTest : KonanLocalTest() {
|
||||
var flags: List<String> = listOf()
|
||||
get() = if (enableKonanAssertions) field + "-ea" else field
|
||||
|
||||
fun getSources() = buildCompileList(outputDirectory)
|
||||
fun getSources(): Provider<List<String>> = project.provider {
|
||||
val sources = buildCompileList(project.file(source).toPath(), outputDirectory)
|
||||
sources.forEach { it.writeTextToFile() }
|
||||
sources.map { it.path }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,7 +368,7 @@ open class KonanDriverTest : KonanStandaloneTest() {
|
||||
add("-target")
|
||||
add(project.testTarget.visibleName)
|
||||
}
|
||||
addAll(getSources())
|
||||
addAll(getSources().get())
|
||||
addAll(flags)
|
||||
addAll(project.globalTestArgs)
|
||||
}
|
||||
|
||||
@@ -5,60 +5,114 @@
|
||||
|
||||
package org.jetbrains.kotlin
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.regex.Pattern
|
||||
|
||||
private const val MODULE_DELIMITER = ",\\s*"
|
||||
private val FILE_OR_MODULE_PATTERN: Pattern = Pattern.compile("(?://\\s*MODULE:\\s*([^()\\n]+)(?:\\(([^()]+(?:" +
|
||||
"$MODULE_DELIMITER[^()]+)*)\\))?\\s*(?:\\(([^()]+(?:$MODULE_DELIMITER[^()]+)*)\\))?\\s*)?//\\s*FILE:\\s*(.*)$",
|
||||
Pattern.MULTILINE)
|
||||
|
||||
/**
|
||||
* Creates files from the given source file that may contain different test directives.
|
||||
* Creates test files from the given source file that may contain different test directives.
|
||||
*
|
||||
* @return list of file names to be compiled
|
||||
* @return list of test files [TestFile] to be compiled
|
||||
*/
|
||||
fun KonanTest.buildCompileList(outputDirectory: String): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
val srcFile = project.file(source)
|
||||
fun buildCompileList(source: Path, outputDirectory: String): List<TestFile> {
|
||||
val result = mutableListOf<TestFile>()
|
||||
val srcFile = source.toFile()
|
||||
// Remove diagnostic parameters in external tests.
|
||||
val srcText = srcFile.readText().replace(Regex("<!.*?!>(.*?)<!>")) { match -> match.groupValues[1] }
|
||||
|
||||
if (srcText.contains("// WITH_COROUTINES")) {
|
||||
val coroutineHelpersFileName = "$outputDirectory/helpers.kt"
|
||||
createFile(coroutineHelpersFileName, createTextForHelpers(true))
|
||||
result.add(coroutineHelpersFileName)
|
||||
result.add(TestFile("helpers.kt", "$outputDirectory/helpers.kt",
|
||||
createTextForHelpers(true), TestModule.support))
|
||||
}
|
||||
|
||||
val filePattern = Pattern.compile("(?m)// *FILE: *(.*)")
|
||||
val matcher = filePattern.matcher(srcText)
|
||||
|
||||
val matcher = FILE_OR_MODULE_PATTERN.matcher(srcText)
|
||||
if (!matcher.find()) {
|
||||
// There is only one file in the input
|
||||
val filePath = "$outputDirectory/${srcFile.name}"
|
||||
registerKtFile(result, filePath, srcText)
|
||||
result.add(TestFile(srcFile.name, "$outputDirectory/${srcFile.name}", srcText))
|
||||
} else {
|
||||
// There are several files
|
||||
var processedChars = 0
|
||||
while (true) {
|
||||
val filePath = "$outputDirectory/${matcher.group(1)}"
|
||||
var module: TestModule = TestModule.default
|
||||
var nextFileExists = true
|
||||
while (nextFileExists) {
|
||||
var moduleName = matcher.group(1)
|
||||
val moduleDependencies = matcher.group(2)
|
||||
val moduleFriends = matcher.group(3)
|
||||
|
||||
if (moduleName != null) {
|
||||
moduleName = moduleName.trim { it <= ' ' }
|
||||
module = TestModule("${srcFile.name}.$moduleName",
|
||||
moduleDependencies.parseModuleList().map {
|
||||
if (it != "support") "${srcFile.name}.$it" else it
|
||||
},
|
||||
moduleFriends.parseModuleList().map { "${srcFile.name}.$it" })
|
||||
}
|
||||
|
||||
val fileName = matcher.group(4)
|
||||
val filePath = "$outputDirectory/$fileName"
|
||||
val start = processedChars
|
||||
val nextFileExists = matcher.find()
|
||||
nextFileExists = matcher.find()
|
||||
val end = if (nextFileExists) matcher.start() else srcText.length
|
||||
val fileText = srcText.substring(start, end)
|
||||
processedChars = end
|
||||
registerKtFile(result, filePath, fileText)
|
||||
if (!nextFileExists) break
|
||||
if (fileName.endsWith(".kt")) {
|
||||
result.add(TestFile(fileName, filePath, fileText, module))
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
internal fun createFile(file: String, text: String) = Paths.get(file).run {
|
||||
parent.toFile()
|
||||
.takeUnless { it.exists() }
|
||||
?.mkdirs()
|
||||
toFile().writeText(text)
|
||||
}
|
||||
private fun String?.parseModuleList() = this
|
||||
?.split(Pattern.compile(MODULE_DELIMITER), 0)
|
||||
?: emptyList()
|
||||
|
||||
internal fun registerKtFile(sourceFiles: MutableList<String>, newFilePath: String, newFileContent: String) {
|
||||
createFile(newFilePath, newFileContent)
|
||||
if (newFilePath.endsWith(".kt")) {
|
||||
sourceFiles.add(newFilePath)
|
||||
/**
|
||||
* Test module from the test source declared by the [FILE_OR_MODULE_PATTERN].
|
||||
* Module should have a [name] and could have [dependencies] on other modules and [friends].
|
||||
*
|
||||
* There are 2 predefined modules:
|
||||
* - [default] that contains all sources that don't declare a module,
|
||||
* - [support] for a helper sources like Coroutines support.
|
||||
*/
|
||||
data class TestModule(
|
||||
val name: String,
|
||||
val dependencies: List<String>,
|
||||
val friends: List<String>
|
||||
) {
|
||||
fun isDefaultModule() = this == default || name.endsWith(".main")
|
||||
|
||||
fun linkDependencies(outputPath: String): List<String> =
|
||||
dependencies.flatMap { listOf("-l", "$outputPath.$it.klib") }
|
||||
|
||||
companion object {
|
||||
val default = TestModule("default", emptyList(), emptyList())
|
||||
val support = TestModule("support", emptyList(), emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent a single test file that belongs to the [module].
|
||||
*/
|
||||
data class TestFile(val name: String,
|
||||
val path: String,
|
||||
var text: String = "",
|
||||
val module: TestModule = TestModule.default
|
||||
) {
|
||||
/**
|
||||
* Writes [text] to the file created from the [path].
|
||||
*/
|
||||
fun writeTextToFile() {
|
||||
Paths.get(path).takeUnless { text.isEmpty() }?.run {
|
||||
parent.toFile()
|
||||
.takeUnless { it.exists() }
|
||||
?.mkdirs()
|
||||
toFile().writeText(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ val Project.testOutputStdlib
|
||||
val Project.testOutputFramework
|
||||
get() = (findProperty("testOutputFramework") as File).toString()
|
||||
|
||||
val Project.testOutputExternal
|
||||
get() = (findProperty("testOutputExternal") as File).toString()
|
||||
|
||||
val Project.kotlinNativeDist
|
||||
get() = this.rootProject.file(this.findProperty("org.jetbrains.kotlin.native.home")
|
||||
?: this.findProperty("konan.home") ?: "dist")
|
||||
@@ -254,8 +257,8 @@ fun compileSwift(project: Project, target: KonanTarget, sources: List<String>, o
|
||||
|stdout: $stdOut
|
||||
|stderr: $stdErr
|
||||
""".trimMargin())
|
||||
check(exitCode == 0, { "Compilation failed" })
|
||||
check(output.toFile().exists(), { "Compiler swiftc hasn't produced an output file: $output" })
|
||||
check(exitCode == 0) { "Compilation failed" }
|
||||
check(output.toFile().exists()) { "Compiler swiftc hasn't produced an output file: $output" }
|
||||
}
|
||||
|
||||
fun targetSupportsMimallocAllocator(targetName: String) =
|
||||
|
||||
Reference in New Issue
Block a user