Skip to content

Commit aaec816

Browse files
committed
Improve error reporting
1 parent dbfa5fd commit aaec816

File tree

2 files changed

+80
-60
lines changed

2 files changed

+80
-60
lines changed
Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package dotty.tools.debug
22

33
import com.sun.jdi.Location
4-
import dotty.tools.io.JFile
4+
import dotty.tools.io.JPath
55
import dotty.tools.readLines
66

77
/**
@@ -10,9 +10,22 @@ import dotty.tools.readLines
1010
*/
1111
private[debug] case class DebugStepAssert[T](step: DebugStep[T], assert: T => Unit)
1212

13+
/** A location in the check file */
14+
private[debug] case class CheckFileLocation(checkFile: JPath, line: Int):
15+
override def toString: String = s"$checkFile:$line"
16+
17+
/** When a DebugStepAssert fails it throws a DebugStepException */
18+
private[debug] case class DebugStepException(message: String, location: CheckFileLocation) extends Exception
19+
20+
private[debug] enum DebugStep[T]:
21+
case Break(className: String, line: Int) extends DebugStep[Location]
22+
case Step extends DebugStep[Location]
23+
case Next extends DebugStep[Location]
24+
case Eval(expression: String) extends DebugStep[Either[String, String]]
25+
1326
private[debug] object DebugStepAssert:
1427
import DebugStep.*
15-
def parseCheckFile(checkFile: JFile): Seq[DebugStepAssert[?]] =
28+
def parseCheckFile(checkFile: JPath): Seq[DebugStepAssert[?]] =
1629
val sym = "[a-zA-Z0-9$.]+"
1730
val line = "\\d+"
1831
val trailing = s"\\s*(?:\\/\\/.*)?".r // empty or comment
@@ -24,13 +37,15 @@ private[debug] object DebugStepAssert:
2437
val result = "result (.*)".r
2538
val error = "error (.*)".r
2639
val multiLineError = s"error$trailing".r
40+
val allLines = readLines(checkFile.toFile)
2741

2842
def loop(lines: List[String], acc: List[DebugStepAssert[?]]): List[DebugStepAssert[?]] =
43+
given CheckFileLocation = CheckFileLocation(checkFile, allLines.size - lines.size + 1)
2944
lines match
3045
case Nil => acc.reverse
3146
case break(className , lineStr) :: tail =>
32-
val line = lineStr.toInt
33-
val step = DebugStepAssert(Break(className, line), checkClassAndLine(className, line))
47+
val breakpointLine = lineStr.toInt
48+
val step = DebugStepAssert(Break(className, breakpointLine), checkClassAndLine(className, breakpointLine))
3449
loop(tail, step :: acc)
3550
case step(pattern) :: tail =>
3651
val step = DebugStepAssert(Step, checkLineOrMethod(pattern))
@@ -52,6 +67,7 @@ private[debug] object DebugStepAssert:
5267
case invalid :: tail => throw new Exception(s"Cannot parse debug step: $invalid")
5368

5469
def parseEvalAssertion(lines: List[String]): (Either[String, String] => Unit, List[String]) =
70+
given CheckFileLocation = CheckFileLocation(checkFile, allLines.size - lines.size + 1)
5571
lines match
5672
case Nil => throw new Exception(s"Missing result or error")
5773
case result(expected) :: tail => (checkResult(expected), tail)
@@ -61,58 +77,54 @@ private[debug] object DebugStepAssert:
6177
(checkError(expected.map(_.stripPrefix(" "))), tail1)
6278
case invalid :: _ => throw new Exception(s"Cannot parse as result or error: $invalid")
6379

64-
loop(readLines(checkFile), Nil)
80+
loop(allLines, Nil)
6581
end parseCheckFile
6682

