From 600c9e4536986d7c1377e7046fb13cbe9722752d Mon Sep 17 00:00:00 2001
From: Chua Chee Seng
Date: Sat, 11 Jun 2022 11:06:36 +0800
Subject: [PATCH 1/4] First step to enhance Prettifier to fail early when
cyclic object is detected when prettifying objects nested in collection.
---
.../main/scala/org/scalactic/Prettifier.scala | 59 ++++++++-----------
1 file changed, 26 insertions(+), 33 deletions(-)
diff --git a/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala b/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
index 2451da40eb..e03a9c8b6b 100644
--- a/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
+++ b/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
@@ -183,51 +183,40 @@ object Prettifier {
*/
implicit val default: Prettifier =
new Prettifier {
- def apply(o: Any): String = {
- try {
+ private def prettify(o: Any, processed: Set[Any]): String =
+ if (processed.contains(o))
+ throw new StackOverflowError("Cyclic relationship detected, let's fail early!")
+ else
o match {
case null => "null"
case aUnit: Unit => "<(), the Unit value>"
case aString: String => "\"" + aString + "\""
case aStringWrapper: org.scalactic.ColCompatHelper.StringOps => "\"" + aStringWrapper.mkString + "\""
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 Some(e) => "Some(" + prettify(e, processed) + ")"
+ case Success(e) => "Success(" + prettify(e, processed) + ")"
+ case Left(e) => "Left(" + prettify(e, processed) + ")"
+ case Right(e) => "Right(" + prettify(e, processed) + ")"
case s: Symbol => "'" + s.name
- 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 anArrayOps if ArrayHelper.isArrayOps(anArrayOps) => "Array(" + (ArrayHelper.asArrayOps(anArrayOps) map apply).mkString(", ") + ")"
+ case Good(e) => "Good(" + prettify(e, processed) + ")"
+ case Bad(e) => "Bad(" + prettify(e, processed) + ")"
+ case One(e) => "One(" + prettify(e, processed) + ")"
+ case many: Many[_] => "Many(" + many.map(prettify(_, processed + many)).mkString(", ") + ")"
+ case anArray: Array[_] => "Array(" + anArray.map(prettify(_, processed + anArray)).mkString(", ") + ")"
+ case aWrappedArray: WrappedArray[_] => "Array(" + aWrappedArray.map(prettify(_, processed + aWrappedArray)).mkString(", ") + ")"
+ case anArrayOps if ArrayHelper.isArrayOps(anArrayOps) => "Array(" + ArrayHelper.asArrayOps(anArrayOps).map(prettify(_, processed + anArrayOps)).mkString(", ") + ")"
case aGenMap: scala.collection.GenMap[_, _] =>
ColCompatHelper.className(aGenMap) + "(" +
(aGenMap.toIterator.map { case (key, value) => // toIterator is needed for consistent ordering
- apply(key) + " -> " + apply(value)
+ prettify(key, processed + aGenMap) + " -> " + prettify(value, processed + aGenMap)
}).mkString(", ") + ")"
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 {
val className = aGenTraversable.getClass.getName
- if (className.startsWith("scala.xml.NodeSeq$") || className == "scala.xml.NodeBuffer")
+ if (className.startsWith("scala.xml.NodeSeq$") || className == "scala.xml.NodeBuffer" || className == "scala.xml.Elem")
aGenTraversable.mkString
else
- ColCompatHelper.className(aGenTraversable) + "(" + aGenTraversable.toIterator.map(apply(_)).mkString(", ") + ")" // toIterator is needed for consistent ordering
- }
-
+ ColCompatHelper.className(aGenTraversable) + "(" + aGenTraversable.toIterator.map(prettify(_, processed + aGenTraversable)).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()
@@ -235,7 +224,7 @@ object Prettifier {
import scala.collection.JavaConverters._
val theToString = javaCol.toString
if (theToString.startsWith("[") && theToString.endsWith("]"))
- "[" + javaCol.iterator().asScala.map(apply(_)).mkString(", ") + "]"
+ "[" + javaCol.iterator().asScala.map(prettify(_, processed + javaCol)).mkString(", ") + "]"
else
theToString
case javaMap: java.util.Map[_, _] =>
@@ -245,13 +234,17 @@ object Prettifier {
val theToString = javaMap.toString
if (theToString.startsWith("{") && theToString.endsWith("}"))
"{" + javaMap.entrySet.iterator.asScala.map { entry =>
- apply(entry.getKey) + "=" + apply(entry.getValue)
+ prettify(entry.getKey, processed + javaMap) + "=" + prettify(entry.getValue, processed + javaMap)
}.mkString(", ") + "}"
else
theToString
// SKIP-SCALATESTJS,NATIVE-END
case anythingElse => anythingElse.toString
- }
+ }
+
+ def apply(o: Any): String = {
+ try {
+ prettify(o, Set.empty)
}
catch {
// This is in case of crazy designs like the one for scala.xml.Node. We handle Node
From 112ecfd6b2ad05022069da326aaf150404baabbb Mon Sep 17 00:00:00 2001
From: Chua Chee Seng
Date: Sat, 11 Jun 2022 14:37:17 +0800
Subject: [PATCH 2/4] Added collection size limit suppport to Prettifier
through java system property.
---
.../main/scala/org/scalactic/Prettifier.scala | 29 ++++++++++++-------
1 file changed, 19 insertions(+), 10 deletions(-)
diff --git a/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala b/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
index e03a9c8b6b..65ff35e3bc 100644
--- a/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
+++ b/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
@@ -183,6 +183,9 @@ object Prettifier {
*/
implicit val default: Prettifier =
new Prettifier {
+
+ val colSizeLimit: Int = Option(System.getProperty("scalactic.prettifier.collection.size.limit")).map(_.toInt).getOrElse(0)
+
private def prettify(o: Any, processed: Set[Any]): String =
if (processed.contains(o))
throw new StackOverflowError("Cyclic relationship detected, let's fail early!")
@@ -201,13 +204,15 @@ object Prettifier {
case Good(e) => "Good(" + prettify(e, processed) + ")"
case Bad(e) => "Bad(" + prettify(e, processed) + ")"
case One(e) => "One(" + prettify(e, processed) + ")"
- case many: Many[_] => "Many(" + many.map(prettify(_, processed + many)).mkString(", ") + ")"
- case anArray: Array[_] => "Array(" + anArray.map(prettify(_, processed + anArray)).mkString(", ") + ")"
- case aWrappedArray: WrappedArray[_] => "Array(" + aWrappedArray.map(prettify(_, processed + aWrappedArray)).mkString(", ") + ")"
- case anArrayOps if ArrayHelper.isArrayOps(anArrayOps) => "Array(" + ArrayHelper.asArrayOps(anArrayOps).map(prettify(_, processed + anArrayOps)).mkString(", ") + ")"
+ case many: Many[_] => "Many(" + (if (colSizeLimit > 0) many.toIterator.take(colSizeLimit) else many.toIterator).map(prettify(_, processed + many)).mkString(", ") + ")"
+ case anArray: Array[_] => "Array(" + (if (colSizeLimit > 0) anArray.take(colSizeLimit) else anArray).map(prettify(_, processed + anArray)).mkString(", ") + ")"
+ case aWrappedArray: WrappedArray[_] => "Array(" + (if (colSizeLimit > 0) aWrappedArray.take(colSizeLimit) else aWrappedArray).map(prettify(_, processed + aWrappedArray)).mkString(", ") + ")"
+ case a if ArrayHelper.isArrayOps(a) =>
+ val anArrayOps = ArrayHelper.asArrayOps(a).iterator
+ "Array(" + (if (colSizeLimit > 0) anArrayOps.take(colSizeLimit) else anArrayOps).map(prettify(_, processed + anArrayOps)).mkString(", ") + ")"
case aGenMap: scala.collection.GenMap[_, _] =>
ColCompatHelper.className(aGenMap) + "(" +
- (aGenMap.toIterator.map { case (key, value) => // toIterator is needed for consistent ordering
+ ((if (colSizeLimit > 0) aGenMap.take(colSizeLimit) else aGenMap).toIterator.map { case (key, value) => // toIterator is needed for consistent ordering
prettify(key, processed + aGenMap) + " -> " + prettify(value, processed + aGenMap)
}).mkString(", ") + ")"
case aGenTraversable: GenTraversable[_] =>
@@ -215,7 +220,7 @@ object Prettifier {
if (className.startsWith("scala.xml.NodeSeq$") || className == "scala.xml.NodeBuffer" || className == "scala.xml.Elem")
aGenTraversable.mkString
else
- ColCompatHelper.className(aGenTraversable) + "(" + aGenTraversable.toIterator.map(prettify(_, processed + aGenTraversable)).mkString(", ") + ")" // toIterator is needed for consistent ordering
+ ColCompatHelper.className(aGenTraversable) + "(" + (if (colSizeLimit > 0) aGenTraversable.take(colSizeLimit) else aGenTraversable).toIterator.map(prettify(_, processed + aGenTraversable)).mkString(", ") + ")" // toIterator is needed for consistent ordering
// SKIP-SCALATESTJS-START
case javaCol: java.util.Collection[_] =>
@@ -223,8 +228,10 @@ object Prettifier {
// 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(prettify(_, processed + javaCol)).mkString(", ") + "]"
+ if (theToString.startsWith("[") && theToString.endsWith("]")) {
+ val itr = javaCol.iterator().asScala
+ "[" + (if (colSizeLimit > 0) itr.take(colSizeLimit) else itr).map(prettify(_, processed + javaCol)).mkString(", ") + "]"
+ }
else
theToString
case javaMap: java.util.Map[_, _] =>
@@ -232,10 +239,12 @@ object Prettifier {
// 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 =>
+ if (theToString.startsWith("{") && theToString.endsWith("}")) {
+ val itr = javaMap.entrySet.iterator.asScala
+ "{" + (if (colSizeLimit > 0) itr.take(colSizeLimit) else itr).map { entry =>
prettify(entry.getKey, processed + javaMap) + "=" + prettify(entry.getValue, processed + javaMap)
}.mkString(", ") + "}"
+ }
else
theToString
// SKIP-SCALATESTJS,NATIVE-END
From 5b3f1b7fa53d87ec9532576dd09ca4bf19a02944 Mon Sep 17 00:00:00 2001
From: Chua Chee Seng
Date: Sun, 12 Jun 2022 16:14:01 +0800
Subject: [PATCH 3/4] Pass the size limit to default prettifier through
parameter.
---
.../main/scala/org/scalactic/Prettifier.scala | 168 +++++++++---------
.../main/scala/org/scalactic/SizeLimit.scala | 21 +++
2 files changed, 106 insertions(+), 83 deletions(-)
create mode 100644 jvm/scalactic/src/main/scala/org/scalactic/SizeLimit.scala
diff --git a/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala b/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
index 65ff35e3bc..1b01b11370 100644
--- a/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
+++ b/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
@@ -131,6 +131,90 @@ trait Prettifier extends Serializable { // I removed the extends (Any => String)
}
}
+private[scalactic] class DefaultPrettifier(sizeLimit: SizeLimit) extends Prettifier {
+
+ val colSizeLimit: Int = sizeLimit.value
+
+ private def prettify(o: Any, processed: Set[Any]): String =
+ if (processed.contains(o))
+ throw new StackOverflowError("Cyclic relationship detected, let's fail early!")
+ else
+ o match {
+ case null => "null"
+ case aUnit: Unit => "<(), the Unit value>"
+ case aString: String => "\"" + aString + "\""
+ case aStringWrapper: org.scalactic.ColCompatHelper.StringOps => "\"" + aStringWrapper.mkString + "\""
+ case aChar: Char => "\'" + aChar + "\'"
+ case Some(e) => "Some(" + prettify(e, processed) + ")"
+ case Success(e) => "Success(" + prettify(e, processed) + ")"
+ case Left(e) => "Left(" + prettify(e, processed) + ")"
+ case Right(e) => "Right(" + prettify(e, processed) + ")"
+ case s: Symbol => "'" + s.name
+ case Good(e) => "Good(" + prettify(e, processed) + ")"
+ case Bad(e) => "Bad(" + prettify(e, processed) + ")"
+ case One(e) => "One(" + prettify(e, processed) + ")"
+ case many: Many[_] => "Many(" + (if (colSizeLimit > 0) many.toIterator.take(colSizeLimit) else many.toIterator).map(prettify(_, processed + many)).mkString(", ") + ")"
+ case anArray: Array[_] => "Array(" + (if (colSizeLimit > 0) anArray.take(colSizeLimit) else anArray).map(prettify(_, processed + anArray)).mkString(", ") + ")"
+ case aWrappedArray: WrappedArray[_] => "Array(" + (if (colSizeLimit > 0) aWrappedArray.take(colSizeLimit) else aWrappedArray).map(prettify(_, processed + aWrappedArray)).mkString(", ") + ")"
+ case a if ArrayHelper.isArrayOps(a) =>
+ val anArrayOps = ArrayHelper.asArrayOps(a).iterator
+ "Array(" + (if (colSizeLimit > 0) anArrayOps.take(colSizeLimit) else anArrayOps).map(prettify(_, processed + anArrayOps)).mkString(", ") + ")"
+ case aGenMap: scala.collection.GenMap[_, _] =>
+ ColCompatHelper.className(aGenMap) + "(" +
+ ((if (colSizeLimit > 0) aGenMap.take(colSizeLimit) else aGenMap).toIterator.map { case (key, value) => // toIterator is needed for consistent ordering
+ prettify(key, processed + aGenMap) + " -> " + prettify(value, processed + aGenMap)
+ }).mkString(", ") + ")"
+ case aGenTraversable: GenTraversable[_] =>
+ val className = aGenTraversable.getClass.getName
+ if (className.startsWith("scala.xml.NodeSeq$") || className == "scala.xml.NodeBuffer" || className == "scala.xml.Elem")
+ aGenTraversable.mkString
+ else
+ ColCompatHelper.className(aGenTraversable) + "(" + (if (colSizeLimit > 0) aGenTraversable.take(colSizeLimit) else aGenTraversable).toIterator.map(prettify(_, processed + aGenTraversable)).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("]")) {
+ val itr = javaCol.iterator().asScala
+ "[" + (if (colSizeLimit > 0) itr.take(colSizeLimit) else itr).map(prettify(_, processed + javaCol)).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("}")) {
+ val itr = javaMap.entrySet.iterator.asScala
+ "{" + (if (colSizeLimit > 0) itr.take(colSizeLimit) else itr).map { entry =>
+ prettify(entry.getKey, processed + javaMap) + "=" + prettify(entry.getValue, processed + javaMap)
+ }.mkString(", ") + "}"
+ }
+ else
+ theToString
+ // SKIP-SCALATESTJS,NATIVE-END
+ case anythingElse => anythingElse.toString
+ }
+
+ def apply(o: Any): String = {
+ try {
+ prettify(o, Set.empty)
+ }
+ 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
+ }
+ }
+
+}
+
/**
* Companion object for `Prettifier` that provides a default `Prettifier` implementation.
*/
@@ -181,89 +265,7 @@ object Prettifier {
* For anything else, it returns the result of invoking `toString`.
*
*/
- implicit val default: Prettifier =
- new Prettifier {
-
- val colSizeLimit: Int = Option(System.getProperty("scalactic.prettifier.collection.size.limit")).map(_.toInt).getOrElse(0)
-
- private def prettify(o: Any, processed: Set[Any]): String =
- if (processed.contains(o))
- throw new StackOverflowError("Cyclic relationship detected, let's fail early!")
- else
- o match {
- case null => "null"
- case aUnit: Unit => "<(), the Unit value>"
- case aString: String => "\"" + aString + "\""
- case aStringWrapper: org.scalactic.ColCompatHelper.StringOps => "\"" + aStringWrapper.mkString + "\""
- case aChar: Char => "\'" + aChar + "\'"
- case Some(e) => "Some(" + prettify(e, processed) + ")"
- case Success(e) => "Success(" + prettify(e, processed) + ")"
- case Left(e) => "Left(" + prettify(e, processed) + ")"
- case Right(e) => "Right(" + prettify(e, processed) + ")"
- case s: Symbol => "'" + s.name
- case Good(e) => "Good(" + prettify(e, processed) + ")"
- case Bad(e) => "Bad(" + prettify(e, processed) + ")"
- case One(e) => "One(" + prettify(e, processed) + ")"
- case many: Many[_] => "Many(" + (if (colSizeLimit > 0) many.toIterator.take(colSizeLimit) else many.toIterator).map(prettify(_, processed + many)).mkString(", ") + ")"
- case anArray: Array[_] => "Array(" + (if (colSizeLimit > 0) anArray.take(colSizeLimit) else anArray).map(prettify(_, processed + anArray)).mkString(", ") + ")"
- case aWrappedArray: WrappedArray[_] => "Array(" + (if (colSizeLimit > 0) aWrappedArray.take(colSizeLimit) else aWrappedArray).map(prettify(_, processed + aWrappedArray)).mkString(", ") + ")"
- case a if ArrayHelper.isArrayOps(a) =>
- val anArrayOps = ArrayHelper.asArrayOps(a).iterator
- "Array(" + (if (colSizeLimit > 0) anArrayOps.take(colSizeLimit) else anArrayOps).map(prettify(_, processed + anArrayOps)).mkString(", ") + ")"
- case aGenMap: scala.collection.GenMap[_, _] =>
- ColCompatHelper.className(aGenMap) + "(" +
- ((if (colSizeLimit > 0) aGenMap.take(colSizeLimit) else aGenMap).toIterator.map { case (key, value) => // toIterator is needed for consistent ordering
- prettify(key, processed + aGenMap) + " -> " + prettify(value, processed + aGenMap)
- }).mkString(", ") + ")"
- case aGenTraversable: GenTraversable[_] =>
- val className = aGenTraversable.getClass.getName
- if (className.startsWith("scala.xml.NodeSeq$") || className == "scala.xml.NodeBuffer" || className == "scala.xml.Elem")
- aGenTraversable.mkString
- else
- ColCompatHelper.className(aGenTraversable) + "(" + (if (colSizeLimit > 0) aGenTraversable.take(colSizeLimit) else aGenTraversable).toIterator.map(prettify(_, processed + aGenTraversable)).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("]")) {
- val itr = javaCol.iterator().asScala
- "[" + (if (colSizeLimit > 0) itr.take(colSizeLimit) else itr).map(prettify(_, processed + javaCol)).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("}")) {
- val itr = javaMap.entrySet.iterator.asScala
- "{" + (if (colSizeLimit > 0) itr.take(colSizeLimit) else itr).map { entry =>
- prettify(entry.getKey, processed + javaMap) + "=" + prettify(entry.getValue, processed + javaMap)
- }.mkString(", ") + "}"
- }
- else
- theToString
- // SKIP-SCALATESTJS,NATIVE-END
- case anythingElse => anythingElse.toString
- }
-
- def apply(o: Any): String = {
- try {
- prettify(o, Set.empty)
- }
- 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
- }
- }
- }
+ implicit val default: Prettifier = new DefaultPrettifier(SizeLimit(0))
/**
* A basic `Prettifier`.
diff --git a/jvm/scalactic/src/main/scala/org/scalactic/SizeLimit.scala b/jvm/scalactic/src/main/scala/org/scalactic/SizeLimit.scala
new file mode 100644
index 0000000000..b46dd929dd
--- /dev/null
+++ b/jvm/scalactic/src/main/scala/org/scalactic/SizeLimit.scala
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2001-2022 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
+
+/**
+ * Size limit value class.
+ */
+case class SizeLimit(value: Int)
\ No newline at end of file
From 0065db6ef3682d19eacf558c9e3fac083c1c4c5d Mon Sep 17 00:00:00 2001
From: Chua Chee Seng
Date: Mon, 13 Jun 2022 09:37:46 +0800
Subject: [PATCH 4/4] Added truncateAt to Prettifier companion object, use
override approach for prettifyCollection.
---
.../scala/org/scalactic/PrettifierSpec.scala | 5 +
.../main/scala/org/scalactic/Prettifier.scala | 172 +++++++++++++-----
2 files changed, 128 insertions(+), 49 deletions(-)
diff --git a/jvm/scalactic-test/src/test/scala/org/scalactic/PrettifierSpec.scala b/jvm/scalactic-test/src/test/scala/org/scalactic/PrettifierSpec.scala
index 2c6f81a57e..fed0616419 100644
--- a/jvm/scalactic-test/src/test/scala/org/scalactic/PrettifierSpec.scala
+++ b/jvm/scalactic-test/src/test/scala/org/scalactic/PrettifierSpec.scala
@@ -397,6 +397,11 @@ class PrettifierSpec extends funspec.AnyFunSpec with matchers.should.Matchers {
Prettifier.default(new Fred) shouldBe "It's Fred all the way down"
}
// SKIP-DOTTY-END
+ it("should truncate collection when used with Prettifier.truncateAt") {
+ val col = List(1, 2, 3)
+ val prettifier = Prettifier.truncateAt(SizeLimit(2))
+ prettifier(col) shouldBe "List(1, 2, ...)"
+ }
}
}
diff --git a/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala b/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
index 1b01b11370..625b73ea6c 100644
--- a/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
+++ b/jvm/scalactic/src/main/scala/org/scalactic/Prettifier.scala
@@ -131,11 +131,58 @@ trait Prettifier extends Serializable { // I removed the extends (Any => String)
}
}
-private[scalactic] class DefaultPrettifier(sizeLimit: SizeLimit) extends Prettifier {
+private[scalactic] class DefaultPrettifier extends Prettifier {
- val colSizeLimit: Int = sizeLimit.value
+ protected def prettifyCollection(o: Any, processed: Set[Any]): String =
+ o match {
+ case many: Many[_] => "Many(" + many.toIterator.map(prettify(_, processed + many)).mkString(", ") + ")"
+ case anArray: Array[_] => "Array(" + anArray.map(prettify(_, processed + anArray)).mkString(", ") + ")"
+ case aWrappedArray: WrappedArray[_] => "Array(" + aWrappedArray.map(prettify(_, processed + aWrappedArray)).mkString(", ") + ")"
+ case a if ArrayHelper.isArrayOps(a) =>
+ val anArrayOps = ArrayHelper.asArrayOps(a).iterator
+ "Array(" + anArrayOps.map(prettify(_, processed + anArrayOps)).mkString(", ") + ")"
+ case aGenMap: scala.collection.GenMap[_, _] =>
+ ColCompatHelper.className(aGenMap) + "(" +
+ (aGenMap.toIterator.map { case (key, value) => // toIterator is needed for consistent ordering
+ prettify(key, processed + aGenMap) + " -> " + prettify(value, processed + aGenMap)
+ }).mkString(", ") + ")"
+ case aGenTraversable: GenTraversable[_] =>
+ val className = aGenTraversable.getClass.getName
+ if (className.startsWith("scala.xml.NodeSeq$") || className == "scala.xml.NodeBuffer" || className == "scala.xml.Elem")
+ aGenTraversable.mkString
+ else
+ ColCompatHelper.className(aGenTraversable) + "(" + aGenTraversable.toIterator.map(prettify(_, processed + aGenTraversable)).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("]")) {
+ val itr = javaCol.iterator().asScala
+ "[" + itr.map(prettify(_, processed + javaCol)).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("}")) {
+ val itr = javaMap.entrySet.iterator.asScala
+ "{" + itr.map { entry =>
+ prettify(entry.getKey, processed + javaMap) + "=" + prettify(entry.getValue, processed + javaMap)
+ }.mkString(", ") + "}"
+ }
+ else
+ theToString
+ // SKIP-SCALATESTJS,NATIVE-END
+ case anythingElse => anythingElse.toString
+ }
- private def prettify(o: Any, processed: Set[Any]): String =
+ protected def prettify(o: Any, processed: Set[Any]): String =
if (processed.contains(o))
throw new StackOverflowError("Cyclic relationship detected, let's fail early!")
else
@@ -153,51 +200,7 @@ private[scalactic] class DefaultPrettifier(sizeLimit: SizeLimit) extends Prettif
case Good(e) => "Good(" + prettify(e, processed) + ")"
case Bad(e) => "Bad(" + prettify(e, processed) + ")"
case One(e) => "One(" + prettify(e, processed) + ")"
- case many: Many[_] => "Many(" + (if (colSizeLimit > 0) many.toIterator.take(colSizeLimit) else many.toIterator).map(prettify(_, processed + many)).mkString(", ") + ")"
- case anArray: Array[_] => "Array(" + (if (colSizeLimit > 0) anArray.take(colSizeLimit) else anArray).map(prettify(_, processed + anArray)).mkString(", ") + ")"
- case aWrappedArray: WrappedArray[_] => "Array(" + (if (colSizeLimit > 0) aWrappedArray.take(colSizeLimit) else aWrappedArray).map(prettify(_, processed + aWrappedArray)).mkString(", ") + ")"
- case a if ArrayHelper.isArrayOps(a) =>
- val anArrayOps = ArrayHelper.asArrayOps(a).iterator
- "Array(" + (if (colSizeLimit > 0) anArrayOps.take(colSizeLimit) else anArrayOps).map(prettify(_, processed + anArrayOps)).mkString(", ") + ")"
- case aGenMap: scala.collection.GenMap[_, _] =>
- ColCompatHelper.className(aGenMap) + "(" +
- ((if (colSizeLimit > 0) aGenMap.take(colSizeLimit) else aGenMap).toIterator.map { case (key, value) => // toIterator is needed for consistent ordering
- prettify(key, processed + aGenMap) + " -> " + prettify(value, processed + aGenMap)
- }).mkString(", ") + ")"
- case aGenTraversable: GenTraversable[_] =>
- val className = aGenTraversable.getClass.getName
- if (className.startsWith("scala.xml.NodeSeq$") || className == "scala.xml.NodeBuffer" || className == "scala.xml.Elem")
- aGenTraversable.mkString
- else
- ColCompatHelper.className(aGenTraversable) + "(" + (if (colSizeLimit > 0) aGenTraversable.take(colSizeLimit) else aGenTraversable).toIterator.map(prettify(_, processed + aGenTraversable)).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("]")) {
- val itr = javaCol.iterator().asScala
- "[" + (if (colSizeLimit > 0) itr.take(colSizeLimit) else itr).map(prettify(_, processed + javaCol)).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("}")) {
- val itr = javaMap.entrySet.iterator.asScala
- "{" + (if (colSizeLimit > 0) itr.take(colSizeLimit) else itr).map { entry =>
- prettify(entry.getKey, processed + javaMap) + "=" + prettify(entry.getValue, processed + javaMap)
- }.mkString(", ") + "}"
- }
- else
- theToString
- // SKIP-SCALATESTJS,NATIVE-END
- case anythingElse => anythingElse.toString
+ case other => prettifyCollection(other, processed)
}
def apply(o: Any): String = {
@@ -215,6 +218,72 @@ private[scalactic] class DefaultPrettifier(sizeLimit: SizeLimit) extends Prettif
}
+private[scalactic] class TruncatingPrettifier(sizeLimit: SizeLimit) extends DefaultPrettifier {
+
+ private def dotDotDotIfTruncated(value: Boolean): String =
+ if (value) ", ..." else ""
+
+ override protected def prettifyCollection(o: Any, processed: Set[Any]): String = {
+ o match {
+ case many: Many[_] =>
+ val (taken, truncated) = if (many.size > sizeLimit.value) (many.toIterator.take(sizeLimit.value), true) else (many.toIterator, false)
+ "Many(" + taken.map(prettify(_, processed + many)).mkString(", ") + dotDotDotIfTruncated(truncated) + ")"
+ case anArray: Array[_] =>
+ val (taken, truncated) = if (anArray.size > sizeLimit.value) (anArray.take(sizeLimit.value), true) else (anArray, false)
+ "Array(" + taken.map(prettify(_, processed + anArray)).mkString(", ") + dotDotDotIfTruncated(truncated) + ")"
+ case aWrappedArray: WrappedArray[_] =>
+ val (taken, truncated) = if (aWrappedArray.size > sizeLimit.value) (aWrappedArray.take(sizeLimit.value), true) else (aWrappedArray, false)
+ "Array(" + taken.map(prettify(_, processed + aWrappedArray)).mkString(", ") + dotDotDotIfTruncated(truncated) + ")"
+ case a if ArrayHelper.isArrayOps(a) =>
+ val anArrayOps = ArrayHelper.asArrayOps(a)//
+ val (taken, truncated) = if (anArrayOps.size > sizeLimit.value) (anArrayOps.iterator.take(sizeLimit.value), true) else (anArrayOps.iterator, false)
+ "Array(" + taken.map(prettify(_, processed + anArrayOps)).mkString(", ") + dotDotDotIfTruncated(truncated) + ")"
+ case aGenMap: scala.collection.GenMap[_, _] =>
+ val (taken, truncated) = if (aGenMap.size > sizeLimit.value) (aGenMap.toIterator.take(sizeLimit.value), true) else (aGenMap.toIterator, false)
+ ColCompatHelper.className(aGenMap) + "(" +
+ (taken.map { case (key, value) => // toIterator is needed for consistent ordering
+ prettify(key, processed + aGenMap) + " -> " + prettify(value, processed + aGenMap)
+ }).mkString(", ") + dotDotDotIfTruncated(truncated) + ")"
+ case aGenTraversable: GenTraversable[_] =>
+ val (taken, truncated) = if (aGenTraversable.size > sizeLimit.value) (aGenTraversable.take(sizeLimit.value), true) else (aGenTraversable, false)
+ val className = aGenTraversable.getClass.getName
+ if (className.startsWith("scala.xml.NodeSeq$") || className == "scala.xml.NodeBuffer" || className == "scala.xml.Elem")
+ aGenTraversable.mkString
+ else
+ ColCompatHelper.className(aGenTraversable) + "(" + taken.toIterator.map(prettify(_, processed + aGenTraversable)).mkString(", ") + dotDotDotIfTruncated(truncated) + ")" // 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("]")) {
+ val itr = javaCol.iterator().asScala
+ val (taken, truncated) = if (javaCol.size > sizeLimit.value) (itr.take(sizeLimit.value), true) else (itr, false)
+ "[" + taken.map(prettify(_, processed + javaCol)).mkString(", ") + dotDotDotIfTruncated(truncated) + "]"
+ }
+ 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("}")) {
+ val itr = javaMap.entrySet.iterator.asScala
+ val (taken, truncated) = if (javaMap.size > sizeLimit.value) (itr.take(sizeLimit.value), true) else (itr, false)
+ "{" + taken.map { entry =>
+ prettify(entry.getKey, processed + javaMap) + "=" + prettify(entry.getValue, processed + javaMap)
+ }.mkString(", ") + dotDotDotIfTruncated(truncated) + "}"
+ }
+ else
+ theToString
+ // SKIP-SCALATESTJS,NATIVE-END
+ case anythingElse => anythingElse.toString
+ }
+ }
+}
+
/**
* Companion object for `Prettifier` that provides a default `Prettifier` implementation.
*/
@@ -265,7 +334,12 @@ object Prettifier {
* For anything else, it returns the result of invoking `toString`.
*
*/
- implicit val default: Prettifier = new DefaultPrettifier(SizeLimit(0))
+ implicit val default: Prettifier = new DefaultPrettifier()
+
+ /**
+ * Create a default prettifier instance with collection size limit.
+ */
+ def truncateAt(limit: SizeLimit): Prettifier = new TruncatingPrettifier(limit)
/**
* A basic `Prettifier`.