11package dotty .tools .debug
22
33import com .sun .jdi .Location
4- import dotty .tools .io .JFile
4+ import dotty .tools .io .JPath
55import dotty .tools .readLines
66
7+ import scala .annotation .tailrec
8+
79/**
810 * A debug step and an associated assertion to validate the step.
911 * A sequence of DebugStepAssert is parsed from the check file in tests/debug
1012 */
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 ]]
1229
1330private [debug] object DebugStepAssert :
1431 import DebugStep .*
15- def parseCheckFile (checkFile : JFile ): Seq [DebugStepAssert [? ]] =
32+ def parseCheckFile (checkFile : JPath ): Seq [DebugStepAssert [? ]] =
1633 val sym = " [a-zA-Z0-9$.]+"
1734 val line = " \\ d+"
1835 val trailing = s " \\ s*(?: \\ / \\ /.*)? " .r // empty or comment
@@ -24,13 +41,16 @@ private[debug] object DebugStepAssert:
2441 val result = " result (.*)" .r
2542 val error = " error (.*)" .r
2643 val multiLineError = s " error $trailing" .r
44+ val allLines = readLines(checkFile.toFile)
2745
46+ @ tailrec
2847 def loop (lines : List [String ], acc : List [DebugStepAssert [? ]]): List [DebugStepAssert [? ]] =
48+ given location : CheckFileLocation = CheckFileLocation (checkFile, allLines.size - lines.size + 1 )
2949 lines match
3050 case Nil => acc.reverse
3151 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 ))
3454 loop(tail, step :: acc)
3555 case step(pattern) :: tail =>
3656 val step = DebugStepAssert (Step , checkLineOrMethod(pattern))
@@ -49,70 +69,70 @@ private[debug] object DebugStepAssert:
4969 val step = DebugStepAssert (Eval (expr), assertion)
5070 loop(tail2, step :: acc)
5171 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) " )
5374
5475 def parseEvalAssertion (lines : List [String ]): (Either [String , String ] => Unit , List [String ]) =
76+ given location : CheckFileLocation = CheckFileLocation (checkFile, allLines.size - lines.size + 1 )
5577 lines match
5678 case Nil => throw new Exception (s " Missing result or error " )
79+ case trailing() :: tail => parseEvalAssertion(tail)
5780 case result(expected) :: tail => (checkResult(expected), tail)
5881 case error(expected) :: tail => (checkError(Seq (expected)), tail)
5982 case multiLineError() :: tail0 =>
6083 val (expected, tail1) = tail0.span(_.startsWith(" " ))
6184 (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) " )
6387
64- loop(readLines(checkFile) , Nil )
88+ loop(allLines , Nil )
6589 end parseCheckFile
6690
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)
7094
71- private def checkLineOrMethod (pattern : String ): Location => Unit =
95+ private def checkLineOrMethod (pattern : String )( using CheckFileLocation ) : Location => Unit =
7296 if " (\\ d+)" .r.matches(pattern) then checkLine(pattern.toInt) else checkMethod(pattern)
7397
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)
76100
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)
78103
79- private def checkResult (expected : String )(obtained : Either [String , String ]): Unit =
104+ private def checkResult (expected : String )(using CheckFileLocation )( obtained : Either [String , String ]): Unit =
80105 obtained match
81106 case Left (obtained) =>
82- val message =
107+ debugStepFailed(
83108 s """ |Evaluation failed:
84109 | ${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)
91112
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 =
93114 obtained match
94115 case Left (obtained) =>
95- val message =
116+ debugStepAssert(
117+ expected.forall(e => e.r.findFirstMatchIn(obtained).isDefined),
96118 s """ |Expected:
97- | ${expected.mkString(" \n " )}
119+ | ${expected.mkString(" \n | " )}
98120 |Obtained:
99121 | ${obtained.replace(" \n " , " \n |" )}""" .stripMargin
100- assert(expected.forall(e => e.r.findFirstMatchIn(obtained).isDefined), message )
122+ )
101123 case Right (obtained) =>
102- val message =
124+ debugStepFailed(
103125 s """ |Evaluation succeeded but failure expected.
104126 |Obtained: $obtained
105127 | """ .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+ )
116129
130+ private def debugStepAssertEquals [T ](obtained : T , expected : T )(using CheckFileLocation ): Unit =
131+ debugStepAssert(obtained == expected, s " Obtained $obtained, Expected: $expected" )
117132
133+ private def debugStepAssert (assertion : Boolean , message : String )(using CheckFileLocation ): Unit =
134+ if ! assertion then debugStepFailed(message)
118135
136+ private def debugStepFailed (message : String )(using location : CheckFileLocation ): Unit =
137+ throw DebugStepException (message, location)
138+ end DebugStepAssert
0 commit comments