org.scalactic.Prettifier.scala Maven / Gradle / Ivy
/*
* Copyright 2001-2013 Artima, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.scalactic
import scala.collection._
import mutable.WrappedArray
import scala.util.Success
import scala.xml
/**
* A function that given any object will produce a “pretty” string representation of that object,
* where “pretty” is in the eye of the implementer.
*
*
* Scala's Any
type declares a toString
that will convert any object to a String
* representation. This String
representation is primarily intended for programmers, and is usually sufficient.
* However, sometimes it can be helpful to provide an alternative implementation of toString
for certain types.
* For example, the toString
implementation on String
prints out the value of the String
:
*
*
*
* scala> "1".toString
* res0: String = 1
*
*
*
* If the error message that resulted from comparing Int
1 with String
"1"
* in a ScalaTest assertion used toString
, therefore, the error message would be:
*
*
*
* 1 did not equal 1
*
*
*
* To make it quicker to figure out why the assertion failed, ScalaTest prettifies the objects involved in
* the error message. The default Prettifier
will place double quotes on either side of a String
s
* toString
result:
*
*
*
* scala> import org.scalactic._
* import org.scalactic._
*
* scala> Prettifier.default("1")
* res1: String = "1"
*
*
*
* Thus the error message resulting from comparing Int
1 with String
"1"
,
* in a ScalaTest assertion is:
*
*
*
* 1 did not equal "1"
*
*
*
* If you wish to prettify an object in production code, for example, to issue a profoundly clear debug message, you can use
* PrettyMethods
and invoke pretty
. Here's an example:
*
*
*
* scala> import PrettyMethods._
* import PrettyMethods._
*
* scala> 1.pretty
* res2: String = 1
*
* scala> "1".pretty
* res3: String = "1"
*
*
*
* For example, the default Prettifier
, Prettifier.default
, transforms:
*
*
*
* Null
to: null
* Unit
to: <() the Unit value>
* String
to: "string"
(the toString
result surrounded by double quotes)
* Char
to: 'c'
(the toString
result surrounded by single quotes)
* Array
to: Array("1", "2", "3")
* scala.Some
to: Some("3")
* scala.util.Left
to: Left("3")
* scala.util.Right
to: Right("3")
* scala.util.Success
to: Success("3")
* org.scalactic.Good
to: Good("3")
* org.scalactic.Bad
to: Bad("3")
* org.scalactic.One
to: One("3")
* org.scalactic.Many
to: Many("1", "2", "3")
* scala.collection.GenTraversable
to: List("1", "2", "3")
* java.util.Collection
to: ["1", "2", "3"]
* java.util.Map
to: {1="one", 2="two", 3="three"}
*
*
*
* For anything else, the default Prettifier
returns the result of invoking toString
.
*
*
*
* Note: Prettifier
is not parameterized (i.e., Prettifier[T]
, where T
is the type
* to prettify) because assertions (including matcher expressions) in ScalaTest would then need to look up Prettifier
s implicitly by type. This would slow
* compilation even though most (let's guess 99.9%) of the time in practice assertions do not fail, and thus 99.9% of the time no error messages need to be generated.
* If no error messages are needed 99.9% of the time, no prettification is needed 99.9% of the time, so the slow down in compile time for the implicit
* look ups is unlikely to be worth the benefit. Only a few types in practice usually need prettification for testing error message purposes, and those will be covered
* by the default Prettifier
. A future version of ScalaTest will provide a simple mechanism to replace the default Prettifier
with a
* custom one when a test actually fails.
*
*/
trait Prettifier extends (Any => String)
/**
* Companion object for Prettifier
that provides a default Prettifier
implementation.
*/
object Prettifier {
/**
* A default Prettifier
.
*
*
* This default Prettifier
is used in ScalaTest to clarify error messages.
*
*
*
* It transforms:
*
*
*
* Null
to: null
* Unit
to: <() the Unit value>
* String
to: "string"
(the toString
result surrounded by double quotes)
* Char
to: 'c'
(the toString
result surrounded by single quotes)
* Array
to: Array("1", "2", "3")
* scala.Some
to: Some("3")
* scala.util.Left
to: Left("3")
* scala.util.Right
to: Right("3")
* scala.util.Success
to: Success("3")
* org.scalactic.Good
to: Good("3")
* org.scalactic.Bad
to: Bad("3")
* org.scalactic.One
to: One("3")
* org.scalactic.Many
to: Many("1", "2", "3")
* scala.collection.GenTraversable
to: List("1", "2", "3")
* java.util.Collection
to: ["1", "2", "3"]
* java.util.Map
to: {1="one", 2="two", 3="three"}
*
*
*
*
* For anything else, it returns the result of invoking toString
.
*
*/
val default: Prettifier =
new Prettifier {
def apply(o: Any): String = {
try {
o match {
case null => "null"
case aUnit: Unit => "<(), the Unit value>"
case aString: String => "\"" + aString + "\""
case aStringWrapper: scala.collection.immutable.StringOps => "\"" + aStringWrapper + "\""
case aChar: Char => "\'" + aChar + "\'"
case Some(e) => "Some(" + apply(e) + ")"
case Success(e) => "Success(" + apply(e) + ")"
case Left(e) => "Left(" + apply(e) + ")"
case Right(e) => "Right(" + apply(e) + ")"
case Good(e) => "Good(" + apply(e) + ")"
case Bad(e) => "Bad(" + apply(e) + ")"
case One(e) => "One(" + apply(e) + ")"
case many: Many[_] => "Many(" + many.toIterator.map(apply(_)).mkString(", ") + ")"
case anArray: Array[_] => "Array(" + (anArray map apply).mkString(", ") + ")"
case aWrappedArray: WrappedArray[_] => "Array(" + (aWrappedArray map apply).mkString(", ") + ")"
case aGenMap: GenMap[_, _] =>
aGenMap.stringPrefix + "(" +
(aGenMap.toIterator.map { case (key, value) => // toIterator is needed for consistent ordering
apply(key) + " -> " + apply(value)
}).mkString(", ") + ")"
// SKIP-SCALATESTJS-START
case anXMLNodeSeq: xml.NodeSeq => anXMLNodeSeq.toString
case anXMLNodeBuffer: xml.NodeBuffer =>
xml.NodeSeq.fromSeq(anXMLNodeBuffer).toString
// SKIP-SCALATESTJS-END
case aGenTraversable: GenTraversable[_] =>
val isSelf =
if (aGenTraversable.size == 1) {
aGenTraversable.head match {
case ref: AnyRef => ref eq aGenTraversable
case other => other == aGenTraversable
}
}
else
false
if (isSelf)
aGenTraversable.toString
else
aGenTraversable.stringPrefix + "(" + aGenTraversable.toIterator.map(apply(_)).mkString(", ") + ")" // toIterator is needed for consistent ordering
// SKIP-SCALATESTJS-START
case javaCol: java.util.Collection[_] =>
// By default java collection follows http://download.java.net/jdk7/archive/b123/docs/api/java/util/AbstractCollection.html#toString()
// let's do our best to prettify its element when it is not overriden
import scala.collection.JavaConverters._
val theToString = javaCol.toString
if (theToString.startsWith("[") && theToString.endsWith("]"))
"[" + javaCol.iterator().asScala.map(apply(_)).mkString(", ") + "]"
else
theToString
case javaMap: java.util.Map[_, _] =>
// By default java map follows http://download.java.net/jdk7/archive/b123/docs/api/java/util/AbstractMap.html#toString()
// let's do our best to prettify its element when it is not overriden
import scala.collection.JavaConverters._
val theToString = javaMap.toString
if (theToString.startsWith("{") && theToString.endsWith("}"))
"{" + javaMap.entrySet.iterator.asScala.map { entry =>
apply(entry.getKey) + "=" + apply(entry.getValue)
}.mkString(", ") + "}"
else
theToString
// SKIP-SCALATESTJS-END
case anythingElse => anythingElse.toString
}
}
catch {
// This is in case of crazy designs like the one for scala.xml.Node. We handle Node
// specially above, but in case someone else creates a collection whose iterator
// returns itself, which will cause infinite recursion, at least we'll pop out and
// give them a string back.
case _: StackOverflowError => o.toString
}
}
}
/**
* A basic Prettifier
.
*
*
* This was the default Prettifier
used in ScalaTest 2.0 release.
*
*
*
* It transforms:
*
*
*
* Null
to: null
* Unit
to: <() the Unit value>
* String
to: "string"
(the toString
result surrounded by double quotes)
* Char
to: 'c'
(the toString
result surrounded by single quotes)
* Array
to: Array("1", "2", "3")
* scala.util.Some
to: Some("3")
*
*
*
* For anything else, it returns the result of invoking toString
.
*
*/
val basic = new BasicPrettifier
private[org] def diffStrings(s: String, t: String): Tuple2[String, String] = {
def findCommonPrefixLength(s: String, t: String): Int = {
val max = s.length.min(t.length) // the maximum potential size of the prefix
var i = 0
var found = false
while (i < max & !found) {
found = (s.charAt(i) != t.charAt(i))
if (!found)
i = i + 1
}
i
}
def findCommonSuffixLength(s: String, t: String): Int = {
val max = s.length.min(t.length) // the maximum potential size of the suffix
var i = 0
var found = false
while (i < max & !found) {
found = (s.charAt(s.length - 1 - i) != t.charAt(t.length - 1 - i))
if (!found)
i = i + 1
}
i
}
if (s != t) {
val commonPrefixLength = findCommonPrefixLength(s, t)
val commonSuffixLength = findCommonSuffixLength(s.substring(commonPrefixLength), t.substring(commonPrefixLength))
val prefix = s.substring(0, commonPrefixLength)
val suffix = if (s.length - commonSuffixLength < 0) "" else s.substring(s.length - commonSuffixLength)
val sMiddleEnd = s.length - commonSuffixLength
val tMiddleEnd = t.length - commonSuffixLength
val sMiddle = s.substring(commonPrefixLength, sMiddleEnd)
val tMiddle = t.substring(commonPrefixLength, tMiddleEnd)
val MaxContext = 20
val shortPrefix = if (commonPrefixLength > MaxContext) "..." + prefix.substring(prefix.length - MaxContext) else prefix
val shortSuffix = if (commonSuffixLength > MaxContext) suffix.substring(0, MaxContext) + "..." else suffix
(shortPrefix + "[" + sMiddle + "]" + shortSuffix, shortPrefix + "[" + tMiddle + "]" + shortSuffix)
}
else
(s, t)
}
private[org] def getObjectsForFailureMessage(a: Any, b: Any) =
a match {
case aStr: String => {
b match {
case bStr: String => {
diffStrings(aStr, bStr)
}
case _ => (a, b)
}
}
case _ => (a, b)
}
private[org] val lineSeparator: String = scala.compat.Platform.EOL
}
private[scalactic] class BasicPrettifier extends Prettifier {
def apply(o: Any): String =
o match {
case null => "null"
case aUnit: Unit => "<(), the Unit value>"
case aString: String => "\"" + aString + "\""
case aChar: Char => "\'" + aChar + "\'"
case anArray: Array[_] => prettifyArrays(anArray)
case aWrappedArray: WrappedArray[_] => prettifyArrays(aWrappedArray)
case anythingElse => anythingElse.toString
}
private def prettifyArrays(o: Any): String =
o match {
case arr: Array[_] => "Array(" + (arr map (a => prettifyArrays(a))).mkString(", ") + ")"
case wrappedArr: WrappedArray[_] => "Array(" + (wrappedArr map (a => prettifyArrays(a))).mkString(", ") + ")"
case _ => if (o != null) o.toString else "null"
}
}