All Downloads are FREE. Search and download functionalities are using the official Maven repository.

utest.asserts.Asserts.scala Maven / Gradle / Ivy

There is a newer version: 0.7.11
Show newest version
package utest
package asserts
//import acyclic.file
import scala.annotation.{tailrec, StaticAnnotation}
import scala.reflect.macros.{ParseException, TypecheckException, Context}
import scala.util.{Failure, Success, Try, Random}
import scala.reflect.ClassTag
import scala.reflect.internal.util.{Position, OffsetPosition}
import scala.reflect.internal.util.OffsetPosition
import scala.reflect.macros.{TypecheckException, Context}

import scala.language.experimental.macros

/**
 * Macro implementation that provides rich error
 * message for boolean expression assertion.
 */
object Asserts {
  def compileError(c: Context)(expr: c.Expr[String]): c.Expr[CompileError] = {
    import c.universe._
    def calcPosMsg(pos: scala.reflect.api.Position) = {
      if (pos == NoPosition) ""
      else pos.lineContent + "\n" + (" " * pos.column) + "^"
    }
    val stringStart =
      expr.tree
         .pos
         .lineContent
         .slice(expr.tree.pos.column, expr.tree.pos.column + 2)


    val quoteOffset = if (stringStart == "\"\"") 2 else 0

    expr.tree match {
      case Literal(Constant(s: String)) =>
        try{

          val tree = c.parse(s)
          for(x <- tree if x.pos != NoPosition){
            import compat._
            x.pos = new OffsetPosition(
              expr.tree.pos.source,
              x.pos.point + expr.tree.pos.point + quoteOffset
            ).asInstanceOf[c.universe.Position]
          }

          val tree2 = c.typeCheck(tree)
          val compileTimeOnlyType = typeOf[scala.reflect.internal.annotations.compileTimeOnly]

          val compileTimeOnlyTree = tree2.collect{ case t =>
            Option(t.symbol)
              .map(_.annotations)
              .toSeq
              .flatten
              .filter(_.tpe =:= compileTimeOnlyType)
              .map(t -> _)
          }.flatten


         compileTimeOnlyTree.headOption match{
            case Some((tree, annot)) =>
              val msg = annot.scalaArgs.head match{
                case Literal(Constant(s: String)) => s
                case t => t.toString
              }
              c.Expr[CompileError](
                q"""utest.CompileError.CompileTimeOnly(${calcPosMsg(tree.pos)}, $msg)"""
              )
            case None =>
              c.abort(
                c.enclosingPosition,
                "compileError check failed to have a compilation error"
              )
          }

        } catch{
          case TypecheckException(pos, msg) =>
            c.Expr[CompileError](q"""utest.CompileError.Type(${calcPosMsg(pos)}, $msg)""")
          case ParseException(pos, msg) =>
            c.Expr[CompileError](q"""utest.CompileError.Parse(${calcPosMsg(pos)}, $msg)""")
          case e: Exception =>
            println("SOMETHING WENT WRONG LOLS " + e)
            throw e
        }
      case e =>
        c.abort(
          expr.tree.pos,
          s"You can only have literal strings in compileError, not ${expr.tree}"
        )
    }
  }

  def assertProxy(c: Context)(exprs: c.Expr[Boolean]*): c.Expr[Unit] = {
    import c.universe._
    Tracer[Boolean](c)(q"utest.asserts.Asserts.assertImpl", exprs:_*)
  }


  def assertImpl(funcs: AssertEntry[Boolean]*) = {
    val tries = for (entry <- funcs) yield Try{
      val (value, die) = getAssertionEntry(entry)
      if (!value) die(null)
    }
    val failures = tries.collect{case util.Failure(thrown) => thrown}
    failures match{
      case Seq() => () // nothing failed, do nothing
      case Seq(failure) => throw failure
      case multipleFailures => throw new MultipleErrors(multipleFailures:_*)
    }
  }

