Factor out stepping test utility functions
We will need them for JS stepping tests as well.
This commit is contained in:
+12
-103
@@ -14,24 +14,17 @@ import org.jetbrains.kotlin.test.TargetBackend
|
|||||||
import org.jetbrains.kotlin.test.model.FrontendKind
|
import org.jetbrains.kotlin.test.model.FrontendKind
|
||||||
import org.jetbrains.kotlin.test.model.FrontendKinds
|
import org.jetbrains.kotlin.test.model.FrontendKinds
|
||||||
import org.jetbrains.kotlin.test.model.TestModule
|
import org.jetbrains.kotlin.test.model.TestModule
|
||||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertEqualsToFile
|
|
||||||
import org.jetbrains.kotlin.test.services.TestServices
|
import org.jetbrains.kotlin.test.services.TestServices
|
||||||
import org.jetbrains.kotlin.test.services.sourceProviders.MainFunctionForBlackBoxTestsSourceProvider.Companion.BOX_MAIN_FILE_NAME
|
import org.jetbrains.kotlin.test.services.sourceProviders.MainFunctionForBlackBoxTestsSourceProvider.Companion.BOX_MAIN_FILE_NAME
|
||||||
|
import org.jetbrains.kotlin.test.utils.SteppingTestLoggedData
|
||||||
|
import org.jetbrains.kotlin.test.utils.checkSteppingTestResult
|
||||||
|
import org.jetbrains.kotlin.test.utils.formatAsSteppingTestExpectation
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
open class LoggedData(val line: Int, val isSynthetic: Boolean, val expectation: String)
|
|
||||||
|
|
||||||
abstract class DebugRunner(testServices: TestServices) : JvmBoxRunner(testServices) {
|
abstract class DebugRunner(testServices: TestServices) : JvmBoxRunner(testServices) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val EXPECTATIONS_MARKER = "// EXPECTATIONS"
|
|
||||||
const val FORCE_STEP_INTO_MARKER = "// FORCE_STEP_INTO"
|
|
||||||
const val JVM_EXPECTATIONS_MARKER = "$EXPECTATIONS_MARKER JVM"
|
|
||||||
const val JVM_IR_EXPECTATIONS_MARKER = "$EXPECTATIONS_MARKER JVM_IR"
|
|
||||||
const val CLASSIC_FRONTEND_EXPECTATIONS_MARKER = "$EXPECTATIONS_MARKER CLASSIC_FRONTEND"
|
|
||||||
const val FIR_EXPECTATIONS_MARKER = "$EXPECTATIONS_MARKER FIR"
|
|
||||||
|
|
||||||
val BOX_MAIN_FILE_CLASS_NAME = BOX_MAIN_FILE_NAME.replace(".kt", "Kt")
|
val BOX_MAIN_FILE_CLASS_NAME = BOX_MAIN_FILE_NAME.replace(".kt", "Kt")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +32,7 @@ abstract class DebugRunner(testServices: TestServices) : JvmBoxRunner(testServic
|
|||||||
private var backend = TargetBackend.JVM
|
private var backend = TargetBackend.JVM
|
||||||
private var frontend: FrontendKind<*> = FrontendKinds.ClassicFrontend
|
private var frontend: FrontendKind<*> = FrontendKinds.ClassicFrontend
|
||||||
|
|
||||||
abstract fun storeStep(loggedItems: ArrayList<LoggedData>, event: Event)
|
abstract fun storeStep(loggedItems: ArrayList<SteppingTestLoggedData>, event: Event)
|
||||||
|
|
||||||
override fun launchSeparateJvmProcess(
|
override fun launchSeparateJvmProcess(
|
||||||
javaExe: File,
|
javaExe: File,
|
||||||
@@ -81,7 +74,7 @@ abstract class DebugRunner(testServices: TestServices) : JvmBoxRunner(testServic
|
|||||||
// Debug event loop to step through a test program.
|
// Debug event loop to step through a test program.
|
||||||
private fun runDebugEventLoop(virtualMachine: VirtualMachine) {
|
private fun runDebugEventLoop(virtualMachine: VirtualMachine) {
|
||||||
val manager = virtualMachine.eventRequestManager()
|
val manager = virtualMachine.eventRequestManager()
|
||||||
val loggedItems = ArrayList<LoggedData>()
|
val loggedItems = ArrayList<SteppingTestLoggedData>()
|
||||||
var inBoxMethod = false
|
var inBoxMethod = false
|
||||||
vmLoop@
|
vmLoop@
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -164,96 +157,12 @@ abstract class DebugRunner(testServices: TestServices) : JvmBoxRunner(testServic
|
|||||||
}
|
}
|
||||||
eventSet.resume()
|
eventSet.resume()
|
||||||
}
|
}
|
||||||
checkResult(wholeFile, loggedItems)
|
checkSteppingTestResult(frontend, backend, wholeFile, loggedItems)
|
||||||
virtualMachine.resume()
|
virtualMachine.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Location.formatAsExpectation(): String {
|
fun Location.formatAsExpectation() =
|
||||||
val synthetic = if (method().isSynthetic) " (synthetic)" else ""
|
formatAsSteppingTestExpectation(sourceName(), lineNumber(), method().name(), method().isSynthetic)
|
||||||
return "${sourceName()}:${lineNumber()} ${method().name()}$synthetic"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkResult(wholeFile: File, loggedItems: List<LoggedData>) {
|
|
||||||
val actual = mutableListOf<String>()
|
|
||||||
val lines = wholeFile.readLines()
|
|
||||||
val forceStepInto = lines.any { it.startsWith(FORCE_STEP_INTO_MARKER) }
|
|
||||||
|
|
||||||
val actualLineNumbers = compressSequencesWithoutLinenumber(loggedItems)
|
|
||||||
.filter {
|
|
||||||
// Ignore synthetic code with no line number information unless force step into behavior is requested.
|
|
||||||
forceStepInto || !it.isSynthetic
|
|
||||||
}
|
|
||||||
.map { "// ${it.expectation}" }
|
|
||||||
val actualLineNumbersIterator = actualLineNumbers.iterator()
|
|
||||||
|
|
||||||
val lineIterator = lines.iterator()
|
|
||||||
for (line in lineIterator) {
|
|
||||||
actual.add(line)
|
|
||||||
if (line.startsWith(EXPECTATIONS_MARKER) || line.startsWith(FORCE_STEP_INTO_MARKER)) break
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentBackend = TargetBackend.ANY
|
|
||||||
var currentFrontend = frontend
|
|
||||||
for (line in lineIterator) {
|
|
||||||
if (line.isEmpty()) {
|
|
||||||
actual.add(line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (line.startsWith(EXPECTATIONS_MARKER)) {
|
|
||||||
actual.add(line)
|
|
||||||
currentBackend = when (line) {
|
|
||||||
EXPECTATIONS_MARKER -> TargetBackend.ANY
|
|
||||||
JVM_EXPECTATIONS_MARKER -> TargetBackend.JVM
|
|
||||||
JVM_IR_EXPECTATIONS_MARKER -> TargetBackend.JVM_IR
|
|
||||||
CLASSIC_FRONTEND_EXPECTATIONS_MARKER -> currentBackend
|
|
||||||
FIR_EXPECTATIONS_MARKER -> currentBackend
|
|
||||||
else -> error("Expected JVM backend: $line")
|
|
||||||
}
|
|
||||||
currentFrontend = when (line) {
|
|
||||||
EXPECTATIONS_MARKER -> frontend
|
|
||||||
JVM_EXPECTATIONS_MARKER -> currentFrontend
|
|
||||||
JVM_IR_EXPECTATIONS_MARKER -> currentFrontend
|
|
||||||
CLASSIC_FRONTEND_EXPECTATIONS_MARKER -> FrontendKinds.ClassicFrontend
|
|
||||||
FIR_EXPECTATIONS_MARKER -> FrontendKinds.FIR
|
|
||||||
else -> error("Expected JVM backend: $line")
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ((currentBackend == TargetBackend.ANY || currentBackend == backend) &&
|
|
||||||
currentFrontend == frontend) {
|
|
||||||
if (actualLineNumbersIterator.hasNext()) {
|
|
||||||
actual.add(actualLineNumbersIterator.next())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
actual.add(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actualLineNumbersIterator.forEach { actual.add(it) }
|
|
||||||
|
|
||||||
assertEqualsToFile(wholeFile, actual.joinToString("\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compresses sequences of the same location without line number in the log:
|
|
||||||
// specifically removes locations without linenumber, that would otherwise
|
|
||||||
// print as byte offsets. This avoids overspecifying code generation
|
|
||||||
// strategy in debug tests.
|
|
||||||
fun compressSequencesWithoutLinenumber(loggedItems: List<LoggedData>): List<LoggedData> {
|
|
||||||
if (loggedItems.isEmpty()) return listOf()
|
|
||||||
|
|
||||||
val logIterator = loggedItems.iterator()
|
|
||||||
var currentItem = logIterator.next()
|
|
||||||
val result = mutableListOf(currentItem)
|
|
||||||
|
|
||||||
for (logItem in logIterator) {
|
|
||||||
if (currentItem.line != -1 || currentItem.expectation != logItem.expectation) {
|
|
||||||
result.add(logItem)
|
|
||||||
currentItem = logItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setupMethodEntryAndExitRequests(virtualMachine: VirtualMachine) {
|
fun setupMethodEntryAndExitRequests(virtualMachine: VirtualMachine) {
|
||||||
val manager = virtualMachine.eventRequestManager()
|
val manager = virtualMachine.eventRequestManager()
|
||||||
@@ -281,11 +190,11 @@ abstract class DebugRunner(testServices: TestServices) : JvmBoxRunner(testServic
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SteppingDebugRunner(testServices: TestServices) : DebugRunner(testServices) {
|
class SteppingDebugRunner(testServices: TestServices) : DebugRunner(testServices) {
|
||||||
override fun storeStep(loggedItems: ArrayList<LoggedData>, event: Event) {
|
override fun storeStep(loggedItems: ArrayList<SteppingTestLoggedData>, event: Event) {
|
||||||
assert(event is LocatableEvent)
|
assert(event is LocatableEvent)
|
||||||
val location = (event as LocatableEvent).location()
|
val location = (event as LocatableEvent).location()
|
||||||
loggedItems.add(
|
loggedItems.add(
|
||||||
LoggedData(
|
SteppingTestLoggedData(
|
||||||
location.lineNumber(),
|
location.lineNumber(),
|
||||||
location.method().isSynthetic,
|
location.method().isSynthetic,
|
||||||
location.formatAsExpectation()
|
location.formatAsExpectation()
|
||||||
@@ -343,7 +252,7 @@ class LocalVariableDebugRunner(testServices: TestServices) : DebugRunner(testSer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun storeStep(loggedItems: ArrayList<LoggedData>, event: Event) {
|
override fun storeStep(loggedItems: ArrayList<SteppingTestLoggedData>, event: Event) {
|
||||||
val locatableEvent = event as LocatableEvent
|
val locatableEvent = event as LocatableEvent
|
||||||
waitUntil { locatableEvent.thread().isSuspended }
|
waitUntil { locatableEvent.thread().isSuspended }
|
||||||
val location = locatableEvent.location()
|
val location = locatableEvent.location()
|
||||||
@@ -357,7 +266,7 @@ class LocalVariableDebugRunner(testServices: TestServices) : DebugRunner(testSer
|
|||||||
listOf()
|
listOf()
|
||||||
}
|
}
|
||||||
loggedItems.add(
|
loggedItems.add(
|
||||||
LoggedData(
|
SteppingTestLoggedData(
|
||||||
location.lineNumber(),
|
location.lineNumber(),
|
||||||
false,
|
false,
|
||||||
"${location.formatAsExpectation()}: ${visibleVars.joinToString(", ")}".trim()
|
"${location.formatAsExpectation()}: ${visibleVars.joinToString(", ")}".trim()
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.test.utils
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.test.TargetBackend
|
||||||
|
import org.jetbrains.kotlin.test.model.FrontendKind
|
||||||
|
import org.jetbrains.kotlin.test.model.FrontendKinds
|
||||||
|
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertEqualsToFile
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
data class SteppingTestLoggedData(val line: Int, val isSynthetic: Boolean, val expectation: String)
|
||||||
|
|
||||||
|
private const val EXPECTATIONS_MARKER = "// EXPECTATIONS"
|
||||||
|
private const val FORCE_STEP_INTO_MARKER = "// FORCE_STEP_INTO"
|
||||||
|
private const val JVM_EXPECTATIONS_MARKER = "$EXPECTATIONS_MARKER JVM"
|
||||||
|
private const val JVM_IR_EXPECTATIONS_MARKER = "$EXPECTATIONS_MARKER JVM_IR"
|
||||||
|
private const val CLASSIC_FRONTEND_EXPECTATIONS_MARKER = "$EXPECTATIONS_MARKER CLASSIC_FRONTEND"
|
||||||
|
private const val FIR_EXPECTATIONS_MARKER = "$EXPECTATIONS_MARKER FIR"
|
||||||
|
|
||||||
|
fun checkSteppingTestResult(
|
||||||
|
frontendKind: FrontendKind<*>,
|
||||||
|
targetBackend: TargetBackend,
|
||||||
|
wholeFile: File,
|
||||||
|
loggedItems: List<SteppingTestLoggedData>
|
||||||
|
) {
|
||||||
|
val actual = mutableListOf<String>()
|
||||||
|
val lines = wholeFile.readLines()
|
||||||
|
val forceStepInto = lines.any { it.startsWith(FORCE_STEP_INTO_MARKER) }
|
||||||
|
|
||||||
|
val actualLineNumbers = compressSequencesWithoutLinenumber(loggedItems)
|
||||||
|
.filter {
|
||||||
|
// Ignore synthetic code with no line number information unless force step into behavior is requested.
|
||||||
|
forceStepInto || !it.isSynthetic
|
||||||
|
}
|
||||||
|
.map { "// ${it.expectation}" }
|
||||||
|
val actualLineNumbersIterator = actualLineNumbers.iterator()
|
||||||
|
|
||||||
|
val lineIterator = lines.iterator()
|
||||||
|
for (line in lineIterator) {
|
||||||
|
actual.add(line)
|
||||||
|
if (line.startsWith(EXPECTATIONS_MARKER) || line.startsWith(FORCE_STEP_INTO_MARKER)) break
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentBackend = TargetBackend.ANY
|
||||||
|
var currentFrontend = frontendKind
|
||||||
|
for (line in lineIterator) {
|
||||||
|
if (line.isEmpty()) {
|
||||||
|
actual.add(line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (line.startsWith(EXPECTATIONS_MARKER)) {
|
||||||
|
actual.add(line)
|
||||||
|
currentBackend = when (line) {
|
||||||
|
EXPECTATIONS_MARKER -> TargetBackend.ANY
|
||||||
|
JVM_EXPECTATIONS_MARKER -> TargetBackend.JVM
|
||||||
|
JVM_IR_EXPECTATIONS_MARKER -> TargetBackend.JVM_IR
|
||||||
|
CLASSIC_FRONTEND_EXPECTATIONS_MARKER -> currentBackend
|
||||||
|
FIR_EXPECTATIONS_MARKER -> currentBackend
|
||||||
|
else -> error("Expected JVM backend: $line")
|
||||||
|
}
|
||||||
|
currentFrontend = when (line) {
|
||||||
|
EXPECTATIONS_MARKER -> frontendKind
|
||||||
|
JVM_EXPECTATIONS_MARKER -> currentFrontend
|
||||||
|
JVM_IR_EXPECTATIONS_MARKER -> currentFrontend
|
||||||
|
CLASSIC_FRONTEND_EXPECTATIONS_MARKER -> FrontendKinds.ClassicFrontend
|
||||||
|
FIR_EXPECTATIONS_MARKER -> FrontendKinds.FIR
|
||||||
|
else -> error("Expected JVM backend: $line")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ((currentBackend == TargetBackend.ANY || currentBackend == targetBackend) &&
|
||||||
|
currentFrontend == frontendKind
|
||||||
|
) {
|
||||||
|
if (actualLineNumbersIterator.hasNext()) {
|
||||||
|
actual.add(actualLineNumbersIterator.next())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actual.add(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actualLineNumbersIterator.forEach { actual.add(it) }
|
||||||
|
|
||||||
|
assertEqualsToFile(wholeFile, actual.joinToString("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compresses sequences of the same location without line number in the log:
|
||||||
|
* specifically removes locations without linenumber, that would otherwise
|
||||||
|
* print as byte offsets. This avoids overspecifying code generation
|
||||||
|
* strategy in debug tests.
|
||||||
|
*/
|
||||||
|
private fun compressSequencesWithoutLinenumber(loggedItems: List<SteppingTestLoggedData>): List<SteppingTestLoggedData> {
|
||||||
|
if (loggedItems.isEmpty()) return listOf()
|
||||||
|
|
||||||
|
val logIterator = loggedItems.iterator()
|
||||||
|
var currentItem = logIterator.next()
|
||||||
|
val result = mutableListOf(currentItem)
|
||||||
|
|
||||||
|
for (logItem in logIterator) {
|
||||||
|
if (currentItem.line != -1 || currentItem.expectation != logItem.expectation) {
|
||||||
|
result.add(logItem)
|
||||||
|
currentItem = logItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatAsSteppingTestExpectation(sourceName: String, lineNumber: Int, functionName: String, isSynthetic: Boolean): String {
|
||||||
|
val synthetic = if (isSynthetic) " (synthetic)" else ""
|
||||||
|
return "$sourceName:$lineNumber $functionName$synthetic"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user