alexarchambault / scalacheck-shapeless

Generation of arbitrary case classes / ADTs instances with scalacheck and shapeless
Apache License 2.0
239 stars 34 forks source link

ClassCastException shrinking value class #91

Open steinybot opened 5 years ago

steinybot commented 5 years ago

I'm using https://github.com/fthomas/refined and I was pretty sure it had been working fine but today I got a ClassCastException when shrinking a case class that contained a field which was a Refined type. My guess is it has something to do with it being a value class.

Here is the exception:

Caused by: java.lang.ClassCastException: java.lang.Integer cannot be cast to eu.timepit.refined.api.Refined
    at com.geneious.nucleus.service.job.api.generators.JobStatusGenerators$anon$macro$381$1.from(JobStatusGenerators.scala:20)
    at com.geneious.nucleus.service.job.api.generators.JobStatusGenerators$anon$macro$381$1.from(JobStatusGenerators.scala:20)
    at org.scalacheck.derive.MkShrink$.$anonfun$genericProduct$2(MkShrink.scala:41)
    at scala.collection.immutable.Stream.map(Stream.scala:415)
    at org.scalacheck.derive.MkShrink$.$anonfun$lazyxmap$1(MkShrink.scala:32)
    at org.scalacheck.Shrink$$anon$1.shrink(Shrink.scala:40)
    at org.scalacheck.derive.MkHListShrink$.$anonfun$hcons$2(MkShrink.scala:88)
    at org.scalacheck.Shrink$$anon$1.shrink(Shrink.scala:40)
    at org.scalacheck.derive.MkHListShrink$.$anonfun$hcons$4(MkShrink.scala:89)
    at scala.collection.immutable.Stream.append(Stream.scala:252)
    at scala.collection.immutable.Stream$ConsWrapper.$hash$colon$colon$colon(Stream.scala:1126)
    at org.scalacheck.derive.MkHListShrink$.$anonfun$hcons$2(MkShrink.scala:88)
    at org.scalacheck.Shrink$$anon$1.shrink(Shrink.scala:40)
    at org.scalacheck.derive.MkHListShrink$.$anonfun$hcons$4(MkShrink.scala:89)
    at scala.collection.immutable.Stream.append(Stream.scala:252)
    at scala.collection.immutable.Stream$ConsWrapper.$hash$colon$colon$colon(Stream.scala:1126)
    at org.scalacheck.derive.MkHListShrink$.$anonfun$hcons$2(MkShrink.scala:88)
    at org.scalacheck.Shrink$$anon$1.shrink(Shrink.scala:40)
    at org.scalacheck.derive.MkHListShrink$.$anonfun$hcons$4(MkShrink.scala:89)
    at scala.collection.immutable.Stream.append(Stream.scala:252)
    at scala.collection.immutable.Stream$ConsWrapper.$hash$colon$colon$colon(Stream.scala:1126)
    at org.scalacheck.derive.MkHListShrink$.$anonfun$hcons$2(MkShrink.scala:88)
    at org.scalacheck.Shrink$$anon$1.shrink(Shrink.scala:40)
    at org.scalacheck.derive.MkShrink$.$anonfun$lazyxmap$1(MkShrink.scala:32)
    at org.scalacheck.Shrink$$anon$1.shrink(Shrink.scala:40)
    at org.scalacheck.Shrink$.shrink(Shrink.scala:44)
    at org.scalacheck.Prop$.$anonfun$forAll$16(Prop.scala:913)
    at org.scalacheck.Prop$.shrinker$1(Prop.scala:778)
    at org.scalacheck.Prop$.$anonfun$forAllShrink$1(Prop.scala:802)
    at org.scalacheck.Prop$.$anonfun$apply$1(Prop.scala:307)
    at org.scalacheck.PropFromFun.apply(Prop.scala:22)
    at org.scalacheck.Prop$.result$1(Prop.scala:762)
    at org.scalacheck.Prop$.$anonfun$forAllShrink$1(Prop.scala:800)
    at org.scalacheck.Prop$.$anonfun$apply$1(Prop.scala:307)
    at org.scalacheck.PropFromFun.apply(Prop.scala:22)
    at org.scalacheck.Test$.workerFun$1(Test.scala:326)
    at org.scalacheck.Test$.$anonfun$check$1(Test.scala:355)
    at org.scalacheck.Test$.$anonfun$check$1$adapted(Test.scala:355)
    at org.scalacheck.Platform$.runWorkers(Platform.scala:40)
    at org.scalacheck.Test$.check(Test.scala:355)
    at org.scalatest.enablers.UnitCheckerAsserting$CheckerAssertingImpl.check(CheckerAsserting.scala:89)
    ... 53 more

Here is the relevant code:

sealed trait JobStatus {
  def kind: String
  def dateTime: OffsetDateTime
  def messages: immutable.Seq[String]
  def progress: Percentage
}

final case class Running(dateTime: OffsetDateTime,
                         messages: immutable.Seq[String],
                         progress: Percentage) extends JobStatus {
   @transient
  override val kind: String = Running.Kind
}
object CommonRefinedTypes {
  type Percentage = Int Refined Interval.Closed[W.`0`.T, W.`100`.T]
  object Percentage extends RefinedTypeOps.Numeric[Percentage, Int]
}
object CommonRefinedTypeGenerators {
  implicit val arbPercentage: Arbitrary[Percentage] = numeric.intervalClosedArbitrary
  implicit val shrinkPercentage: Shrink[Percentage] = shrinkFrom(Percentage)

  private def shrinkFrom[A <: Refined[B, _], B: Shrink](ops: RefinedTypeOps[A, B]): Shrink[A] = Shrink { a =>
    shrink(a.value).flatMap { shrunk =>
      ops.from(shrunk).fold(_ => Stream.empty, Stream(_))
    }
  }
}
import com.geneious.nucleus.service.util.generators.CommonRefinedTypeGenerators._

trait JobStatusGenerators {
  implicit val arbRunningJobStatus: Arbitrary[Running] = MkArbitrary[Running].arbitrary
  implicit val shrinkRunningJobStatus: Shrink[Running] = MkShrink[Running].shrink
}
steinybot commented 5 years ago

If I inline shrinkFrom then it works. Not too sure what is going on there.