[kotlin-native][tests][lldb] adds possibility to run simple lldb scenarious with simulator

This commit is contained in:
Vasily Levchenko
2021-04-22 17:16:44 +02:00
committed by Space
parent ed3542cdf5
commit 6f2af740cb
6 changed files with 132 additions and 82 deletions
@@ -5,8 +5,7 @@
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileNativeBinary import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileNativeBinary
import org.jetbrains.kotlin.* import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.*
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.UtilsKt import org.jetbrains.kotlin.UtilsKt
import java.nio.file.Paths import java.nio.file.Paths
@@ -5248,10 +5247,15 @@ project.tasks.named("test").configure {
} }
project.tasks.register("debugger_test", Test.class) { project.tasks.register("debugger_test", Test.class) {
enabled = (target.family == Family.OSX) // KT-30366 enabled = (target.family.appleFamily) // KT-30366
testLogging { exceptionFormat = 'full' } testLogging { exceptionFormat = 'full' }
UtilsKt.dependsOnDist(it) UtilsKt.dependsOnDist(it)
systemProperties = ['kotlin.native.home': kotlinNativeDist] systemProperties = ['kotlin.native.home': kotlinNativeDist,
'kotlin.native.host': HostManager.@Companion.getHost(),
'kotlin.native.test.target': target,
'kotlin.native.test.debugger.simulator.enabled': findProperty("kotlin.native.test.debugger.simulator.enabled"),
'kotlin.native.test.debugger.simulator.delay': findProperty("kotlin.native.test.debugger.simulator.delay")]
outputs.upToDateWhen { false }
} }
// Configure build for iOS device targets. // Configure build for iOS device targets.
@@ -17,6 +17,7 @@ object DistProperties {
val konanc: Path = dist.resolve("bin/$konancDriver") val konanc: Path = dist.resolve("bin/$konancDriver")
val cinterop: Path = dist.resolve("bin/$cinteropDriver") val cinterop: Path = dist.resolve("bin/$cinteropDriver")
val lldb: Path = Paths.get("lldb") val lldb: Path = Paths.get("lldb")
val xcrun: Path = Paths.get("xcrun")
val devToolsSecurity: Path = Paths.get("DevToolsSecurity") val devToolsSecurity: Path = Paths.get("DevToolsSecurity")
val dwarfDump: Path = Paths.get("dwarfdump") val dwarfDump: Path = Paths.get("dwarfdump")
val swiftc: Path = Paths.get("swiftc") val swiftc: Path = Paths.get("swiftc")
@@ -5,14 +5,12 @@
package org.jetbrains.kotlin.native.test.debugger package org.jetbrains.kotlin.native.test.debugger
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.bc.K2Native import org.jetbrains.kotlin.cli.bc.K2Native
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
import java.io.StringReader import java.io.StringReader
import java.lang.Thread.sleep
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -25,13 +23,18 @@ class ToolDriver(
listOf("-output", output.toString(), source.toString(), *args).toTypedArray() listOf("-output", output.toString(), source.toString(), *args).toTypedArray()
} }
fun compile(output: Path, srcs:Array<Path>, vararg args: String) = compile(output, *args) { fun compile(output: Path, srcs: Array<Path>, vararg args: String) = compile(output, *args) {
listOf("-output", output.toString(), *srcs.map{it.toString()}.toTypedArray(), *args).toTypedArray() listOf("-output", output.toString(), *srcs.map { it.toString() }.toTypedArray(), *args).toTypedArray()
} }
private fun crossPlatform(): Array<String> = if (targetIsHost())
emptyArray()
else
arrayOf("-target", target())
private fun compile(output: Path, vararg args: String, argsCalculator:() -> Array<String>) { private fun compile(output: Path, vararg args: String, argsCalculator:() -> Array<String>) {
check(!Files.exists(output)) check(!Files.exists(output))
val allArgs = argsCalculator() val allArgs = arrayOf(*crossPlatform(), *argsCalculator())
if (useInProcessCompiler) { if (useInProcessCompiler) {
K2Native.main(allArgs) K2Native.main(allArgs)
@@ -54,11 +57,15 @@ class ToolDriver(
} }
fun runLldb(program: Path, commands: List<String>): String { fun runLldb(program: Path, commands: List<String>): String {
val args = listOf("-b", "-o", "command script import \"${DistProperties.lldbPrettyPrinters}\"") + val args = listOf("-b", *program.programOrAttach(), "-o", "command script import \"${DistProperties.lldbPrettyPrinters}\"",
commands.flatMap { listOf("-o", it) } *commands.flatMap { listOf("-o", it) }.toTypedArray())
return subprocess(DistProperties.lldb, program.toString(), "-b", *args.toTypedArray()) if (!targetIsHost()) {
.thrownIfFailed() return subprocess(DistProperties.xcrun, "simctl", "spawn", "-w", "-s", "iPhone 11", program.toString()) {
.stdout sleep(simulatorDelay())
DistProperties.lldb to listOf(*args.toTypedArray(), "-o", "detach")
}.thrownIfFailed().stdout
}
return subprocess(DistProperties.lldb, *args.toTypedArray()).thrownIfFailed().stdout
} }
fun runDwarfDump(program: Path, vararg args:String = emptyArray(), processor:List<DwarfTag>.()->Unit) { fun runDwarfDump(program: Path, vararg args:String = emptyArray(), processor:List<DwarfTag>.()->Unit) {
@@ -73,6 +80,8 @@ class ToolDriver(
} }
} }
private fun Path.programOrAttach() = if (targetIsHost()) arrayOf(toString()) else arrayOf("-o", "process attach -n ${this.toString()}")
data class ProcessOutput( data class ProcessOutput(
val program: Path, val program: Path,
val process: Process, val process: Process,
@@ -94,7 +103,7 @@ data class ProcessOutput(
} }
} }
fun subprocess(program: Path, vararg args: String): ProcessOutput { fun subprocess(program: Path, vararg args: String, action: (() -> Pair<Path, List<String>>)? = null): ProcessOutput {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val process = ProcessBuilder(program.toString(), *args).start() val process = ProcessBuilder(program.toString(), *args).start()
val out = GlobalScope.async(Dispatchers.IO) { val out = GlobalScope.async(Dispatchers.IO) {
@@ -105,20 +114,26 @@ fun subprocess(program: Path, vararg args: String): ProcessOutput {
readStream(process, process.errorStream.buffered()) readStream(process, process.errorStream.buffered())
} }
return runBlocking { val actionOutput = action?.let {
try { val p = it()
val status = process.waitFor(5L, TimeUnit.MINUTES) subprocess(p.first, *p.second.toTypedArray())
if (!status) { }
out.cancel()
err.cancel() try {
error("$program timeouted") val status = process.waitFor(5L, TimeUnit.MINUTES)
} if (!status) {
}catch (e:Exception) {
out.cancel() out.cancel()
err.cancel() err.cancel()
error(e) error("$program timeouted")
} }
ProcessOutput(program, process, out.await(), err.await(), System.currentTimeMillis() - start) }catch (e:Exception) {
out.cancel()
err.cancel()
error(e)
}
return actionOutput ?: runBlocking {
ProcessOutput(program, process, out.await(), err.await(), System.currentTimeMillis() - start)
} }
} }
@@ -3,6 +3,7 @@
* that can be found in the LICENSE file. * that can be found in the LICENSE file.
*/ */
import org.jetbrains.kotlin.native.test.debugger.lldbCommandRunOrContinue
import org.jetbrains.kotlin.native.test.debugger.lldbComplexTest import org.jetbrains.kotlin.native.test.debugger.lldbComplexTest
import org.jetbrains.kotlin.native.test.debugger.lldbTest import org.jetbrains.kotlin.native.test.debugger.lldbTest
import org.junit.Test import org.junit.Test
@@ -20,7 +21,7 @@ class LldbTests {
> b main.kt:2 > b main.kt:2
Breakpoint 1: [..] Breakpoint 1: [..]
> r > ${lldbCommandRunOrContinue()}
Process [..] stopped Process [..] stopped
[..] stop reason = breakpoint 1.1 [..] stop reason = breakpoint 1.1
[..] at main.kt:2[..] [..] at main.kt:2[..]
@@ -53,7 +54,7 @@ class LldbTests {
} }
""", """ """, """
> b main.kt:7 > b main.kt:7
> r > ${lldbCommandRunOrContinue()}
> fr var > fr var
(char) a = '\x01' (char) a = '\x01'
(int) b = 2 (int) b = 2
@@ -76,7 +77,7 @@ class LldbTests {
} }
""", """ """, """
> b main.kt:4 > b main.kt:4
> r > ${lldbCommandRunOrContinue()}
> fr var > fr var
(ObjHeader *) args = [] (ObjHeader *) args = []
(ObjHeader *) point = [x: ..., y: ...] (ObjHeader *) point = [x: ..., y: ...]
@@ -98,7 +99,7 @@ class LldbTests {
data class Point(val x: Int, val y: Int) data class Point(val x: Int, val y: Int)
""", """ """, """
> b main.kt:8 > b main.kt:8
> r > ${lldbCommandRunOrContinue()}
> fr var > fr var
(ObjHeader *) args = [] (ObjHeader *) args = []
(ObjHeader *) xs = [..., ..., ...] (ObjHeader *) xs = [..., ..., ...]
@@ -115,7 +116,7 @@ class LldbTests {
data class Point(val x: Int, val y: Int) data class Point(val x: Int, val y: Int)
""", """ """, """
> b main.kt:3 > b main.kt:3
> r > ${lldbCommandRunOrContinue()}
> fr var xs > fr var xs
(ObjHeader *) xs = [..., ..., ...] (ObjHeader *) xs = [..., ..., ...]
""") """)
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.native.test.debugger package org.jetbrains.kotlin.native.test.debugger
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.konan.target.hostTargetSuffix
import org.junit.Assert.fail import org.junit.Assert.fail
import java.io.IOException import java.io.IOException
import java.nio.file.Files import java.nio.file.Files
@@ -63,6 +64,11 @@ fun lldbTest(@Language("kotlin") programText: String, lldbSession: String) {
return return
} }
if (!targetIsHost() && !simulatorTestEnabled()) {
println("simulator tests disabled, check 'kotlin.native.test.debugger.simulator.enabled' property")
return
}
if (!isOsxDevToolsEnabled) { if (!isOsxDevToolsEnabled) {
println("""Development tools aren't available. println("""Development tools aren't available.
|Please consider to execute: |Please consider to execute:
@@ -96,8 +102,8 @@ private val isOsxDevToolsEnabled: Boolean by lazy {
} }
fun dwarfDumpTest(@Language("kotlin") programText: String, flags: List<String>, test:List<DwarfTag>.()->Unit) { fun dwarfDumpTest(@Language("kotlin") programText: String, flags: List<String>, test:List<DwarfTag>.()->Unit) {
if (!haveDwarfDump) { if (!haveDwarfDump || !targetIsHost()) {
println("Skipping test: no dwarfdump") println("Skipping test")
return return
} }
@@ -155,8 +161,8 @@ class ToolDriverHelper(private val driver: ToolDriver, val root:Path) {
} }
fun dwarfDumpComplexTest(test:ToolDriverHelper.()->Unit) { fun dwarfDumpComplexTest(test:ToolDriverHelper.()->Unit) {
if (!haveDwarfDump) { if (!haveDwarfDump || !targetIsHost()) {
println("Skipping test: no dwarfdump") println("Skipping test")
return return
} }
@@ -168,8 +174,8 @@ fun dwarfDumpComplexTest(test:ToolDriverHelper.()->Unit) {
} }
fun lldbComplexTest(test:ToolDriverHelper.()->Unit) { fun lldbComplexTest(test:ToolDriverHelper.()->Unit) {
if (!haveLldb) { if (!haveLldb || !targetIsHost()) {
println("Skipping test: no lldb") println("Skipping test")
return return
} }
@@ -180,43 +186,6 @@ fun lldbComplexTest(test:ToolDriverHelper.()->Unit) {
} }
} }
private val haveDwarfDump: Boolean by lazy {
val version = try {
subprocess(DistProperties.dwarfDump, "--version")
.takeIf { it.process.exitValue() == 0 }
?.stdout
} catch (e: IOException) {
null
}
if (version == null) {
println("No LLDB found")
} else {
println("Using $version")
}
version != null
}
private val haveLldb: Boolean by lazy {
val lldbVersion = try {
subprocess(DistProperties.lldb, "-version")
.takeIf { it.process.exitValue() == 0 }
?.stdout
} catch (e: IOException) {
null
}
if (lldbVersion == null) {
println("No LLDB found")
} else {
println("Using $lldbVersion")
}
lldbVersion != null
}
private class LldbSessionSpecification private constructor( private class LldbSessionSpecification private constructor(
val commands: List<String>, val commands: List<String>,
val patterns: List<List<String>> val patterns: List<List<String>>
@@ -224,11 +193,17 @@ private class LldbSessionSpecification private constructor(
fun match(output: String) { fun match(output: String) {
val blocks = output.split("""(?=\(lldb\))""".toRegex()).filterNot(String::isEmpty) val blocks = output.split("""(?=\(lldb\))""".toRegex()).filterNot(String::isEmpty)
check(blocks[0].startsWith("(lldb) target create")) { "Missing block \"target create\". Got: ${blocks[0]}" } if (targetIsHost()) {
check(blocks[1].startsWith("(lldb) command script import")) { check(blocks[0].startsWith("(lldb) target create")) { "Missing block \"target create\". Got: ${blocks[0]}" }
"Missing block \"command script import\". Got: ${blocks[0]}" check(blocks[1].startsWith("(lldb) command script import")) {
"Missing block \"command script import\". Got: ${blocks[0]}"
}
} }
val responses = blocks.drop(2) val responses = if (targetIsHost())
blocks.drop(2)
else
blocks.drop(2).dropLast(1)
val executedCommands = responses.map { it.lines().first() } val executedCommands = responses.map { it.lines().first() }
val bodies = responses.map { it.lines().drop(1) } val bodies = responses.map { it.lines().drop(1) }
val responsesMatch = executedCommands.size == commands.size val responsesMatch = executedCommands.size == commands.size
@@ -238,8 +213,8 @@ private class LldbSessionSpecification private constructor(
val message = """ val message = """
|Responses do not match commands. |Responses do not match commands.
| |
|COMMANDS: |$commands |COMMANDS: |$commands (${commands.size})
|RESPONSES: |$executedCommands |RESPONSES: |$executedCommands (${executedCommands.size})
| |
|FULL SESSION: |FULL SESSION:
|$output |$output
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.native.test.debugger
import java.io.IOException
internal fun targetIsHost() = System.getProperty("kotlin.native.host") == System.getProperty("kotlin.native.test.target")
internal fun simulatorTestEnabled() = System.getProperty("kotlin.native.test.debugger.simulator.enabled")?.toBoolean() ?: false
internal fun simulatorDelay() = System.getProperty("kotlin.native.test.debugger.simulator.delay")?.toLongOrNull() ?: 300
internal fun target() = System.getProperty("kotlin.native.test.target")
internal val haveDwarfDump: Boolean by lazy {
val version = try {
subprocess(DistProperties.dwarfDump, "--version")
.takeIf { it.process.exitValue() == 0 }
?.stdout
} catch (e: IOException) {
null
}
if (version == null) {
println("No LLDB found")
} else {
println("Using $version")
}
version != null
}
internal val haveLldb: Boolean by lazy {
val lldbVersion = try {
subprocess(DistProperties.lldb, "-version")
.takeIf { it.process.exitValue() == 0 }
?.stdout
} catch (e: IOException) {
null
}
if (lldbVersion == null) {
println("No LLDB found")
} else {
println("Using $lldbVersion")
}
lldbVersion != null
}
internal fun lldbCommandRunOrContinue() = if (targetIsHost()) "r" else "c"