67-
private def checkClassAndLine(className: String, line: Int)(location: Location): Unit =
68-
assert(className == location.declaringType.name, s"obtained ${location.declaringType.name}, expected ${className}")
69-
checkLine(line)(location)
83+
private def checkClassAndLine(className: String, breakpointLine: Int)(using CheckFileLocation)(location: Location): Unit =
84+
debugStepAssertEquals(location.declaringType.name, className)
85+
checkLine(breakpointLine)(location)
7086

71-
private def checkLineOrMethod(pattern: String): Location => Unit =
87+
private def checkLineOrMethod(pattern: String)(using CheckFileLocation): Location => Unit =
7288
if "(\\d+)".r.matches(pattern) then checkLine(pattern.toInt) else checkMethod(pattern)
7389

74-
private def checkLine(line: Int)(location: Location): Unit =
75-
assert(location.lineNumber == line, s"obtained ${location.lineNumber}, expected $line")
90+
private def checkLine(expected: Int)(using CheckFileLocation)(location: Location): Unit =
91+
debugStepAssertEquals(location.lineNumber, expected)
7692

77-
private def checkMethod(methodName: String)(location: Location): Unit = assert(methodName == location.method.name)
93+
private def checkMethod(expected: String)(using CheckFileLocation)(location: Location): Unit =
94+
debugStepAssertEquals(location.method.name, expected)
7895

79-
private def checkResult(expected: String)(obtained: Either[String, String]): Unit =
96+
private def checkResult(expected: String)(using CheckFileLocation)(obtained: Either[String, String]): Unit =
8097
obtained match
8198
case Left(obtained) =>
82-
val message =
99+
debugStepFailed(
83100
s"""|Evaluation failed:
84101
|${obtained.replace("\n", "\n|")}""".stripMargin
85-
throw new AssertionError(message)
86-
case Right(obtained) =>
87-
val message =
88-
s"""|Expected: $expected
89-
|Obtained: $obtained""".stripMargin
90-
assert(expected.r.matches(obtained.toString), message)
102+
)
103+
case Right(obtained) => debugStepAssertEquals(obtained, expected)
91104

92-
private def checkError(expected: Seq[String])(obtained: Either[String, String]): Unit =
105+
private def checkError(expected: Seq[String])(using CheckFileLocation)(obtained: Either[String, String]): Unit =
93106
obtained match
94107
case Left(obtained) =>
95-
val message =
108+
debugStepAssert(
109+
expected.forall(e => e.r.findFirstMatchIn(obtained).isDefined),
96110
s"""|Expected:
97-
|${expected.mkString("\n")}
111+
|${expected.mkString("\n|")}
98112
|Obtained:
99113
|${obtained.replace("\n", "\n|")}""".stripMargin
100-
assert(expected.forall(e => e.r.findFirstMatchIn(obtained).isDefined), message)
114+
)
101115
case Right(obtained) =>
102-
val message =
116+
debugStepFailed(
103117
s"""|Evaluation succeeded but failure expected.
104118
|Obtained: $obtained
105119
|""".stripMargin
106-
throw new AssertionError(message)
107-
108-
109-
end DebugStepAssert
110-
111-
private[debug] enum DebugStep[T]:
112-
case Break(className: String, line: Int) extends DebugStep[Location]
113-
case Step extends DebugStep[Location]
114-
case Next extends DebugStep[Location]
115-
case Eval(expression: String) extends DebugStep[Either[String, String]]
120+
)
116121

122+
private def debugStepAssertEquals[T](obtained: T, expected: T)(using CheckFileLocation): Unit =
123+
debugStepAssert(obtained == expected, s"Obtained $obtained, Expected: $expected")
117124

125+
private def debugStepAssert(assertion: Boolean, message: String)(using CheckFileLocation): Unit =
126+
if !assertion then debugStepFailed(message)
118127

128+
private def debugStepFailed(message: String)(using location: CheckFileLocation): Unit =
129+
throw DebugStepException(message, location)
130+
end DebugStepAssert

