From 3645c22f8dc4192a060859774f79fdfbb67ff040 Mon Sep 17 00:00:00 2001 From: Pavel Punegov <32519625+PavelPunegov@users.noreply.github.com> Date: Tue, 20 Mar 2018 14:15:19 +0300 Subject: [PATCH] Execute tests with iOS simulator --- backend.native/tests/build.gradle | 7 +- .../org/jetbrains/kotlin/ExecRemote.groovy | 106 ----------- .../org/jetbrains/kotlin/KonanTest.groovy | 21 +-- .../org/jetbrains/kotlin/ExecutorService.kt | 170 ++++++++++++++++++ 4 files changed, 177 insertions(+), 127 deletions(-) delete mode 100644 buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/ExecRemote.groovy create mode 100644 buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 4a49da47810..bd14f312f08 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -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" } diff --git a/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/ExecRemote.groovy b/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/ExecRemote.groovy deleted file mode 100644 index e3dad8b2d1c..00000000000 --- a/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/ExecRemote.groovy +++ /dev/null @@ -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, 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 closure) { - this.execRemote(ConfigureUtil.configureUsing(closure)) - } - - ExecResult execRemote(Action action) { - this.executor.apply(action) - } - - private class SSHExecutor implements Function, ExecResult> { - private final String remote - private final String remoteDir - - SSHExecutor(String remote) { - this.remote = remote - this.remoteDir = uniqueSessionName() - } - - @Override - ExecResult apply(Action 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) - } - } - } -} diff --git a/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy b/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy index f2f4f0681bf..04e1e2da2a4 100644 --- a/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy +++ b/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy @@ -237,9 +237,9 @@ fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation = 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 = ob if (!exitCodeMismatch && !goldValueMismatch && this.expectedFail) println("Unexpected pass") } - - List 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 { diff --git a/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt b/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt new file mode 100644 index 00000000000..2972fb89a7f --- /dev/null +++ b/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt @@ -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): ExecResult? = execute(ConfigureUtil.configureUsing(closure)) + fun execute(action: Action): 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): 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): 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): 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): 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): 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) + } + } +} \ No newline at end of file