Skip to content

Commit

Permalink
Merge pull request #536 from adpi2/fix-535
Browse files Browse the repository at this point in the history
Fix format local method with unloaded parameters + refactor some tests
  • Loading branch information
adpi2 committed Jul 30, 2023
2 parents 50df1e5 + 200a193 commit faccb7f
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,12 @@ abstract class ScalaUnpickler(scalaVersion: ScalaVersion, testMode: Boolean) ext
}

private def formatJava(method: Method): String = {
val declaringType = method.declaringType().name.split("\\.").last
val methodName = method.name()
val argumentTypes = method.argumentTypes.asScala.toList
.map(t => t.name().split("\\.").last)
val declaringType = method.declaringType.name.split("\\.").last
val argumentTypes = method.argumentTypeNames.asScala
.map(arg => arg.split("\\.").last)
.mkString(",")
val returnType = method.returnTypeName().split("\\.").last
s"$declaringType.$methodName(${if (argumentTypes.nonEmpty) argumentTypes else ""}): $returnType"
val returnType = method.returnTypeName.split("\\.").last
s"$declaringType.${method.name}($argumentTypes): $returnType"
}

private def isStaticMain(m: Method): Boolean =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,65 +11,65 @@ import scala.Console.*
import DebugStepAssert.*

sealed trait DebugStepAssert
final case class SingleStepAssert[T](step: DebugStep[T], assertion: T => Unit) extends DebugStepAssert
final case class ParallelStepsAsserts[T](steps: Seq[SingleStepAssert[T]]) extends DebugStepAssert

sealed trait DebugStep[+T]
final case class Breakpoint(sourceFile: Path, line: Int, condition: Option[String]) extends DebugStep[List[StackFrame]]
final case class Logpoint(sourceFile: Path, line: Int, logMessage: String) extends DebugStep[String]
final case class StepIn() extends DebugStep[List[StackFrame]]
final case class StepOut() extends DebugStep[List[StackFrame]]
final case class StepOver() extends DebugStep[List[StackFrame]]
final case class Evaluation(expression: String) extends DebugStep[Either[String, String]]
final case class Outputed() extends DebugStep[String]
final case class NoStep() extends DebugStep[Nothing]
final case class Watch(variable: String) extends DebugStep[Array[Variable]]

final case class ObjectRef(clsName: String)

