Rewrite tests infrastructure to Kotlin.
Split build and run logic. Build tests with Konan gradle plugin. Add property for konan plugin to control generation of run tasks.
This commit is contained in:
committed by
Pavel Punegov
parent
29e0dfc029
commit
83aa459357
+876
-723
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@ import java.nio.file.Paths
|
||||
import java.util.function.Function
|
||||
import java.util.regex.Pattern
|
||||
|
||||
abstract class KonanTest extends JavaExec {
|
||||
abstract class OldKonanTest extends JavaExec {
|
||||
public boolean inDevelopersRun = false
|
||||
|
||||
public String source
|
||||
@@ -79,7 +79,7 @@ abstract class KonanTest extends JavaExec {
|
||||
}
|
||||
}
|
||||
|
||||
KonanTest() {
|
||||
OldKonanTest() {
|
||||
// We don't build the compiler if a custom dist path is specified.
|
||||
if (!project.ext.useCustomDist) {
|
||||
dependsOn(project.rootProject.tasks['dist'])
|
||||
@@ -164,104 +164,6 @@ abstract class KonanTest extends JavaExec {
|
||||
return sourceFiles
|
||||
}
|
||||
|
||||
String createTextForHelpers() {
|
||||
def coroutinesPackage = "kotlin.coroutines"
|
||||
|
||||
def emptyContinuationBody =
|
||||
"""
|
||||
|override fun resumeWith(result: Result<Any?>) { result.getOrThrow() }
|
||||
""".stripMargin()
|
||||
|
||||
def handleResultContinuationBody = """
|
||||
|override fun resumeWith(result: Result<T>) { x(result.getOrThrow()) }
|
||||
""".stripMargin()
|
||||
|
||||
def handleExceptionContinuationBody = """
|
||||
|override fun resumeWith(result: Result<Any?>) {
|
||||
| val exception = result.exceptionOrNull() ?: return
|
||||
| x(exception)
|
||||
|}
|
||||
""".stripMargin()
|
||||
|
||||
return """
|
||||
|package helpers
|
||||
|import $coroutinesPackage.*
|
||||
|
|
||||
|fun <T> handleResultContinuation(x: (T) -> Unit): Continuation<T> = object: Continuation<T> {
|
||||
| override val context = EmptyCoroutineContext
|
||||
| $handleResultContinuationBody
|
||||
|}
|
||||
|
|
||||
|
|
||||
|fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation<Any?> = object: Continuation<Any?> {
|
||||
| override val context = EmptyCoroutineContext
|
||||
| $handleExceptionContinuationBody
|
||||
|}
|
||||
|
|
||||
|open class EmptyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<Any?> {
|
||||
| companion object : EmptyContinuation()
|
||||
| $emptyContinuationBody
|
||||
|}
|
||||
|
|
||||
|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) {
|
||||
| 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 (!finished) error("Wrong state-machine generated: it is not finished yet")
|
||||
| }
|
||||
|}
|
||||
|val StateMachineChecker = StateMachineCheckerClass()
|
||||
|object CheckStateMachineContinuation: ContinuationAdapter<Unit>() {
|
||||
| override val context: CoroutineContext
|
||||
| get() = EmptyCoroutineContext
|
||||
|
|
||||
| override fun resume(value: Unit) {
|
||||
| StateMachineChecker.proceed = {
|
||||
| StateMachineChecker.finished = true
|
||||
| }
|
||||
| }
|
||||
|
|
||||
| override fun resumeWithException(exception: Throwable) {
|
||||
| throw exception
|
||||
| }
|
||||
|}
|
||||
""".stripMargin()
|
||||
}
|
||||
|
||||
// TODO refactor
|
||||
List<String> buildCompileList() {
|
||||
def result = []
|
||||
@@ -272,7 +174,7 @@ abstract class KonanTest extends JavaExec {
|
||||
|
||||
if (srcText.contains('// WITH_COROUTINES')) {
|
||||
def coroutineHelpersFileName = "$outputDirectory/helpers.kt"
|
||||
createFile(coroutineHelpersFileName, createTextForHelpers())
|
||||
createFile(coroutineHelpersFileName, CoroutineTestUtilKt.createTextForHelpers(true))
|
||||
result.add(coroutineHelpersFileName)
|
||||
}
|
||||
|
||||
@@ -389,332 +291,7 @@ abstract class KonanTest extends JavaExec {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ExtKonanTest extends KonanTest {
|
||||
|
||||
ExtKonanTest() {
|
||||
super()
|
||||
}
|
||||
|
||||
@Override
|
||||
String buildExePath() {
|
||||
// a single executable for all tests
|
||||
return "$outputDirectory/program.tr"
|
||||
}
|
||||
|
||||
// The same as its super() version but doesn't create a new dir for each test
|
||||
@Override
|
||||
void createOutputDirectory() {
|
||||
if (outputDirectory != null) {
|
||||
return
|
||||
}
|
||||
|
||||
def outputSourceSet = project.findProperty(getOutputSourceSetName())
|
||||
if (outputSourceSet != null) {
|
||||
outputDirectory = outputSourceSet.absolutePath
|
||||
project.file(outputDirectory).mkdirs()
|
||||
} else {
|
||||
outputDirectory = getTemporaryDir().absolutePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds tests with TestRunner enabled
|
||||
*/
|
||||
class BuildKonanTest extends ExtKonanTest {
|
||||
|
||||
public List<String> compileList
|
||||
public List<String> excludeList
|
||||
|
||||
BuildKonanTest() {
|
||||
super()
|
||||
}
|
||||
|
||||
@Override
|
||||
List<String> buildCompileList() {
|
||||
assert compileList != null
|
||||
|
||||
// convert exclude list to paths
|
||||
def excludeFiles = new ArrayList<String>()
|
||||
excludeList.each { excludeFiles.add(project.file(it).absolutePath) }
|
||||
|
||||
// create list of tests to compile
|
||||
def compileFiles = new ArrayList<String>()
|
||||
compileList.each {
|
||||
def file = project.file(it)
|
||||
if (file.isDirectory()) {
|
||||
file.eachFileRecurse {
|
||||
if (it.isFile() && it.name.endsWith(".kt") && !excludeFiles.contains(it.absolutePath)) {
|
||||
compileFiles.add(it.absolutePath)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
compileFiles.add(file.absolutePath)
|
||||
}
|
||||
}
|
||||
compileFiles
|
||||
}
|
||||
|
||||
@Override
|
||||
void compileTest(List<String> filesToCompile, String exe) {
|
||||
flags = flags ?: []
|
||||
// compile with test runner enabled
|
||||
flags.add("-tr")
|
||||
runCompiler(filesToCompile, exe, flags)
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
@Override
|
||||
void executeTest() {
|
||||
// only build tests
|
||||
createOutputDirectory()
|
||||
def program = buildExePath()
|
||||
compileTest(buildCompileList(), program)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs test built with Konan's TestRunner
|
||||
*/
|
||||
class RunKonanTest extends ExtKonanTest {
|
||||
/**
|
||||
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
|
||||
*/
|
||||
public def inDevelopersRun = true
|
||||
|
||||
public def buildTaskName = 'buildKonanTests'
|
||||
public def runnerLogger = Logger.SILENT
|
||||
public def useFilter = true
|
||||
|
||||
enum Logger {
|
||||
GTEST,
|
||||
TEAMCITY,
|
||||
SIMPLE,
|
||||
SILENT
|
||||
}
|
||||
|
||||
@Inject
|
||||
RunKonanTest() {
|
||||
super()
|
||||
dependsOn(buildTaskName)
|
||||
}
|
||||
|
||||
RunKonanTest(def depends) {
|
||||
buildTaskName = depends
|
||||
dependsOn(buildTaskName)
|
||||
}
|
||||
|
||||
@Override
|
||||
void compileTest(List<String> filesToCompile, String exe) {
|
||||
// tests should be already compiled
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
@Override
|
||||
void executeTest() {
|
||||
arguments = arguments ?: []
|
||||
// Print only test's output
|
||||
arguments.add("--ktest_logger=" + runnerLogger.toString())
|
||||
if (useFilter) {
|
||||
arguments.add("--ktest_filter=" + convertToPattern(source))
|
||||
}
|
||||
super.executeTest()
|
||||
}
|
||||
|
||||
private String convertToPattern(String source) {
|
||||
return source.replace('/', '.')
|
||||
.replace(".kt", "")
|
||||
.concat(".*")
|
||||
}
|
||||
}
|
||||
|
||||
class RunStdlibTest extends RunKonanTest {
|
||||
/**
|
||||
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
|
||||
*/
|
||||
public def inDevelopersRun = false
|
||||
public def statistics = new Statistics()
|
||||
|
||||
RunStdlibTest() {
|
||||
super('buildKonanStdlibTests')
|
||||
}
|
||||
|
||||
@Override
|
||||
void executeTest() {
|
||||
try {
|
||||
super.executeTest()
|
||||
} finally {
|
||||
def output = out.toString("UTF-8")
|
||||
|
||||
// NOTE: these regexs assert that GTEST output format is used
|
||||
def matcher = (output =~ ~/\[==========\] Running ([0-9]*) tests from ([0-9]*) test cases \. .*/)
|
||||
def testsTotal = 0
|
||||
if (matcher) {
|
||||
testsTotal = matcher[0][1] as Integer
|
||||
}
|
||||
|
||||
matcher = (output =~ ~/\[ PASSED \] ([0-9]*) tests\./)
|
||||
if (matcher) {
|
||||
def n = matcher[0][1] as Integer
|
||||
statistics.pass(n)
|
||||
}
|
||||
matcher = (output =~ ~/\[ FAILED \] ([0-9]*) tests.*/)
|
||||
if (matcher) {
|
||||
def n = matcher[0][1] as Integer
|
||||
statistics.fail(n)
|
||||
}
|
||||
use(KonanTestSuiteReportKt) {
|
||||
if (statistics.total == 0) {
|
||||
statistics.error(testsTotal != 0 ? testsTotal : 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles and executes test as a standalone binary
|
||||
*/
|
||||
class RunStandaloneKonanTest extends KonanTest {
|
||||
/**
|
||||
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
|
||||
*/
|
||||
public def inDevelopersRun = true
|
||||
|
||||
void compileTest(List<String> filesToCompile, String exe) {
|
||||
runCompiler(filesToCompile, exe, flags?:[])
|
||||
}
|
||||
}
|
||||
|
||||
// This is another way to run the compiler.
|
||||
// Don't use this task for regular testing as
|
||||
// project.exec + a shell script isolate the jvm
|
||||
// from IDEA. Use the RunKonanTest instead.
|
||||
class RunDriverKonanTest extends KonanTest {
|
||||
/**
|
||||
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
|
||||
*/
|
||||
public def inDevelopersRun = true
|
||||
|
||||
RunDriverKonanTest() {
|
||||
super()
|
||||
// We don't build the compiler if a custom konan.home path is specified.
|
||||
if (!project.hasProperty("konan.home")) {
|
||||
dependsOn(project.rootProject.tasks['cross_dist'])
|
||||
}
|
||||
}
|
||||
|
||||
void compileTest(List<String> filesToCompile, String exe) {
|
||||
runCompiler(filesToCompile, exe, flags?:[])
|
||||
}
|
||||
|
||||
protected void runCompiler(List<String> filesToCompile, String output, List<String> moreArgs) {
|
||||
def log = new ByteArrayOutputStream()
|
||||
project.exec {
|
||||
commandLine konanc
|
||||
args = ["-output", output,
|
||||
*filesToCompile,
|
||||
*moreArgs,
|
||||
*project.globalTestArgs]
|
||||
if (project.testTarget) {
|
||||
args "-target", target.visibleName
|
||||
}
|
||||
if (enableKonanAssertions) {
|
||||
args "-ea"
|
||||
}
|
||||
standardOutput = log
|
||||
errorOutput = log
|
||||
}
|
||||
def logString = log.toString("UTF-8")
|
||||
project.file("${output}.compilation.log").write(logString)
|
||||
println(logString)
|
||||
}
|
||||
}
|
||||
|
||||
class RunInteropKonanTest extends KonanTest {
|
||||
/**
|
||||
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
|
||||
*/
|
||||
public def inDevelopersRun = true
|
||||
|
||||
private String interop
|
||||
private NamedNativeInteropConfig interopConf
|
||||
|
||||
void setInterop(String value) {
|
||||
this.interop = value
|
||||
this.interopConf = project.kotlinNativeInterop[value]
|
||||
this.interopConf.target = target.visibleName
|
||||
this.dependsOn(this.interopConf.genTask)
|
||||
}
|
||||
|
||||
void compileTest(List<String> filesToCompile, String exe) {
|
||||
String interopBc = exe + "-interop.bc"
|
||||
runCompiler([interopConf.generatedSrcDir.absolutePath], interopBc, ['-produce', 'library'])
|
||||
|
||||
String interopStubsBc = new File(interopConf.nativeLibsDir, interop + "stubs.bc").absolutePath
|
||||
|
||||
List<String> linkerArguments = interopConf.linkerOpts // TODO: add arguments from .def file
|
||||
|
||||
List<String> compilerArguments = ["-library", interopBc, "-native-library", interopStubsBc] +
|
||||
linkerArguments.collectMany { ["-linker-options", it] }
|
||||
|
||||
runCompiler(filesToCompile, exe, compilerArguments)
|
||||
}
|
||||
}
|
||||
|
||||
class LinkKonanTest extends KonanTest {
|
||||
/**
|
||||
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
|
||||
*/
|
||||
public def inDevelopersRun = true
|
||||
protected String lib
|
||||
|
||||
void compileTest(List<String> filesToCompile, String exe) {
|
||||
def libname = "testklib"
|
||||
def klib = "$outputDirectory/$libname"
|
||||
|
||||
runCompiler(lib, klib, ['-produce', 'library'] + ((flags != null) ? flags :[]))
|
||||
runCompiler(filesToCompile, exe, ['-library', klib] + ((flags != null) ? flags :[]))
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicKonanTest extends KonanTest {
|
||||
/**
|
||||
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
|
||||
*/
|
||||
public def inDevelopersRun = true
|
||||
|
||||
protected String cSource
|
||||
|
||||
void compileTest(List<String> filesToCompile, String exe) {
|
||||
def libname = "testlib"
|
||||
def dylib = "$outputDirectory/$libname"
|
||||
def realExe = "${exe}.${target.family.exeSuffix}"
|
||||
|
||||
runCompiler(filesToCompile, dylib, ['-produce', 'dynamic'] + ((flags != null) ? flags :[]))
|
||||
runClang([cSource], realExe, ['-I', outputDirectory, '-L', outputDirectory, '-l', libname])
|
||||
}
|
||||
|
||||
void runClang(List<String> cSources, String output, List<String> moreArgs) {
|
||||
def log = new ByteArrayOutputStream()
|
||||
project.execKonanClang(project.target) {
|
||||
workingDir outputDirectory
|
||||
|
||||
executable "clang"
|
||||
args cSources
|
||||
args '-o', output
|
||||
args moreArgs
|
||||
args "-Wl,-rpath,$outputDirectory"
|
||||
|
||||
standardOutput = log
|
||||
errorOutput = log
|
||||
}
|
||||
def logString = log.toString("UTF-8")
|
||||
project.file("${output}.compilation.log").write(logString)
|
||||
println(logString)
|
||||
}
|
||||
}
|
||||
class RunExternalTestGroup extends RunStandaloneKonanTest {
|
||||
class RunExternalTestGroup extends OldKonanTest {
|
||||
/**
|
||||
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
|
||||
*/
|
||||
@@ -972,7 +549,6 @@ fun runTest() {
|
||||
if (!findLinesWithPrefixesRemoved(text, "// JVM_TARGET:").isEmpty()) { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,20 +22,20 @@ import org.gradle.api.Project
|
||||
import org.gradle.process.ExecResult
|
||||
import org.gradle.process.ExecSpec
|
||||
import org.gradle.util.ConfigureUtil
|
||||
import org.jetbrains.kotlin.konan.target.AppleConfigurables
|
||||
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.konan.target.PlatformManager
|
||||
import org.jetbrains.kotlin.konan.target.Xcode
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
/**
|
||||
* A replacement of the standard exec {}
|
||||
* A replacement of the standard `exec {}`
|
||||
* @see org.gradle.api.Project.exec
|
||||
*/
|
||||
interface ExecutorService {
|
||||
@@ -47,8 +47,8 @@ interface ExecutorService {
|
||||
* Creates an ExecutorService depending on a test target -Ptest_target
|
||||
*/
|
||||
fun create(project: Project): ExecutorService {
|
||||
val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager
|
||||
val testTarget = platformManager.targetManager(project.findProperty("testTarget") as String?).target
|
||||
val platformManager = project.platformManager
|
||||
val testTarget = project.testTarget
|
||||
val platform = platformManager.platform(testTarget)
|
||||
val absoluteTargetToolchain = platform.absoluteTargetToolchain
|
||||
val absoluteTargetSysRoot = platform.absoluteTargetSysRoot
|
||||
@@ -95,7 +95,7 @@ fun create(project: Project): ExecutorService {
|
||||
}
|
||||
}
|
||||
|
||||
data class ProcessOutput(val stdOut: String, val stdErr: String, val exitCode: Int)
|
||||
data class ProcessOutput(var stdOut: String, var stdErr: String, var exitCode: Int)
|
||||
|
||||
/**
|
||||
* Runs process using a given executor.
|
||||
@@ -128,6 +128,44 @@ fun runProcess(executor: (Action<in ExecSpec>) -> ExecResult?,
|
||||
fun runProcess(executor: (Action<in ExecSpec>) -> ExecResult?,
|
||||
executable: String, vararg args: String) = runProcess(executor, executable, args.toList())
|
||||
|
||||
/**
|
||||
* Runs process using a given executor.
|
||||
*
|
||||
* @param executor a method that is able to run a given executable, e.g. ExecutorService::execute
|
||||
* @param executable a process executable to be run
|
||||
* @param args arguments for a process
|
||||
* @param input an input string to be passed through the standard input stream
|
||||
*/
|
||||
fun runProcessWithInput(executor: (Action<in ExecSpec>) -> ExecResult?,
|
||||
executable: String, args: List<String>, input: String) : ProcessOutput {
|
||||
val outStream = ByteArrayOutputStream()
|
||||
val errStream = ByteArrayOutputStream()
|
||||
val inStream = ByteArrayInputStream(input.toByteArray())
|
||||
|
||||
val execResult = executor(Action {
|
||||
it.executable = executable
|
||||
it.args = args.toList()
|
||||
it.standardOutput = outStream
|
||||
it.errorOutput = errStream
|
||||
it.isIgnoreExitValue = true
|
||||
it.standardInput = inStream
|
||||
})
|
||||
|
||||
checkNotNull(execResult)
|
||||
|
||||
val stdOut = outStream.toString("UTF-8")
|
||||
val stdErr = errStream.toString("UTF-8")
|
||||
|
||||
return ProcessOutput(stdOut, stdErr, execResult.exitValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* The [ExecutorService] being set in the given project.
|
||||
* @throws IllegalStateException if there are no executor in the project.
|
||||
*/
|
||||
val Project.executor: ExecutorService
|
||||
get() = this.convention.plugins["executor"] as? ExecutorService ?: throw IllegalStateException("Executor wasn't found")
|
||||
|
||||
/**
|
||||
* Creates a new executor service with additional action [actionParameter] executed after the main one.
|
||||
* The following is an example how to pass an environment parameter
|
||||
@@ -142,7 +180,25 @@ fun ExecutorService.add(actionParameter: Action<in ExecSpec>) = object : Executo
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Project's process executor
|
||||
* Executes the [executable] with the given [arguments]
|
||||
* and checks that the program finished with zero exit code.
|
||||
*/
|
||||
fun Project.executeAndCheck(executable: Path, arguments: List<String> = emptyList()) {
|
||||
val (stdOut, stdErr, exitCode) = runProcess(
|
||||
executor = executor::execute,
|
||||
executable = executable.toString(),
|
||||
args = arguments
|
||||
)
|
||||
|
||||
println("""
|
||||
|stdout: $stdOut
|
||||
|stderr: $stdErr
|
||||
""".trimMargin())
|
||||
check(exitCode == 0) { "Execution failed with exit code: $exitCode "}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns [project]'s process executor.
|
||||
* @see Project.exec
|
||||
*/
|
||||
fun localExecutor(project: Project) = { a: Action<in ExecSpec> -> project.exec(a) }
|
||||
|
||||
@@ -31,19 +31,17 @@ open class FrameworkTest : DefaultTask() {
|
||||
@Input
|
||||
var fullBitcode: Boolean = false
|
||||
|
||||
private val testOutput: String by lazy {
|
||||
project.file(project.property("testOutputFramework")!!).absolutePath
|
||||
}
|
||||
private val testOutput: String = project.testOutputFramework
|
||||
|
||||
override fun configure(config: Closure<*>): Task {
|
||||
super.configure(config)
|
||||
val target = project.testTarget().name
|
||||
val target = project.testTarget.name
|
||||
|
||||
// set crossdist build dependency if custom konan.home wasn't set
|
||||
if (!(project.property("useCustomDist") as Boolean)) {
|
||||
setRootDependency("${target}CrossDist", "${target}CrossDistRuntime", "commonDistRuntime", "distCompiler")
|
||||
}
|
||||
check(::frameworkName.isInitialized, { "Framework name should be set" })
|
||||
check(::frameworkName.isInitialized) { "Framework name should be set" }
|
||||
dependsOn(project.tasks.getByName("compileKonan$frameworkName"))
|
||||
return this
|
||||
}
|
||||
@@ -52,7 +50,7 @@ open class FrameworkTest : DefaultTask() {
|
||||
|
||||
@TaskAction
|
||||
fun run() {
|
||||
val frameworkParentDirPath = "$testOutput/$frameworkName/${project.testTarget().name}"
|
||||
val frameworkParentDirPath = "$testOutput/$frameworkName/${project.testTarget.name}"
|
||||
val frameworkPath = "$frameworkParentDirPath/$frameworkName.framework"
|
||||
val frameworkBinaryPath = "$frameworkPath/$frameworkName"
|
||||
validateBitcodeEmbedding(frameworkBinaryPath)
|
||||
@@ -78,14 +76,14 @@ open class FrameworkTest : DefaultTask() {
|
||||
listOf(provider.toString(), swiftMain)
|
||||
val options = listOf("-g", "-Xlinker", "-rpath", "-Xlinker", frameworkParentDirPath, "-F", frameworkParentDirPath)
|
||||
val testExecutable = Paths.get(testOutput, frameworkName, "swiftTestExecutable")
|
||||
compileSwift(project, project.testTarget(), sources, options, testExecutable, fullBitcode)
|
||||
compileSwift(project, project.testTarget, sources, options, testExecutable, fullBitcode)
|
||||
|
||||
runTest(testExecutable)
|
||||
}
|
||||
|
||||
private fun runTest(testExecutable: Path) {
|
||||
val target = project.testTarget()
|
||||
val platform = project.platformManager().platform(target)
|
||||
val target = project.testTarget
|
||||
val platform = project.platformManager.platform(target)
|
||||
val configs = platform.configurables as AppleConfigurables
|
||||
val swiftPlatform = when (target) {
|
||||
KonanTarget.IOS_X64 -> "iphonesimulator"
|
||||
@@ -114,7 +112,7 @@ open class FrameworkTest : DefaultTask() {
|
||||
|stdout: $stdOut
|
||||
|stderr: $stdErr
|
||||
""".trimMargin())
|
||||
check(exitCode == 0, { "Execution failed with exit code: $exitCode "})
|
||||
check(exitCode == 0) { "Execution failed with exit code: $exitCode "}
|
||||
}
|
||||
|
||||
private fun validateBitcodeEmbedding(frameworkBinary: String) {
|
||||
@@ -122,8 +120,8 @@ open class FrameworkTest : DefaultTask() {
|
||||
if (!fullBitcode) {
|
||||
return
|
||||
}
|
||||
val testTarget = project.testTarget()
|
||||
val configurables = project.platformManager().platform(testTarget).configurables as AppleConfigurables
|
||||
val testTarget = project.testTarget
|
||||
val configurables = project.platformManager.platform(testTarget).configurables as AppleConfigurables
|
||||
|
||||
val bitcodeBuildTool = "${configurables.absoluteAdditionalToolsDir}/bin/bitcode-build-tool"
|
||||
val ldPath = "${configurables.absoluteTargetToolchain}/usr/bin/ld"
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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 org.jetbrains.kotlin
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
|
||||
import org.gradle.process.ExecSpec
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinTest
|
||||
|
||||
import java.io.File
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
|
||||
abstract class KonanTest : DefaultTask() {
|
||||
enum class Logger {
|
||||
EMPTY, // Built without test runner
|
||||
GTEST, // Google test log output
|
||||
TEAMCITY, // TeamCity log output
|
||||
SIMPLE, // Prints simple messages of passed/failed tests
|
||||
SILENT // Prints no log of passed/failed tests
|
||||
}
|
||||
|
||||
var disabled: Boolean
|
||||
get() = !enabled
|
||||
set(value) { enabled = !value }
|
||||
|
||||
/**
|
||||
* Test output directory. Used to store processed sources and binary artifacts.
|
||||
*/
|
||||
abstract val outputDirectory: String
|
||||
|
||||
/**
|
||||
* Test logger to be used for the test built with TestRunner (`-tr` option).
|
||||
*/
|
||||
abstract var testLogger: Logger
|
||||
|
||||
/**
|
||||
* Test executable arguments.
|
||||
*/
|
||||
@Input
|
||||
var arguments = mutableListOf<String>()
|
||||
|
||||
/**
|
||||
* Test executable.
|
||||
*/
|
||||
abstract val executable: String
|
||||
|
||||
/**
|
||||
* Test source.
|
||||
*/
|
||||
lateinit var source: String
|
||||
|
||||
/**
|
||||
* Sets test filtering to choose the exact test in the executable built with TestRunner.
|
||||
*/
|
||||
@Input
|
||||
var useFilter = true
|
||||
|
||||
/**
|
||||
* An action to be executed before the build.
|
||||
* As this run task comes after the build task all actions for doFirst
|
||||
* should be done before the build and not run.
|
||||
*/
|
||||
@Input @Optional
|
||||
var doBefore: Action<in Task>? = null
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
override fun configure(config: Closure<*>): Task {
|
||||
super.configure(config)
|
||||
|
||||
// Set Gradle properties for the better navigation
|
||||
group = LifecycleBasePlugin.VERIFICATION_GROUP
|
||||
description = "Kotlin/Native test infrastructure task"
|
||||
|
||||
if (testLogger != Logger.EMPTY) {
|
||||
arguments.add("--ktest_logger=$testLogger")
|
||||
}
|
||||
if (useFilter && ::source.isInitialized) {
|
||||
arguments.add("--ktest_filter=${source.convertToPattern()}")
|
||||
}
|
||||
this.dependsOnDist()
|
||||
return this
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
open fun run() = project.executeAndCheck(project.file(executable).toPath(), arguments)
|
||||
|
||||
// Converts to runner's pattern
|
||||
private fun String.convertToPattern() = this.replace('/', '.').replace(".kt", "") + (".*")
|
||||
|
||||
internal fun ProcessOutput.print(prepend: String = "") {
|
||||
if (project.verboseTest)
|
||||
println(prepend + """
|
||||
|stdout:
|
||||
|$stdOut
|
||||
|stderr:
|
||||
|$stdErr
|
||||
|exit code: $exitCode
|
||||
""".trimMargin())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test task of the given type. Supports configuration with Closure passed form build.gradle file.
|
||||
*/
|
||||
fun <T: KonanTest> Project.createTest(name: String, type: Class<T>, config: Closure<*>): T =
|
||||
project.tasks.create(name, type).apply {
|
||||
// Apply closure set in build.gradle to get all parameters.
|
||||
this.configure(config)
|
||||
if (enabled) {
|
||||
// Configure test task.
|
||||
val target = project.testTarget
|
||||
// If run task depends on something, compile task should also depend on this.
|
||||
val compileTask = project.tasks.getByName("compileKonan${name.capitalize()}${target.name.capitalize()}")
|
||||
compileTask.sameDependenciesAs(this)
|
||||
// Run task should depend on compile task
|
||||
this.dependsOn(compileTask)
|
||||
if (doBefore != null) compileTask.doFirst(doBefore!!)
|
||||
compileTask.enabled = enabled
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to run tests compiled with TestRunner.
|
||||
* Runs tests with GTEST output and parses it to create statistics info
|
||||
*/
|
||||
open class KonanGTest : KonanTest() {
|
||||
override val outputDirectory = "${project.testOutputStdlib}/$name"
|
||||
|
||||
// Use GTEST logger to parse test results later
|
||||
override var testLogger = Logger.GTEST
|
||||
|
||||
override val executable: String
|
||||
get() = "$outputDirectory/${project.testTarget.name}/$name.${project.testTarget.family.exeSuffix}"
|
||||
|
||||
var statistics = Statistics()
|
||||
|
||||
@TaskAction
|
||||
override fun run() = runProcess(
|
||||
executor = project.executor::execute,
|
||||
executable = executable,
|
||||
args = arguments
|
||||
).let {
|
||||
parse(it.stdOut)
|
||||
it.print()
|
||||
check(it.exitCode == 0) { "Test $executable exited with ${it.exitCode}" }
|
||||
}
|
||||
|
||||
private fun parse(output: String) = statistics.apply {
|
||||
Pattern.compile("\\[ PASSED ] ([0-9]*) tests\\.").matcher(output)
|
||||
.apply { if (find()) pass(group(1).toInt()) }
|
||||
|
||||
Pattern.compile("\\[ FAILED ] ([0-9]*) tests.*").matcher(output)
|
||||
.apply { if (find()) fail(group(1).toInt()) }
|
||||
if (total == 0) {
|
||||
// No test were run. Try to find if we've tried to run something
|
||||
this.error(Pattern.compile("\\[={10}] Running ([0-9]*) tests from ([0-9]*) test cases\\..*")
|
||||
.matcher(output)
|
||||
.run { if (find()) group(1).toInt() else 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to run tests built into a single predefined binary named `localTest`.
|
||||
* Note: this task should depend on task that builds a test binary.
|
||||
*/
|
||||
open class KonanLocalTest : KonanTest() {
|
||||
override val outputDirectory = project.testOutputLocal
|
||||
|
||||
// local tests built into a single binary with the known name
|
||||
override val executable: String
|
||||
get() = "$outputDirectory/${project.testTarget.name}/localTest.${project.testTarget.family.exeSuffix}"
|
||||
|
||||
override var testLogger = Logger.SILENT
|
||||
|
||||
@Input @Optional
|
||||
var expectedExitStatus = 0
|
||||
|
||||
/**
|
||||
* Should this test fail or not.
|
||||
*/
|
||||
@Input @Optional
|
||||
var expectedFail = false
|
||||
|
||||
/**
|
||||
* Used to validate output as a gold value.
|
||||
*/
|
||||
@Input @Optional
|
||||
var goldValue: String? = null
|
||||
|
||||
/**
|
||||
* Checks test's output against gold value and returns true if the output matches the expectation.
|
||||
*/
|
||||
@Input @Optional
|
||||
var outputChecker: (String) -> Boolean = { str -> goldValue == null || goldValue == str }
|
||||
/**
|
||||
* Input test data to be passed to process' stdin.
|
||||
*/
|
||||
@Input @Optional
|
||||
var testData: String? = null
|
||||
|
||||
/**
|
||||
* Should compiler message be read and validated with output checker or gold value.
|
||||
*/
|
||||
@Input @Optional
|
||||
var compilerMessages = false
|
||||
|
||||
@Input @Optional
|
||||
var multiRuns = false
|
||||
|
||||
@Input @Optional
|
||||
var multiArguments: List<List<String>>? = null
|
||||
|
||||
@TaskAction
|
||||
override fun run() {
|
||||
val times = if (multiRuns && multiArguments != null) multiArguments!!.size else 1
|
||||
var output = ProcessOutput("", "", 0)
|
||||
for (i in 1..times) {
|
||||
val args = arguments + (multiArguments?.get(i - 1) ?: emptyList())
|
||||
output += if (testData != null)
|
||||
runProcessWithInput(project.executor::execute, executable, args, testData!!)
|
||||
else
|
||||
runProcess(project.executor::execute, executable, args)
|
||||
}
|
||||
if (compilerMessages) {
|
||||
// TODO: as for now it captures output only in the driver task.
|
||||
// It should capture output from the build task using Gradle's LoggerManager and LoggerOutput
|
||||
val compilationLog = project.file("$executable.compilation.log").readText()
|
||||
output.stdOut = compilationLog + output.stdOut
|
||||
}
|
||||
output.check()
|
||||
output.print()
|
||||
}
|
||||
|
||||
private operator fun ProcessOutput.plus(other: ProcessOutput) = ProcessOutput(
|
||||
stdOut + other.stdOut,
|
||||
stdErr + other.stdErr,
|
||||
exitCode + other.exitCode)
|
||||
|
||||
private fun ProcessOutput.check() {
|
||||
val exitCodeMismatch = exitCode != expectedExitStatus
|
||||
if (exitCodeMismatch) {
|
||||
val message = "Expected exit status: $expectedExitStatus, actual: $exitCode"
|
||||
check(expectedFail) { """
|
||||
|Test failed. $message
|
||||
|stdout:
|
||||
|$stdOut
|
||||
|stderr:
|
||||
|$stdErr
|
||||
""".trimMargin()
|
||||
}
|
||||
println("Expected failure. $message")
|
||||
}
|
||||
|
||||
val result = stdOut + stdErr
|
||||
val goldValueMismatch = !outputChecker(result.replace(System.lineSeparator(), "\n"))
|
||||
if (goldValueMismatch) {
|
||||
val message = if (goldValue != null)
|
||||
"Expected output: $goldValue, actual output: $result"
|
||||
else
|
||||
"Actual output doesn't match with output checker: $result"
|
||||
|
||||
check(expectedFail) { "Test failed. $message" }
|
||||
println("Expected failure. $message")
|
||||
}
|
||||
|
||||
check ((exitCodeMismatch || goldValueMismatch) || !expectedFail) { """
|
||||
|Unexpected pass:
|
||||
| * exit code mismatch: $exitCodeMismatch
|
||||
| * gold value mismatch: $goldValueMismatch
|
||||
| * expected fail: $expectedFail
|
||||
""".trimMargin()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a standalone tests provided with either @param executable or by the tasks @param name.
|
||||
* The executable itself should be built by the konan plugin.
|
||||
*/
|
||||
open class KonanStandaloneTest : KonanLocalTest() {
|
||||
init {
|
||||
useFilter = false
|
||||
}
|
||||
|
||||
override val outputDirectory: String
|
||||
get() = "${project.testOutputLocal}/$name"
|
||||
|
||||
override var testLogger = Logger.EMPTY
|
||||
|
||||
override val executable: String
|
||||
get() = "$outputDirectory/${project.testTarget.name}/$name.${project.testTarget.family.exeSuffix}"
|
||||
|
||||
@Input @Optional
|
||||
var enableKonanAssertions = true
|
||||
|
||||
/**
|
||||
* Compiler flags used to build a test.
|
||||
*/
|
||||
var flags: List<String> = listOf()
|
||||
get() = if (enableKonanAssertions) field + "-ea" else field
|
||||
|
||||
fun getSources() = buildCompileList(outputDirectory)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is another way to run the konanc compiler. It runs a konanc shell script.
|
||||
*
|
||||
* @note This task is not intended for regular testing as project.exec + a shell script isolate the jvm from IDEA.
|
||||
* @see KonanLocalTest to be used as a regular task.
|
||||
*/
|
||||
open class KonanDriverTest : KonanStandaloneTest() {
|
||||
override fun configure(config: Closure<*>): Task {
|
||||
super.configure(config)
|
||||
doBefore?.let { doFirst(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
override fun run() {
|
||||
konan()
|
||||
super.run()
|
||||
}
|
||||
|
||||
private fun konan() {
|
||||
val dist = project.rootProject.file(project.findProperty("org.jetbrains.kotlin.native.home") ?:
|
||||
project.findProperty("konan.home") ?: "dist")
|
||||
val konancDriver = if (HostManager.hostIsMingw) "konanc.bat" else "konanc"
|
||||
val konanc = File("${dist.canonicalPath}/bin/$konancDriver").absolutePath
|
||||
|
||||
File(executable).parentFile.mkdirs()
|
||||
|
||||
val args = mutableListOf("-output", executable).apply {
|
||||
if (project.testTarget != HostManager.host) {
|
||||
add("-target")
|
||||
add(project.testTarget.visibleName)
|
||||
}
|
||||
addAll(getSources())
|
||||
addAll(flags)
|
||||
addAll(project.globalTestArgs)
|
||||
}
|
||||
|
||||
// run konanc compiler locally
|
||||
runProcess(localExecutor(project), konanc, args).let {
|
||||
it.print("Konanc compiler execution:")
|
||||
project.file("$executable.compilation.log").run {
|
||||
writeText(it.stdOut)
|
||||
writeText(it.stdErr)
|
||||
}
|
||||
check(it.exitCode == 0) { "Compiler failed with exit code ${it.exitCode}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class KonanInteropTest : KonanStandaloneTest() {
|
||||
/**
|
||||
* Name of the interop library
|
||||
*/
|
||||
@Input
|
||||
lateinit var interop: String
|
||||
}
|
||||
|
||||
open class KonanLinkTest : KonanStandaloneTest() {
|
||||
@Input
|
||||
lateinit var lib: String
|
||||
}
|
||||
|
||||
/**
|
||||
* Test task to check a library built by `-produce dynamic`.
|
||||
* C source code should contain `testlib` as a reference to a testing library.
|
||||
* It will be replaced then by the actual library name.
|
||||
*/
|
||||
open class KonanDynamicTest : KonanStandaloneTest() {
|
||||
/**
|
||||
* File path to the C source.
|
||||
*/
|
||||
@Input
|
||||
lateinit var cSource: String
|
||||
|
||||
@TaskAction
|
||||
override fun run() {
|
||||
clang()
|
||||
super.run()
|
||||
}
|
||||
|
||||
// Replace testlib_api.h and all occurrences of the testlib with the actual name of the test
|
||||
private fun processCSource(): String {
|
||||
val sourceFile = File(cSource)
|
||||
val res = sourceFile.readText()
|
||||
.replace("#include \"testlib_api.h\"", "#include \"lib${name}_api.h\"")
|
||||
.replace("testlib", "lib${name}")
|
||||
val newFileName = "$outputDirectory/${sourceFile.name}"
|
||||
println(newFileName)
|
||||
File(newFileName).run {
|
||||
createNewFile()
|
||||
writeText(res)
|
||||
}
|
||||
return newFileName
|
||||
}
|
||||
|
||||
private fun clang() {
|
||||
val log = ByteArrayOutputStream()
|
||||
val plugin = project.convention.getPlugin(ExecClang::class.java)
|
||||
val execResult = plugin.execKonanClang(project.testTarget, Action<ExecSpec> {
|
||||
it.workingDir = File(outputDirectory)
|
||||
it.executable = "clang"
|
||||
val artifactsDir = "$outputDirectory/${project.testTarget}"
|
||||
it.args = listOf(processCSource(),
|
||||
"-o", executable,
|
||||
"-I", artifactsDir,
|
||||
"-L", artifactsDir,
|
||||
"-l", name,
|
||||
"-Wl,-rpath,$artifactsDir")
|
||||
|
||||
it.standardOutput = log
|
||||
it.errorOutput = log
|
||||
it.isIgnoreExitValue = true
|
||||
})
|
||||
log.toString("UTF-8").also {
|
||||
project.file("$executable.compilation.log").writeText(it)
|
||||
println(it)
|
||||
}
|
||||
execResult.assertNormalExitValue()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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 org.jetbrains.kotlin
|
||||
|
||||
import java.nio.file.Paths
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Creates files from the given source file that may contain different test directives.
|
||||
*
|
||||
* @return list of file names to be compiled
|
||||
*/
|
||||
fun KonanTest.buildCompileList(outputDirectory: String): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
val srcFile = project.file(source)
|
||||
// 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)
|
||||
}
|
||||
|
||||
val filePattern = Pattern.compile("(?m)// *FILE: *(.*)")
|
||||
val matcher = filePattern.matcher(srcText)
|
||||
|
||||
if (!matcher.find()) {
|
||||
// There is only one file in the input
|
||||
val filePath = "$outputDirectory/${srcFile.name}"
|
||||
registerKtFile(result, filePath, srcText)
|
||||
} else {
|
||||
// There are several files
|
||||
var processedChars = 0
|
||||
while (true) {
|
||||
val filePath = "$outputDirectory/${matcher.group(1)}"
|
||||
val start = processedChars
|
||||
val 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
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
internal fun createFile(file: String, text: String) = Paths.get(file).run {
|
||||
parent.toFile()
|
||||
.takeUnless { it.exists() }
|
||||
?.mkdirs()
|
||||
toFile().writeText(text)
|
||||
}
|
||||
|
||||
internal fun registerKtFile(sourceFiles: MutableList<String>, newFilePath: String, newFileContent: String) {
|
||||
createFile(newFilePath, newFileContent)
|
||||
if (newFilePath.endsWith(".kt")) {
|
||||
sourceFiles.add(newFilePath)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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 org.jetbrains.kotlin
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.jetbrains.kotlin.konan.target.AppleConfigurables
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.konan.target.PlatformManager
|
||||
|
||||
import java.io.FileInputStream
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
@@ -16,25 +21,108 @@ import java.util.Base64
|
||||
import org.jetbrains.report.json.*
|
||||
import java.nio.file.Path
|
||||
|
||||
//region Project properties.
|
||||
|
||||
val Project.platformManager
|
||||
get() = findProperty("platformManager") as PlatformManager
|
||||
|
||||
val Project.testTarget
|
||||
get() = findProperty("target") as KonanTarget
|
||||
|
||||
val Project.verboseTest
|
||||
get() = hasProperty("test_verbose")
|
||||
|
||||
val Project.testOutputLocal
|
||||
get() = (findProperty("testOutputLocal") as File).toString()
|
||||
|
||||
val Project.testOutputStdlib
|
||||
get() = (findProperty("testOutputStdlib") as File).toString()
|
||||
|
||||
val Project.testOutputFramework
|
||||
get() = (findProperty("testOutputFramework") as File).toString()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val Project.globalTestArgs: List<String>
|
||||
get() = with(findProperty("globalTestArgs")) {
|
||||
if (this is Array<*>) this.toList() as List<String>
|
||||
else this as List<String>
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
fun Project.platformManager() = findProperty("platformManager") as PlatformManager
|
||||
fun Project.testTarget() = findProperty("target") as KonanTarget
|
||||
|
||||
/**
|
||||
* Ad-hoc signing of the specified path
|
||||
* Ad-hoc signing of the specified path.
|
||||
*/
|
||||
fun codesign(project: Project, path: String) {
|
||||
check(HostManager.hostIsMac, { "Apple specific code signing" })
|
||||
check(HostManager.hostIsMac) { "Apple specific code signing" }
|
||||
val (stdOut, stdErr, exitCode) = runProcess(executor = localExecutor(project), executable = "/usr/bin/codesign",
|
||||
args = listOf("--verbose", "-s", "-", path))
|
||||
check(exitCode == 0, {
|
||||
"""
|
||||
|Codesign failed with exitCode: $exitCode
|
||||
|stdout: $stdOut
|
||||
|stderr: $stdErr
|
||||
""".trimMargin()
|
||||
})
|
||||
check(exitCode == 0) { """
|
||||
|Codesign failed with exitCode: $exitCode
|
||||
|stdout: $stdOut
|
||||
|stderr: $stdErr
|
||||
""".trimMargin()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of file paths to be compiled from the given [compile] list with regard to [exclude] list.
|
||||
*/
|
||||
fun Project.getFilesToCompile(compile: List<String>, exclude: List<String>): List<String> {
|
||||
// convert exclude list to paths
|
||||
val excludeFiles = exclude.map { project.file(it).absolutePath }.toList()
|
||||
|
||||
// create list of tests to compile
|
||||
return compile.flatMap { f ->
|
||||
project.file(f)
|
||||
.walk()
|
||||
.filter { it.isFile && it.name.endsWith(".kt") && !excludeFiles.contains(it.absolutePath) }
|
||||
.map(File::getAbsolutePath)
|
||||
.asIterable()
|
||||
}
|
||||
}
|
||||
|
||||
//region Task dependency.
|
||||
|
||||
fun Project.dependsOnDist(taskName: String) {
|
||||
project.tasks.getByName(taskName).dependsOnDist()
|
||||
}
|
||||
|
||||
fun Task.dependsOnDist() {
|
||||
val rootTasks = project.rootProject.tasks
|
||||
// We don't build the compiler if a custom dist path is specified.
|
||||
if (!(project.findProperty("useCustomDist") as Boolean)) {
|
||||
dependsOn(rootTasks.getByName("dist"))
|
||||
val target = project.testTarget
|
||||
if (target != HostManager.host) {
|
||||
// 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(rootTasks.getByName("${target.name}CrossDist"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the same dependencies for the receiver task from the given [task]
|
||||
*/
|
||||
fun String.sameDependenciesAs(task: Task) {
|
||||
val t = task.project.tasks.getByName(this)
|
||||
t.sameDependenciesAs(task)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the same dependencies for the receiver task from the given [task]
|
||||
*/
|
||||
fun Task.sameDependenciesAs(task: Task) {
|
||||
val dependencies = task.dependsOn.toList() // save to the list, otherwise it will cause cyclic dependency.
|
||||
this.dependsOn(dependencies)
|
||||
}
|
||||
|
||||
//endregion
|
||||
// Run command line from string.
|
||||
fun Array<String>.runCommand(workingDir: File = File("."),
|
||||
timeoutAmount: Long = 60,
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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(isReleaseCoroutines: Boolean): String {
|
||||
val coroutinesPackage = "kotlin.coroutines"
|
||||
|
||||
val emptyContinuationBody =
|
||||
if (isReleaseCoroutines)
|
||||
"""
|
||||
|override fun resumeWith(result: Result<Any?>) {
|
||||
| result.getOrThrow()
|
||||
|}
|
||||
""".trimMargin()
|
||||
else
|
||||
"""
|
||||
|override fun resume(data: Any?) {}
|
||||
|override fun resumeWithException(exception: Throwable) { throw exception }
|
||||
""".trimMargin()
|
||||
|
||||
val handleResultContinuationBody =
|
||||
if (isReleaseCoroutines)
|
||||
"""
|
||||
|override fun resumeWith(result: Result<T>) {
|
||||
| x(result.getOrThrow())
|
||||
|}
|
||||
""".trimMargin()
|
||||
else
|
||||
"""
|
||||
|override fun resumeWithException(exception: Throwable) {
|
||||
| throw exception
|
||||
|}
|
||||
|
|
||||
|override fun resume(data: T) = x(data)
|
||||
""".trimMargin()
|
||||
|
||||
val handleExceptionContinuationBody =
|
||||
if (isReleaseCoroutines)
|
||||
"""
|
||||
|override fun resumeWith(result: Result<Any?>) {
|
||||
| result.exceptionOrNull()?.let(x)
|
||||
|}
|
||||
""".trimMargin()
|
||||
else
|
||||
"""
|
||||
|override fun resumeWithException(exception: Throwable) {
|
||||
| x(exception)
|
||||
|}
|
||||
|
|
||||
|override fun resume(data: Any?) {}
|
||||
""".trimMargin()
|
||||
|
||||
val continuationAdapterBody =
|
||||
if (isReleaseCoroutines)
|
||||
"""
|
||||
|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)
|
||||
""".trimMargin()
|
||||
else
|
||||
""
|
||||
|
||||
return """
|
||||
|package helpers
|
||||
|import $coroutinesPackage.*
|
||||
|
|
||||
|fun <T> handleResultContinuation(x: (T) -> Unit): Continuation<T> = object: Continuation<T> {
|
||||
| override val context = EmptyCoroutineContext
|
||||
| $handleResultContinuationBody
|
||||
|}
|
||||
|
|
||||
|
|
||||
|fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation<Any?> = object: Continuation<Any?> {
|
||||
| override val context = EmptyCoroutineContext
|
||||
| $handleExceptionContinuationBody
|
||||
|}
|
||||
|
|
||||
|open class EmptyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<Any?> {
|
||||
| companion object : EmptyContinuation()
|
||||
| $emptyContinuationBody
|
||||
|}
|
||||
|
|
||||
|abstract class ContinuationAdapter<in T> : Continuation<T> {
|
||||
| override val context: CoroutineContext = EmptyCoroutineContext
|
||||
| $continuationAdapterBody
|
||||
|}
|
||||
|class StateMachineCheckerClass {
|
||||
| private var counter = 0
|
||||
| var finished = false
|
||||
|
|
||||
| var proceed: () -> Unit = {}
|
||||
|
|
||||
| suspend fun suspendHere() = suspendCoroutine<Unit> { c ->
|
||||
| counter++
|
||||
| proceed = { c.resume(Unit) }
|
||||
| }
|
||||
|
|
||||
| fun check(numberOfSuspensions: Int) {
|
||||
| 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 (!finished) error("Wrong state-machine generated: it is not finished yet")
|
||||
| }
|
||||
|}
|
||||
|val StateMachineChecker = StateMachineCheckerClass()
|
||||
|object CheckStateMachineContinuation: ContinuationAdapter<Unit>() {
|
||||
| override val context: CoroutineContext
|
||||
| get() = EmptyCoroutineContext
|
||||
|
|
||||
| override fun resume(value: Unit) {
|
||||
| StateMachineChecker.proceed = {
|
||||
| StateMachineChecker.finished = true
|
||||
| }
|
||||
| }
|
||||
|
|
||||
| override fun resumeWithException(exception: Throwable) {
|
||||
| throw exception
|
||||
| }
|
||||
|}
|
||||
""".trimMargin()
|
||||
}
|
||||
+1
-1
@@ -270,7 +270,7 @@ open class KonanCompileProgramTask: KonanCompileTask() {
|
||||
// Create tasks to run supported executables.
|
||||
override fun init(config: KonanBuildingConfig<*>, destinationDir: File, artifactName: String, target: KonanTarget) {
|
||||
super.init(config, destinationDir, artifactName, target)
|
||||
if (!isCrossCompile) {
|
||||
if (!isCrossCompile && !project.hasProperty("konanNoRun")) {
|
||||
runTask = project.tasks.create("run${artifactName.capitalize()}", Exec::class.java).apply {
|
||||
group = "run"
|
||||
dependsOn(this@KonanCompileProgramTask)
|
||||
|
||||
Reference in New Issue
Block a user