  def interceptProxy[T: c.WeakTypeTag]
                    (c: Context)
                    (exprs: c.Expr[Unit])
                    (t: c.Expr[ClassTag[T]]): c.Expr[T] = {
    import c.universe._
    val typeTree = implicitly[c.WeakTypeTag[T]]

    val x = Tracer[Unit](c)(q"utest.asserts.Asserts.interceptImpl[$typeTree]", exprs)
    c.Expr[T](q"$x($t)")
  }

  /**
   * Asserts that the given block raises the expected exception. The exception
   * is returned if raised, and an `AssertionError` is raised if the expected
   * exception does not appear.
   */
  def interceptImpl[T: ClassTag](entry: AssertEntry[Unit]): T = {
    val (res, logged, src) = runAssertionEntry(entry)
    res match{
      case Failure(e: T) => e
      case Failure(e: Throwable) => assertError(src, logged, e)
      case Success(v) => assertError(src, logged, null)
    }
  }

  def assertMatchProxy(c: Context)
                      (t: c.Expr[Any])
                      (pf: c.Expr[PartialFunction[Any, Unit]]): c.Expr[Unit] = {
    import c.universe._
    val x = Tracer[Any](c)(q"utest.asserts.Asserts.assertMatchImpl", t)
    c.Expr[Unit](q"$x($pf)")
  }

  /**
   * Asserts that the given block raises the expected exception. The exception
   * is returned if raised, and an `AssertionError` is raised if the expected
   * exception does not appear.
   */
  def assertMatchImpl(entry: AssertEntry[Any])
                     (pf: PartialFunction[Any, Unit]): Unit = {
    val (value, die) = getAssertionEntry(entry)
    if (pf.isDefinedAt(value)) ()
    else die(null)
  }
}
object DummyTypeclass {
  implicit def DummyImplicit[T] = new DummyTypeclass[T]
}
class DummyTypeclass[+T]
class Show extends StaticAnnotation
trait Asserts[V[_]]{
  def assertPrettyPrint[T: V](t: T): String

  /**
    * Provides a nice syntax for asserting things are equal, that is pretty
    * enough to embed in documentation and examples
    */
  implicit class ArrowAssert[T](lhs: T){
    def ==>[V](rhs: V) = {
      (lhs, rhs) match{
          // Hack to make Arrays compare sanely; at some point we may want some
          // custom, extensible, typesafe equality check but for now this will do
          case (lhs: Array[_], rhs: Array[_]) =>
            Predef.assert(lhs.toSeq == rhs.toSeq, s"==> assertion failed: ${lhs.toSeq} != ${rhs.toSeq}")
          case (lhs, rhs) => 
            Predef.assert(lhs == rhs, s"==> assertion failed: $lhs != $rhs")
        }
    }
  }

  /**
    * Asserts that the given expression fails to compile, and returns a
    * [[framework.CompileError]] containing the message of the failure. If the expression
    * compile successfully, this macro itself will raise a compilation error.
    */
  def compileError(expr: String): CompileError = macro Asserts.compileError
  /**
    * Checks that one or more expressions are true; otherwises raises an
    * exception with some debugging info
    */
  def assert(exprs: Boolean*): Unit = macro Asserts.assertProxy
  /**
    * Checks that one or more expressions all become true within a certain
    * period of time. Polls at a regular interval to check this.
    */
  def eventually(exprs: Boolean*): Unit = macro Parallel.eventuallyProxy
  /**
    * Checks that one or more expressions all remain true within a certain
    * period of time. Polls at a regular interval to check this.
    */
  def continually(exprs: Boolean*): Unit = macro Parallel.continuallyProxy

  /**
    * Asserts that the given value matches the PartialFunction. Useful for using
    * pattern matching to validate the shape of a data structure.
    */
  def assertMatch(t: Any)(pf: PartialFunction[Any, Unit]): Unit =  macro Asserts.assertMatchProxy


  /**
    * Asserts that the given block raises the expected exception. The exception
    * is returned if raised, and an `AssertionError` is raised if the expected
    * exception does not appear.
    */
  def intercept[T: ClassTag](exprs: Unit): T = macro Asserts.interceptProxy[T]

  @tailrec final def retry[T](n: Int)(body: => T): T = {
    try body
    catch{case e: Throwable =>
      if (n > 0) retry(n-1)(body)
      else throw e
    }
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy