Execute tests with iOS simulator

This commit is contained in:
Pavel Punegov
2018-03-20 14:15:19 +03:00
committed by Pavel Punegov
parent 68fe66a21f
commit 3645c22f8d
4 changed files with 177 additions and 127 deletions
+5 -2
View File
@@ -66,7 +66,9 @@ externalTestsDir.mkdirs()
ext.platformManager = project.rootProject.platformManager
ext.target = platformManager.targetManager(project.testTarget).target
project.convention.plugins.remoteExec = new org.jetbrains.kotlin.ExecRemote(project)
// Add executor to run tests depending on a target
project.convention.plugins.executor = ExecutorServiceKt.create(project)
allprojects {
// Root directories for test output (logs, compiled files, statistics etc). Only single path must be in each set.
@@ -2529,7 +2531,8 @@ if (isMac()) {
if (!useCustomDist) {
dependsOn ':distPlatformLibs'
}
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
disabled = (project.testTarget == 'wasm32') || // No interop for wasm yet.
(project.testTarget == 'ios_x64') // No GLUT in iOS
run = false
source = "interop/basics/opengl_teapot.kt"
}
@@ -1,106 +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.Action
import org.gradle.api.Project
import org.gradle.process.ExecResult
import org.gradle.process.ExecSpec
import org.gradle.util.ConfigureUtil
import java.nio.file.Paths
import java.util.function.Function
/**
* This class provides process.execRemote -- a drop-in replacement for process.exec.
* If we provide -Premote=user@host the binaries are executed on a remote host
* If we omit -Premote then the binary is executed locally as usual.
*/
class ExecRemote {
private final Project project
private final Function<Action<? super ExecSpec>, ExecResult> executor
ExecRemote(Project project) {
this.project = project
if (project.hasProperty('remote')) {
def remote = project.property('remote') as String
executor = new SSHExecutor(remote)
} else {
executor = { project.exec(it) }
}
}
ExecResult execRemote(Closure<? super ExecSpec> closure) {
this.execRemote(ConfigureUtil.configureUsing(closure))
}
ExecResult execRemote(Action<? super ExecSpec> action) {
this.executor.apply(action)
}
private class SSHExecutor implements Function<Action<? super ExecSpec>, ExecResult> {
private final String remote
private final String remoteDir
SSHExecutor(String remote) {
this.remote = remote
this.remoteDir = uniqueSessionName()
}
@Override
ExecResult apply(Action<? super ExecSpec> action) {
String execFile
createRemoteDir()
def execResult = project.exec { ExecSpec execSpec ->
action.execute(execSpec)
execSpec.with {
upload(executable)
execFile = executable = "$remoteDir/${new File(executable).name}"
commandLine = ['/usr/bin/ssh', remote] + commandLine
}
}
cleanup(execFile)
return execResult
}
private String uniqueSessionName() {
def date = new Date().format('yyyyMMddHHmmss')
Paths.get(project.ext.remoteRoot.toString(), "tmp",
System.properties['user.name'].toString() + "_" + date).toString()
}
private createRemoteDir() {
project.exec {
commandLine('ssh', remote, 'mkdir', '-p', remoteDir)
}
}
private upload(String fileName) {
project.exec {
commandLine('scp', fileName, "${remote}:${remoteDir}")
}
}
private cleanup(String fileName) {
project.exec {
commandLine('ssh', remote, 'rm', fileName)
}
}
}
}
@@ -237,9 +237,9 @@ fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation<Any?> = ob
def out = new ByteArrayOutputStream()
//TODO Add test timeout
ExecResult execResult = project.execRemote {
ExecResult execResult = project.execute {
commandLine executionCommandLine(exe)
commandLine exe
if (arguments != null) {
args arguments
@@ -277,23 +277,6 @@ fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation<Any?> = ob
if (!exitCodeMismatch && !goldValueMismatch && this.expectedFail) println("Unexpected pass")
}
List<String> executionCommandLine(String exe) {
def absoluteTargetToolchain = platformManager.platform(target).absoluteTargetToolchain
def absoluteTargetSysRoot = platformManager.platform(target).absoluteTargetSysRoot
if (target instanceof KonanTarget.WASM32) {
def d8 = "$absoluteTargetToolchain/bin/d8"
def launcherJs = "${exe}.js"
return [d8, '--expose-wasm', launcherJs, '--', exe]
} else if (target instanceof KonanTarget.LINUX_MIPS32 || target instanceof KonanTarget.LINUX_MIPSEL32) {
def qemu = target instanceof KonanTarget.LINUX_MIPS32 ? "qemu-mips" : "qemu-mipsel"
def absoluteQemu = "$absoluteTargetToolchain/bin/$qemu"
return [absoluteQemu, "-L", absoluteTargetSysRoot, exe]
} else {
return [exe]
}
}
}
class TestFailedException extends RuntimeException {
@@ -0,0 +1,170 @@
/*
* Copyright 2010-2018 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 groovy.lang.Closure
import org.gradle.api.Action
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.KonanTarget
import org.jetbrains.kotlin.konan.target.PlatformManager
import org.jetbrains.kotlin.konan.target.Xcode
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.file.Paths
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
/**
* A replacement of the standard exec {}
* @see org.gradle.api.Project.exec
*/
interface ExecutorService {
fun execute(closure: Closure<in ExecSpec>): ExecResult? = execute(ConfigureUtil.configureUsing(closure))
fun execute(action: Action<in ExecSpec>): ExecResult?
}
fun create(project: Project): ExecutorService {
val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager
val testTarget = platformManager.targetManager(project.findProperty("testTarget") as String?).target
val platform = platformManager.platform(testTarget)
val absoluteTargetToolchain = platform.absoluteTargetToolchain
val absoluteTargetSysRoot = platform.absoluteTargetSysRoot
return when (testTarget) {
KonanTarget.WASM32 -> object : ExecutorService {
override fun execute(action: Action<in ExecSpec>): ExecResult? = project.exec { execSpec ->
action.execute(execSpec)
with(execSpec) {
val exe = executable
val d8 = "$absoluteTargetToolchain/bin/d8"
val launcherJs = "$executable.js"
executable = d8
args = listOf("--expose-wasm", launcherJs, "--", exe) + args
}
}
}
KonanTarget.LINUX_MIPS32, KonanTarget.LINUX_MIPSEL32 -> object : ExecutorService {
override fun execute(action: Action<in ExecSpec>): ExecResult? = project.exec { execSpec ->
action.execute(execSpec)
with(execSpec) {
val qemu = if (platform.target === KonanTarget.LINUX_MIPS32) "qemu-mips" else "qemu-mipsel"
val absoluteQemu = "$absoluteTargetToolchain/bin/$qemu"
val exe = executable
executable = absoluteQemu
args = listOf("-L", absoluteTargetSysRoot, exe) + args
}
}
}
KonanTarget.IOS_X64 -> simulator(project)
else -> {
if (project.hasProperty("remote")) sshExecutor(project)
else object : ExecutorService {
override fun execute(action: Action<in ExecSpec>): ExecResult? = project.exec(action)
}
}
}
}
/**
* Executes a given action with iPhone Simulator.
*
* The test target should be specified with -Ptest_target=ios_x64
* @see KonanTarget.IOS_X64
* @param iosDevice an optional project property used to control simulator's device type
* Specify -PiosDevice=iPhone X to set it
*/
private fun simulator(project: Project) : ExecutorService = object : ExecutorService {
private val simctl by lazy {
val sdk = Xcode.current.iphonesimulatorSdk
val out = ByteArrayOutputStream()
val result = project.exec {
it.commandLine("/usr/bin/xcrun", "--find", "simctl", "--sdk", sdk)
it.standardOutput = out
}
result.assertNormalExitValue()
out.toString("UTF-8").trim()
}
private val iosDevice = project.findProperty("iosDevice")?.toString() ?: "iPhone 8"
override fun execute(action: Action<in ExecSpec>): ExecResult? = project.exec { execSpec ->
action.execute(execSpec)
with(execSpec) { commandLine = listOf(simctl, "spawn", iosDevice, executable) + args }
}
}
/**
* Remote process executor.
*
* @param remote makes binaries be executed on a remote host
* Specify it as -Premote=user@host
*/
private fun sshExecutor(project: Project) : ExecutorService = object : ExecutorService {
private val remote: String = project.property("remote").toString()
// Unique remote dir name to be used in the target host
private val remoteDir = run {
val date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
Paths.get(project.findProperty("remoteRoot").toString(), "tmp",
System.getProperty("user.name") + "_" + date).toString()
}
override fun execute(action: Action<in ExecSpec>): ExecResult {
var execFile: String? = null
createRemoteDir()
val execResult = project.exec { execSpec ->
action.execute(execSpec)
with(execSpec) {
upload(executable)
executable = "$remoteDir/${File(executable).name}"
execFile = executable
commandLine = arrayListOf("/usr/bin/ssh", remote) + commandLine
}
}
cleanup(execFile!!)
return execResult
}
private fun createRemoteDir() {
project.exec {
it.commandLine("ssh", remote, "mkdir", "-p", remoteDir)
}
}
private fun upload(fileName: String) {
project.exec {
it.commandLine("scp", fileName, "$remote:$remoteDir")
}
}
private fun cleanup(fileName: String) {
project.exec {
it.commandLine("ssh", remote, "rm", fileName)
}
}
}