Search code examples
scalasbtscalacheck

How to display entire stack trace for thrown exceptions from ScalaCheck tests?


I'm running ScalaCheck tests in sbt, and if my test fails because the code under test throws an exception, the test report shows the failed test, the thrown exception and the message, but not the entire stack trace (note the mere Exception: java.lang.NullPointerException: exception exception message below).

[info] Loading global plugins from /Users/jacek/.sbt/0.13/plugins
[info] Set current project to scalacheck (in build file:/Users/jacek/sandbox/scalacheck/)
[info] Updating {file:/Users/jacek/sandbox/scalacheck/}scalacheck...
[info] Resolving jline#jline;2.11 ...
[info] Done updating.
[info] Compiling 1 Scala source to /Users/jacek/sandbox/scalacheck/target/scala-2.11/test-classes...
[info] ! String.throw exception: Exception raised on property evaluation.
[info] > ARG_0: ""
[info] > Exception: java.lang.NullPointerException: exception
[error] Error: Total 1, Failed 0, Errors 1, Passed 0
[error] Error during tests:
[error]     StringSpecification
[error] (test:test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 5 s, completed Jun 25, 2014 3:25:47 AM

I found https://groups.google.com/forum/#!msg/scalacheck/AGBgE_JlqpI/B2eSG84_QzYJ from 2008 which seems to report the same issue, and indicates it should be fixed in the next release. I'm currently using the latest release 1.11.4.

I also found http://www.scala-sbt.org/release/docs/Testing.html which indicates sbt has a testOptions key which I suppose seems reasonable to use, and I know ScalaTest has a setting for full stack traces, "-F", but that doesn't work for ScalaCheck. Even the example from the above page, testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-d", "-g") gives me an error:

[error] Could not run test org.example.myproject.MyTestClass: java.lang.Exception: [1.1] failure: option name expected
[error] 
[error] -d -g
[error] ^

How do I use these test arguments, is there a list of these arguments anywhere, and finally, is it possible to get a stacktrace out of it all, or am I chasing a red herring?


Solution

  • tl;dr There's no support for verbosity under sbt with the released version of ScalaCheck. You'd have to build a version from the sources yourself to have the feature.

    The available options for ScalaCheck are described in Test Execution:

    Available options:
      -workers, -w: Number of threads to execute in parallel for testing
      -minSize, -n: Minimum data generation size
      -verbosity, -v: Verbosity level
      -minSuccessfulTests, -s: Number of tests that must succeed in order to pass a property
      -maxDiscardRatio, -r: The maximum ratio between discarded and succeeded tests allowed before ScalaCheck stops testing a property. At least minSuccessfulTests will always be tested, though.
      -maxSize, -x: Maximum data generation size
    

    The source code of org.scalacheck.util.Pretty tells us more about the different levels of vebosity:

    implicit def prettyThrowable(e: Throwable) = Pretty { prms =>
      val strs = e.getStackTrace.map { st =>
        import st._
        getClassName+"."+getMethodName + "("+getFileName+":"+getLineNumber+")"
      }
    
      val strs2 =
        if(prms.verbosity <= 0) Array[String]()
        else if(prms.verbosity <= 1) strs.take(5)
        else strs
    
      e.getClass.getName + ": " + e.getMessage / strs2.mkString("\n")
    }
    

    So, 0 gives nothing, 1 takes 5 lines out of a stack trace, whereas a number greater than 1 gives you the entire stack trace as follows:

    ➜  scalacheck  scala -cp .:/Users/jacek/.ivy2/cache/org.scalacheck/scalacheck_2.11/jars/scalacheck_2.11-1.11.4.jar StringSpecification -verbosity 3
    + String.startsWith: OK, passed 100 tests.
    Elapsed time: 0.242 sec
    ! String.concatenate: Falsified after 0 passed tests.
    > ARG_0: ""
    > ARG_1: ""
    Elapsed time: 0.003 sec
    + String.substring: OK, passed 100 tests.
    Elapsed time: 0.126 sec
    ! String.throw exception: Exception raised on property evaluation.
    > ARG_0: ""
    > Exception: java.lang.NullPointerException: exception
    StringSpecification$$anonfun$14.apply(StringSpecification.scala:19)
    StringSpecification$$anonfun$14.apply(StringSpecification.scala:18)
    scala.Function1$$anonfun$andThen$1.apply(Function1.scala:55)
    org.scalacheck.Prop$$anonfun$forAllShrink$1$$anonfun$3.apply(Prop.scala:622
      )
    org.scalacheck.Prop$$anonfun$forAllShrink$1$$anonfun$3.apply(Prop.scala:622
      )
    org.scalacheck.Prop$.secure(Prop.scala:473)
    org.scalacheck.Prop$$anonfun$forAllShrink$1.org$scalacheck$Prop$$anonfun$$r
      esult$1(Prop.scala:622)
    org.scalacheck.Prop$$anonfun$forAllShrink$1.apply(Prop.scala:659)
    org.scalacheck.Prop$$anonfun$forAllShrink$1.apply(Prop.scala:616)
    org.scalacheck.Prop$$anon$1.apply(Prop.scala:309)
    org.scalacheck.Test$.org$scalacheck$Test$$workerFun$1(Test.scala:335)
    org.scalacheck.Test$$anonfun$org$scalacheck$Test$$worker$1$1.apply(Test.sca
      la:316)
    org.scalacheck.Test$$anonfun$org$scalacheck$Test$$worker$1$1.apply(Test.sca
      la:316)
    org.scalacheck.Test$.check(Test.scala:385)
    org.scalacheck.Test$$anonfun$checkProperties$1.apply(Test.scala:402)
    org.scalacheck.Test$$anonfun$checkProperties$1.apply(Test.scala:395)
    scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala
      :245)
    scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala
      :245)
    scala.collection.immutable.List.foreach(List.scala:383)
    scala.collection.generic.TraversableForwarder$class.foreach(TraversableForw
      arder.scala:35)
    scala.collection.mutable.ListBuffer.foreach(ListBuffer.scala:45)
    scala.collection.TraversableLike$class.map(TraversableLike.scala:245)
    scala.collection.AbstractTraversable.map(Traversable.scala:104)
    org.scalacheck.Test$.checkProperties(Test.scala:395)
    org.scalacheck.Properties.mainRunner(Properties.scala:62)
    org.scalacheck.Prop$class.main(Prop.scala:106)
    org.scalacheck.Properties.main(Properties.scala:27)
    StringSpecification.main(StringSpecification.scala:-1)
    sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:
      -2)
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:5
      7)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImp
      l.java:43)
    java.lang.reflect.Method.invoke(Method.java:606)
    scala.reflect.internal.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClas
      sLoader.scala:68)
    scala.reflect.internal.util.ScalaClassLoader$class.asContext(ScalaClassLoad
      er.scala:31)
    scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.asContext(Scala
      ClassLoader.scala:99)
    scala.reflect.internal.util.ScalaClassLoader$class.run(ScalaClassLoader.sca
      la:68)
    scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassL
      oader.scala:99)
    scala.tools.nsc.CommonRunner$class.run(ObjectRunner.scala:22)
    scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:39)
    scala.tools.nsc.CommonRunner$class.runAndCatch(ObjectRunner.scala:29)
    scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:39)
    scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:72)
    scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:94)
    scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:103)
    scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala:-1)
    Elapsed time: 0.000 sec
    

    You're right that the documentation for sbt is incorect. There are no "-d", "-g" options and I believe they're simply a copy-and-paste error in the documentation. It was already fixed in a pull request that's soon to be accepted.

    The verbosity option is not supported under sbt in the recent version of ScalaCheck 1.11.4. The following is the entire build definition of a sample project.

    build.sbt

    scalaVersion := "2.11.1"
    
    libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.11.4" % "test"
    
    testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "3")
    

    Even when the verbosity parameter is properly specified in build.sbt, test execution won't print more than a single line.

    ➜  scalacheck  xsbt test
    [info] Loading global plugins from /Users/jacek/.sbt/0.13/plugins
    [info] Set current project to scalacheck (in build file:/Users/jacek/sandbox/scalacheck/)
    [info] + String.startsWith: OK, passed 100 tests.
    [info] ! String.concatenate: Falsified after 0 passed tests.
    [info] > ARG_0: ""
    [info] > ARG_1: ""
    [info] + String.substring: OK, passed 100 tests.
    [info] ! String.throw exception: Exception raised on property evaluation.
    [info] > ARG_0: ""
    [info] > Exception: java.lang.NullPointerException: exception
    [error] Error: Total 4, Failed 1, Errors 1, Passed 2
    [error] Error during tests:
    [error]     StringSpecification
    [error] (test:test) sbt.TestsFailedException: Tests unsuccessful
    [error] Total time: 1 s, completed Jun 25, 2014 2:57:08 AM
    

    The reason is that org.scalacheck.ScalaCheckFramework has the following implementation and assumes verbosity always be 0:

    override def onTestResult(n: String, r: Test.Result) = {
      for (l <- loggers) {
        import Pretty._
        l.info(
          (if (r.passed) "+ " else "! ") + n + ": " + pretty(r, Params(0))
        )
      }
      handler.handle(asEvent((n,r)))
    }
    

    A pull request to fix it has already been accepted to the repo under Support verbosity under sbt for TestFrameworks.ScalaCheck. You'd have to build ScalaCheck yourself with sbt publishLocal in the directory where you cloned the repo. Don't forget to use 1.12.0-SNAPSHOT version in build.sbt to pick up the changes.