Skip to content
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

Original file line number Diff line number Diff line change
Expand Up @@ -726,4 +726,9 @@ analysis=Analysis:

deprecatedChosenStyleWarning=Warning: The chosen style has been deprecated and will be removed in future version of ScalaTest, because each style trait will be available as its own module.

flexmarkClassNotFound=Flexmark Pegdown adapter class not found, please add flexmark-all (https://mvnrepository.com/artifact/com.vladsch.flexmark/flexmark/0.62.2) to your test classpath.
flexmarkClassNotFound=Flexmark Pegdown adapter class not found, please add flexmark-all (https://mvnrepository.com/artifact/com.vladsch.flexmark/flexmark/0.62.2) to your test classpath.

unableToSerializePayload=Warning: Unable to serialize payload of type {0} for {1}, wrapping it as NotSerializableWrapperException.
unableToSerializeThrowable=Warning: Unable to serialize throwable of type {0} for {1}, setting it as NotSerializableWrapperException.
unableToReadSerializedEvent=Warning: Unable to read from client, please check on client for futher details of the problem.
unableToContinueRun=Fatal: Existing as unable to continue running tests, after 3 failing attempts to read event from server socket.
127 changes: 127 additions & 0 deletions jvm/core/src/main/scala/org/scalatest/events/Event.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Copy link
Contributor

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?

}

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
}
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)

}
Copy link
Contributor

Choose a reason for hiding this comment

The 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?


/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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
}

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
Loading