1
1
package dotty .tools .debug
2
2
3
3
import com .sun .jdi .Location
4
- import dotty .tools .io .JFile
4
+ import dotty .tools .io .JPath
5
5
import dotty .tools .readLines
6
6
7
+ import scala .annotation .tailrec
8
+
7
9
/**
8
10
* A debug step and an associated assertion to validate the step.
9
11
* A sequence of DebugStepAssert is parsed from the check file in tests/debug
10
12
*/
11
- private [debug] case class DebugStepAssert [T ](step : DebugStep [T ], assert : T => Unit )
13
+ private [debug] case class DebugStepAssert [T ](step : DebugStep [T ], assert : T => Unit )(
14
+ using val location : CheckFileLocation
15
+ )
16
+
17
+ /** A location in the check file */
18
+ private [debug] case class CheckFileLocation (checkFile : JPath , line : Int ):
19
+ override def toString : String = s " $checkFile: $line"
20
+
21
+ /** When a DebugStepAssert fails it throws a DebugStepException */
22
+ private [debug] case class DebugStepException (message : String , location : CheckFileLocation ) extends Exception
23
+
24
+ private [debug] enum DebugStep [T ]:
25
+ case Break (className : String , line : Int ) extends DebugStep [Location ]
26
+ case Step extends DebugStep [Location ]
27
+ case Next extends DebugStep [Location ]
28
+ case Eval (expression : String ) extends DebugStep [Either [String , String ]]
12
29
13
30
private [debug] object DebugStepAssert :
14
31
import DebugStep .*
15
- def parseCheckFile (checkFile : JFile ): Seq [DebugStepAssert [? ]] =
32
+ def parseCheckFile (checkFile : JPath ): Seq [DebugStepAssert [? ]] =
16
33
val sym = " [a-zA-Z0-9$.]+"
17
34
val line = " \\ d+"
18
35
val trailing = s " \\ s*(?: \\ / \\ /.*)? " .r // empty or comment
@@ -24,13 +41,16 @@ private[debug] object DebugStepAssert:
24
41
val result = " result (.*)" .r
25
42
val error = " error (.*)" .r
26
43
val multiLineError = s " error $trailing" .r
44
+ val allLines = readLines(checkFile.toFile)
27
45
46
+ @ tailrec
28
47
def loop (lines : List [String ], acc : List [DebugStepAssert [? ]]): List [DebugStepAssert [? ]] =
48
+ given location : CheckFileLocation = CheckFileLocation (checkFile, allLines.size - lines.size + 1 )
29
49
lines match
30
50
case Nil => acc.reverse
31
51
case break(className , lineStr) :: tail =>
32
- val line = lineStr.toInt
33
- val step = DebugStepAssert (Break (className, line ), checkClassAndLine(className, line ))
52
+ val breakpointLine = lineStr.toInt
53
+ val step = DebugStepAssert (Break (className, breakpointLine ), checkClassAndLine(className, breakpointLine ))
34
54
loop(tail, step :: acc)
35
55
case step(pattern) :: tail =>
36
56
val step = DebugStepAssert (Step , checkLineOrMethod(pattern))
@@ -49,70 +69,70 @@ private[debug] object DebugStepAssert:
49
69
val step = DebugStepAssert (Eval (expr), assertion)
50
70
loop(tail2, step :: acc)
51
71
case trailing() :: tail => loop(tail, acc)
52
- case invalid :: tail => throw new Exception (s " Cannot parse debug step: $invalid" )
72
+ case invalid :: tail =>
73
+ throw new Exception (s " Cannot parse debug step: $invalid ( $location) " )
53
74
54
75
def parseEvalAssertion (lines : List [String ]): (Either [String , String ] => Unit , List [String ]) =
76
+ given location : CheckFileLocation = CheckFileLocation (checkFile, allLines.size - lines.size + 1 )
55
77
lines match
56
78
case Nil => throw new Exception (s " Missing result or error " )
79
+ case trailing() :: tail => parseEvalAssertion(tail)
57
80
case result(expected) :: tail => (checkResult(expected), tail)
58
81
case error(expected) :: tail => (checkError(Seq (expected)), tail)
59
82
case multiLineError() :: tail0 =>
60
83
val (expected, tail1) = tail0.span(_.startsWith(" " ))
61
84
(checkError(expected.map(_.stripPrefix(" " ))), tail1)
62
- case invalid :: _ => throw new Exception (s " Cannot parse as result or error: $invalid" )
85
+ case invalid :: _ =>
86
+ throw new Exception (s " Cannot parse as result or error: $invalid ( $location) " )
63
87
64
- loop(readLines(checkFile) , Nil )
88
+ loop(allLines , Nil )
65
89
end parseCheckFile
66
90
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)
91
+ private def checkClassAndLine (className : String , breakpointLine : Int )( using CheckFileLocation )(location : Location ): Unit =
92
+ debugStepAssertEquals( location.declaringType.name, className)
93
+ checkLine(breakpointLine )(location)
70
94
71
- private def checkLineOrMethod (pattern : String ): Location => Unit =
95
+ private def checkLineOrMethod (pattern : String )( using CheckFileLocation ) : Location => Unit =
72
96
if " (\\ d+)" .r.matches(pattern) then checkLine(pattern.toInt) else checkMethod(pattern)
73
97
74
- private def checkLine (line : Int )(location : Location ): Unit =
75
- assert (location.lineNumber == line, s " obtained ${location.lineNumber} , expected $line " )
98
+ private def checkLine (expected : Int )( using CheckFileLocation )(location : Location ): Unit =
99
+ debugStepAssertEquals (location.lineNumber, expected)
76
100
77
- private def checkMethod (methodName : String )(location : Location ): Unit = assert(methodName == location.method.name)
101
+ private def checkMethod (expected : String )(using CheckFileLocation )(location : Location ): Unit =
102
+ debugStepAssertEquals(location.method.name, expected)
78
103
79
- private def checkResult (expected : String )(obtained : Either [String , String ]): Unit =
104
+ private def checkResult (expected : String )(using CheckFileLocation )( obtained : Either [String , String ]): Unit =
80
105
obtained match
81
106
case Left (obtained) =>
82
- val message =
107
+ debugStepFailed(
83
108
s """ |Evaluation failed:
84
109
| ${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)
110
+ )
111
+ case Right (obtained) => debugStepAssertEquals(obtained, expected)
91
112
92
- private def checkError (expected : Seq [String ])(obtained : Either [String , String ]): Unit =
113
+ private def checkError (expected : Seq [String ])(using CheckFileLocation )( obtained : Either [String , String ]): Unit =
93
114
obtained match
94
115
case Left (obtained) =>
95
- val message =
116
+ debugStepAssert(
117
+ expected.forall(e => e.r.findFirstMatchIn(obtained).isDefined),
96
118
s """ |Expected:
97
- | ${expected.mkString(" \n " )}
119
+ | ${expected.mkString(" \n | " )}
98
120
|Obtained:
99
121
| ${obtained.replace(" \n " , " \n |" )}""" .stripMargin
100
- assert(expected.forall(e => e.r.findFirstMatchIn(obtained).isDefined), message )
122
+ )
101
123
case Right (obtained) =>
102
- val message =
124
+ debugStepFailed(
103
125
s """ |Evaluation succeeded but failure expected.
104
126
|Obtained: $obtained
105
127
| """ .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 ]]
128
+ )
116
129
130
+ private def debugStepAssertEquals [T ](obtained : T , expected : T )(using CheckFileLocation ): Unit =
131
+ debugStepAssert(obtained == expected, s " Obtained $obtained, Expected: $expected" )
117
132
133
+ private def debugStepAssert (assertion : Boolean , message : String )(using CheckFileLocation ): Unit =
134
+ if ! assertion then debugStepFailed(message)
118
135
136
+ private def debugStepFailed (message : String )(using location : CheckFileLocation ): Unit =
137
+ throw DebugStepException (message, location)
138
+ end DebugStepAssert
0 commit comments