diff --git a/docs/hard-fork-changes.md b/docs/hard-fork-changes.md new file mode 100644 index 000000000..9750143f2 --- /dev/null +++ b/docs/hard-fork-changes.md @@ -0,0 +1,27 @@ +## A list of hard-fork changes + +Please describe here all changes which may lead to hard fork (HF for short). + +**Pull requests based on the next HF branch should be rejected, +if they contain HF change, which is not described here**. + +### Hard-fork changes in v0.7.0 (since v0.6.0) + +1. Removed RW rule `case IsNumericToLong(Def(IsNumericToInt(x))) if x.elem == LongElement => x` + This is a bug, but its fix may lead to hard-fork. + Example: + The follwing expression `MaxLong.toInt.toLong == MaxLong` + with this rule will evaluate to `true`, + without this rule will throw ArithmeticException. + MaxLong here can be any Long which is larger than MaxInt. + With this rule the expression becomes `MaxLong == MaxLong` + + 2. Removed RW rule `case CM.map(CM.map(_xs, f: RFunc[a, b]), _g: RFunc[_,c]) =>`. + Such kind of transformations in general don't preserve expression equivalence + in a strict (Call-By-Value) language. + Having such rule is another bug, which is safe by itself, but cannot + be fixed without HF. + + 3. CReplColl.updated (bug fixed) + Added index boundary check throwing IndexOutOfBoundsException. + This check is necessary to preserve Coll.updated contract. diff --git a/library-impl/src/main/scala/scalan/RTypeUtil.scala b/library-impl/src/main/scala/scalan/RTypeUtil.scala new file mode 100644 index 000000000..11fc567b7 --- /dev/null +++ b/library-impl/src/main/scala/scalan/RTypeUtil.scala @@ -0,0 +1,39 @@ +package scalan + +import spire.syntax.all._ +import spire.syntax.trig.trigOps + +object RTypeUtil { + import scalan.RType._ + import special.collection._ + + def clone[T](value: T)(implicit t: RType[T]): T = t match { + case prim: PrimitiveType[a] => value + case arrayType: ArrayType[a] => + val array = value.asInstanceOf[Array[a]] + var copy = Array.ofDim[a](array.length)(arrayType.tA.classTag) + cfor(0)(_ < array.length, _ + 1) { i => + copy(i) = clone(array(i))(arrayType.tA).asInstanceOf[a] + } + copy.asInstanceOf[T] + case pairType: PairType[a, b] => + val pair = value.asInstanceOf[Tuple2[a, b]] + return (clone(pair._1)(pairType.tFst), clone(pair._2)(pairType.tSnd)).asInstanceOf[T] + case optionType: OptionType[a] => + val option = value.asInstanceOf[Option[a]] + val cloned = if (option.isDefined) Some(clone(option.get)(optionType.tA)) else None + cloned.asInstanceOf[T] + case replCollType: ReplCollType[a] => + val coll = value.asInstanceOf[ReplColl[a]] + val cloned = clone(coll.value)(replCollType.tItem) + (new CReplColl(cloned, coll.length)(replCollType.tItem)).asInstanceOf[T] + case collType: CollType[a] => + val coll = value.asInstanceOf[Coll[a]] + val cloned = clone(coll.toArray)(ArrayType(collType.tItem)) + coll.builder.fromArray(cloned)(collType.tItem).asInstanceOf[T] + case StringType => + val arr = value.asInstanceOf[String].toArray + arr.mkString.asInstanceOf[T] + case _ => throw new RuntimeException(s"Can't clone ${t}.") + } +} diff --git a/library-impl/src/main/scala/special/collection/CollsOverArrays.scala b/library-impl/src/main/scala/special/collection/CollsOverArrays.scala index 503eea11c..d371926ad 100644 --- a/library-impl/src/main/scala/special/collection/CollsOverArrays.scala +++ b/library-impl/src/main/scala/special/collection/CollsOverArrays.scala @@ -121,12 +121,11 @@ class CollOverArray[@specialized A](val toArray: Array[A])(implicit tA: RType[A] override def updateMany(indexes: Coll[Int], values: Coll[A]): Coll[A] = { requireSameLength(indexes, values) val resArr = toArray.clone() - var i = 0 - while (i < indexes.length) { + val limit = indexes.length + cfor(0)(_ < limit, _ + 1) { i => val pos = indexes(i) if (pos < 0 || pos >= toArray.length) throw new IndexOutOfBoundsException(pos.toString) resArr(pos) = values(i) - i += 1 } builder.fromArray(resArr) } @@ -194,10 +193,10 @@ class CollOverArray[@specialized A](val toArray: Array[A])(implicit tA: RType[A] @Internal override def equals(obj: scala.Any): Boolean = obj match { - case obj: CollOverArray[_] if obj.tItem == this.tItem => - util.Objects.deepEquals(obj.toArray, toArray) case repl: CReplColl[A]@unchecked if repl.tItem == this.tItem => isReplArray(repl.length, repl.value) + case otherColl: Coll[A] if otherColl.tItem == this.tItem => + util.Objects.deepEquals(otherColl.toArray, toArray) case _ => false } @@ -326,14 +325,16 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R @Internal override def equals(that: scala.Any) = (this eq that.asInstanceOf[AnyRef]) || (that match { - case that: PairColl[_,_] if that.tItem == this.tItem => ls == that.ls && rs == that.rs - case that: ReplColl[(L,R)]@unchecked if that.tItem == this.tItem => - ls.isReplArray(that.length, that.value._1) && - rs.isReplArray(that.length, that.value._2) + case that: PairColl[_,_] if that.tItem == this.tItem => + ls == that.ls && rs == that.rs + case that: Coll[_] if that.tItem == this.tItem => + util.Objects.deepEquals(that.toArray, toArray) case _ => false }) + @Internal - override def hashCode() = ls.hashCode() * 41 + rs.hashCode() + override def hashCode() = CollectionUtil.deepHashCode(toArray) + @Internal @inline implicit def tL = ls.tItem @Internal @inline @@ -374,17 +375,15 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R cfor(0)(_ < limit, _ + 1) { i => res(i) = f((ls(i), rs(i))) } - new CollOverArray(res) + builder.fromArray(res) } @NeverInline override def exists(p: ((L, R)) => Boolean): Boolean = { val len = ls.length - var i = 0 - while (i < len) { + cfor(0)(_ < len, _ + 1) { i => val found = p((ls(i), rs(i))) if (found) return true - i += 1 } false } @@ -392,11 +391,9 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R @NeverInline override def forall(p: ((L, R)) => Boolean): Boolean = { val len = ls.length - var i = 0 - while (i < len) { + cfor(0)(_ < len, _ + 1) { i => val ok = p((ls(i), rs(i))) if (!ok) return false - i += 1 } true } @@ -405,8 +402,7 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R val len = ls.length val resL: Buffer[L] = Buffer.empty[L](ls.tItem.classTag) val resR: Buffer[R] = Buffer.empty[R](rs.tItem.classTag) - var i = 0 - while (i < len) { + cfor(0)(_ < len, _ + 1) { i => val l = ls.apply(i) val r = rs.apply(i) val ok = p((l, r)) @@ -414,7 +410,6 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R resL += l resR += r } - i += 1 } builder.pairCollFromArrays(resL.toArray(), resR.toArray()) } @@ -510,13 +505,12 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R requireSameLength(indexes, values) val resL = ls.toArray.clone() val resR = rs.toArray.clone() - var i = 0 - while (i < indexes.length) { + val limit = indexes.length + cfor(0)(_ < limit, _ + 1) { i => val pos = indexes(i) if (pos < 0 || pos >= length) throw new IndexOutOfBoundsException(pos.toString) resL(pos) = values(i)._1 resR(pos) = values(i)._2 - i += 1 } builder.pairColl(builder.fromArray(resL), builder.fromArray(resR)) } @@ -541,17 +535,13 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R resR += item._2 } } - var i = 0 - val thisLen = math.min(ls.length, rs.length) - while (i < thisLen) { + val thisLimit = math.min(ls.length, rs.length) + cfor(0)(_ < thisLimit, _ + 1) { i => addToSet((ls(i), rs(i))) - i += 1 } - i = 0 - val thatLen = that.length - while (i < thatLen) { + val thatLimit = that.length + cfor(0)(_ < thatLimit, _ + 1) { i => addToSet(that(i)) - i += 1 } builder.pairCollFromArrays(resL.toArray, resR.toArray) } @@ -598,7 +588,7 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp @NeverInline def getOrElse(i: Int, default: A): A = if (i >= 0 && i < this.length) value else default - def map[@specialized B: RType](f: A => B): Coll[B] = new CReplColl(f(value), length) + def map[@specialized B: RType](f: A => B): Coll[B] = builder.replicate(length, f(value)) @NeverInline def foreach(f: A => Unit): Unit = (0 until length).foreach(_ => f(value)) @NeverInline @@ -610,7 +600,7 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp if (length == 0) this else if (p(value)) this - else new CReplColl(value, 0) + else builder.replicate(0, value) @NeverInline def foldLeft[B](zero: B, op: ((B, A)) => B): B = @@ -626,13 +616,13 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp val lo = math.max(from, 0) val hi = math.min(math.max(until, 0), length) val size = math.max(hi - lo, 0) - new CReplColl(value, size) + builder.replicate(size, value) } @NeverInline def append(other: Coll[A]): Coll[A] = other match { - case repl: ReplColl[A@unchecked] if this.value == repl.value => - new CReplColl(value, this.length + repl.length) + case repl: ReplColl[A@unchecked] if this.value == repl.value && this.length > 0 && repl.length > 0 => + builder.replicate(this.length + repl.length, value) case _ => builder.fromArray(toArray).append(builder.fromArray(other.toArray)) } @@ -680,7 +670,7 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp if (n <= 0) builder.emptyColl else { val m = new RichInt(n).min(length) - new CReplColl(value, m) + builder.replicate(m, value) } @NeverInline @@ -696,6 +686,8 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp @NeverInline override def updated(index: Int, elem: A): Coll[A] = { + if (index < 0 || index >= length) + throw new IndexOutOfBoundsException if (elem == value) this else { val res = toArray.updated(index, elem) @@ -707,12 +699,10 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp override def updateMany(indexes: Coll[Int], values: Coll[A]): Coll[A] = { requireSameLength(indexes, values) val resArr = toArray.clone() - var i = 0 - while (i < indexes.length) { + cfor(0)(_ < indexes.length, _ + 1) { i => val pos = indexes(i) if (pos < 0 || pos >= length) throw new IndexOutOfBoundsException(pos.toString) resArr(pos) = values(i) - i += 1 } builder.fromArray(resArr) } @@ -722,10 +712,8 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp if (length <= 0) return builder.pairColl(builder.emptyColl[K], builder.emptyColl[V]) val (k, v) = m(value) var reducedV = v - var i = 1 - while (i < length) { + cfor(1)(_ < length, _ + 1) { i => reducedV = r((reducedV, v)) - i += 1 } builder.pairColl(builder.fromItems(k), builder.fromItems(reducedV)) } @@ -737,19 +725,19 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp if (repl.length > 0) { if (value == repl.value) { // both replications have the same element `value`, just return it in a singleton set - new CReplColl(value, 1) + builder.replicate(1, value) } else { builder.fromItems(value, repl.value) } } else - new CReplColl(value, 1) + builder.replicate(1, value) } else { if (repl.length > 0) { - new CReplColl(repl.value, 1) + builder.replicate(1, repl.value) } else - new CReplColl(value, 0) // empty set + builder.replicate(0, value) // empty set } case _ => if (this.length > 0) diff --git a/library-impl/src/main/scala/special/collection/Helpers.scala b/library-impl/src/main/scala/special/collection/Helpers.scala index 9f131b3f5..1531eb47e 100644 --- a/library-impl/src/main/scala/special/collection/Helpers.scala +++ b/library-impl/src/main/scala/special/collection/Helpers.scala @@ -23,9 +23,8 @@ object Helpers { val keyPositions = new java.util.HashMap[K, Int](32) val keys = mutable.ArrayBuilder.make[K] val values = Array.ofDim[V](arr.length) - var i = 0 var nValues = 0 - while (i < arr.length) { + cfor(0)(_ < arr.length, _ + 1) { i => val (key, value) = m(arr(i)) val pos = keyPositions.getOrDefault(key, 0) if (pos == 0) { @@ -36,7 +35,6 @@ object Helpers { } else { values(pos - 1) = r((values(pos - 1), value)) } - i += 1 } val resValues = Array.ofDim[V](nValues) Array.copy(values, 0, resValues, 0, nValues) diff --git a/library-impl/src/main/scala/special/collection/ViewColls.scala b/library-impl/src/main/scala/special/collection/ViewColls.scala index 4b4a3397f..fc50607f9 100644 --- a/library-impl/src/main/scala/special/collection/ViewColls.scala +++ b/library-impl/src/main/scala/special/collection/ViewColls.scala @@ -1,18 +1,38 @@ package special.collection -import scalan.{NeverInline, RType} +import java.util.Objects + +import scalan.util.CollectionUtil +import scalan.{Internal, NeverInline, RType} import spire.syntax.all.cfor +import java.util + +import scalan.RType.PrimitiveType +/** Collection lazily computed from `source` using function `f`, such that + * `CViewColl(xs, f)` is equals to `xs.map(f)`, but computed lazily. + * This implementation is not thread-safe. + */ class CViewColl[@specialized A, @specialized B](val source: Coll[A], val f: A => B)(implicit val tItem: RType[B]) extends Coll[B] { - private var isCalculated: Array[Boolean] = Array.ofDim[Boolean](source.length)(RType.BooleanType.classTag) + private var isCalculated: Array[Boolean] = new Array[Boolean](source.length) private var items: Array[B] = Array.ofDim[B](source.length)(tItem.classTag) - private var calculatedCount = 0 + + /** How many items has been calculated. */ + private var calculatedCount: Int = 0 + + @inline private def isAllItemsCalculated(): Boolean = calculatedCount == length def fromPartialCalculation(calculated: Array[Boolean], calculatedItems: Array[B]): CViewColl[A, B] = { - if (calculated.length != source.length || calculatedItems.length != source.length) - throw new RuntimeException("Can't make partial collection: calculated items dimension != source dimension") + val calcLen = calculated.length + if (calcLen != calculatedItems.length) + throw new RuntimeException( + s"Can't make partial collection: calculatedItems.length != calculatedItems.length (${calcLen}, ${calculatedItems.length})") + val len = source.length + if (calcLen != len) + throw new RuntimeException( + s"Can't make partial collection: calculated.length != source.length (${calcLen}, $len)") isCalculated = calculated items = calculatedItems calculatedCount = 0 @@ -21,23 +41,22 @@ class CViewColl[@specialized A, @specialized B](val source: Coll[A], val f: A => calculatedCount += 1 } } - this } - private def isAllItemsCalculated(): Boolean = calculatedCount == length - - private def calculateItem(index: Int): Unit = { + /** Calculate item without incrementing counter. */ + @inline private def calculateItem(index: Int): Unit = { items(index) = f(source(index)) isCalculated(index) = true } - private def ensureItemNoCalcCountChange(index: Int): Unit = { + @inline private def ensureItemNoCalcCountChange(index: Int): Unit = { if (!isCalculated(index)) { calculateItem(index) } } + /** Compute item if needed and increment counter. */ private def ensureItem(index: Int): Unit = { if (!isCalculated(index)) { calculateItem(index) @@ -45,6 +64,7 @@ class CViewColl[@specialized A, @specialized B](val source: Coll[A], val f: A => } } + /** Compute if needed and return item at `index`. Doesn't check that the index is in range. */ @inline private def ensureAndGetItem(index: Int): B = { ensureItem(index) items(index) @@ -105,16 +125,16 @@ class CViewColl[@specialized A, @specialized B](val source: Coll[A], val f: A => override def forall(p: B => Boolean): Boolean = toArray.forall(p) @NeverInline - override def filter(p: B => Boolean): Coll[B] = builder.fromArray(toArray)(tItem).filter(p) + override def filter(p: B => Boolean): Coll[B] = builder.fromArray(toArray)(tItem).filter(p) // TODO optimize @NeverInline override def foldLeft[C](zero: C, op: ((C, B)) => C): C = toArray.foldLeft(zero)((item1, item2) => op((item1, item2))) @NeverInline - override def indices: Coll[Int] = builder.fromArray((0 until source.length).toArray) + override def indices: Coll[Int] = builder.fromArray((0 until source.length).toArray) // TODO optimize @NeverInline - override def flatMap[C: RType](g: B => Coll[C]): Coll[C] = builder.fromArray(toArray)(tItem).flatMap(g) + override def flatMap[C: RType](g: B => Coll[C]): Coll[C] = builder.fromArray(toArray)(tItem).flatMap(g) // TODO optimize @NeverInline override def segmentLength(p: B => Boolean, from: Int): Int = { @@ -139,7 +159,7 @@ class CViewColl[@specialized A, @specialized B](val source: Coll[A], val f: A => } @NeverInline - override def lastIndexWhere(p: B => Boolean, end: Int): Int = toArray.lastIndexWhere(p, end) + override def lastIndexWhere(p: B => Boolean, end: Int): Int = toArray.lastIndexWhere(p, end) // TODO optimize @NeverInline override def take(n: Int): Coll[B] = { @@ -151,7 +171,9 @@ class CViewColl[@specialized A, @specialized B](val source: Coll[A], val f: A => } @NeverInline - override def partition(pred: B => Boolean): (Coll[B], Coll[B]) = builder.fromArray(toArray)(tItem).partition(pred) + override def partition(pred: B => Boolean): (Coll[B], Coll[B]) = { + builder.fromArray(toArray)(tItem).partition(pred) // TODO optimize + } @NeverInline override def patch(from: Int, @@ -264,5 +286,57 @@ class CViewColl[@specialized A, @specialized B](val source: Coll[A], val f: A => builder.makePartialView(source.reverse, f, isCalculated.reverse, items.reverse)(tItem) } - override private[collection] def isReplArray(len: Int, value: B) = ??? + @Internal + protected def isAllPrimValue(value: B): Boolean = { + cfor(0)(_ < length, _ + 1) { i => + if (this(i) != value) return false + } + true + } + + @Internal + protected def isAllDeepEquals(value: Any): Boolean = { + cfor(0)(_ < length, _ + 1) { i => + if (!Objects.deepEquals(this(i), value)) return false + } + true + } + + /* + * We could just verify that source is repl array and match f(source(0)) == value, + * assuming that 'f' gives every time the same result on same data. + */ + @Internal + override private[collection] def isReplArray(len: Int, value: B): Boolean = { + if (length != len) return false + if (length > 0) { + /* + * The calculation of `f` may take a long time. + * We use `this(0)` which returns value calculate on demand (Call-by-need). + * `f(source(0))` will calculate value no matter if it was already done (Call-by-name) + */ + this(0) == value && source.isReplArray(len, source(0)) + } else { + true + } + } + + @Internal + override def equals(obj: scala.Any): Boolean = obj match { + case repl: CReplColl[B]@unchecked if repl.tItem == this.tItem => + isReplArray(repl.length, repl.value) + case pair: PairColl[a, b] if pair.tItem == this.tItem && this.length == pair.length => + cfor(0)(_ < length, _ + 1) { i => + val current = this(i).asInstanceOf[(a, b)] + if (pair.ls(i) != current._1 || pair.rs(i) != current._2) + return false + } + true + case otherColl: Coll[B] if otherColl.tItem == this.tItem => + util.Objects.deepEquals(otherColl.toArray, toArray) + case _ => false + } + + @Internal + override def hashCode(): Int = CollectionUtil.deepHashCode(toArray) } diff --git a/library/src/test/scala/scalan/RTypeGenCoverageChecker.scala b/library/src/test/scala/scalan/RTypeGenCoverageChecker.scala new file mode 100644 index 000000000..9d92556ac --- /dev/null +++ b/library/src/test/scala/scalan/RTypeGenCoverageChecker.scala @@ -0,0 +1,75 @@ +package scalan + +import spire.syntax.all.cfor + +/** + * In future we could have some other RType generators, + * so we will to unify coverage checks through this trait. + */ +trait RTypeGenCoverageChecker { + /** Take type into consideration. + * + * @param item RType[_] value to be marked as generated + */ + def consider(item: RType[_]): Unit + + /** Check if all required types are generated. + * + * @param depth There could be nested types. This parameter show how deep this nesting should be checked. + * @return `true` if every required types has been generated, `false` otherwise. + */ + def isFullyCovered(depth: Int): Boolean +} + +class FullTypeCoverageChecker extends RTypeGenCoverageChecker { + import RType._ + import special.collection._ + + private type TypePosition = (Class[_], Int) + private var typePositions: Set[TypePosition] = Set.empty + + override def consider(item: RType[_]): Unit = { + decomposeValue(item) + } + + override def isFullyCovered(depth: Int): Boolean = { + val typesForCoverage = Seq(classOf[PrimitiveType[_]], classOf[PairType[_, _]], classOf[ArrayType[_]], + classOf[CollType[_]], classOf[ReplCollType[_]], classOf[OptionType[_]], StringType.classTag.getClass) + cfor(0)(_ < depth, _ + 1) { i => + for (currentType <- typesForCoverage) { + if (!typePositions.contains(new TypePosition(currentType, i))) { + println(s"Type ${currentType} is not found at depth ${i}") + return false + } + } + } + true + } + + private def attachResult(typeName: Class[_], depth: Int) = { + val newTypePosition: TypePosition = (typeName, depth) + typePositions = typePositions + newTypePosition + } + + private def decomposeValue(item: RType[_], depth: Int = 0): Unit = item match { + case prim: PrimitiveType[a] => attachResult(classOf[PrimitiveType[_]], depth) + case pair: PairType[a, b]@unchecked => + attachResult(classOf[PairType[_, _]], depth) + decomposeValue(pair.tFst, depth + 1) + decomposeValue(pair.tSnd, depth + 1) + case array: ArrayType[a] => + attachResult(classOf[ArrayType[_]], depth) + decomposeValue(array.tA, depth + 1) + case opt: OptionType[a] => + attachResult(classOf[OptionType[_]], depth) + decomposeValue(opt.tA, depth + 1) + case coll: CollType[a] => + attachResult(classOf[CollType[_]], depth) + decomposeValue(coll.tItem, depth + 1) + case replColl: ReplCollType[a] => + attachResult(classOf[ReplCollType[_]], depth) + decomposeValue(replColl.tItem, depth + 1) + case StringType => attachResult(StringType.classTag.getClass, depth) + case _ => throw new RuntimeException(s"Unknown generated RType: ${item}") + } +} diff --git a/library/src/test/scala/scalan/RTypeGens.scala b/library/src/test/scala/scalan/RTypeGens.scala new file mode 100644 index 000000000..7036f51ec --- /dev/null +++ b/library/src/test/scala/scalan/RTypeGens.scala @@ -0,0 +1,217 @@ +package scalan + +import org.scalacheck.{Arbitrary, Gen} + +class GenConfiguration( + val maxArrayLength: Int = 100, + val byteBorders: (Byte, Byte) = (Byte.MinValue, Byte.MaxValue), + val shortBorders: (Short, Short) = (Short.MinValue, Short.MaxValue), + val intBorders: (Int, Int) = (Int.MinValue, Int.MaxValue), + val longBorders: (Long, Long) = (Long.MinValue, Long.MaxValue), + val charBorders: (Char, Char) = (Char.MinValue, Char.MaxValue), + val floatBorders: (Float, Float) = (Float.MinValue, Float.MaxValue), + val doubleBorders: (Double, Double) = (Double.MinValue, Double.MaxValue) + ) + +trait RTypeGens { + import Gen._ + import RType._ + import special.collection._ + + /* + * There're three generators for primitive types, since from one side, we want to have just numeric/char types, + * sometimes there's a need to put UnitType (seldom), sometimes not, that's why additional generator has been made. + */ + val primitiveTypeGen = Gen.oneOf[RType[_]](BooleanType, ByteType, ShortType, + IntType, LongType, CharType, FloatType, DoubleType) + + val primitiveTypeWithUnitGen = Gen.oneOf[RType[_]](primitiveTypeGen, UnitType) + + // The reason why StringType is distinguished is that in type hierarchy StringType consists of Chars + val dataTypeGen = Gen.oneOf[RType[_]](primitiveTypeGen, StringType) + + def checkDepth(depth: Int): Unit = { + if (depth <= 0) { + throw new RuntimeException(s"Generation depth should be positive, found ${depth}") + } + } + + def pairTypeGen(itemGen: Gen[RType[_]], depth: Int): Gen[PairType[_, _]] = { + def pairTypeGenFinal(itemGenLeft: Gen[RType[_]], itemGenRight: Gen[RType[_]]): Gen[PairType[_, _]] = { + for { left <- itemGenLeft; right <- itemGenRight } yield new PairType(left, right) + } + depth match { + case 1 => + pairTypeGenFinal(itemGen, itemGen) + case _ => + checkDepth(depth) + val lg = pairTypeGen(itemGen, depth - 1) + val rg = pairTypeGen(itemGen, depth - 1) + Gen.oneOf( + pairTypeGenFinal(itemGen, itemGen), + pairTypeGenFinal(lg, itemGen), + pairTypeGenFinal(itemGen, rg), + pairTypeGenFinal(lg, rg), + ) + } + } + + def arrayTypeGen(itemGen: Gen[RType[_]], depth: Int): Gen[ArrayType[_]] = { + def arrayTypeGenFinal(itemGen: Gen[RType[_]]): Gen[ArrayType[_]] = { + for { item <- itemGen } yield new ArrayType(item) + } + depth match { + case 1 => + arrayTypeGenFinal(itemGen) + case _ => + checkDepth(depth) + Gen.oneOf( + arrayTypeGenFinal(itemGen), + arrayTypeGenFinal(arrayTypeGen(itemGen, depth - 1)) + ) + } + } + + def getFullTypeGen(depth: Int): Gen[RType[_]] = depth match { + case 1 => Gen.oneOf(dataTypeGen, pairTypeGen(dataTypeGen, 1), arrayTypeGen(dataTypeGen, 1)) + case _ => + checkDepth(depth) + Gen.oneOf(dataTypeGen, pairTypeGen(getFullTypeGen(depth - 1), 1), arrayTypeGen(getFullTypeGen(depth - 1), 1)) + } + + def getArrayGen(depth: Int) = arrayTypeGen(getFullTypeGen(depth - 1), 1) + + def getPairGen(depth: Int) = pairTypeGen(getFullTypeGen(depth - 1), 1) + + def optionTypeGen(itemGen: Gen[RType[_]], depth: Int): Gen[OptionType[_]] = { + def optionTypeGenFinal(itemGen: Gen[RType[_]]): Gen[OptionType[_]] = { + for {item <- itemGen } yield new OptionType(item) + } + depth match { + case 1 => + optionTypeGenFinal(itemGen) + case _ => + checkDepth(depth) + Gen.oneOf( + optionTypeGenFinal(itemGen), + optionTypeGenFinal(optionTypeGen(itemGen, depth - 1)) + ) + } + } + + def collTypeGen(itemGen: Gen[RType[_]], depth: Int): Gen[CollType[_]] = { + def collTypeGenFinal(itemGen: Gen[RType[_]]): Gen[CollType[_]] = { + for { item <- itemGen } yield new CollType(item) + } + depth match { + case 1 => + collTypeGenFinal(itemGen) + case _ => + checkDepth(depth) + Gen.oneOf( + collTypeGenFinal(itemGen), + collTypeGenFinal(collTypeGen(itemGen, depth - 1)) + ) + } + } + + def replCollTypeGen(itemGen: Gen[RType[_]], depth: Int): Gen[ReplCollType[_]] = { + def replCollTypeGenFinal(itemGen: Gen[RType[_]]): Gen[ReplCollType[_]] = { + for { item <- itemGen } yield new ReplCollType(item) + } + depth match { + case 1 => + replCollTypeGenFinal(itemGen) + case _ => + checkDepth(depth) + Gen.oneOf( + replCollTypeGenFinal(itemGen), + replCollTypeGenFinal(replCollTypeGen(itemGen, depth - 1)) + ) + } + } + + def rtypeGen(depth: Int): Gen[RType[_]] = depth match { + case 1 => Gen.oneOf(dataTypeGen, pairTypeGen(dataTypeGen, 1), optionTypeGen(dataTypeGen, 1), + arrayTypeGen(dataTypeGen, 1), collTypeGen(dataTypeGen, 1), replCollTypeGen(dataTypeGen, 1)) + case _ => + checkDepth(depth) + Gen.oneOf(dataTypeGen, pairTypeGen(rtypeGen(depth - 1), 1), optionTypeGen(rtypeGen(depth - 1), 1), + arrayTypeGen(rtypeGen(depth - 1), 1), collTypeGen(rtypeGen(depth - 1), 1), + replCollTypeGen(rtypeGen(depth - 1), 1) + ) + } + + def collTypeGen(depth: Int): Gen[RType[Coll[_]]] = { + checkDepth(depth) + val innerGen = rtypeGen(depth - 1) + Gen.oneOf(collTypeGen(innerGen, 1).asInstanceOf[Gen[RType[Coll[_]]]], + replCollTypeGen(innerGen, 1).asInstanceOf[Gen[RType[Coll[_]]]]) + } + + def collTypeGen[T](itemGen: Gen[RType[T]]): Gen[RType[Coll[T]]] = { + Gen.oneOf(collTypeGen(itemGen, 1).asInstanceOf[Gen[RType[Coll[T]]]], + replCollTypeGen(itemGen, 1).asInstanceOf[Gen[RType[Coll[T]]]]) + } + + def primitiveValueGen[T](conf: GenConfiguration)(implicit t: PrimitiveType[T]): Gen[T] = t match { + case ByteType => choose[Byte](conf.byteBorders._1, conf.byteBorders._2).asInstanceOf[Gen[T]] + case ShortType => choose[Short](conf.shortBorders._1, conf.shortBorders._2).asInstanceOf[Gen[T]] + case IntType => choose[Int](conf.intBorders._1, conf.intBorders._2).asInstanceOf[Gen[T]] + case CharType => choose[Char](conf.charBorders._1, conf.charBorders._2).asInstanceOf[Gen[T]] + case LongType => choose[Long](conf.longBorders._1, conf.longBorders._2).asInstanceOf[Gen[T]] + case FloatType => choose[Float](conf.floatBorders._1, conf.floatBorders._2).asInstanceOf[Gen[T]] + case DoubleType => choose[Double](conf.doubleBorders._1, conf.doubleBorders._2).asInstanceOf[Gen[T]] + case BooleanType => Gen.oneOf(true, false).asInstanceOf[Gen[T]] + case _ => throw new RuntimeException(s"Can't interpret ${t} as non-unit primitive type.") + } + + val builder: CollBuilder = new CollOverArrayBuilder + + def getArrayGen[T](valGen: Gen[T], length: Int)(implicit t: RType[T]): Gen[Array[T]] = { + containerOfN[Array, T](length, valGen) + } + + def getCollOverArrayGen[T: RType](valGen: Gen[T], length: Int): Gen[Coll[T]] = { + getArrayGen(valGen, length).map(builder.fromArray(_)) + } + + def getCollReplGen[T: RType](valGen: Gen[T], count: Int): Gen[Coll[T]] = { + for { l <- choose(0, count); v <- valGen } yield new CReplColl(v, l) + } + + def getCollViewGen[A: RType](valGen: Gen[Coll[A]]): Gen[Coll[A]] = { + valGen.map(builder.makeView(_, identity[A])) + } + + def getCollViewGen[A, B: RType](valGen: Gen[Coll[A]], f: A => B): Gen[Coll[B]] = { + valGen.map(builder.makeView(_, f)) + } + + def rtypeValueGen[T](conf: GenConfiguration)(implicit t: RType[T]): Gen[T] = t match { + case prim: PrimitiveType[a] => + primitiveValueGen(conf)(prim) + case arrayType: ArrayType[a] => + getArrayGen(rtypeValueGen(conf)(arrayType.tA).asInstanceOf[Gen[a]], conf.maxArrayLength)(arrayType.tA) + case pairType: PairType[a, b] => + for { left <- rtypeValueGen(conf)(pairType.tFst); right <- rtypeValueGen(conf)(pairType.tSnd) } + yield (left.asInstanceOf[a], right.asInstanceOf[b]) + case StringType => + Gen.asciiPrintableStr + case collType: CollType[a] => collType.tItem match { + case pairType: PairType[fst, snd] => + val tA = pairType.tFst + val tB = pairType.tSnd + for { + left <- getCollOverArrayGen(rtypeValueGen(conf)(tA), conf.maxArrayLength)(tA); + right <- getCollOverArrayGen(rtypeValueGen(conf)(tB), conf.maxArrayLength)(tB) + } yield new PairOfCols(left, right) + case _ => getCollOverArrayGen(rtypeValueGen(conf)(collType.tItem), conf.maxArrayLength)(collType.tItem) + } + case replCollType: ReplCollType[a] => + getCollReplGen(rtypeValueGen(conf)(replCollType.tItem), conf.maxArrayLength)(replCollType.tItem).asInstanceOf[Gen[T]] + case optionType: OptionType[a] => + Gen.option(rtypeValueGen(conf)(optionType.tA)) + case _ => throw new RuntimeException(s"Can't create generator for ${t}: this type is still not supported.") + } +} diff --git a/library/src/test/scala/scalan/RTypeTestUtil.scala b/library/src/test/scala/scalan/RTypeTestUtil.scala new file mode 100644 index 000000000..2895a9a9e --- /dev/null +++ b/library/src/test/scala/scalan/RTypeTestUtil.scala @@ -0,0 +1,89 @@ +package scalan + +import special.collection.CollType +import spire.syntax.all._ + +object RTypeTestUtil { + import scalan.RType._ + import special.collection._ + + def valueMatchesRType[T](value: T, tA: RType[_]): Boolean = tA match { + case prim: PrimitiveType[a] => value match { + case b: Byte => prim == ByteType + case b: Short => prim == ShortType + case b: Int => prim == IntType + case b: Long => prim == LongType + case b: Char => prim == CharType + case b: Float => prim == FloatType + case b: Double => prim == DoubleType + case b: Boolean => prim == BooleanType + case b: Unit => prim == UnitType + case _ => false + } + case arrayType: ArrayType[a] => value match { + case arr: Array[_] => arr.forall(item => valueMatchesRType(item, arrayType.tA)) + case _ => false + } + case pairType: PairType[a, b] => value match { + case pair: Tuple2[_, _] => valueMatchesRType(pair._1, pairType.tFst) && valueMatchesRType(pair._2, pairType.tSnd) + case _ => false + } + case StringType => value match { + case str: String => true + case _ => false + } + case collType: CollType[a] => value match { + case coll: Coll[_] => coll.tItem == collType.tItem && coll.forall(item => valueMatchesRType(item, collType.tItem)) + case _ => false + } + case replCollType: ReplCollType[a] => value match { + case coll: ReplColl[_] => coll.tItem == replCollType.tItem && coll.forall(item => valueMatchesRType(item, replCollType.tItem)) + case _ => false + } + case optionType: OptionType[a] => value match { + case op: Option[_] => op.forall(item => valueMatchesRType(item, optionType.tA)) + case _ => false + } + + case _ => false + } + + def deepEqualityChecker[A](value: A, copy: A)(implicit tA: RType[A]): Boolean = tA match { + case arrayType: ArrayType[a] => + val valInstance = value.asInstanceOf[Array[a]] + val copyInstance = copy.asInstanceOf[Array[a]] + if (valInstance.length != copyInstance.length) return false + cfor(0)(_ < valInstance.length, _ + 1) { i => + if (!deepEqualityChecker(valInstance(i), copyInstance(i))(arrayType.tA)) + return false + } + true + case prim: PrimitiveType[a] => + copy == value + case pairType: PairType[a, b] => + val valInstance = value.asInstanceOf[Tuple2[a, b]] + val copyInstance = copy.asInstanceOf[Tuple2[a, b]] + deepEqualityChecker(valInstance._1, copyInstance._1)(pairType.tFst) && deepEqualityChecker(valInstance._2, copyInstance._2)(pairType.tSnd) + case StringType => + copy == value + case opt: OptionType[a] => + val copyOpt = copy.asInstanceOf[Option[a]] + val valueOpt = value.asInstanceOf[Option[a]] + if (copyOpt.isDefined != valueOpt.isDefined) return false + if (copyOpt.isDefined) deepEqualityChecker(copyOpt.get, valueOpt.get)(opt.tA) else true + case coll: ReplCollType[a] => + val copyColl = copy.asInstanceOf[ReplColl[a]] + val valueColl = value.asInstanceOf[ReplColl[a]] + copyColl.length == valueColl.length && deepEqualityChecker(valueColl.value, copyColl.value)(coll.tItem) + case coll: CollType[a] => + val copyColl = copy.asInstanceOf[Coll[a]] + val valueColl = value.asInstanceOf[Coll[a]] + if (copyColl.length != valueColl.length) return false + cfor(0)(_ < valueColl.length, _ + 1) { i => + if (!deepEqualityChecker(valueColl(i), copyColl(i))(coll.tItem)) + return false + } + true + case _ => copy == value + } +} diff --git a/library/src/test/scala/scalan/RTypeTests.scala b/library/src/test/scala/scalan/RTypeTests.scala new file mode 100644 index 000000000..823615ec9 --- /dev/null +++ b/library/src/test/scala/scalan/RTypeTests.scala @@ -0,0 +1,63 @@ +package scalan + +import org.scalacheck.Gen +import org.scalactic.anyvals.PosInt +import org.scalatest.{Matchers, PropSpec} +import org.scalatest.prop.PropertyChecks +import scalan.RTypeTestUtil.deepEqualityChecker +import spire.syntax.all._ + +class RTypeTests extends PropSpec with PropertyChecks with Matchers with RTypeGens { + testSuite => + + import Gen._ + import RType._ + import special.collection._ + + val typeGenDepth = 5 + val coverageThreshold = 100 + + val testConfiguration = new GenConfiguration(maxArrayLength = 10) + def extendedValueGen[T](t: RType[T]): Gen[T] = rtypeValueGen(testConfiguration)(t) + + property("RType FullTypeGen coverage") { + val minSuccess = MinSuccessful(PosInt.from(coverageThreshold).get) + + val typeCoverageChecker = new FullTypeCoverageChecker() + forAll(rtypeGen(typeGenDepth), minSuccess) { t: RType[_] => + typeCoverageChecker.consider(t) + } + typeCoverageChecker.isFullyCovered(typeGenDepth) shouldBe true + } + + property("RType generate value by type") { + import scala.runtime.ScalaRunTime._ + val minSuccess = MinSuccessful(PosInt.from(coverageThreshold).get) + forAll(rtypeGen(typeGenDepth), minSuccess) { t: RType[_] => + forAll(extendedValueGen(t)) { value => + RTypeTestUtil.valueMatchesRType(value, t) shouldBe true + } + } + } + + property("RType clone value") { + def checkCopy[T](value: T, tA: RType[T]): Unit = { + val copy: T = RTypeUtil.clone(value)(tA) + + deepEqualityChecker(copy, value)(tA) shouldBe true + val haveDifferentAddresses = tA match { + case prim: PrimitiveType[a] => true + case optionType: OptionType[a] if value.asInstanceOf[Option[a]].isEmpty => true + case _ => !(copy.asInstanceOf[AnyRef] eq value.asInstanceOf[AnyRef]) + } + haveDifferentAddresses shouldBe true + } + + val minSuccess = MinSuccessful(PosInt.from(coverageThreshold).get) + forAll(rtypeGen(typeGenDepth), minSuccess) { t => + forAll(extendedValueGen(t)) { value => + checkCopy(value, t.asInstanceOf[RType[Any]]) + } + } + } +} diff --git a/library/src/test/scala/special/collections/CViewCollBenchmark.scala b/library/src/test/scala/special/collections/CViewCollBenchmark.scala index 42a02a87b..7354ae294 100644 --- a/library/src/test/scala/special/collections/CViewCollBenchmark.scala +++ b/library/src/test/scala/special/collections/CViewCollBenchmark.scala @@ -8,9 +8,10 @@ import special.collection.ExtensionMethods._ import spire.syntax.all._ trait CViewCollBenchmarkCases extends CollGens { suite: Bench[Double] => - val sizes = Gen.exponential("size")(10, 100000, 10) + val maxSize = 100000 + val sizes = Gen.exponential("size")(10, maxSize, 10) - val ranges = for { size <- sizes } yield (0 until size, 100000 / size) + val ranges = for { size <- sizes } yield (0 until size, maxSize / size) val arrays = ranges.map { case (r, i) => (r.toArray, i) } @@ -36,30 +37,30 @@ trait CViewCollBenchmarkCases extends CollGens { suite: Bench[Double] => } } performance of "map creation" in { + var res: Coll[Int] = null measure method "of CViewColl" in { using(colls) in { case (coll, n) => cfor(0)(_ < n, _ + 1) { _ => - builder.makeView(coll, inc) + res = builder.makeView(coll, inc) } - } } measure method "of Coll" in { using(colls) in { case (c, n) => cfor(0)(_ < n, _ + 1) { _ => - c.map(inc) + res = c.map(inc) } } } } + var sum = 0 performance of "map usage" in { - measure method "of CViewColl" in { - using(colls) in { case (coll, n) => + using(colls) in { case (c, n) => // n == maxSize / size cfor(0)(_ < n, _ + 1) { _ => - val view = builder.makeView[Int, Int](coll, x => x * 10) - var sum = 0 - cfor(0)(_ < view.length, _ + 2) { i => + val view = builder.makeView[Int, Int](c, x => x * 10) + sum = 0 + cfor(0)(_ < c.length, _ + 2) { i => // sum every second elements, not items are calculated sum += view(i) } } @@ -69,10 +70,10 @@ trait CViewCollBenchmarkCases extends CollGens { suite: Bench[Double] => measure method "of Coll" in { using(colls) in { case (c, n) => cfor(0)(_ < n, _ + 1) { _ => - val coll = c.map(x => x * 10) - var sum = 0 + val coll = c.map(x => x * 10) // all items are calculated + sum = 0 cfor(0)(_ < c.length, _ + 2) { i => - sum += c(i) + sum += coll(i) } } } diff --git a/library/src/test/scala/special/collections/CollGens.scala b/library/src/test/scala/special/collections/CollGens.scala index b77e4d305..2f99a1a8d 100644 --- a/library/src/test/scala/special/collections/CollGens.scala +++ b/library/src/test/scala/special/collections/CollGens.scala @@ -4,129 +4,24 @@ import scala.collection.mutable.ArrayBuffer import org.scalacheck.util.Buildable import scala.collection.mutable -import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.{Gen, Arbitrary} import scalan._ -import special.collection.{Coll, CollBuilder, CollOverArray, CollOverArrayBuilder, PairColl, ReplColl} +import special.collection.{Coll, ReplColl} -import scala.reflect.ClassTag -import scala.util.Random -trait CollGens { testSuite => +trait CollGens extends RTypeGens { testSuite => import Gen._ - val builder: CollBuilder = new CollOverArrayBuilder - val monoid = builder.Monoids.intPlusMonoid - val valGen = choose(-100, 100) - val byteGen = choose[Byte](-100, 100) - val indexGen = choose(0, 100) - val replacedGen = choose(0, 100) - val lenGen = choose(0, 100) - - val shortGen = choose[Short](Short.MinValue, Short.MaxValue) - val intGen = choose[Int](Int.MinValue, Int.MaxValue) - val longGen = choose[Long](Long.MinValue, Long.MaxValue) - val charGen = choose[Char](Char.MinValue, Char.MaxValue) - val floatGen = choose[Float](Float.MinValue, Float.MaxValue) - val doubleGen = choose[Double](Double.MinValue, Double.MaxValue) - - def getArrayGen[T](valGen: Gen[T], count: Int = 100) - (implicit evb: Buildable[T,Array[T]], evt: Array[T] => Traversable[T]): Gen[Array[T]] = { - containerOfN[Array, T](count, valGen) - } - - def getCollOverArrayGen[T: RType](valGen: Gen[T], count: Int = 100): Gen[Coll[T]] = { - containerOfN[Array, T](count, valGen).map(builder.fromArray(_)) - } - - def getCollReplGen[T: RType](lenGen: Gen[Int], valGen: Gen[T], count: Int = 100): Gen[Coll[T]] = { - for { l <- lenGen; v <- valGen } yield builder.replicate(l, v) - } - - def getCollViewGen[A: RType](valGen: Gen[Coll[A]]): Gen[Coll[A]] = { - valGen.map(builder.makeView(_, identity[A])) - } - - def getCollViewGen[A, B: RType](valGen: Gen[Coll[A]], f: A => B): Gen[Coll[B]] = { - valGen.map(builder.makeView(_, f)) - } - - def getCollPairGenFinal[A: RType, B: RType](collGenLeft: Gen[Coll[A]], collGenRight: Gen[Coll[B]]): Gen[PairColl[A, B]] = { - for { left <- collGenLeft; right <- collGenRight } yield builder.pairColl(left, right) - } - - def getCollPairGenRight[A: RType, B: RType, C: RType](collGenLeft: Gen[Coll[A]], collGenRight: Gen[PairColl[B, C]]): Gen[PairColl[A, (B, C)]] = { - for { left <- collGenLeft; right <- collGenRight } yield builder.pairColl(left, right) - } - - def getCollPairGenLeft[A: RType, B: RType, C: RType](collGenLeft: Gen[PairColl[A, B]], collGenRight: Gen[Coll[C]]): Gen[PairColl[(A, B), C]] = { - for { left <- collGenLeft; right <- collGenRight } yield builder.pairColl(left, right) - } - - def getCollPairGenBoth[A: RType, B: RType, C: RType, D: RType](collGenLeft: Gen[PairColl[A, B]], collGenRight: Gen[PairColl[C, D]]): Gen[PairColl[(A, B), (C, D)]] = { - for { left <- collGenLeft; right <- collGenRight } yield builder.pairColl(left, right) - } - - // TODO: there's a need in generator that produces collections with different elements and the same type scheme - def getSuperGen[T: RType](length: Int = 1, collGen: Gen[Coll[T]]): Gen[PairColl[_, _]] = { - length match { - case 0 => { - Gen.oneOf(getCollPairGenFinal(collGen, collGen), - getCollPairGenFinal(collGen, collGen)) - } - case _ => { - getSuperGen(length - 1, collGen) match { - case lg: Gen[PairColl[RType[_], RType[_]]]@unchecked => { - getSuperGen(length - 1, collGen) match { - case rg: Gen[PairColl[RType[_], RType[_]]]@unchecked => { - val gen = Gen.oneOf( - getCollPairGenFinal(collGen, collGen), - getCollPairGenLeft(lg, collGen), - getCollPairGenRight(collGen, rg), - getCollPairGenBoth(lg, rg), - ) - return gen - } - case _ => throw new RuntimeException("Invalid rGen") - } - } - case _ => throw new RuntimeException("Invalid lGen") - } - } - } - } - - val bytesArrayGen: Gen[Array[Byte]] = getArrayGen[Byte](byteGen) //containerOfN[Array, Byte](100, byteGen) - val arrayGen: Gen[Array[Int]] = getArrayGen[Int](valGen) //containerOfN[Array, Int](100, valGen) - val indexesGen = containerOfN[Array, Int](10, indexGen).map(arr => builder.fromArray(arr.distinct.sorted)) - - val collOverArrayGen = getCollOverArrayGen(valGen) //arrayGen.map(builder.fromArray(_)) - - val bytesOverArrayGen = getCollOverArrayGen(byteGen) - val replCollGen = getCollReplGen(lenGen, valGen) - val replBytesCollGen = getCollReplGen(lenGen, byteGen) - - val lazyCollGen = getCollViewGen(collOverArrayGen) - val lazyByteGen = getCollViewGen(bytesOverArrayGen) - - def easyFunction(arg: Int): Int = arg * 20 + 300 - def inverseEasyFunction(arg: Int): Int = (arg - 300) / 20 - - val lazyFuncCollGen = getCollViewGen[Int, Int](collOverArrayGen, easyFunction) - val lazyUnFuncCollGen = getCollViewGen[Int, Int](lazyFuncCollGen, inverseEasyFunction) - - val collGen = Gen.oneOf(collOverArrayGen, replCollGen, lazyCollGen, lazyUnFuncCollGen) - val bytesGen = Gen.oneOf(bytesOverArrayGen, replBytesCollGen, lazyByteGen) - - val innerGen = Gen.oneOf(collOverArrayGen, replCollGen) - - val superGenInt = getSuperGen(1, Gen.oneOf(collOverArrayGen, replCollGen, lazyCollGen, lazyUnFuncCollGen)) - val superGenByte = getSuperGen(1, Gen.oneOf(bytesOverArrayGen, replBytesCollGen, lazyByteGen)) - val superGen = Gen.oneOf(superGenInt, superGenByte) + val indexGen = choose(0, 100) + val byteGen = Arbitrary.arbByte.arbitrary + val intGen = Arbitrary.arbInt.arbitrary + val doubleGen = Arbitrary.arbDouble.arbitrary - val allGen = Gen.oneOf(superGen, collGen) + val monoid = builder.Monoids.intPlusMonoid - implicit val arbColl = Arbitrary(collGen) - implicit val arbBytes = Arbitrary(bytesGen) + def hashCodeLt0[T](x: T) = x.hashCode() < 0 + def hashCodeInc[T](x: T) = x.hashCode() + 1 + def plusHashcode[T](p: (T,T)) = plus(p._1.hashCode(), p._2.hashCode()) def eq0(x: Int) = x == 0 def lt0(x: Int) = x < 0 @@ -134,22 +29,13 @@ trait CollGens { testSuite => val plusF = (p: (Int,Int)) => plus(p._1, p._2) val predF = (p: (Int,Int)) => plus(p._1, p._2) > 0 def inc(x: Int) = x + 1 + def dec(x: Int) = x - 1 def collMatchRepl[B](coll: B): Boolean = coll match { case _ : ReplColl[_] => true case _ => false } - def complexFunction(arg: Int): Int = { - var i = 0 - var res = 0 - while (i < 10) { - res += arg - i - i += 1 - } - res - } - implicit def buildableColl[T:RType] = new Buildable[T,Coll[T]] { def builder = new mutable.Builder[T,Coll[T]] { val al = new ArrayBuffer[T] diff --git a/library/src/test/scala/special/collections/CollsTests.scala b/library/src/test/scala/special/collections/CollsTests.scala index 236840573..20ca98fff 100644 --- a/library/src/test/scala/special/collections/CollsTests.scala +++ b/library/src/test/scala/special/collections/CollsTests.scala @@ -1,49 +1,116 @@ package special.collections -import special.collection.{ReplColl, PairOfCols, PairColl, CReplColl, Coll} +import special.collection.{CollOverArray, ReplColl, PairOfCols, PairColl, CReplColl, Coll} import org.scalacheck.{Shrink, Gen} import org.scalatest.{PropSpec, Matchers} import org.scalatest.prop.PropertyChecks -import scalan.RType -import scalan.RType.PairType +import scalan.{GenConfiguration, RType, RTypeTestUtil, RTypeUtil} class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGens { testSuite => import Gen._ + import scalan.RType._ + import special.collection._ import special.collection.ExtensionMethods._ - property("Coll.indices") { - val minSuccess = MinSuccessful(30) - forAll(collGen, collGen, minSuccess) { (col1: Coll[Int], col2: Coll[Int]) => - col1.indices.toArray shouldBe col1.toArray.indices.toArray -// col1.zip(col2).length shouldBe math.min(col1.length, col2.length) -// TODO col1.zip(col2).indices.arr shouldBe col1.arr.zip(col2.arr).indices.toArray + def checkEquality(left: Coll[_], right: Coll[_]) = { + left.equals(right) shouldBe true + right.equals(left) shouldBe true + right.hashCode() shouldBe left.hashCode() + } + + val typeGenerationDepth = 5 + val testConfiguration = new GenConfiguration(maxArrayLength = 10) + def valueGen[T](t: RType[T]): Gen[T] = rtypeValueGen(testConfiguration)(t) + + val testMinSuccess = MinSuccessful(100) + val typeMinSuccess = MinSuccessful(5) + val successfulAtLeastOnce = MinSuccessful(1) + + val intCollRtype = CollType(IntType) + + /* Test example: + + property("Some prop") { + forAll(extendedCollTypeGen(typeGenerationDepth), testMinSuccess) { t: RType[Coll[_]] => + forAll(valueGen(t), valueGen(t), typeMinSuccess) { (col1: Coll[_], col2: Coll[_]) => + } } - forAll(superGen, minSuccess) { cl => - cl.indices.toArray shouldBe cl.toArray.indices.toArray + } + + */ + import scala.runtime.ScalaRunTime._ + + def arrayEq[T](first: Array[T], second: Array[T]) = { + (first.length == second.length && first.zip(second).forall(pair => pair._1 == pair._2)) shouldBe true + } + + def tItem(collType: RType[Coll[_]]): RType[_] = collType.asInstanceOf[RType[_]] match { + case ct: CollType[a] => + ct.tItem + case rct: ReplCollType[a] => rct.tItem + case _ => throw new RuntimeException("Not a collection") + } + + property("Coll.slice") { + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), indexGen, indexGen, typeMinSuccess) { (col, from, until) => + val res = col.slice(from, until) + res.toArray shouldBe col.toArray.slice(from, until) + } + } + } + + property("Coll.indices") { + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), valueGen(tCol), typeMinSuccess) { (col1: Coll[_], col2: Coll[_]) => + col1.indices.toArray shouldBe col1.toArray.indices.toArray + col1.zip(col2).length shouldBe math.min(col1.length, col2.length) + col1.zip(col2).indices.toArray shouldBe col1.toArray.zip(col2.toArray).indices.toArray + } } } property("Coll.flatMap") { - forAll(containerOfN[Coll, Int](3, valGen), collGen) { (zs, col) => - val matrix = zs.map(_ => col) - val res = zs.zip(matrix).flatMap(_._2) - res.toArray shouldBe zs.toArray.flatMap(_ => col.toArray) + def runTest[T](externalCol: Coll[Coll[T]], internalCol: Coll[T])(implicit tC: RType[Coll[T]]) = { + val matrix = externalCol.map(_ => internalCol)(tC) + val res = externalCol.zip(matrix) + val ret = res.flatMap(x => x._2)(tItem(tC.asInstanceOf[RType[Coll[_]]]).asInstanceOf[RType[T]]) + + val first = ret.toArray + val second = externalCol.toArray.flatMap(_ => internalCol.toArray).array.asInstanceOf[Array[T]] + arrayEq(first, second) + } + forAll(arrayTypeGen(collTypeGen(typeGenerationDepth - 1), 1).asInstanceOf[Gen[ArrayType[Coll[_]]]], testMinSuccess) { t => + forAll(valueGen(t.tA), valueGen(t), typeMinSuccess) { (col, zsArr) => + val arrayItemType = t.tA + val zs = builder.fromArray(zsArr)(arrayItemType) + runTest(zs.asInstanceOf[Coll[Coll[Any]]], col.asInstanceOf[Coll[Any]])(arrayItemType.asInstanceOf[RType[Coll[Any]]]) + } } } property("Coll.segmentLength") { - forAll(collGen, indexGen) { (col, from) => - col.segmentLength(lt0, from) shouldBe col.toArray.segmentLength(lt0, from) + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tItem(tCol)), valueGen(tCol), indexGen, typeMinSuccess) { (item, col, from) => + col.segmentLength(_.equals(item), from) shouldBe col.toArray.segmentLength(_.equals(item), from) + } } - val minSuccess = minSuccessful(30) - forAll(superGen, indexGen, minSuccess) { (col, from) => - col.segmentLength(collMatchRepl, from) shouldBe col.toArray.segmentLength(collMatchRepl, from) + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), indexGen, typeMinSuccess) { (col, from) => + col.segmentLength(collMatchRepl, from) shouldBe col.toArray.segmentLength(collMatchRepl, from) + } } } property("Coll.indexWhere") { - forAll(collGen, indexGen) { (col, from) => + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tItem(tCol)), valueGen(tCol), indexGen, typeMinSuccess) { (item, col, from) => + col.segmentLength(_.equals(item), from) shouldBe col.toArray.segmentLength(_.equals(item), from) + } + } + + forAll(valueGen(intCollRtype), indexGen, testMinSuccess) { (col: Coll[Int], from: Int) => col.indexWhere(eq0, from) shouldBe col.toArray.indexWhere(eq0, from) def p2(ab: (Int, Int)) = eq0(ab._1) && eq0(ab._2) col.zip(col).indexWhere(p2, from) shouldBe col.toArray.zip(col.toArray).indexWhere(p2, from) @@ -51,14 +118,25 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen } property("Coll.indexOf") { - forAll(collGen, indexGen, valGen) { (col, from, elem) => - col.indexOf(elem, from) shouldBe col.toArray.indexOf(elem, from) - col.zip(col).indexOf((elem, elem), from) shouldBe col.toArray.zip(col.toArray).indexOf((elem, elem), from) + def runTest[T](colItem: T, col: Coll[T], from: Int)(implicit t: RType[T]) = { + col.indexOf(colItem, from) shouldBe col.toArray.indexOf(colItem, from) + col.zip(col).indexOf((colItem, colItem), from) shouldBe col.toArray.zip(col.toArray).indexOf((colItem, colItem), from) + } + + forAll(collTypeGen(typeGenerationDepth - 1), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tItem(tCol)), valueGen(tCol), indexGen, typeMinSuccess) { (item, col, from) => + runTest(item, col.asInstanceOf[Coll[Any]], from)(tCol.asInstanceOf[RType[Any]]) + } } } property("Coll.lastIndexWhere") { - forAll(collGen, indexGen) { (col, end) => + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tItem(tCol)), valueGen(tCol), indexGen, typeMinSuccess) { (item, col, end) => + col.lastIndexWhere(_.equals(item), end) shouldBe col.lastIndexWhere(_.equals(item), end) + } + } + forAll(valueGen(intCollRtype), indexGen, testMinSuccess) { (col: Coll[Int], end: Int) => col.lastIndexWhere(eq0, end) shouldBe col.lastIndexWhere(eq0, end) def p2(ab: (Int, Int)) = eq0(ab._1) && eq0(ab._2) col.zip(col).lastIndexWhere(p2, end) shouldBe col.toArray.zip(col.toArray).lastIndexWhere(p2, end) @@ -66,7 +144,15 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen } property("Coll.partition") { - forAll(collGen) { col => + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), typeMinSuccess) { (col) => + val (lsC, rsC) = col.partition(equals) + val (ls, rs) = col.toArray.partition(equals) + lsC.toArray shouldBe ls + rsC.toArray shouldBe rs + } + } + forAll(valueGen(intCollRtype)) { col => val (lsC, rsC) = col.partition(lt0) val (ls, rs) = col.toArray.partition(lt0) lsC.toArray shouldBe ls @@ -75,51 +161,77 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen } property("Coll.patch") { - forAll(collGen, choose(-100, 100), collGen, replacedGen) { (col, from, patch, replaced) => - whenever(from < col.length ) { - val patchedC = col.patch(from, patch, replaced) - val patched = col.toArray.patch(from, patch.toArray, replaced) - patchedC.toArray shouldBe patched + def runTest[T](col: Coll[T], patch: Coll[T], from: Int, replaced: Int)(implicit t: RType[T]) = { + val patchedC = col.patch(from, patch, replaced) + val patched = col.toArray.patch(from, patch.toArray, replaced) + arrayEq(patchedC.toArray, patched.array.asInstanceOf[Array[T]]) + } + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), valueGen(tCol), choose(-100, 100), indexGen, typeMinSuccess) { (col, patch, from, replaced) => + whenever(from < col.length) { + runTest(col.asInstanceOf[Coll[Any]], patch.asInstanceOf[Coll[Any]], from, replaced)(tItem(tCol).asInstanceOf[RType[Any]]) + } } } } - property("Coll.updated") { - forAll(collGen, indexGen, valGen) { (col, index, elem) => - whenever(index < col.length ) { - val patchedC = col.updated(index, elem) - val patched = col.toArray.updated(index, elem) - patchedC.toArray shouldBe patched - } - an[IndexOutOfBoundsException] should be thrownBy { - col.updated(col.length, elem) + property("Coll.updated") { + def runTest[T](col: Coll[T], patch: T, index: Int)(implicit t: RType[T]) = { + if (0 <= index && index < col.length) { + val patchedC = col.updated(index, patch) + val patched = col.toArray.updated(index, patch) + arrayEq(patchedC.toArray, patched.array.asInstanceOf[Array[T]]) + } else { + an[IndexOutOfBoundsException] should be thrownBy { + col.updated(index, patch) + } + an[IndexOutOfBoundsException] should be thrownBy { + col.updated(-1, patch) + } } - an[IndexOutOfBoundsException] should be thrownBy { - col.updated(-1, elem) + } + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tItem(tCol)), valueGen(tCol), indexGen, typeMinSuccess) { (patch, testColl, index) => + runTest(testColl.asInstanceOf[Coll[Any]], patch.asInstanceOf[Any], index)(tItem(tCol).asInstanceOf[RType[Any]]) } } } property("Coll.updateMany") { - forAll(collGen, indexesGen) { (col, indexes) => - whenever(indexes.forall(_ < col.length)) { - val updatedC = col.updateMany(indexes, indexes) + def runTest[T](col: Coll[T], patch: Coll[T], indexes: Coll[Int])(implicit t: RType[T]) = { + if (indexes.forall(x => x < col.length && x > -1)) { + val updatedC = col.updateMany(indexes, patch) val updated = col.toArray.clone() - for (i <- indexes) - updated.update(i, i) + for ((i, value) <- indexes.zip(patch)) + updated.update(i, value) updatedC.toArray shouldBe updated + } else { + if (col.length > 0) { + an[IndexOutOfBoundsException] should be thrownBy { + col.updateMany(builder.fromItems(col.length), col.slice(0, 1)) + } + an[IndexOutOfBoundsException] should be thrownBy { + col.updateMany(builder.fromItems(-1), col.slice(0, 1)) + } + } } - an[IndexOutOfBoundsException] should be thrownBy { - col.updateMany(builder.fromItems(col.length), builder.fromItems(0)) - } - an[IndexOutOfBoundsException] should be thrownBy { - col.updateMany(builder.fromItems(-1), builder.fromItems(0)) + } + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), valueGen(tCol), valueGen(CollType(IntType)), typeMinSuccess) { (col, update, indexes) => + runTest(col.asInstanceOf[Coll[Any]], + update.slice(0, + if (indexes.length > update.length) update.length else indexes.length + ).asInstanceOf[Coll[Any]], indexes)(tItem(tCol).asInstanceOf[RType[Any]]) } } } property("Coll methods") { - forAll(collGen, indexGen) { (col, index) => + val intTunedConf = new GenConfiguration( + maxArrayLength = testConfiguration.maxArrayLength, + intBorders = (-100, 100) + ) + forAll(rtypeValueGen(intTunedConf)(intCollRtype), indexGen) { (col, index) => { val res = col.sum(monoid) res shouldBe col.toArray.sum @@ -157,140 +269,129 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen val op = (in: (Int,(Int,Int))) => in._1 + in._2._1 + in._2._2 pairs.foldLeft(0, op) shouldBe pairs.toArray.foldLeft(0)((b,a) => op((b,a))) } - whenever(index < col.length) { + if (index < col.length) { val res = col(index) res shouldBe col.toArray(index) val res2 = col.getOrElse(index, index) res2 shouldBe col.toArray(index) } - + col.getOrElse(col.length, index) shouldBe index col.getOrElse(-1, index) shouldBe index } - forAll(superGen, indexGen) { (col, index) => - whenever(index < col.length) { - val res = col(index) - res shouldBe col.toArray(index) - } - } } - property("Coll.slice") { - forAll(collGen, indexGen, indexGen) { (col, from, until) => - whenever(until < col.length) { - val res = col.slice(from, until) - res.toArray shouldBe col.toArray.slice(from, until) + property("Coll.apply") { + def runTest[T](col: Coll[T], index: Int)(implicit t: RType[T]) = { + if (index < col.length) { + val res = col(index) + res shouldBe col.toArray(index) + } else { + an[IndexOutOfBoundsException] should be thrownBy { + col(index) + } } } - - forAll(superGen, indexGen, indexGen) { (col, from, until) => - whenever(until < col.length) { - val res = col.slice(from, until) - res.toArray shouldBe col.toArray.slice(from, until) + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), indexGen, typeMinSuccess) { (col, index) => + runTest(col.asInstanceOf[Coll[Any]], index)(tItem(tCol).asInstanceOf[RType[Any]]) } } } property("Coll.append") { - forAll(collGen, collGen, valGen, MinSuccessful(50)) { (col1, col2, v) => - - { - val res = col1.append(col2) - res.toArray shouldBe (col1.toArray ++ col2.toArray) - val pairs1 = col1.zip(col1) - val pairs2 = col2.zip(col2) - val apairs = pairs1.append(pairs2) - apairs.toArray shouldBe (pairs1.toArray ++ pairs2.toArray) - } - - { - val repl1 = builder.replicate(col1.length, v) - val repl2 = builder.replicate(col2.length, v) - val arepl = repl1.append(repl2) - assert(arepl.isInstanceOf[CReplColl[Int]]) - arepl.toArray shouldBe (repl1.toArray ++ repl2.toArray) - - val pairs1 = repl1.zip(repl1) - val pairs2 = repl2.zip(repl2) - val apairs = pairs1.append(pairs2) - apairs.toArray shouldBe (pairs1.toArray ++ pairs2.toArray) - - apairs match { - case ps: PairOfCols[_,_] => - assert(ps.ls.isInstanceOf[CReplColl[Int]]) - assert(ps.rs.isInstanceOf[CReplColl[Int]]) - case _ => - assert(false, "Invalid type") - } + def runTest[T](col1: Coll[T], col2: Coll[T])(implicit t: RType[T]) = { + val res = col1.append(col2) + arrayEq(res.toArray, (col1.toArray ++ col2.toArray).array.asInstanceOf[Array[T]]) + val pairs1 = col1.zip(col1) + val pairs2 = col2.zip(col2) + val apairs = pairs1.append(pairs2) + arrayEq(apairs.toArray, (pairs1.toArray ++ pairs2.toArray)) + } + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), valueGen(tCol), typeMinSuccess) { (col1, col2) => + val collItemType = tItem(tCol) + runTest(col1.asInstanceOf[Coll[Any]], col2.asInstanceOf[Coll[Any]])(collItemType.asInstanceOf[RType[Any]]) } } } property("Coll.mapReduce") { import scalan.util.CollectionUtil.TraversableOps - def m(x: Int) = (math.abs(x) % 10, x) - forAll(collGen) { col => - val res = col.mapReduce(m, plusF) + def m[T](x: T) = (x.hashCode() % 10, x) + def projectionSnd[T](x: (T, T)): T = x._2 + def takeSnd[T](x1: T, x2: T): T = x2 + def runTest[T](col: Coll[T])(implicit t: RType[T]) = { + val res = col.mapReduce(m, projectionSnd[T])(IntType, t.asInstanceOf[RType[T]]) val (ks, vs) = builder.unzip(res) - vs.toArray.sum shouldBe col.toArray.sum ks.length <= 10 shouldBe true - res.toArray shouldBe col.toArray.toIterable.mapReduce(m)(plus).toArray + res.toArray shouldBe col.toArray.toIterable.mapReduce(m)(takeSnd[T]).toArray + } + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), typeMinSuccess) { (col) => + val collItemType = tItem(tCol) + runTest(col.asInstanceOf[Coll[Any]])(collItemType.asInstanceOf[RType[Any]]) + } } } property("Coll.groupBy") { - def key(x: Int) = math.abs(x) % 10 - forAll(collGen) { col => + def runTest[T](col: Coll[T])(implicit t: RType[T]) = { val res = col.groupBy(key) val (ks, vs) = builder.unzip(res) - vs.flatten.toArray.sum shouldBe col.toArray.sum + vs.flatten.map(key).toArray.sum shouldBe col.toArray.map(key).sum ks.length <= 10 shouldBe true - val pairs = col.map(x => (key(x), x)) + val pairs = col.map(x => (key(x), x))(pairRType(IntType, col.tItem)) val res2 = pairs.groupByKey - val (ks2, vs2) = builder.unzip(res) + val (ks2, vs2) = builder.unzip(res2) ks shouldBe ks2 vs shouldBe vs2 } + def key[T](x: T) = x.hashCode() % 10 + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), typeMinSuccess) { (col) => + val collItemType = tItem(tCol) + runTest(col.asInstanceOf[Coll[Any]])(collItemType.asInstanceOf[RType[Any]]) + } + } } property("Coll.reverse") { - val minSuccess = minSuccessful(50) - forAll(allGen, minSuccess) { col => - val res = col.reverse - res.toArray shouldBe col.toArray.reverse - val pairs = col.zip(col) - pairs.reverse.toArray shouldBe pairs.toArray.reverse -// TODO should work -// val c1 = col.asInstanceOf[Coll[Any]] -// val appended = c1.append(c1) -// appended.toArray shouldBe (c1.toArray ++ c1.toArray) + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), typeMinSuccess) { (col) => + val res = col.reverse + res.toArray shouldBe col.toArray.reverse + val pairs = col.zip(col) + pairs.reverse.toArray shouldBe pairs.toArray.reverse + + val c1 = col.asInstanceOf[Coll[Any]] + val appended = c1.append(c1) + appended.toArray shouldBe (c1.toArray ++ c1.toArray) + } } } property("Coll.take") { - val minSuccess = minSuccessful(50) - forAll(allGen, minSuccess) { col => - val n = col.length / 2 - val res = col.take(n) - res.toArray shouldBe col.toArray.take(n) - val pairs = col.zip(col) - pairs.take(n).toArray shouldBe pairs.toArray.take(n) + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), typeMinSuccess) { (col) => + val n = col.length / 2 + val res = col.take(n) + res.toArray shouldBe col.toArray.take(n) + val pairs = col.zip(col) + pairs.take(n).toArray shouldBe pairs.toArray.take(n) + } } } property("Coll.distinct") { - forAll(collGen) { col => - val res = col.distinct - res.toArray shouldBe col.toArray.distinct - val pairs = col.zip(col) - pairs.distinct.toArray shouldBe pairs.toArray.distinct - } - forAll(superGen) { col => + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), typeMinSuccess) { (col) => val res = col.distinct res.toArray shouldBe col.toArray.distinct val pairs = col.zip(col) pairs.distinct.toArray shouldBe pairs.toArray.distinct + } } } @@ -314,159 +415,74 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen zip3.hashCode() shouldBe zip4.hashCode() zip4.hashCode() shouldBe zip1.hashCode() } - val minSuccess = minSuccessful(50) - forAll(byteGen, indexGen, minSuccess) { (x, n) => - val repl = builder.replicate(n, x) - val coll = builder.fromArray(Array.fill(n)(x)) - - checkColls(repl, coll) - } - forAll(shortGen, indexGen, minSuccess) { (x, n) => - val repl = builder.replicate(n, x) - val coll = builder.fromArray(Array.fill(n)(x)) - - checkColls(repl, coll) - } - forAll(intGen, indexGen, minSuccess) { (x, n) => - val repl = builder.replicate(n, x) - val coll = builder.fromArray(Array.fill(n)(x)) - - checkColls(repl, coll) - } - forAll(longGen, indexGen, minSuccess) { (x, n) => - val repl = builder.replicate(n, x) - val coll = builder.fromArray(Array.fill(n)(x)) - - checkColls(repl, coll) - } - forAll(charGen, indexGen, minSuccess) { (x, n) => - val repl = builder.replicate(n, x) - val coll = builder.fromArray(Array.fill(n)(x)) - - checkColls(repl, coll) - } - forAll(floatGen, indexGen, minSuccess) { (x, n) => - val repl = builder.replicate(n, x) - val coll = builder.fromArray(Array.fill(n)(x)) - - checkColls(repl, coll) - } - forAll (doubleGen, indexGen, minSuccess) { (x, n) => - val repl = builder.replicate(n, x) - val coll = builder.fromArray(Array.fill(n)(x)) - - checkColls(repl, coll) - } - forAll (indexGen, minSuccess) { (n) => - val replTrue = builder.replicate(n, true) - val collTrue = builder.fromArray(Array.fill(n)(true)) - val replFalse = builder.replicate(n, false) - val collFalse = builder.fromArray(Array.fill(n)(false)) - - checkColls(replTrue, collTrue) - checkColls(replFalse, collFalse) - } - forAll(indexGen, minSuccess) { n => - val repl = builder.replicate(n, builder.fromItems(Array(1, 2, 3))) - val coll = builder.fromArray(Array.fill(n)(builder.fromItems(Array(1, 2, 3)))) - - checkColls(repl, coll) - } - forAll(indexGen, indexGen, minSuccess) { (n, m) => - val repl = builder.replicate(n, builder.replicate(m, 1)) - val coll = builder.fromArray(Array.fill(n)(builder.fromArray(Array.fill(m)(1)))) - - checkColls(repl, coll) - } - // This tuple tests fail with previous implementation - forAll (byteGen, doubleGen, intGen, indexGen, minSuccess) { (b, d, i, n) => - val repl = builder.replicate(n, (b, i)) - val coll = builder.fromArray(Array.fill[(Byte, Int)](n)((b, i))) - - checkColls(repl, coll) - } - forAll (byteGen, doubleGen, intGen, indexGen, minSuccess) { (b, d, i, n) => - val repl = builder.replicate(n, (b, (i, (d, b)))) - val coll = builder.fromArray(Array.fill[(Byte, (Int, (Double, Byte)))](n)((b, (i, (d, b))))) - - checkColls(repl, coll) - } - forAll (byteGen, doubleGen, intGen, indexGen, indexGen, minSuccess) { (b, d, i, n, m) => - val repl = builder.replicate(n, (b, ((i, (("string", builder.replicate(m, n)), Array(1, 2, 3, 4))), (d, b)))) - val coll = builder.fromArray(Array.fill(n)((b, ((i, (("string", builder.fromArray(Array.fill(m)(n))), Array(1, 2, 3, 4))), (d, b))))) - - checkColls(repl, coll) + forAll(rtypeGen(typeGenerationDepth), testMinSuccess) { t: RType[_] => + forAll(valueGen(t), indexGen, typeMinSuccess) { (item, n) => + val repl = new CReplColl(item.asInstanceOf[Any], n)(t.asInstanceOf[RType[Any]]) + val coll = new CollOverArray(Array.fill(n)(item.asInstanceOf[Any]))(t.asInstanceOf[RType[Any]]) + checkColls(repl, coll) + } } + // TODO: Test with builder } - property("PairColl.mapFirst") { - val minSuccess = minSuccessful(30) - - forAll(collGen, minSuccess) { col => - val pairs = col.zip(col) - pairs.mapFirst(inc).toArray shouldBe pairs.toArray.map { case (x, y) => (inc(x), y) } - pairs.mapSecond(inc).toArray shouldBe pairs.toArray.map { case (x, y) => (x, inc(y)) } + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), typeMinSuccess) { col => + val pairs = col.zip(col) + pairs.mapFirst(hashCodeInc).toArray shouldBe pairs.toArray.map { case (x, y) => (hashCodeInc(x), y) } + pairs.mapSecond(hashCodeInc).toArray shouldBe pairs.toArray.map { case (x, y) => (x, hashCodeInc(y)) } + } } } property("Coll.unionSet") { - forAll(collGen, collGen) { (col1, col2) => + def runTest[T](col1: Coll[T], col2: Coll[T])(implicit t: RType[T]) = { val res = col1.unionSet(col2) - res.toArray shouldBe (col1.toArray.union(col2.toArray).distinct) + arrayEq(res.toArray, (col1.toArray.union(col2.toArray).distinct).array.asInstanceOf[Array[T]]) } - builder.replicate(2, 10).unionSet(builder.replicate(3, 10)).toArray shouldBe Array(10) - forAll(superGen) { - case cl1: Coll[(_, _)] => { - val res = cl1.unionSet(cl1) - res.toArray shouldBe (cl1.toArray.union(cl1.toArray).distinct) + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), valueGen(tCol), typeMinSuccess) { (col1, col2) => + runTest(col1.asInstanceOf[Coll[Any]], col2.asInstanceOf[Coll[Any]])(tItem(tCol).asInstanceOf[RType[Any]]) } - case _ => assert(false, "Generator returned invalid PairColl") } - /* TODO: simplify the above code - * match-case removal gives the following compilation error: - type mismatch; - found : special.collection.PairColl[_$1(in value res),_$2(in value res)] where type _$2(in value res), type _$1(in value res) - required: special.collection.Coll[(_$1(in method getSuperGen), _$2(in method getSuperGen))] - val res = col1.unionSet(col1) - */ } property("Coll.diff") { - forAll(collGen, collGen) { (col1, col2) => + def runTest[T](col1: Coll[T], col2: Coll[T])(implicit t: RType[T]) = { val res = col1.diff(col2) res.toArray shouldBe (col1.toArray.diff(col2.toArray)) } - forAll(superGen) { - case col: Coll[(_, _)] => - val res = col.diff(col) - res.toArray shouldBe (col.toArray.diff(col.toArray)) - case _ => assert(false, "Generator returned invalid PairColl") // TODO make similar gens + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), valueGen(tCol), typeMinSuccess) { (col1, col2) => + runTest(col1.asInstanceOf[Coll[Any]], col2.asInstanceOf[Coll[Any]])(tItem(tCol).asInstanceOf[RType[Any]]) + } } - /* TODO: simplify the above code - * match-case removal gives the following compilation error: - type mismatch; - found : special.collection.PairColl[_$1(in value res),_$2(in value res)] where type _$2(in value res), type _$1(in value res) - required: special.collection.Coll[(_$1(in method getSuperGen), _$2(in method getSuperGen))] - val res = col.diff(col) - */ - builder.replicate(2, 10).diff(builder.replicate(1, 10)).toArray shouldBe Array(10) } property("Coll.intersect") { - forAll(collGen, collGen) { (col1, col2) => + def runTest[T](col1: Coll[T], col2: Coll[T])(implicit t: RType[T]) = { val res = col1.intersect(col2) res.toArray shouldBe (col1.toArray.intersect(col2.toArray)) } - builder.replicate(2, 10).intersect(builder.replicate(3, 10)).toArray shouldBe Array(10, 10) + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), valueGen(tCol), typeMinSuccess) { (col1, col2) => + runTest(col1.asInstanceOf[Coll[Any]], col2.asInstanceOf[Coll[Any]])(tItem(tCol).asInstanceOf[RType[Any]]) + } + } } property("CollBuilder.xor") { - forAll(bytesGen, bytesGen) { (col1, col2) => - val n = col1.length min col2.length - val c1 = col1.take(n) - val c2 = col2.take(n) - builder.xor(c1, c2).toArray shouldBe c1.toArray.zip(c2.toArray).map { case (l,r) => (l ^ r).toByte } + def runTest[T](col1: Coll[T], col2: Coll[T])(implicit t1: RType[T]) = { + val n = if (col1.length < col2.length) col1.length else col2.length + val c1 = col1.take(n).asInstanceOf[Coll[Byte]] + val c2 = col2.take(n).asInstanceOf[Coll[Byte]] + builder.xor(c1, c2).toArray shouldBe c1.toArray.zip(c2.toArray).map { case (l, r) => (l ^ r).toByte } + } + forAll(valueGen(CollType(ByteType)), valueGen(ReplCollType(ByteType)), typeMinSuccess) { (col1, col2) => + runTest(col1, col1)(ByteType) + runTest(col1, col2)(ByteType) + runTest(col2, col1)(ByteType) + runTest(col2, col2)(ByteType) } } @@ -488,19 +504,59 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen val (ks, vs) = builder.unzip(res) vs.sum(monoid) shouldBe (col.sum(monoid) * 2 + col.map(_ + 5).sum(monoid)) } -// test(builder.fromItems(0)) -// val gen = containerOfN[Array, Int](100, choose(20, 100)) -// .map(xs => builder.fromArray(xs.distinct)) - forAll(collGen) { col => + val intTunedConf = new GenConfiguration( + maxArrayLength = testConfiguration.maxArrayLength, + intBorders = (-100, 100) + ) + forAll(rtypeValueGen(intTunedConf)(intCollRtype)) { (col) => test(col) } } property("CViewColl.correctWork") { - forAll(collGen) { coll => - val view = builder.makeView(coll, complexFunction) - val usual = coll.map(complexFunction) - view.toArray shouldBe usual.toArray + forAll(collTypeGen(typeGenerationDepth), testMinSuccess) { tCol: RType[Coll[_]] => + forAll(valueGen(tCol), typeMinSuccess) { (col) => + val view = builder.makeView(col, hashCodeInc) + val usual = col.map(hashCodeInc) + view.toArray shouldBe usual.toArray + view shouldBe usual + assert(view == usual) + } + } + } + + // TODO: improve ViewColl and CollOverArray equality with complex data + property("ViewColl vs CollOverArray complex equality") { + forAll(indexGen, indexGen) { (n, item) => + def f(i: Int): (Int, Int) = (i + 10, i - 10) + val view = builder.makeView(builder.replicate(n, item), f) + val repl = builder.replicate(n, item).map(f) + + checkEquality(view, repl) + } + } + + property("CViewColl.equality") { + forAll(indexGen, intGen) { (n, item) => + def f(i: Int): Int = i + 10 + val view = builder.makeView(builder.replicate(n, item), f) + val repl = builder.replicate(n, item).map(f) + checkEquality(view, repl) + + val newView = builder.makeView(builder.makeView(view, inc), dec) + checkEquality(newView, view) + checkEquality(newView, repl) + } + forAll(getCollOverArrayGen(intGen, 10), testMinSuccess) { coll => + def f(i: Int): Int = i * 10 + val view = builder.makeView(coll, f) + val mapped = coll.map(f) + checkEquality(view, mapped) + } + forAll (byteGen, doubleGen, intGen, indexGen) { (b, d, i, n) => + val repl = builder.replicate(n, (b, i)) + val view = builder.makeView(repl, (t: (Byte, Int)) => ((t._1 / 2).toByte, t._2 * 2)) + view.equals(repl.map((t: (Byte, Int)) => ((t._1 / 2).toByte, t._2 * 2))) shouldBe true } } @@ -516,7 +572,6 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen val tokensArr = ids.zip(arr1) case class NoShrink[T](x: T) - val collGen = Gen.oneOf(builder.fromArray(arr1), builder.fromArray(arr2), builder.fromItems(1, 2, 3)).map(NoShrink(_)) val replGen = Gen.oneOf(builder.fromArray(repl1), builder.replicate(3, 1)).map(NoShrink(_)) val idsGen = Gen.oneOf(builder.fromArray(ids), builder.fromArray(ids)).map(NoShrink(_)) @@ -540,7 +595,7 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen ).map(NoShrink(_)) val minSuccess = MinSuccessful(30) - + forAll(collGen, collGen, minSuccess) { (c1, c2) => assert(c1.x == c2.x) assert(c2.x == c1.x) diff --git a/meta/src/main/scala/scalan/meta/Base.scala b/meta/src/main/scala/scalan/meta/Base.scala index 24b1cf0aa..3de6e9af5 100644 --- a/meta/src/main/scala/scalan/meta/Base.scala +++ b/meta/src/main/scala/scalan/meta/Base.scala @@ -17,7 +17,7 @@ object Base { } catch { case _: Throwable => {} } - prop.putAll(System.getProperties.asInstanceOf[util.Hashtable[Any, Any]]) + prop.asInstanceOf[util.Hashtable[Object,Object]].putAll(System.getProperties.asInstanceOf[util.Map[Object, Object]]) prop }