PageRenderTime 43ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/scala/org/specs2/execute/Result.scala

http://github.com/etorreborre/specs2
Scala | 332 lines | 205 code | 28 blank | 99 comment | 17 complexity | ab46bb9703282fa6a5d60c59e263feb4 MD5 | raw file
  1. package org.specs2
  2. package execute
  3. import control.Throwablex
  4. import control.Throwablex._
  5. import text.AnsiColors._
  6. import text.NotNullStrings._
  7. import main.Arguments
  8. import org.specs2.internal.scalaz.Monoid
  9. /**
  10. * The result of an execution, either:
  11. *
  12. * - a success: the execution is ok
  13. * - a failure: an expectation is not met
  14. * - an error: an exception occurred
  15. * - a pending execution: the user has decided that execution must not be performed
  16. * - a skipped execution: based on dynamic conditions (a database not available for instance)
  17. * the execution is not performed
  18. *
  19. * A Result has:
  20. * - a message describing the outcome
  21. * - a message describing the expectation
  22. * - possibly a number of expectations
  23. * when it is the outcome of several checks (this is used for the reporting of ScalaCheck properties).
  24. *
  25. */
  26. sealed abstract class Result(val message: String = "", val expected: String = "", val expectationsNb: Int = 1) {
  27. /**
  28. * @return the colored textual status of the result
  29. */
  30. def coloredStatus(implicit args: Arguments = Arguments()): String = {
  31. if (args.plan)
  32. args.pendingColor("*")
  33. else {
  34. this match {
  35. case Success(_) => args.successColor("+")
  36. case Failure(_, _, _, _) => args.failureColor("x")
  37. case Error(_, _) => args.errorColor ("!")
  38. case Pending(_) => args.pendingColor("*")
  39. case Skipped(_, _) => args.skippedColor("o")
  40. case DecoratedResult(_, r) => r.coloredStatus(args)
  41. }
  42. }
  43. }
  44. private val nocolor = Arguments("nocolor")
  45. /**
  46. * @return the uncolored textual status of the result
  47. */
  48. def status: String = coloredStatus(nocolor)
  49. /** @return the textual status of the result */
  50. def statusName(implicit args: Arguments = Arguments()): String =
  51. if (args.plan)
  52. "info"
  53. else {
  54. this match {
  55. case Success(_) => "success"
  56. case Failure(_, _, _, _) => "failure"
  57. case Error(_, _) => "error"
  58. case Pending(_) => "pending"
  59. case Skipped(_, _) => "skipped"
  60. case DecoratedResult(_, r) => r.statusName(args)
  61. }
  62. }
  63. /** update the message of a result, keeping the subclass type */
  64. def updateMessage(msg: String): Result =
  65. this match {
  66. case Success(m) => Success(msg)
  67. case Failure(m, e, st, d) => Failure(msg, e, st, d)
  68. case Error(m, st) => Error(msg, st)
  69. case Skipped(m, e) => Skipped(msg, e)
  70. case Pending(m) => Pending(msg)
  71. case DecoratedResult(t, r) => DecoratedResult(t, r.updateMessage(msg))
  72. }
  73. /**
  74. * increment the number of expectations
  75. */
  76. def addExpectationsNb(n: Int): Result
  77. /**
  78. * @return the logical and combination of 2 results
  79. */
  80. def and(r: =>Result): Result = this.addExpectationsNb(r.expectationsNb)
  81. /**
  82. * @return the logical or combination of 2 results
  83. */
  84. def or(r: =>Result) = this.addExpectationsNb(r.expectationsNb)
  85. /**
  86. * @return true if the result is a Success instance
  87. */
  88. def isSuccess: Boolean = false
  89. /**
  90. * @return true if the result is an Error instance
  91. */
  92. def isError: Boolean = false
  93. /**
  94. * @return true if the result is a Skipped instance
  95. */
  96. def isSkipped: Boolean = false
  97. /**
  98. * @return true if the result is a Pending instance
  99. */
  100. def isPending: Boolean = false
  101. /**
  102. * @return true if the result is a Failure instance
  103. */
  104. def isFailure: Boolean = false
  105. /**
  106. * @return the result with no message
  107. */
  108. def mute: Result
  109. /**
  110. * @return Success if it is a failure and vice-versa
  111. */
  112. def not: Result = this
  113. }
  114. object Result {
  115. implicit val ResultMonoid: Monoid[Result] = new Monoid[Result] {
  116. val zero = Success()
  117. def append(m1: Result, m2: =>Result) = (m1, m2) match {
  118. case (Success(msg1), Success(msg2)) => Success(msg1+"; "+msg2)
  119. case (Success(msg1), Skipped(msg2, e2)) => Success(msg1+"; "+msg2)
  120. case (Skipped(msg1, e2), Success(msg2)) => Success(msg1+"; "+msg2)
  121. case (Pending(msg1), Success(msg2)) => Success(msg1+"; "+msg2)
  122. case (Success(msg1), Pending(msg2)) => Success(msg1+"; "+msg2)
  123. case (Success(msg1), Failure(msg2, e2, st1, d2)) => m2.updateMessage(msg1+"; "+msg2)
  124. case (Failure(msg1, e1, st1, d1), Failure(msg2, e2, st2, d2)) => Failure(msg1+"; "+msg2, e1+"; "+e2, st1, NoDetails())
  125. case (Success(msg1), Error(msg2, st1)) => m2.updateMessage(msg1+"; "+msg2)
  126. case (Error(msg1, st1), Error(msg2, st2)) => Error(msg1+"; "+msg2, st1)
  127. case (Error(msg1, st1), Failure(msg2, e2, st2, d2)) => Error(msg1+"; "+msg2, st1)
  128. case (Skipped(msg1, e1), Skipped(msg2, e2)) => Skipped(msg1+"; "+msg2, e1+"; "+e2)
  129. case (Skipped(msg1, e1), Pending(msg2)) => Pending(msg1+"; "+msg2)
  130. case (Pending(msg1), Skipped(msg2, e2)) => Pending(msg1+"; "+msg2)
  131. case (Pending(msg1), Pending(msg2)) => Pending(msg1+"; "+msg2)
  132. case (DecoratedResult(t, r), other) => DecoratedResult(t, append(r, other))
  133. case (other, DecoratedResult(t, r)) => DecoratedResult(t, append(other, r))
  134. case (Failure(msg1, e1, st, d), _) => m1
  135. case (Error(msg1, st), _) => m1
  136. case (_, Failure(msg1, e1, st, d)) => m2
  137. case (_, Error(msg1, st)) => m2
  138. }
  139. }
  140. /**
  141. * This monoids "absorbs" success messages if the result of the |+| is not a success
  142. */
  143. implicit val ResultFailureMonoid: Monoid[Result] = new Monoid[Result] {
  144. val zero = Success()
  145. def append(m1: Result, m2: =>Result) = (m1, m2) match {
  146. case (Success(msg1), Success(msg2)) => Success(msg1+"; "+msg2)
  147. case (Success(msg1), other) => other
  148. case (other, Success(msg2)) => other
  149. case (Failure(msg1, e1, st1, d1), Failure(msg2, e2, st2, d2)) => Failure(msg1+"; "+msg2, e1+"; "+e2, st1, NoDetails())
  150. case (Error(msg1, st1), Error(msg2, st2)) => Error(msg1+"; "+msg2, st1)
  151. case (Error(msg1, st1), Failure(msg2, e2, st2, d2)) => Error(msg1+"; "+msg2, st1)
  152. case (Skipped(msg1, e1), Skipped(msg2, e2)) => Skipped(msg1+"; "+msg2, e1+"; "+e2)
  153. case (Skipped(msg1, e1), Pending(msg2)) => Pending(msg1+"; "+msg2)
  154. case (Pending(msg1), Skipped(msg2, e2)) => Pending(msg1+"; "+msg2)
  155. case (Pending(msg1), Pending(msg2)) => Pending(msg1+"; "+msg2)
  156. case (DecoratedResult(t, r), other) => DecoratedResult(t, append(r, other))
  157. case (other, DecoratedResult(t, r)) => DecoratedResult(t, append(other, r))
  158. case (Failure(msg1, e1, st, d), _) => m1
  159. case (Error(msg1, st), _) => m1
  160. case (_, Failure(msg1, e1, st, d)) => m2
  161. case (_, Error(msg1, st)) => m2
  162. }
  163. }
  164. }
  165. /**
  166. * This class represents the success of an execution
  167. */
  168. case class Success(m: String = "") extends Result(m, m) {
  169. override def and(res: =>Result): Result = {
  170. val r = res
  171. r match {
  172. case Success(m) => if (message == m || message.isEmpty) Success(m, expectationsNb + r.expectationsNb)
  173. else Success(message+" and "+m, expectationsNb + r.expectationsNb)
  174. case e @ Error(_, _) => r.addExpectationsNb(expectationsNb)
  175. case Failure(_, _, _, _) => r.addExpectationsNb(expectationsNb)
  176. case _ => super.and(r)
  177. }
  178. }
  179. override def isSuccess = true
  180. def addExpectationsNb(n: Int): Result = Success(m, expectationsNb + n)
  181. def mute = Success()
  182. /**
  183. * @return Success if it is a failure and vice-versa
  184. */
  185. override def not: Result = Failure(m)
  186. }
  187. /**
  188. * Companion object to the Success class providing
  189. * a method to set the expectations number
  190. */
  191. object Success {
  192. def apply(m: String, expNb: Int) = new Success(m) {
  193. override val expectationsNb = expNb
  194. }
  195. }
  196. /**
  197. * This class represents the failure of an execution.
  198. * It has a message and a stacktrace
  199. */
  200. case class Failure(m: String = "", e: String = "", stackTrace: List[StackTraceElement] = new Exception().getStackTrace.toList, details: Details = NoDetails())
  201. extends Result(m, e) with ResultStackTrace { outer =>
  202. /** @return an exception created from the message and the stackTraceElements */
  203. def exception = Throwablex.exception(m, stackTrace)
  204. override def or(res: =>Result): Result = {
  205. val r = res
  206. r match {
  207. case s @ Success(m) => if (message == m) r.addExpectationsNb(expectationsNb) else Success(message+" and "+m, expectationsNb + s.expectationsNb)
  208. case Failure(m, e, st, d) => Failure(message+" and "+m, e, stackTrace ::: st, d).addExpectationsNb(expectationsNb)
  209. case _ => super.or(r)
  210. }
  211. }
  212. def addExpectationsNb(n: Int): Result = new Failure(m, e, stackTrace, details) {
  213. override val expectationsNb = outer.expectationsNb + n
  214. }
  215. def mute = copy(m = "", e = "")
  216. override def toString = m
  217. override def equals(o: Any) = {
  218. o match {
  219. case Failure(m2, e2, _, _) => m == m2 && e == e2
  220. case _ => false
  221. }
  222. }
  223. override def hashCode = m.hashCode + e.hashCode
  224. override def isFailure: Boolean = true
  225. /**
  226. * @return a Success
  227. */
  228. override def not: Result = Success(m)
  229. }
  230. /**
  231. * Trait to model detailled information for failures so that smart differences can be computed
  232. */
  233. sealed trait Details
  234. case class FailureDetails(expected: String, actual: String) extends Details
  235. case class NoDetails() extends Details
  236. /**
  237. * This class represents an exception occurring during an execution.
  238. */
  239. case class Error(m: String, e: Exception) extends Result(m) with ResultStackTrace { outer =>
  240. /** @return an exception created from the message and the stackTraceElements */
  241. def exception = e
  242. def stackTrace = e.getFullStackTrace.toList
  243. override def equals(o: Any) = {
  244. o match {
  245. case Error(m2, e2) => m == m2 && e.getMessage == e2.getMessage
  246. case _ => false
  247. }
  248. }
  249. def addExpectationsNb(n: Int): Result = new Error(m, e) {
  250. override val expectationsNb = outer.expectationsNb + n
  251. }
  252. def mute = copy(m = "")
  253. override def hashCode = m.hashCode
  254. override def isError: Boolean = true
  255. }
  256. /**
  257. * This object allows to create an Error from an exception
  258. */
  259. case object Error {
  260. def apply(e: Exception) = new Error(e.getMessage.notNull, e)
  261. def apply(t: Throwable) = new Error(t.getMessage.notNull, new ThrowableException(t))
  262. case class ThrowableException(t: Throwable) extends Exception(t)
  263. def apply(m: String = "") = new Error(m, new Exception(m))
  264. }
  265. /**
  266. * Pending result
  267. * @see Result for description
  268. */
  269. case class Pending(m: String = "") extends Result(m) { outer =>
  270. def mute = Pending()
  271. def addExpectationsNb(n: Int): Result = new Pending(m) {
  272. override val expectationsNb = outer.expectationsNb + n
  273. }
  274. override def isPending: Boolean = true
  275. }
  276. /**
  277. * Skipped result
  278. * @see Result for description
  279. */
  280. case class Skipped(m: String = "", e: String = "") extends Result(m, e) { outer =>
  281. def mute = Skipped()
  282. def addExpectationsNb(n: Int): Result = new Skipped(m) {
  283. override val expectationsNb = outer.expectationsNb + n
  284. }
  285. override def isSkipped: Boolean = true
  286. }
  287. /**
  288. * This result allows to embed additional data with a given result for further display
  289. *
  290. * Is is used to provide a way to display properly the data tables in the HtmlExporter
  291. */
  292. case class DecoratedResult[T](decorator: T, result: Result) extends Result(result.message, result.expected) { outer =>
  293. def mute = DecoratedResult(decorator, result.mute)
  294. def addExpectationsNb(n: Int): Result = new DecoratedResult(decorator, result) {
  295. override val expectationsNb = outer.expectationsNb + n
  296. }
  297. override def and(r: =>Result): Result = DecoratedResult(decorator, result and r)
  298. override def or(r2: =>Result): Result = DecoratedResult(decorator, result or r2)
  299. override def isSuccess: Boolean = result.isSuccess
  300. override def isError: Boolean = result.isError
  301. override def isSkipped: Boolean = result.isSkipped
  302. override def isPending: Boolean = result.isPending
  303. override def isFailure: Boolean = result.isFailure
  304. }