升级到scalacheck ["Foo", "bar"].sort(Intl.Collator().compare); //["bar", "Foo"]
后,我遇到了一个奇怪的问题,即导出1.13.3
的实例,其中A => B Or C
基本上是一个轻型Or
,几乎总是失败。
这是我可以编写的最简单的代码来重现这个问题:
Either
这失败了:
import org.scalatest.FunSuite
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalacheck.Shapeless._
class Testor extends FunSuite with GeneratorDrivenPropertyChecks {
sealed trait Or[+A, +B] extends Product with Serializable
case class Left[A](a: A) extends Or[A, Nothing]
case class Right[B](b: B) extends Or[Nothing, B]
test("reproduce") {
forAll { (i: Int, f: Int ⇒ Float Or Boolean) ⇒
f(i)
}
}
}
请注意,提供明确的RetrievalError was thrown during property evaluation.
Message: couldn't generate value
Occurred when passed generated values (
arg0 = 0, // 30 shrinks
arg1 = <function1>
)
可以解决问题,因此很明显问题出在通用推导中。
我不相信问题在于Arbitrary[Float Or Boolean]
- 我尝试编写自己的泛型推导,看它是否有帮助,但它以完全相同的方式失败。
奇怪的东西,但可能是由于任意函数的工作方式,是函数实际生成,但在评估时失败。
感谢任何帮助/建议,因为我有点卡住了。
答案 0 :(得分:3)
问题似乎是双边的:
scalacheck(org.scalacheck.Test.Parameters.default
)和scalatest中的默认测试参数的minSize
为0
。 Prop.check
尝试在启动时生成此大小的值。
MkCoproductArbitrary.ccons
在生成任意副产品时将大小零解释为终止条件,因此失败。
作为临时解决方法,在Travis的回复示例中,可以通过以下方式检查该属性:
prop.check(Test.Parameters.default.withMinSize(1))
使用在问题中使用的scalatest,min size参数也可以更改,可能是通过输入类似
的内容implicit val config = PropertyCheckConfiguration(minSize = PosZInt(1))
在测试用例之前(警告:我没有尝试/检查最新的解决方案,不像之前的纯scalacheck)。
答案 1 :(得分:2)
这是一个很好的问题,我没有真正的解决方案,但它与我现在遇到的其他一些问题类似,我们在ScalaCheck 1.13中有很好的Arbitrary
实例。
首先,这是一个不依赖于ScalaTest的最小化:
import org.scalacheck._, Shapeless._
sealed trait Foo; case object Bar extends Foo
val prop = Prop.forAll { (f: Int => Foo) => f(0); true }
然后:
scala> prop.check
! Exception raised on property evaluation.
> ARG_0: <function1>
> Exception: org.scalacheck.Gen$RetrievalError: couldn't generate value
org.scalacheck.Gen.loop$1(Gen.scala:57)
org.scalacheck.Gen.doPureApply(Gen.scala:58)
...
当我在1.13之前碰到这样的事情时,核心问题一直是当它给出的大小为零时失败的生成器,事实上罪魁祸首似乎是case 0 => Gen.fail
在{ {3}}在scalacheck-shapeless。
我们需要请求Alexandre确定,但这似乎是一种事后尝试避免递归ADT上的堆栈溢出。这是上下文:
Gen.sized {
case 0 => Gen.fail
case size =>
val sig = math.signum(size)
Gen.frequency(
1 -> Gen.resize(size - sig, Gen.lzy(headArbitrary.value.arbitrary)).map(Inl(_)),
n() -> Gen.resize(size - sig, Gen.lzy(tailArbitrary.arbitrary.arbitrary)).map(Inr(_))
)
}
在当前的实施中,sig
始终为1
,所以我猜最初case 0
不在那里,目的是做math.min(0, size - 1)
之类的事情(对于非负整数,它总是等于size - math.signum(size)
。)
如果删除case 0 => Gen.fail
行(或者如果将最小大小设置为大于零的值),则代码可以正常工作。问题是没有case 0
行,像这样的ADT的派生实例可能会溢出堆栈:
sealed abstract class Tree
final case class Node(left: Tree, right: Tree, v: Int) extends Tree
case object Leaf extends Tree
...因为即使Node
为size
,您仍可继续选择递归0
分支。
当size
为零时,你想要的是能够说出“给我一个值而不是来自递归构造函数”这样的东西,而且我一眼就看不到一种方法发生。
显然case 0 => Gen.fail
在我们新的Cogen
- ful世界中很糟糕。如果scalacheck-shapeless是我的库,那么我很想做一些非常糟糕的事情,比如删除那行,然后抓住StackOverflowError
和Gen.fail
进行递归ADT。那将是一场黑客攻击,但它仍然比目前的情况要好。
我们应该把它放在一个scalacheck-shapedless问题中。在此期间,我只需手动为Arbitrary
写Or
(您应该能够导出Right
和Left
,所以这不是太糟糕了。)