From 4f9faff9370fb7b5ceb767204bca309075a1a62d Mon Sep 17 00:00:00 2001 From: Mike Skells Date: Wed, 25 Jan 2017 22:34:10 +0000 Subject: Test IndexedSeq, including ArrayOps, WrappedArray. Specifically, the `slice` and `take` methods. --- test/junit/scala/collection/IndexedSeqTest.scala | 567 +++++++++++++++++++++++ 1 file changed, 567 insertions(+) create mode 100644 test/junit/scala/collection/IndexedSeqTest.scala diff --git a/test/junit/scala/collection/IndexedSeqTest.scala b/test/junit/scala/collection/IndexedSeqTest.scala new file mode 100644 index 0000000000..badad2b60e --- /dev/null +++ b/test/junit/scala/collection/IndexedSeqTest.scala @@ -0,0 +1,567 @@ +package scala.collection + +import org.junit.Test +import org.junit.Assert.{assertEquals, _} +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** + * base class for testing common methods on a various implementations + * + * @tparam T the collection type + * @tparam E the element type + */ +@RunWith(classOf[JUnit4]) +abstract class IndexedTest[T, E] { + + protected def size = 10 + + /** + * create a new instance of the test data with known values + */ + protected def underTest(size: Int): T + + /** + * returns the value of the data that is expected to be present int the original data at index {{{index}}} + * This is conceptully the same as the normal apply operation but unavaialbe due to the base types of T not supporting apply + * + * @param index the index to use for the returned value + * @return the value at the specified index + */ + protected def expectedValueAtIndex(index: Int): E + /** + * check some simple indexed access + */ + @Test def checkIndexedAccess: Unit = { + val test = underTest(size) + for (i <- 0 until size) { + assertEquals(s" at index $i", expectedValueAtIndex(i), get(test, i)) + } + } + + /** + * check simple equallity of the initial data. + * More a test of the infra that we use in this est than a full test of equallity + */ + @Test def checkEquals: Unit = { + val test1 = underTest(size) + val test2 = underTest(size) + doAssertEquals("", test1, test2) + assertNotSame(test1, test2) + expectSameContent("basic equallity", false, test1, test2, 0, size) + } + + protected def expectSameContent(txt: String, canBeSame:Boolean, orig: T, test: T, offset: Int, len: Int): Unit = { + val txtAndState = s"$txt canBeSame $canBeSame isMutableContent $isMutableContent isTakeAllSame $isTakeAllSame offset $offset len $len length(test) ${length(test)}" + val isValidSame = canBeSame && !isMutableContent && offset == 0 && len == size + if (isValidSame && isTakeAllSame) + assertSame(txtAndState, orig, test) + else + assertNotSame(txtAndState, orig, test) + assertSame(txtAndState, len, length(test)) + for (i <- 0 until len) { + assertEquals(s" $txtAndState $i $offset $len", expectedValueAtIndex(i + offset), get(test, i)) + } + } + + /** + * check the operation of {{{take}}} when the parameter is less than the size of the test data + */ + @Test def checkTakeNormal: Unit = { + val orig = underTest(size) + for (len <- 0 until size) { + val taken = take(orig, len) + expectSameContent(s" len $len", true, orig, taken, 0, len) + } + } + + /** + * check the operation of {{{slice}}} within the bounds of the source + */ + @Test def checkSliceNormal: Unit = { + val orig = underTest(size) + for ( + from <- 0 until size; + to <- from until size) { + + val sliced = slice(orig, from, to) + expectSameContent(s"from $from, to $to", true, orig, sliced, from, to - from) + } + } + + /** + * check the operation of {{{take}}} works for size of 0 + * There is a special case tha for some implementations empty will be a singleton + */ + @Test def checkTakeEmpty: Unit = { + val orig = underTest(size) + val empty1 = take(orig, 0) + val empty2 = take(orig, 0) + assertEquals(0, length(empty1)) + if (isEmptyConstant) assertSame(empty1, empty2) + } + + /** + * check the operation of {{{slice}}} works for size of 0 + * There is a special case tha for some implementations empty will be a singleton + */ + @Test def checkSliceEmpty: Unit = { + val orig = underTest(size) + for (start <- 0 until size) { + val empty1 = slice(orig, start, start) + val empty2 = slice(orig, start, start) + assertEquals(s"start $start", 0, length(empty1)) + if (isEmptyConstant) assertSame(s"start $start", empty1, empty2) + } + } + + /** + * check the operation of {{{take}}} works for the entire content + * There is a special case that for some immutable implementations they can share the result + */ + @Test def checkTakeAll: Unit = { + val orig = underTest(size) + val all = take(orig, size) + assertEquals(size, length(all)) + expectSameContent("take all", true, orig, all, 0, size) + if (isMutableContent) + assertNotSame(orig, all) + else if (isTakeAllSame) + assertSame(orig, all) + } + + /** + * check the operation of {{{slice}}} works for the entire content + * There is a special case that for some immutable implementations they can share the result + */ + @Test def checkSliceAll: Unit = { + val orig = underTest(size) + val all = slice(orig, 0, size) + assertEquals(size, length(all)) + expectSameContent("", true, orig, all, 0, size) + if (isMutableContent) + assertNotSame(orig, all) + else if (isTakeAllSame) + assertSame(orig, all) + } + + /** + * check that take operates appropriately for negative values + * take and slice should be lenient and silently ignore any data outside valid ranges + */ + @Test def checkTakeNeg: Unit = { + val orig = underTest(size) + val e = take(orig, 0) + for (len <- List(-1, -10, -99, Int.MinValue)) { + val empty = take(orig, len) + assertEquals(s"len $len", 0, length(empty)) + if (isEmptyConstant) assertSame(s"len $len", e, empty) + } + } + + /** + * check that take operates appropriately for lengths that exceed the input size + * take and slice should be lenient and silently ignore any data outside valid ranges + */ + @Test def checkTakeTooBig: Unit = { + val orig = underTest(size) + val e = take(orig, 0) + for (len <- List(size + 1, size + 10, Int.MaxValue)) { + val all = take(orig, len) + assertEquals(s"len $len", size, length(all)) + expectSameContent("", true, orig, all, 0, size) + } + } + + /** + * check that slice operates appropriately for negative start point + * take and slice should be lenient and silently ignore any data outside valid ranges + */ + @Test def checkSliceFromNeg: Unit = { + val orig = underTest(size) + for ( + from <- List(-1, -10, -99, Int.MinValue); + to <- List(-1, 0, 1, 5)) { + val start = slice(orig, from, to) + expectSameContent(s"from $from, to $to", true, orig, start, 0, Math.max(0, to)) + } + } + + /** + * check that slice operates appropriately for out of range end values + * take and slice should be lenient and silently ignore any data outside valid ranges + */ + @Test def checkSliceToTooBig: Unit = { + val orig = underTest(size) + for ( + from <- List(-1, -10, -99, Int.MinValue, 0, 1, 5); + to <- List(size + 1, size + 10, Int.MaxValue)) { + val start = slice(orig, from, to) + val realStart = Math.max(0, from) + val realLen = size - realStart + expectSameContent(s"from $from, to $to", true, orig, start, realStart, realLen) + } + } + + /** + * check that slice operates appropriately for negative values start and ends too large + * take and slice should be lenient and silently ignore any data outside valid ranges + */ + @Test def checkSliceFromNegAndToTooBig: Unit = { + val orig = underTest(size) + for ( + from <- List(-1, -10, -99, Int.MinValue); + to <- List(size + 1, size + 10, Int.MaxValue)) { + val all = slice(orig, from, to) + expectSameContent(s"from $from, to $to", true, orig, all, 0, size) + } + } + + protected def intercept[EX <: Exception : Manifest](fn: => Any) { + try { + val res = fn + fail(s"expected exception was not thrown: $res") + } catch { + case failed: AssertionError => throw failed + case e: Exception if manifest[EX].runtimeClass.isAssignableFrom(e.getClass) => + } + } + + //accessors + //the length of underTest + def length(underTest: T): Int + + //the value at index i of underTest + def get(underTest: T, i: Int): E + + def slice(underTest: T, from: Int, to: Int): T + + def take(underTest: T, size: Int): T + + //behaviour + /** is an empty value the same JVM instance */ + def isEmptyConstant: Boolean + + /** is a take / slice that results in all the data returned return this + * This is only relevant if !isMutableContent + */ + def isTakeAllSame: Boolean + + /** is the content of the collection mutable. + * If mutable there is not data sharing allowed by take/slice, if immutable then data sharing is possible + * and tested based on isTakeAllSame + */ + def isMutableContent: Boolean + + //helpers + //delegate equals check for support arrays + def doAssertEquals(txt: String, expected: T, actual: T) + +} +package IndexedTestImpl { + import java.lang.reflect.{Array => jlArray} + import java.lang.{Boolean => jlBoolean} + import java.lang.{Byte => jlByte} + import java.lang.{Short => jlShort} + import java.lang.{Integer => jlInt} + import java.lang.{Long => jlLong} + import java.lang.{Float => jlFloat} + import java.lang.{Double => jlDouble} + import java.lang.{Character => jlChar} + + import scala.collection.immutable.{StringLike, StringOps, WrappedString} + import scala.collection.mutable + import scala.runtime.BoxedUnit + trait DataProvider[E] { + protected def expectedValueAtIndex(index: Int): E = { + val someNumber = index + jlInt.bitCount(index) + toType(someNumber) + } + + protected def toType(n: Int): E + } + trait StringTestData extends DataProvider [String] { + def toType(n: Int) = n.toString + } + trait ByteTestData extends DataProvider [Byte] { + def toType(n: Int) = n.toByte + } + trait ShortTestData extends DataProvider [Short] { + def toType(n: Int) = n.toShort + } + trait IntTestData extends DataProvider [Int] { + def toType(n: Int) = n + } + trait LongTestData extends DataProvider [Long] { + def toType(n: Int) = n + } + trait FloatTestData extends DataProvider [Float] { + def toType(n: Int) = n + } + trait DoubleTestData extends DataProvider [Double] { + def toType(n: Int) = n + } + trait CharTestData extends DataProvider [Char] { + def toType(n: Int)= (n+64).toChar + } + trait BooleanTestData extends DataProvider [Boolean] { + def toType(n: Int)= (n & 0) == 0 + } + trait UnitTestData extends DataProvider [BoxedUnit] { + def toType(n: Int)= if ((n & 0) == 0) null else BoxedUnit.UNIT + } + + abstract class ArrayTest[E] ( + //the object or primative type of the array + val TYPE: Class[_]) extends IndexedTest[Array[E], E]{ + override final def length(underTest: Array[E]) = underTest.length + + override def get(underTest: Array[E], i: Int) = underTest(i) + + override def slice(underTest: Array[E], from: Int, to: Int) = underTest.slice(from, to) + + override def take(underTest: Array[E], size: Int) = underTest.take(size) + + override def isEmptyConstant = false + + override def isMutableContent = true + + override def isTakeAllSame = false + + override def doAssertEquals(txt: String, expected: Array[E], actual: Array[E]): Unit = { + assertEquals(txt, expected.mkString("'"), actual.mkString("'")) + } + + override def underTest(size: Int): Array[E] = { + val res = jlArray.newInstance(TYPE, size) + for (i <- 0 until size) { + jlArray.set(res, i, expectedValueAtIndex(i)) + } + res.asInstanceOf[Array[E]] + } + } + + + abstract class WrappedArrayTest[E]( + //the object or primative type of the array + val TYPE: Class[_]) extends IndexedTest[mutable.WrappedArray[E], E] with DataProvider[E]{ + import mutable.WrappedArray + override final def length(underTest: WrappedArray[E]) = underTest.length + + override def get(underTest: WrappedArray[E], i: Int) = underTest(i) + + override def slice(underTest: WrappedArray[E], from: Int, to: Int) = underTest.slice(from, to) + + override def take(underTest: WrappedArray[E], size: Int) = underTest.take(size) + + override def isEmptyConstant = false + + override def isMutableContent = true + + override def isTakeAllSame = false + + override def doAssertEquals(txt: String, expected: WrappedArray[E], actual: WrappedArray[E]): Unit = { + assertEquals(txt, expected.mkString("'"), actual.mkString("'")) + } + + override def underTest(size: Int): WrappedArray[E] = { + val res = jlArray.newInstance(TYPE, size) + for (i <- 0 until size) { + jlArray.set(res, i, expectedValueAtIndex(i)) + } + WrappedArray.make(res.asInstanceOf[Array[E]]) + } + } + + //construct the data using java as much as possible to avoid invalidating the test + + abstract class MutableIndexedSeqTest[T <: mutable.Seq[E], E] extends IndexedTest[T, E] with DataProvider[E]{ + override final def length(underTest: T) = underTest.length + + override def get(underTest: T, i: Int) = underTest(i) + + override def slice(underTest: T, from: Int, to: Int) = underTest.slice(from, to).asInstanceOf[T] + + override def take(underTest: T, size: Int) = underTest.take(size).asInstanceOf[T] + + override def isEmptyConstant = false + + override def isMutableContent = true + + override def isTakeAllSame = true + + override def doAssertEquals(txt: String, expected: T, actual: T): Unit = { + assertEquals(txt, expected, actual) + } + + def createEmpty(size: Int) : T + + override protected def underTest(size: Int): T = { + val res:T = createEmpty(size) + for (i <- 0 until size) + res(i) = expectedValueAtIndex(i) + res + } + + } + abstract class ImmutableIndexedSeqTest[T <: SeqLike[E, T], E] extends IndexedTest[T, E] with DataProvider[E] { + override final def length(underTest: T) = underTest.length + + override def get(underTest: T, i: Int) = underTest(i) + + override def slice(underTest: T, from: Int, to: Int) = underTest.slice(from, to) + + override def take(underTest: T, size: Int) = underTest.take(size) + + override def isEmptyConstant = false + + override def isMutableContent = false + + override def isTakeAllSame = true + + override def doAssertEquals(txt: String, expected: T, actual: T): Unit = { + assertEquals(txt, expected, actual) + } + + } + abstract class StringOpsBaseTest extends IndexedTest[StringOps, Char] with DataProvider[Char] { + override final def length(underTest: StringOps) = underTest.length + + override def get(underTest: StringOps, i: Int) = underTest(i) + + override def slice(underTest: StringOps, from: Int, to: Int) = underTest.slice(from, to) + + override def take(underTest: StringOps, size: Int) = underTest.take(size) + + override def isEmptyConstant = false + + override def isMutableContent = false + + override def isTakeAllSame = false + + override def doAssertEquals(txt: String, expected: StringOps, actual: StringOps): Unit = { + assertEquals(txt, expected, actual) + } + + } + + class BooleanArrayTest extends ArrayTest[Boolean](jlBoolean.TYPE) with BooleanTestData + class ByteArrayTest extends ArrayTest[Byte](jlByte.TYPE) with ByteTestData + class ShortArrayTest extends ArrayTest[Short](jlShort.TYPE) with ShortTestData + class IntArrayTest extends ArrayTest[Int](jlInt.TYPE) with IntTestData + class LongArrayTest extends ArrayTest[Long](jlLong.TYPE) with LongTestData + class DoubleArrayTest extends ArrayTest[Double](jlDouble.TYPE) with DoubleTestData + class FloatArrayTest extends ArrayTest[Float](jlFloat.TYPE) with FloatTestData + class CharArrayTest extends ArrayTest[Char](jlChar.TYPE) with CharTestData + class UnitArrayTest extends ArrayTest[BoxedUnit](null) with UnitTestData { + override def underTest(size: Int): Array[BoxedUnit] = { + val res = new Array[Unit](size) + for (i <- 0 until size) { + jlArray.set(res, i, expectedValueAtIndex(i)) + } + res.asInstanceOf[Array[BoxedUnit]] + } + } + class RefArrayTest extends ArrayTest[String](classOf[String]) with StringTestData + + class BooleanWrappedArrayTest extends WrappedArrayTest[Boolean](jlBoolean.TYPE) with BooleanTestData + class ByteWrappedArrayTest extends WrappedArrayTest[Byte](jlByte.TYPE) with ByteTestData + class ShortWrappedArrayTest extends WrappedArrayTest[Short](jlShort.TYPE) with ShortTestData + class IntWrappedArrayTest extends WrappedArrayTest[Int](jlInt.TYPE) with IntTestData + class LongWrappedArrayTest extends WrappedArrayTest[Long](jlLong.TYPE) with LongTestData + class DoubleWrappedArrayTest extends WrappedArrayTest[Double](jlDouble.TYPE) with DoubleTestData + class FloatWrappedArrayTest extends WrappedArrayTest[Float](jlFloat.TYPE) with FloatTestData + class CharWrappedArrayTest extends WrappedArrayTest[Char](jlChar.TYPE) with CharTestData + class UnitWrappedArrayTest extends WrappedArrayTest[BoxedUnit](null) with UnitTestData { + import mutable.WrappedArray + override def underTest(size: Int): WrappedArray[BoxedUnit] = { + val res = new Array[Unit](size) + for (i <- 0 until size) { + jlArray.set(res, i, expectedValueAtIndex(i)) + } + WrappedArray.make(res.asInstanceOf[Array[Unit]]) + } + } + class RefWrappedArrayTest extends WrappedArrayTest[String](classOf[String]) with StringTestData + + class ListBufferTest extends MutableIndexedSeqTest[mutable.ListBuffer[String], String] with StringTestData { + import mutable.ListBuffer + override def createEmpty(size: Int): ListBuffer[String] = { + val res = new ListBuffer[String] + for (i <- 0 until size) + res += expectedValueAtIndex(i) + res + } + } + class ArraySeqTest extends MutableIndexedSeqTest[mutable.ArraySeq[String], String] with StringTestData { + import mutable.ArraySeq + override def createEmpty(size: Int): ArraySeq[String] = { + val res = new ArraySeq[String](size) + for (i <- 0 until size) + res (i) = expectedValueAtIndex(i) + res + } + } + class ArrayBufferTest extends MutableIndexedSeqTest[mutable.ArrayBuffer[String], String] with StringTestData { + import mutable.ArrayBuffer + override def createEmpty(size: Int): ArrayBuffer[String] = { + val res = new ArrayBuffer[String](size) + for (i <- 0 until size) + res += expectedValueAtIndex(i) + res + } + } + class ListTest extends ImmutableIndexedSeqTest[List[String], String] with StringTestData { + + override protected def underTest(size: Int): List[String] = { + var res:List[String] = Nil + var index = size-1 + while (index >= 0) { + res = expectedValueAtIndex(index) :: res + index -= 1 + } + res + } + } + class StringBuilderTest extends MutableIndexedSeqTest[StringBuilder, Char] with CharTestData { + + override def createEmpty(size: Int): StringBuilder = new StringBuilder(size) + + override protected def underTest(size: Int): StringBuilder = { + var res = createEmpty(size) + for (i <- 0 until size) + res += expectedValueAtIndex(i) + res + } + } + class StringOpsTest extends StringOpsBaseTest with CharTestData { + + override protected def underTest(size: Int): StringOps = { + var res = new StringBuilder(size) + for (i <- 0 until size) + res += expectedValueAtIndex(i) + res.toString + } + } + class WrappedStringTest extends ImmutableIndexedSeqTest[WrappedString, Char] with CharTestData { + + override def isTakeAllSame: Boolean = false + + override protected def underTest(size: Int): WrappedString = { + var res = new StringBuilder(size) + for (i <- 0 until size) + res += expectedValueAtIndex(i) + new WrappedString(res.toString) + } + } + class VectorTest extends ImmutableIndexedSeqTest[Vector[String], String] with StringTestData { + + override protected def underTest(size: Int): Vector[String] = { + var res = Vector.newBuilder[String] + for (i <- 0 until size) + res += expectedValueAtIndex(i) + res.result() + } + } + +} \ No newline at end of file -- cgit v1.2.3