[JS IR] Improve stepping test executor
Don't search for box function in the generated code; instead, stop the execution right before calling it. This makes stepping tests more accurate.
This commit is contained in:
committed by
Space Team
parent
edd0e5e0bf
commit
0e3f48314a
+8
-1
@@ -34,4 +34,11 @@ fun getD() = true
|
|||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
// test.kt:3 box
|
// test.kt:3 box
|
||||||
// test.kt:11 box
|
// test.kt:13 getA
|
||||||
|
// test.kt:4 box
|
||||||
|
// test.kt:15 getB
|
||||||
|
// test.kt:5 box
|
||||||
|
// test.kt:17 getC
|
||||||
|
// test.kt:9 box
|
||||||
|
// test.kt:3 box
|
||||||
|
// test.kt:11 box
|
||||||
|
|||||||
+2
-1
@@ -31,4 +31,5 @@ inline fun foo(n: Int) {}
|
|||||||
// test.kt:3 box
|
// test.kt:3 box
|
||||||
// test.kt:3 box
|
// test.kt:3 box
|
||||||
// test.kt:3 box
|
// test.kt:3 box
|
||||||
// test.kt:6 box
|
// test.kt:3 box
|
||||||
|
// test.kt:6 box
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ fun nop() {}
|
|||||||
// test.kt:21 box
|
// test.kt:21 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test.kt:24 box
|
||||||
// test.kt:3 box
|
// test.kt:3 box
|
||||||
// test.kt:4 box
|
// test.kt:4 box
|
||||||
// test.kt:30 nop
|
// test.kt:30 nop
|
||||||
@@ -74,4 +75,4 @@ fun nop() {}
|
|||||||
// test.kt:15 box
|
// test.kt:15 box
|
||||||
// test.kt:19 box
|
// test.kt:19 box
|
||||||
// test.kt:30 nop
|
// test.kt:30 nop
|
||||||
// test.kt:21 box
|
// test.kt:21 box
|
||||||
|
|||||||
+3
-5
@@ -33,10 +33,10 @@ val jesse1VarDecl = "var jesse1 = _.exclamate(jesse);"
|
|||||||
|
|
||||||
// FILE: test.kt
|
// FILE: test.kt
|
||||||
|
|
||||||
fun noop() {}
|
|
||||||
|
|
||||||
fun box() {
|
fun box() {
|
||||||
noop()
|
|
||||||
exclamateAndConcat(
|
exclamateAndConcat(
|
||||||
"hello",
|
"hello",
|
||||||
"world")
|
"world")
|
||||||
@@ -56,8 +56,6 @@ fun box() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EXPECTATIONS
|
// EXPECTATIONS
|
||||||
// test.kt:39 box
|
|
||||||
// test.kt:36 noop
|
|
||||||
// test.kt:41 box
|
// test.kt:41 box
|
||||||
// test.kt:42 box
|
// test.kt:42 box
|
||||||
// a.kt:11 box
|
// a.kt:11 box
|
||||||
@@ -79,4 +77,4 @@ fun box() {
|
|||||||
// a.kt:6 exclamate
|
// a.kt:6 exclamate
|
||||||
// a.kt:29 box
|
// a.kt:29 box
|
||||||
// a.kt:22 value
|
// a.kt:22 value
|
||||||
// test.kt:56 box
|
// test.kt:56 box
|
||||||
|
|||||||
+2
-1
@@ -19,6 +19,7 @@ inline fun foo() = {
|
|||||||
// test.kt:5 box
|
// test.kt:5 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test1.kt:9 box
|
||||||
// test.kt:4 box
|
// test.kt:4 box
|
||||||
// test1.kt:6 box$lambda
|
// test1.kt:6 box$lambda
|
||||||
// test.kt:5 box
|
// test.kt:5 box
|
||||||
|
|||||||
+2
-1
@@ -25,7 +25,8 @@ inline fun foo() = {
|
|||||||
// test.kt:6 box
|
// test.kt:6 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test1.kt:10 box
|
||||||
// test.kt:4 box
|
// test.kt:4 box
|
||||||
// test.kt:5 box
|
// test.kt:5 box
|
||||||
// test1.kt:7 box$lambda
|
// test1.kt:7 box$lambda
|
||||||
// test.kt:6 box
|
// test.kt:6 box
|
||||||
|
|||||||
+2
-1
@@ -38,6 +38,7 @@ fun baz(v:(() -> Unit)) {
|
|||||||
// test.kt:7 box
|
// test.kt:7 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test1.kt:11 box
|
||||||
// test.kt:4 box
|
// test.kt:4 box
|
||||||
// test3.kt:14 baz
|
// test3.kt:14 baz
|
||||||
// test1.kt:8 box$lambda
|
// test1.kt:8 box$lambda
|
||||||
@@ -48,4 +49,4 @@ fun baz(v:(() -> Unit)) {
|
|||||||
// test3.kt:14 baz
|
// test3.kt:14 baz
|
||||||
// test1.kt:8 box$lambda
|
// test1.kt:8 box$lambda
|
||||||
// test3.kt:15 baz
|
// test3.kt:15 baz
|
||||||
// test.kt:7 box
|
// test.kt:7 box
|
||||||
|
|||||||
@@ -65,5 +65,12 @@ inline fun html(init: () -> Unit) {
|
|||||||
// test.kt:21 box
|
// test.kt:21 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test.kt:7 box
|
||||||
|
// 1.kt:33 box
|
||||||
|
// 1.kt:36 box
|
||||||
|
// 1.kt:37 box
|
||||||
|
// 1.kt:37 box
|
||||||
|
// 1.kt:41 box
|
||||||
|
// test.kt:15 box
|
||||||
// test.kt:19 box
|
// test.kt:19 box
|
||||||
// test.kt:21 box
|
// test.kt:21 box
|
||||||
|
|||||||
@@ -38,3 +38,6 @@ fun box(): String {
|
|||||||
// test.kt:10 box
|
// test.kt:10 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test.kt:3 box
|
||||||
|
// test.kt:5 box
|
||||||
|
// test.kt:6 box
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ fun fail() : String {
|
|||||||
// test.kt:9 box
|
// test.kt:9 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test.kt:16 box
|
||||||
// test.kt:4 box
|
// test.kt:4 box
|
||||||
// test.kt:20 fail
|
// test.kt:20 fail
|
||||||
// test.kt:4 box
|
// test.kt:4 box
|
||||||
@@ -47,4 +48,4 @@ fun fail() : String {
|
|||||||
// test.kt:16 box
|
// test.kt:16 box
|
||||||
// test.kt:7 box
|
// test.kt:7 box
|
||||||
// test.kt:13 checkEquals
|
// test.kt:13 checkEquals
|
||||||
// test.kt:9 box
|
// test.kt:9 box
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ fun fail() : String {
|
|||||||
// test.kt:11 box
|
// test.kt:11 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test.kt:14 box
|
||||||
// test.kt:6 box
|
// test.kt:6 box
|
||||||
// test.kt:18 fail
|
// test.kt:18 fail
|
||||||
// test.kt:6 box
|
// test.kt:6 box
|
||||||
@@ -45,4 +46,4 @@ fun fail() : String {
|
|||||||
// test.kt:14 box
|
// test.kt:14 box
|
||||||
// test.kt:9 box
|
// test.kt:9 box
|
||||||
// test.kt:3 execute
|
// test.kt:3 execute
|
||||||
// test.kt:11 box
|
// test.kt:11 box
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ fun fail() : String {
|
|||||||
// test.kt:9 box
|
// test.kt:9 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test.kt:16 box
|
||||||
// test.kt:4 box
|
// test.kt:4 box
|
||||||
// test.kt:5 box
|
// test.kt:5 box
|
||||||
// test.kt:20 fail
|
// test.kt:20 fail
|
||||||
@@ -47,4 +48,4 @@ fun fail() : String {
|
|||||||
// test.kt:16 box
|
// test.kt:16 box
|
||||||
// test.kt:8 box
|
// test.kt:8 box
|
||||||
// test.kt:16 box
|
// test.kt:16 box
|
||||||
// test.kt:9 box
|
// test.kt:9 box
|
||||||
|
|||||||
@@ -37,10 +37,11 @@ fun fail() : String {
|
|||||||
// test.kt:9 box
|
// test.kt:9 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test.kt:12 box
|
||||||
// test.kt:4 box
|
// test.kt:4 box
|
||||||
// test.kt:16 fail
|
// test.kt:16 fail
|
||||||
// test.kt:7 box
|
// test.kt:7 box
|
||||||
// test.kt:16 fail
|
// test.kt:16 fail
|
||||||
// test.kt:12 box
|
// test.kt:12 box
|
||||||
// test.kt:7 box
|
// test.kt:7 box
|
||||||
// test.kt:9 box
|
// test.kt:9 box
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ suspend fun box() {
|
|||||||
// test.kt:11 box
|
// test.kt:11 box
|
||||||
|
|
||||||
// EXPECTATIONS JS_IR
|
// EXPECTATIONS JS_IR
|
||||||
|
// test.kt:7 box
|
||||||
|
// test.kt:7 <init>
|
||||||
|
// test.kt:7 <init>
|
||||||
|
// test.kt:7 box
|
||||||
|
// test.kt:7 box
|
||||||
|
// test.kt:7 box
|
||||||
// test.kt:8 doResume
|
// test.kt:8 doResume
|
||||||
// test.kt:4 foo
|
// test.kt:4 foo
|
||||||
// test.kt:4 foo
|
// test.kt:4 foo
|
||||||
// test.kt:9 box$lambda
|
// test.kt:9 box$lambda
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ const code = fs.readFileSync(testFilePath, 'utf-8');
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
vm.runInContext(code, sandbox, testFilePath);
|
vm.runInContext(code, sandbox, testFilePath);
|
||||||
vm.runInContext('main.box()', sandbox);
|
// language=JavaScript
|
||||||
|
vm.runInContext(`
|
||||||
|
debugger;
|
||||||
|
main.box();
|
||||||
|
`,
|
||||||
|
sandbox
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore any exceptions
|
// Ignore any exceptions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,12 @@
|
|||||||
package org.jetbrains.kotlin.js.test.handlers
|
package org.jetbrains.kotlin.js.test.handlers
|
||||||
|
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.jetbrains.kotlin.KtPsiSourceFileLinesMapping
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
|
||||||
import org.jetbrains.kotlin.js.parser.sourcemaps.*
|
import org.jetbrains.kotlin.js.parser.sourcemaps.*
|
||||||
import org.jetbrains.kotlin.js.test.debugger.*
|
import org.jetbrains.kotlin.js.test.debugger.*
|
||||||
import org.jetbrains.kotlin.js.test.utils.getAllFilesForRunner
|
import org.jetbrains.kotlin.js.test.utils.getAllFilesForRunner
|
||||||
import org.jetbrains.kotlin.js.test.utils.getBoxFunction
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
|
||||||
import org.jetbrains.kotlin.test.TargetBackend
|
import org.jetbrains.kotlin.test.TargetBackend
|
||||||
import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives
|
import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives
|
||||||
import org.jetbrains.kotlin.test.model.TestFile
|
|
||||||
import org.jetbrains.kotlin.test.model.TestModule
|
import org.jetbrains.kotlin.test.model.TestModule
|
||||||
import org.jetbrains.kotlin.test.services.TestServices
|
import org.jetbrains.kotlin.test.services.TestServices
|
||||||
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
|
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
|
||||||
@@ -29,7 +25,7 @@ import java.net.URISyntaxException
|
|||||||
/**
|
/**
|
||||||
* This class is an analogue of the [DebugRunner][org.jetbrains.kotlin.test.backend.handlers.DebugRunner] from JVM stepping tests.
|
* This class is an analogue of the [DebugRunner][org.jetbrains.kotlin.test.backend.handlers.DebugRunner] from JVM stepping tests.
|
||||||
*
|
*
|
||||||
* It runs a generated JavaScript file under a debugger, sets a breakpoint in the beginning of the `box` function
|
* It runs a generated JavaScript file under a debugger, stops right before entering the `box` function,
|
||||||
* and performs the "step into" action until there is nothing more to step into. On each pause it records the source file name,
|
* and performs the "step into" action until there is nothing more to step into. On each pause it records the source file name,
|
||||||
* the source line and the function name of the current call frame, and compares this data with the expectations written in the test file.
|
* the source line and the function name of the current call frame, and compares this data with the expectations written in the test file.
|
||||||
*
|
*
|
||||||
@@ -72,47 +68,54 @@ class JsDebugRunner(testServices: TestServices) : AbstractJsArtifactsCollector(t
|
|||||||
sourceMap: SourceMap,
|
sourceMap: SourceMap,
|
||||||
mainModule: TestModule,
|
mainModule: TestModule,
|
||||||
) {
|
) {
|
||||||
val (testFileWithBoxFunction, boxFunctionStartLine) = getBoxFunctionStartLocation(mainModule)
|
val originalFile = mainModule.files.first { !it.isAdditional }.originalFile
|
||||||
val originalFileWithBoxFunction = testFileWithBoxFunction.originalFile
|
|
||||||
|
|
||||||
val boxFunctionLineInGeneratedFile =
|
|
||||||
sourceMap.breakpointLineInGeneratedFile(originalFileWithBoxFunction, boxFunctionStartLine)
|
|
||||||
|
|
||||||
if (boxFunctionLineInGeneratedFile < 0)
|
|
||||||
error("Could not find the location of the 'box' function in the generated file")
|
|
||||||
|
|
||||||
val debuggerFacade = NodeJsDebuggerFacade(jsFilePath)
|
val debuggerFacade = NodeJsDebuggerFacade(jsFilePath)
|
||||||
|
|
||||||
val jsFileURI = File(jsFilePath).absoluteFile.toURI().withAuthority("")
|
val jsFile = File(jsFilePath)
|
||||||
|
|
||||||
|
val jsFileURI = jsFile.makeURI()
|
||||||
|
|
||||||
val loggedItems = mutableListOf<SteppingTestLoggedData>()
|
val loggedItems = mutableListOf<SteppingTestLoggedData>()
|
||||||
|
|
||||||
debuggerFacade.run {
|
debuggerFacade.run {
|
||||||
with(debuggerFacade) {
|
debugger.resume()
|
||||||
val boxFunctionBreakpoint = debugger.setBreakpointByUrl(boxFunctionLineInGeneratedFile, jsFileURI.toString())
|
waitForResumeEvent()
|
||||||
debugger.resume()
|
waitForPauseEvent {
|
||||||
waitForResumeEvent()
|
it.reason == Debugger.PauseReason.OTHER // hit the 'debugger' statement
|
||||||
waitForPauseEvent {
|
}
|
||||||
it.reason == Debugger.PauseReason.OTHER && it.hitBreakpoints.contains(boxFunctionBreakpoint.breakpointId)
|
|
||||||
}
|
suspend fun repeatedlyStepInto(action: suspend (Debugger.CallFrame) -> Boolean) {
|
||||||
while (true) {
|
while (true) {
|
||||||
val topMostCallFrame = waitForPauseEvent().callFrames[0]
|
val topMostCallFrame = waitForPauseEvent().callFrames[0]
|
||||||
try {
|
if (!action(topMostCallFrame)) break
|
||||||
if (URI(scriptUrlByScriptId(topMostCallFrame.location.scriptId)) != jsFileURI) break
|
|
||||||
} catch (_: URISyntaxException) {
|
|
||||||
// Probably something like 'evalmachine.<anonymous>' brought us here. Ignore.
|
|
||||||
}
|
|
||||||
addCallFrameInfoToLoggedItems(sourceMap, topMostCallFrame, loggedItems)
|
|
||||||
debugger.stepInto()
|
debugger.stepInto()
|
||||||
waitForResumeEvent()
|
waitForResumeEvent()
|
||||||
}
|
}
|
||||||
debugger.resume()
|
|
||||||
waitForResumeEvent()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Debugger.CallFrame.isInFileUnderTest() = try {
|
||||||
|
URI(scriptUrlByScriptId(location.scriptId)) == jsFileURI
|
||||||
|
} catch (_: URISyntaxException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
repeatedlyStepInto {
|
||||||
|
!it.isInFileUnderTest()
|
||||||
|
}
|
||||||
|
repeatedlyStepInto { callFrame ->
|
||||||
|
callFrame.isInFileUnderTest().also {
|
||||||
|
if (it)
|
||||||
|
addCallFrameInfoToLoggedItems(sourceMap, callFrame, loggedItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debugger.resume()
|
||||||
|
waitForResumeEvent()
|
||||||
}
|
}
|
||||||
checkSteppingTestResult(
|
checkSteppingTestResult(
|
||||||
mainModule.frontendKind,
|
mainModule.frontendKind,
|
||||||
mainModule.targetBackend ?: TargetBackend.JS_IR,
|
mainModule.targetBackend ?: TargetBackend.JS_IR,
|
||||||
originalFileWithBoxFunction,
|
originalFile,
|
||||||
loggedItems
|
loggedItems
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -123,82 +126,24 @@ class JsDebugRunner(testServices: TestServices) : AbstractJsArtifactsCollector(t
|
|||||||
loggedItems: MutableList<SteppingTestLoggedData>
|
loggedItems: MutableList<SteppingTestLoggedData>
|
||||||
) {
|
) {
|
||||||
val originalFunctionName = topMostCallFrame.functionLocation?.let {
|
val originalFunctionName = topMostCallFrame.functionLocation?.let {
|
||||||
sourceMap.getSourceLineForGeneratedLocation(it)?.name
|
sourceMap.segmentForGeneratedLocation(it.lineNumber, it.columnNumber)?.name
|
||||||
}
|
}
|
||||||
sourceMap.getSourceLineForGeneratedLocation(topMostCallFrame.location)?.let { (_, sourceFile, sourceLine, _, _) ->
|
sourceMap.segmentForGeneratedLocation(
|
||||||
|
topMostCallFrame.location.lineNumber,
|
||||||
|
topMostCallFrame.location.columnNumber
|
||||||
|
)?.let { (_, sourceFile, sourceLine, _, _) ->
|
||||||
if (sourceFile == null || sourceLine < 0) return@let
|
if (sourceFile == null || sourceLine < 0) return@let
|
||||||
val testFileName = testFileNameFromMappedLocation(sourceFile, sourceLine) ?: return
|
val testFileName = testFileNameFromMappedLocation(sourceFile, sourceLine) ?: return
|
||||||
val expectation =
|
val expectation = formatAsSteppingTestExpectation(
|
||||||
formatAsSteppingTestExpectation(testFileName, sourceLine + 1, originalFunctionName ?: topMostCallFrame.functionName, false)
|
testFileName,
|
||||||
|
sourceLine + 1,
|
||||||
|
originalFunctionName ?: topMostCallFrame.functionName,
|
||||||
|
false,
|
||||||
|
)
|
||||||
loggedItems.add(SteppingTestLoggedData(sourceLine + 1, false, expectation))
|
loggedItems.add(SteppingTestLoggedData(sourceLine + 1, false, expectation))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the test file and the line number in that file where the body of the `box` function begins.
|
|
||||||
*/
|
|
||||||
private fun getBoxFunctionStartLocation(mainModule: TestModule): Pair<TestFile, Int> {
|
|
||||||
val boxFunction = getBoxFunction(testServices) ?: error("Missing 'box' function")
|
|
||||||
val file = boxFunction.containingKtFile
|
|
||||||
val mapping = KtPsiSourceFileLinesMapping(file)
|
|
||||||
val firstStatementOffset = boxFunction.bodyBlockExpression?.firstStatement?.startOffset
|
|
||||||
?: boxFunction.bodyExpression?.startOffset
|
|
||||||
?: boxFunction.startOffset
|
|
||||||
return mainModule.files.single { it.name == file.name } to mapping.getLineByOffset(firstStatementOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps the location in the source file to the location in the generated file.
|
|
||||||
*
|
|
||||||
* The Node.js debugger is not sourcemap-aware, so we need to set a breakpoint in the `box` function in the generated JS file.
|
|
||||||
*
|
|
||||||
* We don't know where the generated `box` function is located, so we use the source map to figure it out.
|
|
||||||
*
|
|
||||||
* This is basically what Intellij IDEA's built-in JavaScript debugger does when you set a breakpoint in a source file: it tries
|
|
||||||
* to map the location of the breakpoint in the source file to a location in the generated file. Here we use a simplified
|
|
||||||
* algorithm for that.
|
|
||||||
*/
|
|
||||||
private fun SourceMap.breakpointLineInGeneratedFile(sourceFile: File, sourceLine: Int): Int {
|
|
||||||
val sourceFileAbsolutePath = sourceFile.absoluteFile.normalize()
|
|
||||||
var candidateSegment: Pair<Int, SourceMapSegment>? = null
|
|
||||||
for ((generatedLineNumber, group) in groups.withIndex()) {
|
|
||||||
for (segment in group.segments) {
|
|
||||||
if (segment.sourceFileName?.let { File(it).absoluteFile.normalize() } != sourceFileAbsolutePath ||
|
|
||||||
segment.sourceLineNumber != sourceLine)
|
|
||||||
continue
|
|
||||||
if (candidateSegment == null)
|
|
||||||
candidateSegment = generatedLineNumber to segment
|
|
||||||
// Find the segment that points to the earliest column in the source file
|
|
||||||
if (segment.sourceColumnNumber < candidateSegment.second.sourceColumnNumber)
|
|
||||||
candidateSegment = generatedLineNumber to segment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return candidateSegment?.first ?: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps [location] in the generated JavaScript file to the corresponding location in a source file.
|
|
||||||
* @return The source file path (as specified in the source map) and the line number in that source file.
|
|
||||||
*/
|
|
||||||
private fun SourceMap.getSourceLineForGeneratedLocation(location: Debugger.Location): SourceMapSegment? {
|
|
||||||
|
|
||||||
val group = groups.getOrNull(location.lineNumber)?.takeIf { it.segments.isNotEmpty() } ?: return null
|
|
||||||
val columnNumber = location.columnNumber ?: return group.segments[0]
|
|
||||||
return if (columnNumber <= group.segments[0].generatedColumnNumber) {
|
|
||||||
group.segments[0]
|
|
||||||
} else {
|
|
||||||
val candidateIndex = group.segments.indexOfFirst {
|
|
||||||
columnNumber <= it.generatedColumnNumber
|
|
||||||
}
|
|
||||||
if (candidateIndex < 0)
|
|
||||||
null
|
|
||||||
else if (candidateIndex == 0 || group.segments[candidateIndex].generatedColumnNumber == columnNumber)
|
|
||||||
group.segments[candidateIndex]
|
|
||||||
else
|
|
||||||
group.segments[candidateIndex - 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An original test file may represent multiple source files (by using the `// FILE: myFile.kt` comments).
|
* An original test file may represent multiple source files (by using the `// FILE: myFile.kt` comments).
|
||||||
* Sourcemaps contain paths to original test files. However, in test expectations we write names as in the `// FILE:` comments.
|
* Sourcemaps contain paths to original test files. However, in test expectations we write names as in the `// FILE:` comments.
|
||||||
@@ -206,9 +151,10 @@ class JsDebugRunner(testServices: TestServices) : AbstractJsArtifactsCollector(t
|
|||||||
*/
|
*/
|
||||||
private fun testFileNameFromMappedLocation(originalFilePath: String, originalFileLineNumber: Int): String? {
|
private fun testFileNameFromMappedLocation(originalFilePath: String, originalFileLineNumber: Int): String? {
|
||||||
val originalFile = File(originalFilePath)
|
val originalFile = File(originalFilePath)
|
||||||
return testServices.moduleStructure.modules.flatMap { it.files }.findLast {
|
return testServices.moduleStructure.modules.asSequence().flatMap { module -> module.files.asSequence().filter { !it.isAdditional } }
|
||||||
it.originalFile.absolutePath == originalFile.absolutePath && it.startLineNumberInOriginalFile <= originalFileLineNumber
|
.findLast {
|
||||||
}?.name
|
it.originalFile.absolutePath == originalFile.absolutePath && it.startLineNumberInOriginalFile <= originalFileLineNumber
|
||||||
|
}?.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,26 +196,52 @@ private class NodeJsDebuggerFacade(jsFilePath: String) {
|
|||||||
/**
|
/**
|
||||||
* By the time [body] is called, the execution is paused, no code is executed yet.
|
* By the time [body] is called, the execution is paused, no code is executed yet.
|
||||||
*/
|
*/
|
||||||
fun <T> run(body: suspend NodeJsInspectorClientContext.() -> T) = inspector.run {
|
fun <T> run(body: suspend Context.() -> T) = inspector.run {
|
||||||
debugger.enable()
|
debugger.enable()
|
||||||
debugger.setSkipAllPauses(false)
|
debugger.setSkipAllPauses(false)
|
||||||
runtime.runIfWaitingForDebugger()
|
runtime.runIfWaitingForDebugger()
|
||||||
waitForPauseEvent { it.reason == Debugger.PauseReason.BREAK_ON_START }
|
|
||||||
|
|
||||||
withTimeout(30000) {
|
with(Context(this)) {
|
||||||
body()
|
waitForPauseEvent { it.reason == Debugger.PauseReason.BREAK_ON_START }
|
||||||
|
|
||||||
|
withTimeout(30000) {
|
||||||
|
body()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scriptUrlByScriptId(scriptId: Runtime.ScriptId) = scriptUrls[scriptId] ?: error("unknown scriptId")
|
inner class Context(private val underlying: NodeJsInspectorClientContext) : NodeJsInspectorClientContext by underlying {
|
||||||
|
|
||||||
suspend fun NodeJsInspectorClientContext.waitForPauseEvent(suchThat: (Debugger.Event.Paused) -> Boolean = { true }) =
|
fun scriptUrlByScriptId(scriptId: Runtime.ScriptId) = scriptUrls[scriptId] ?: error("unknown scriptId $scriptId")
|
||||||
waitForValueToBecomeNonNull {
|
|
||||||
pausedEvent?.takeIf(suchThat)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun NodeJsInspectorClientContext.waitForResumeEvent() = waitForConditionToBecomeTrue { pausedEvent == null }
|
suspend fun waitForPauseEvent(suchThat: (Debugger.Event.Paused) -> Boolean = { true }) =
|
||||||
|
waitForValueToBecomeNonNull {
|
||||||
|
pausedEvent?.takeIf(suchThat)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun waitForResumeEvent() = waitForConditionToBecomeTrue { pausedEvent == null }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun File.makeURI(): URI = absoluteFile.toURI().withAuthority("")
|
||||||
|
|
||||||
private fun URI.withAuthority(newAuthority: String?) =
|
private fun URI.withAuthority(newAuthority: String?) =
|
||||||
URI(scheme, newAuthority, path, query, fragment)
|
URI(scheme, newAuthority, path, query, fragment)
|
||||||
|
|
||||||
|
private fun SourceMap.segmentForGeneratedLocation(lineNumber: Int, columnNumber: Int?): SourceMapSegment? {
|
||||||
|
|
||||||
|
val group = groups.getOrNull(lineNumber)?.takeIf { it.segments.isNotEmpty() } ?: return null
|
||||||
|
return if (columnNumber == null || columnNumber <= group.segments[0].generatedColumnNumber) {
|
||||||
|
group.segments[0]
|
||||||
|
} else {
|
||||||
|
val candidateIndex = group.segments.indexOfFirst {
|
||||||
|
columnNumber <= it.generatedColumnNumber
|
||||||
|
}
|
||||||
|
if (candidateIndex < 0)
|
||||||
|
null
|
||||||
|
else if (candidateIndex == 0 || group.segments[candidateIndex].generatedColumnNumber == columnNumber)
|
||||||
|
group.segments[candidateIndex]
|
||||||
|
else
|
||||||
|
group.segments[candidateIndex - 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user