object DebugStepAssert {
def inParallel(steps: SingleStepAssert[Either[String, String]]*): ParallelStepsAsserts[Either[String, String]] =
ParallelStepsAsserts(steps)

def assertOnFrame(expectedSource: Path, expectedLine: Int, expectedStackTrace: Option[List[String]])(
frames: List[StackFrame]
)(implicit location: Location): Unit = {
def assertOnFrame(expectedSource: Path, expectedLine: Int)(frames: Seq[StackFrame])(implicit
location: Location
): Unit = {
assertEquals(frames.head.source.path, expectedSource.toString)
assertEquals(frames.head.line, expectedLine)
expectedStackTrace.foreach { expected =>
val obtained = frames.map(frame => frame.name)
assertEquals(obtained, expected)
}
}

def assertOnFrame(expectedName: String)(frames: List[StackFrame])(implicit loc: Location): Unit =
def assertOnFrame(expectedName: String)(frames: Seq[StackFrame])(implicit loc: Location): Unit =
assertEquals(frames.head.name, expectedName)

def assertOnFrame(expected: Seq[String])(stackTrace: Seq[StackFrame])(implicit
location: Location
): Unit = {
val obtained = stackTrace.map(frame => frame.name)
assertEquals(obtained, expected)
}
}

final case class SingleStepAssert[T](step: DebugStep[T], assertion: T => Unit) extends DebugStepAssert
final case class ParallelStepsAsserts[T](steps: Seq[SingleStepAssert[T]]) extends DebugStepAssert

sealed trait DebugStep[+T]

final case class Breakpoint(sourceFile: Path, line: Int, condition: Option[String]) extends DebugStep[Seq[StackFrame]]
object Breakpoint {
def apply(line: Int)(implicit ctx: TestingContext, location: Location): SingleStepAssert[List[StackFrame]] = {
def apply(line: Int)(implicit ctx: TestingContext, location: Location): SingleStepAssert[Seq[StackFrame]] = {
val breakpoint = Breakpoint(ctx.mainSource, line, None)
SingleStepAssert(breakpoint, assertOnFrame(ctx.mainSource, line, None))
SingleStepAssert(breakpoint, assertOnFrame(ctx.mainSource, line))
}

def apply(sourceFile: Path, line: Int): SingleStepAssert[List[StackFrame]] = {
def apply(sourceFile: Path, line: Int): SingleStepAssert[Seq[StackFrame]] = {
val breakpoint = Breakpoint(sourceFile, line, None)
SingleStepAssert(breakpoint, assertOnFrame(sourceFile, line, None))
SingleStepAssert(breakpoint, assertOnFrame(sourceFile, line))
}
def apply(line: Int, expectedStackTrace: List[String])(implicit
def apply(line: Int, expectedStackTrace: Seq[String])(implicit
ctx: TestingContext,
location: Location
): SingleStepAssert[List[StackFrame]] = {
): SingleStepAssert[Seq[StackFrame]] = {
val breakpoint = Breakpoint(ctx.mainSource, line, None)
SingleStepAssert(breakpoint, assertOnFrame(ctx.mainSource, line, Some(expectedStackTrace)))
SingleStepAssert(
breakpoint,
{ trace =>
assertOnFrame(ctx.mainSource, line)(trace); assertOnFrame(expectedStackTrace)(trace)
}
)
}

def apply(line: Int, condition: String)(implicit ctx: TestingContext): SingleStepAssert[List[StackFrame]] = {
def apply(line: Int, condition: String)(implicit ctx: TestingContext): SingleStepAssert[Seq[StackFrame]] = {
val breakpoint = Breakpoint(ctx.mainSource, line, Some(condition))
SingleStepAssert(breakpoint, assertOnFrame(ctx.mainSource, line, None))
SingleStepAssert(breakpoint, assertOnFrame(ctx.mainSource, line))
}
}

final case class Logpoint(sourceFile: Path, line: Int, logMessage: String) extends DebugStep[String]
object Logpoint {
def apply(line: Int, logMessage: String, expected: String)(implicit
ctx: TestingContext,
Expand All @@ -80,30 +80,31 @@ object Logpoint {
}
}

object StepIn {
def line(line: Int)(implicit ctx: TestingContext, location: Location): SingleStepAssert[List[StackFrame]] =
SingleStepAssert(StepIn(), assertOnFrame(ctx.mainSource, line, None))
object StepIn extends DebugStep[Seq[StackFrame]] {
def line(line: Int)(implicit ctx: TestingContext, location: Location): SingleStepAssert[Seq[StackFrame]] =
SingleStepAssert(StepIn, assertOnFrame(ctx.mainSource, line))

def method(methodName: String)(implicit loc: Location): SingleStepAssert[List[StackFrame]] =
SingleStepAssert(StepIn(), assertOnFrame(methodName))
def method(methodName: String)(implicit loc: Location): SingleStepAssert[Seq[StackFrame]] =
SingleStepAssert(StepIn, assertOnFrame(methodName))
}

object StepOut {
def line(line: Int)(implicit ctx: TestingContext): SingleStepAssert[List[StackFrame]] =
SingleStepAssert(StepOut(), assertOnFrame(ctx.mainSource, line, None))
object StepOut extends DebugStep[Seq[StackFrame]] {
def line(line: Int)(implicit ctx: TestingContext): SingleStepAssert[Seq[StackFrame]] =
SingleStepAssert(StepOut, assertOnFrame(ctx.mainSource, line))

def method(methodName: String): SingleStepAssert[List[StackFrame]] =
SingleStepAssert(StepOut(), assertOnFrame(methodName))
def method(methodName: String): SingleStepAssert[Seq[StackFrame]] =
SingleStepAssert(StepOut, assertOnFrame(methodName))
}

object StepOver {
def line(line: Int)(implicit ctx: TestingContext): SingleStepAssert[List[StackFrame]] =
SingleStepAssert(StepOver(), assertOnFrame(ctx.mainSource, line, None))
object StepOver extends DebugStep[Seq[StackFrame]] {
def line(line: Int)(implicit ctx: TestingContext): SingleStepAssert[Seq[StackFrame]] =
SingleStepAssert(StepOver, assertOnFrame(ctx.mainSource, line))

def method(methodName: String): SingleStepAssert[List[StackFrame]] =
SingleStepAssert(StepOver(), assertOnFrame(methodName))
def method(methodName: String): SingleStepAssert[Seq[StackFrame]] =
SingleStepAssert(StepOver, assertOnFrame(methodName))
}

final case class Evaluation(expression: String) extends DebugStep[Either[String, String]]
object Evaluation {
def ignore(expression: String, expected: Any)(implicit
ctx: TestingContext
Expand Down Expand Up @@ -208,27 +209,25 @@ object Evaluation {
}
}

object Watch {
def success(variable: String)(p: Array[Variable] => Boolean)(implicit
ctx: TestingContext,
final case class LocalVariable(name: String) extends DebugStep[Array[Variable]]
object LocalVariable {
def inspect(variable: String)(p: Array[Variable] => Boolean)(implicit
location: Location
): SingleStepAssert[Array[Variable]] =
new SingleStepAssert(new Watch(variable), assertSuccess(p))

def assertSuccess(p: Array[Variable] => Boolean)(
response: Array[Variable]
)(implicit ctx: TestingContext, location: Location): Unit = assert(p(response))
new SingleStepAssert(new LocalVariable(variable), values => assert(p(values)))
}

object Outputed {
object Outputed extends DebugStep[String] {
def apply(expected: String): SingleStepAssert[String] =
apply(message => assertEquals(message, expected))

def apply(assertion: String => Unit): SingleStepAssert[String] =
new SingleStepAssert(Outputed(), assertion)
new SingleStepAssert(Outputed, assertion)
}

object NoStep {
object NoStep extends DebugStep[Nothing] {
def apply(): SingleStepAssert[Nothing] =
new SingleStepAssert[Nothing](new NoStep(), _ => ())
new SingleStepAssert[Nothing](NoStep, _ => ())
}

final case class ObjectRef(clsName: String)
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import ch.epfl.scala.debugadapter.DebugConfig
import scala.concurrent.Future
import scala.concurrent.Await
import com.microsoft.java.debug.core.protocol.Types.Variable
import scala.util.Properties

abstract class DebugTestSuite extends FunSuite with DebugTest {
override def munitTimeout: Duration = 120.seconds
Expand All @@ -29,6 +30,9 @@ trait DebugTest {

protected def defaultConfig: DebugConfig = DebugConfig.default.copy(autoCloseSession = false, testMode = true)

def javaVersion: String = Properties.javaVersion

def isJava8 = javaVersion.contains("1.8")
def isScala3(implicit ctx: TestingContext) = ctx.scalaVersion.isScala3
def isScala2(implicit ctx: TestingContext) = ctx.scalaVersion.isScala2
def isScala213(implicit ctx: TestingContext) = ctx.scalaVersion.isScala213
Expand Down Expand Up @@ -141,13 +145,12 @@ trait DebugTest {
}
}

def evaluateWatchExpression(watch: Watch, assertion: Array[Variable] => Unit): Unit = {
println(s"(watch)$$ ${watch.variable}")
val localScopeRef = client.scopes(topFrame.id).find(_.name == "Local").map(_.variablesReference)
val variable =
localScopeRef.flatMap(i => client.variables(i).find(_.name == watch.variable).map(_.variablesReference))
val body = variable.map(i => client.variables(i)).getOrElse(Array[Variable]())
assertion(body)
def inspect(variable: LocalVariable, assertion: Array[Variable] => Unit): Unit = {
val values = for {
localScopeRef <- client.scopes(topFrame.id).find(_.name == "Local").map(_.variablesReference)
variableRef <- client.variables(localScopeRef).find(_.name == variable.name).map(_.variablesReference)
} yield client.variables(variableRef)
assertion(values.getOrElse(throw new NoSuchElementException(variable.name)))
}

def assertStop(assertion: List[StackFrame] => Unit): Unit = {
Expand All @@ -169,24 +172,26 @@ trait DebugTest {
val event = client.outputed(m => m.category == Category.stdout, 16.seconds)
print(s"> ${event.output}")
assertion(event.output.trim)
case SingleStepAssert(_: StepIn, assertion) =>
case SingleStepAssert(StepIn, assertion) =>
println(s"Stepping in, at ${formatFrame(topFrame)}")
client.stepIn(threadId)
assertStop(assertion)
case SingleStepAssert(_: StepOut, assertion) =>
case SingleStepAssert(StepOut, assertion) =>
println(s"Stepping out, at ${formatFrame(topFrame)}")
client.stepOut(threadId)
assertStop(assertion)
case SingleStepAssert(_: StepOver, assertion) =>
???
case SingleStepAssert(StepOver, assertion) =>
println(s"Stepping over, at ${formatFrame(topFrame)}")
client.stepOver(threadId)
assertStop(assertion)
case SingleStepAssert(eval: Evaluation, assertion) =>
Await.result(evaluateExpression(eval, assertion), 16.seconds)
case SingleStepAssert(Outputed(), assertion) =>
case SingleStepAssert(Outputed, assertion) =>
continueIfPaused()
val event = client.outputed(m => m.category == Category.stdout)
print(s"> ${event.output}")
assertion(event.output.trim)
case SingleStepAssert(_: NoStep, _) => ()
case SingleStepAssert(NoStep, _) => ()
case ParallelStepsAsserts(steps) =>
val evaluations = steps.map { step =>
evaluateExpression(
Expand All @@ -195,8 +200,8 @@ trait DebugTest {
)
}
Await.result(Future.sequence(evaluations), 64.seconds)
case SingleStepAssert(watch: Watch, assertion) =>
evaluateWatchExpression(watch, assertion)
case SingleStepAssert(localVariable: LocalVariable, assertion) =>
inspect(localVariable, assertion)
}
continueIfPaused()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ class TestingDebugClient(socket: Socket, logger: Logger)(implicit
Await.result(response, timeout)
}

def stepOver(threadId: Long, timeout: Duration = 1.second): Unit = {
val args = new NextArguments()
args.threadId = threadId
val request = createRequest(Command.NEXT, args)
val response = sendRequest(request)
Await.result(response, timeout)
}

def evaluate(
expression: String,
frameId: Int
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ch.epfl.scala.debugadapter.internal

import ch.epfl.scala.debugadapter.ScalaVersion
import ch.epfl.scala.debugadapter.testfmk.*

class LocalVariableTests extends DebugTestSuite {
val scalaVersion = ScalaVersion.`3.1+`

test("Should set the right expression for array elements") {
val source =
"""|package example
|
|object Main {
| def main(args: Array[String]): Unit = {
| val array = Array(1, 2, 3)
| println("ok")
| }
|}""".stripMargin
implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion)
val regexp = """array\(\d+\)""".r
check(
Breakpoint(6),
LocalVariable.inspect("array")(
_.forall(v => """array\(\d+\)""".r.unapplySeq(v.evaluateName).isDefined)
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,26 @@ class ScalaStackTraceTests extends DebugTestSuite {
)
)
}

test("i535") {
assume(isJava8)
val source =
"""|package example
|
|import java.lang.ref.ReferenceQueue
|
|object Main {
| def main(args: Array[String]): Unit = {
| new ReferenceQueue[Any]()
| }
|}
|""".stripMargin
implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion)
check(
Breakpoint(7),
StepIn.method("ReferenceQueue.<init>(): void"),
StepOver.method("ReferenceQueue.<init>(): void"),
StepIn.method("ReferenceQueue$Lock.<init>(ReferenceQueue$1): void")
)
}
}

This file was deleted.

0 comments on commit faccb7f

Please sign in to comment.