-
Notifications
You must be signed in to change notification settings - Fork 333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle Non-Serializable Exception Payload #2086
Merged
bvenners
merged 8 commits into
scalatest:3.2.x-new
from
cheeseng:feature-handle-non-serializable-exception-payload
Jan 16, 2022
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
1708001
Initial version to set throwable to None when throwable in TestFailed…
cheeseng 3f45b03
Implemented ensureSerializable() function in Events.
cheeseng fc78717
Changed to print warning messages read from message bundle, printed a…
cheeseng 21060af
Use more specific exception types instead of Throwable.
cheeseng 3c35601
Made Framework's server socket to abort after 3 attempts to read from…
cheeseng c27191a
Refactored Event.scala to reduce code duplication for serialization h…
cheeseng 949df3b
Wrapped non-serializable exception as NotSerializableWrapperException…
cheeseng d83d71e
Added pattern guide for case _: Throwable in SocketReporter to catch …
cheeseng File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,11 +20,13 @@ import org.scalactic.Requirements._ | |
import java.io.BufferedWriter | ||
import java.io.PrintWriter | ||
import java.io.StringWriter | ||
import java.io.NotSerializableException | ||
import java.util.Date | ||
// SKIP-SCALATESTJS,NATIVE-START | ||
import scala.xml.Elem | ||
// SKIP-SCALATESTJS,NATIVE-END | ||
import exceptions.StackDepthException | ||
import exceptions.NotSerializableWrapperException | ||
|
||
/** | ||
* A base class for the events that can be passed to the report function passed | ||
|
@@ -281,6 +283,47 @@ sealed abstract class Event extends Ordered[Event] with Product with Serializabl | |
} | ||
} | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]): Event | ||
|
||
private[events] def withThrowable(newThrowable: Option[Throwable]): Event = this | ||
|
||
private[events] def serializeRoundtrip(a: Any): Boolean = { | ||
try { | ||
val baos = new java.io.ByteArrayOutputStream | ||
val oos = new java.io.ObjectOutputStream(baos) | ||
oos.writeObject(a) | ||
oos.flush() | ||
val ois = new java.io.ObjectInputStream(new java.io.ByteArrayInputStream(baos.toByteArray)) | ||
ois.readObject | ||
true | ||
} | ||
catch { | ||
case _: NotSerializableException => false | ||
} | ||
} | ||
|
||
private[scalatest] def ensureSerializable(): Event = ensurePayloadSerializable(payload) | ||
|
||
private[scalatest] def ensurePayloadSerializable(payload: Option[Any]): Event = | ||
payload match { | ||
case Some(p) if !serializeRoundtrip(p) => | ||
println(Resources.unableToSerializePayload(p.getClass().getName(), this.toString())) | ||
withPayload(None) | ||
|
||
case _ => this | ||
} | ||
|
||
private[scalatest] def ensureThrowableSerializable(throwable: Option[Throwable]): Event = | ||
throwable match { | ||
case Some(t) if !serializeRoundtrip(t) => | ||
val className = t.getClass().getName() | ||
println(Resources.unableToSerializeThrowable(className, this.toString())) | ||
val ex = new NotSerializableWrapperException(t.getMessage, className, t.getStackTrace) | ||
withThrowable(Some(ex)) | ||
|
||
case _ => this | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -395,6 +438,8 @@ final case class TestStarting ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "TestStarting", "ordinal": ${ordinal.runStamp}, "suiteName": ${string(suiteName)}, "suiteId": ${string(suiteId)}, "suiteClassName": ${stringOption(suiteClassName)}, "testName": ${string(testName)}, "testText": ${string(testText)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "rerunner": ${stringOption(rerunner)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -502,6 +547,9 @@ final case class TestSucceeded ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "TestSucceeded", "ordinal": ${ordinal.runStamp}, "suiteName": ${string(suiteName)}, "suiteId": ${string(suiteId)}, "suiteClassName": ${stringOption(suiteClassName)}, "duration": ${duration.getOrElse("null")}, "testName": ${string(testName)}, "testText": ${string(testText)}, "recordedEvents" : [${recordedEvents.map(_.toJson).mkString(", ")}], "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "rerunner": ${stringOption(rerunner)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
|
||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cheeseng Seems like quite a bit of duplication. Maybe factor out a method that takes params and does this match? |
||
|
||
/** | ||
|
@@ -619,6 +667,13 @@ final case class TestFailed ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "TestFailed", "ordinal": ${ordinal.runStamp}, "message": ${string(message)}, "suiteName": ${string(suiteName)}, "suiteId": ${string(suiteId)}, "suiteClassName": ${stringOption(suiteClassName)}, "duration": ${duration.getOrElse("null")}, "testName": ${string(testName)}, "testText": ${string(testText)}, "recordedEvents" : [${recordedEvents.map(_.toJson).mkString(", ")}], "throwable": ${throwableOption(throwable)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "rerunner": ${stringOption(rerunner)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
|
||
private[events] override def withThrowable(newThrowable: Option[Throwable]): Event = copy(throwable = newThrowable) | ||
|
||
private[scalatest] override def ensureSerializable(): Event = | ||
ensurePayloadSerializable(payload).ensureThrowableSerializable(throwable) | ||
} | ||
|
||
/** | ||
|
@@ -714,6 +769,8 @@ final case class TestIgnored ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "TestIgnored", "ordinal": ${ordinal.runStamp}, "suiteName": ${string(suiteName)}, "suiteId": ${string(suiteId)}, "suiteClassName": ${stringOption(suiteClassName)}, "testName": ${string(testName)}, "testText": ${string(testText)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -810,6 +867,8 @@ final case class TestPending ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "TestPending", "ordinal": ${ordinal.runStamp}, "suiteName": ${string(suiteName)}, "suiteId": ${string(suiteId)}, "suiteClassName": ${stringOption(suiteClassName)}, "duration": ${duration.getOrElse("null")}, "testName": ${string(testName)}, "testText": ${string(testText)}, "recordedEvents" : [${recordedEvents.map(_.toJson).mkString(", ")}], "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -920,6 +979,13 @@ final case class TestCanceled ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "TestCanceled", "ordinal": ${ordinal.runStamp}, "message": ${string(message)}, "suiteName": ${string(suiteName)}, "suiteId": ${string(suiteId)}, "suiteClassName": ${stringOption(suiteClassName)}, "duration": ${duration.getOrElse("null")}, "testName": ${string(testName)}, "testText": ${string(testText)}, "recordedEvents" : [${recordedEvents.map(_.toJson).mkString(", ")}], "throwable": ${throwableOption(throwable)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "rerunner": ${stringOption(rerunner)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = this | ||
|
||
private[events] override def withThrowable(newThrowable: Option[Throwable]): Event = copy(throwable = newThrowable) | ||
|
||
private[scalatest] override def ensureSerializable(): Event = | ||
ensurePayloadSerializable(payload).ensureThrowableSerializable(throwable) | ||
} | ||
|
||
/** | ||
|
@@ -1011,6 +1077,8 @@ final case class SuiteStarting ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "SuiteStarting", "ordinal": ${ordinal.runStamp}, "suiteName": ${string(suiteName)}, "suiteId": ${string(suiteId)}, "suiteClassName": ${stringOption(suiteClassName)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "rerunner": ${stringOption(rerunner)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -1107,6 +1175,8 @@ final case class SuiteCompleted ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "SuiteCompleted", "ordinal": ${ordinal.runStamp}, "suiteName": ${string(suiteName)}, "suiteId": ${string(suiteId)}, "suiteClassName": ${stringOption(suiteClassName)}, "duration": ${duration.getOrElse("null")}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "rerunner": ${stringOption(rerunner)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -1214,6 +1284,13 @@ final case class SuiteAborted ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "SuiteAborted", "ordinal": ${ordinal.runStamp}, "message": ${string(message)}, "suiteName": ${string(suiteName)}, "suiteId": ${string(suiteId)}, "suiteClassName": ${stringOption(suiteClassName)}, "duration": ${duration.getOrElse("null")}, "throwable": ${throwableOption(throwable)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "rerunner": ${stringOption(rerunner)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
|
||
private[events] override def withThrowable(newThrowable: Option[Throwable]): Event = copy(throwable = newThrowable) | ||
|
||
private[scalatest] override def ensureSerializable(): Event = | ||
ensurePayloadSerializable(payload).ensureThrowableSerializable(throwable) | ||
} | ||
|
||
/** | ||
|
@@ -1300,6 +1377,8 @@ final case class RunStarting ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "RunStarting", "ordinal": ${ordinal.runStamp}, "testCount": ${testCount}, "configMap": { ${configMap.map(e => string(e._1) + ": " + string(e._2.toString)).mkString(", ")} }, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -1389,6 +1468,8 @@ final case class RunCompleted ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "RunCompleted", "ordinal": ${ordinal.runStamp}, "duration": ${duration.getOrElse(0L)}, "summary": ${summaryOption(summary)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -1479,6 +1560,8 @@ final case class RunStopped ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "RunStopped", "ordinal": ${ordinal.runStamp}, "duration": ${duration.getOrElse(0L)}, "summary": ${summaryOption(summary)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -1568,6 +1651,13 @@ final case class RunAborted ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "RunAborted", "ordinal": ${ordinal.runStamp}, "message": ${string(message)}, "throwable": ${throwableOption(throwable)}, "duration": ${duration.getOrElse(0L)}, "summary": ${summaryOption(summary)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
|
||
private[events] override def withThrowable(newThrowable: Option[Throwable]): Event = copy(throwable = newThrowable) | ||
|
||
private[scalatest] override def ensureSerializable(): Event = | ||
ensurePayloadSerializable(payload).ensureThrowableSerializable(throwable) | ||
} | ||
|
||
/** | ||
|
@@ -1651,6 +1741,13 @@ final case class InfoProvided ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "InfoProvided", "ordinal": ${ordinal.runStamp}, "message": ${string(message)}, "nameInfo": ${nameInfoOption(nameInfo)}, "throwable": ${throwableOption(throwable)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
|
||
private[events] override def withThrowable(newThrowable: Option[Throwable]): Event = copy(throwable = newThrowable) | ||
|
||
private[scalatest] override def ensureSerializable(): Event = | ||
ensurePayloadSerializable(payload).ensureThrowableSerializable(throwable) | ||
} | ||
|
||
/** | ||
|
@@ -1743,6 +1840,13 @@ final case class AlertProvided ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "AlertProvided", "ordinal": ${ordinal.runStamp}, "message": ${string(message)}, "nameInfo": ${nameInfoOption(nameInfo)}, "throwable": ${throwableOption(throwable)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
|
||
private[events] override def withThrowable(newThrowable: Option[Throwable]): Event = copy(throwable = newThrowable) | ||
|
||
private[scalatest] override def ensureSerializable(): Event = | ||
ensurePayloadSerializable(payload).ensureThrowableSerializable(throwable) | ||
} | ||
|
||
/** | ||
|
@@ -1835,6 +1939,13 @@ final case class NoteProvided ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "NoteProvided", "ordinal": ${ordinal.runStamp}, "message": ${string(message)}, "nameInfo": ${nameInfoOption(nameInfo)}, "throwable": ${throwableOption(throwable)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
|
||
private[events] override def withThrowable(newThrowable: Option[Throwable]): Event = copy(throwable = newThrowable) | ||
|
||
private[scalatest] override def ensureSerializable(): Event = | ||
ensurePayloadSerializable(payload).ensureThrowableSerializable(throwable) | ||
} | ||
|
||
/** | ||
|
@@ -1912,6 +2023,8 @@ final case class MarkupProvided ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "MarkupProvided", "ordinal": ${ordinal.runStamp}, "text": ${string(text)}, "nameInfo": ${nameInfoOption(nameInfo)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -1988,6 +2101,8 @@ final case class ScopeOpened ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "ScopeOpened", "ordinal": ${ordinal.runStamp}, "message": ${string(message)}, "nameInfo": ${nmInfo(nameInfo)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -2063,6 +2178,8 @@ final case class ScopeClosed ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "ScopeClosed", "ordinal": ${ordinal.runStamp}, "message": ${string(message)}, "nameInfo": ${nmInfo(nameInfo)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -2136,6 +2253,8 @@ final case class ScopePending ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "ScopePending", "ordinal": ${ordinal.runStamp}, "message": ${string(message)}, "nameInfo": ${nmInfo(nameInfo)}, "formatter": ${formatterOption(formatter)}, "location": ${locationOption(location)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = copy(payload = newPayload) | ||
} | ||
|
||
/** | ||
|
@@ -2201,6 +2320,10 @@ final case class DiscoveryStarting ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "DiscoveryStarting", "ordinal": ${ordinal.runStamp}, "configMap": { ${configMap.map(e => string(e._1) + ": " + string(e._2.toString)).mkString(", ")} }, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = this | ||
|
||
private[scalatest] override def ensureSerializable(): Event = this | ||
} | ||
|
||
/** | ||
|
@@ -2257,5 +2380,9 @@ final case class DiscoveryCompleted ( | |
import EventJsonHelper._ | ||
s"""{ "eventType": "DiscoveryCompleted", "ordinal": ${ordinal.runStamp}, "duration": ${duration.getOrElse(0L)}, "threadName": ${string(threadName)}, "timeStamp": ${timeStamp} }""".stripMargin | ||
} | ||
|
||
private[events] def withPayload(newPayload: Option[Any]) = this | ||
|
||
private[scalatest] override def ensureSerializable(): Event = this | ||
} | ||
|
18 changes: 18 additions & 0 deletions
18
jvm/core/src/main/scala/org/scalatest/exceptions/NotSerializableWrapperException.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* | ||
* 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.scalatest.exceptions | ||
|
||
class NotSerializableWrapperException(msg: String, exceptionClassName: String, exceptionStackTrace: Array[StackTraceElement]) extends Exception with Serializable |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cheeseng We shouldn't catch Throwable, but something more specific. This will turn an OutOfMemoryError into false, for example. Any reason this isn't catching NotSerializableException?