compiler/test/dotty/tools/debug/DebugTests.scala

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,32 +45,26 @@ object DebugTests extends ParallelTesting:
4545
private def verifyDebug(dir: JFile, testSource: TestSource, warnings: Int, reporters: Seq[TestReporter], logger: LoggedRunnable) =
4646
if Properties.testsNoRun then addNoRunWarning()
4747
else
48-
val checkFile = testSource.checkFile.getOrElse(throw new Exception("Missing check file"))
48+
val checkFile = testSource.checkFile.getOrElse(throw new Exception("Missing check file")).toPath
4949
val debugSteps = DebugStepAssert.parseCheckFile(checkFile)
50-
val expressionEvaluator = ExpressionEvaluator(testSource.sourceFiles, testSource.flags, testSource.runClassPath, testSource.outDir)
51-
val status = debugMain(testSource.runClassPath): debuggee =>
52-
val debugger = Debugger(debuggee.jdiPort, expressionEvaluator, maxDuration/* , verbose = true */)
53-
// configure the breakpoints before starting the debuggee
54-
val breakpoints = debugSteps.map(_.step).collect { case b: DebugStep.Break => b }
55-
for b <- breakpoints do debugger.configureBreakpoint(b.className, b.line)
56-
try
57-
debuggee.launch()
58-
playDebugSteps(debugger, debugSteps/* , verbose = true */)
59-
finally
60-
// stop debugger to let debuggee terminate its execution
61-
debugger.dispose()
62-
status match
63-
case Success(output) => ()
64-
case Failure(output) =>
65-
if output == "" then
66-
echo(s"Test '${testSource.title}' failed with no output")
67-
else
68-
echo(s"Test '${testSource.title}' failed with output:")
69-
echo(output)
70-
failTestSource(testSource)
71-
case Timeout =>
72-
echo("failed because test " + testSource.title + " timed out")
73-
failTestSource(testSource, TimeoutFailure(testSource.title))
50+
val expressionEvaluator =
51+
ExpressionEvaluator(testSource.sourceFiles, testSource.flags, testSource.runClassPath, testSource.outDir)
52+
try
53+
val status = debugMain(testSource.runClassPath): debuggee =>
54+
val debugger = Debugger(debuggee.jdiPort, expressionEvaluator, maxDuration/* , verbose = true */)
55+
// configure the breakpoints before starting the debuggee
56+
val breakpoints = debugSteps.map(_.step).collect { case b: DebugStep.Break => b }
57+
for b <- breakpoints do debugger.configureBreakpoint(b.className, b.line)
58+
try
59+
debuggee.launch()
60+
playDebugSteps(debugger, debugSteps/* , verbose = true */)
61+
finally
62+
// stop debugger to let debuggee terminate its execution
63+
debugger.dispose()
64+
reportDebuggeeStatus(testSource, status)
65+
catch case DebugStepException(message, location) =>
66+
echo(s"\nDebug step failed: $location\n" + message)
67+
failTestSource(testSource)
7468
end verifyDebug
7569

7670
private def playDebugSteps(debugger: Debugger, steps: Seq[DebugStepAssert[?]], verbose: Boolean = false): Unit =
@@ -111,4 +105,18 @@ object DebugTests extends ParallelTesting:
111105
if verbose then println(s"eval $expr $result")
112106
assert(result)
113107
end playDebugSteps
108+
109+
private def reportDebuggeeStatus(testSource: TestSource, status: Status): Unit =
110+
status match
111+
case Success(output) => ()
112+
case Failure(output) =>
113+
if output == "" then
114+
echo(s"Test '${testSource.title}' failed with no output")
115+
else
116+
echo(s"Test '${testSource.title}' failed with output:")
117+
echo(output)
118+
failTestSource(testSource)
119+
case Timeout =>
120+
echo("failed because test " + testSource.title + " timed out")
121+
failTestSource(testSource, TimeoutFailure(testSource.title))
114122
end DebugTest

0 commit comments

Comments
 (0)