All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zio.test.TestRandom.scala Maven / Gradle / Ivy

There is a newer version: 2.1.14
Show newest version
/*
 * Copyright 2019-2024 John A. De Goes and the ZIO Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package zio.test

import zio._
import zio.internal.stacktracer.Tracer
import zio.stacktracer.TracingImplicits.disableAutoTrace

import java.util.UUID
import scala.annotation.tailrec
import scala.collection.immutable.Queue
import scala.math.{log, sqrt}

/**
 * `TestRandom` allows for deterministically testing effects involving
 * randomness.
 *
 * `TestRandom` operates in two modes. In the first mode, `TestRandom` is a
 * purely functional pseudo-random number generator. It will generate
 * pseudo-random values just like `scala.util.Random` except that no internal
 * state is mutated. Instead, methods like `nextInt` describe state transitions
 * from one random state to another that are automatically composed together
 * through methods like `flatMap`. The random seed can be set using `setSeed`
 * and `TestRandom` is guaranteed to return the same sequence of values for any
 * given seed. This is useful for deterministically generating a sequence of
 * pseudo-random values and powers the property based testing functionality in
 * ZIO Test.
 *
 * In the second mode, `TestRandom` maintains an internal buffer of values that
 * can be "fed" with methods such as `feedInts` and then when random values of
 * that type are generated they will first be taken from the buffer. This is
 * useful for verifying that functions produce the expected output for a given
 * sequence of "random" inputs.
 *
 * {{{
 * import zio.Random
 * import zio.test.TestRandom
 *
 * for {
 *   _ <- TestRandom.feedInts(4, 5, 2)
 *   x <- Random.nextIntBounded(6)
 *   y <- Random.nextIntBounded(6)
 *   z <- Random.nextIntBounded(6)
 * } yield x + y + z == 11
 * }}}
 *
 * `TestRandom` will automatically take values from the buffer if a value of the
 * appropriate type is available and otherwise generate a pseudo-random value,
 * so there is nothing you need to do to switch between the two modes. Just
 * generate random values as you normally would to get pseudo-random values, or
 * feed in values of your own to get those values back. You can also use methods
 * like `clearInts` to clear the buffer of values of a given type so you can
 * fill the buffer with new values or go back to pseudo-random number
 * generation.
 */
trait TestRandom extends Random with Restorable {
  def clearBooleans(implicit trace: Trace): UIO[Unit]
  def clearBytes(implicit trace: Trace): UIO[Unit]
  def clearChars(implicit trace: Trace): UIO[Unit]
  def clearDoubles(implicit trace: Trace): UIO[Unit]
  def clearFloats(implicit trace: Trace): UIO[Unit]
  def clearInts(implicit trace: Trace): UIO[Unit]
  def clearLongs(implicit trace: Trace): UIO[Unit]
  def clearStrings(implicit trace: Trace): UIO[Unit]
  def clearUUIDs(implicit trace: Trace): UIO[Unit]
  def feedBooleans(booleans: Boolean*)(implicit trace: Trace): UIO[Unit]
  def feedBytes(bytes: Chunk[Byte]*)(implicit trace: Trace): UIO[Unit]
  def feedChars(chars: Char*)(implicit trace: Trace): UIO[Unit]
  def feedDoubles(doubles: Double*)(implicit trace: Trace): UIO[Unit]
  def feedFloats(floats: Float*)(implicit trace: Trace): UIO[Unit]
  def feedInts(ints: Int*)(implicit trace: Trace): UIO[Unit]
  def feedLongs(longs: Long*)(implicit trace: Trace): UIO[Unit]
  def feedStrings(strings: String*)(implicit trace: Trace): UIO[Unit]
  def feedUUIDs(UUIDs: UUID*)(implicit trace: Trace): UIO[Unit]
  def getSeed(implicit trace: Trace): UIO[Long]
  def setSeed(seed: => Long)(implicit trace: Trace): UIO[Unit]
}

object TestRandom extends Serializable {

  /**
   * Adapted from @gzmo work in Scala.js
   * (https://github.com/scala-js/scala-js/pull/780)
   */
  final case class Test(randomState: Ref.Atomic[Data], bufferState: Ref.Atomic[Buffer]) extends TestRandom {

    /**
     * Clears the buffer of booleans.
     */
    def clearBooleans(implicit trace: Trace): UIO[Unit] =
      bufferState.update(_.copy(booleans = List.empty))

    /**
     * Clears the buffer of bytes.
     */
    def clearBytes(implicit trace: Trace): UIO[Unit] =
      bufferState.update(_.copy(bytes = List.empty))

    /**
     * Clears the buffer of characters.
     */
    def clearChars(implicit trace: Trace): UIO[Unit] =
      bufferState.update(_.copy(chars = List.empty))

    /**
     * Clears the buffer of doubles.
     */
    def clearDoubles(implicit trace: Trace): UIO[Unit] =
      bufferState.update(_.copy(doubles = List.empty))

    /**
     * Clears the buffer of floats.
     */
    def clearFloats(implicit trace: Trace): UIO[Unit] =
      bufferState.update(_.copy(floats = List.empty))

    /**
     * Clears the buffer of integers.
     */
    def clearInts(implicit trace: Trace): UIO[Unit] =
      bufferState.update(_.copy(integers = List.empty))

    /**
     * Clears the buffer of longs.
     */
    def clearLongs(implicit trace: Trace): UIO[Unit] =
      bufferState.update(_.copy(longs = List.empty))

    /**
     * Clears the buffer of strings.
     */
    def clearStrings(implicit trace: Trace): UIO[Unit] =
      bufferState.update(_.copy(strings = List.empty))

    /**
     * Clears the buffer of UUIDs.
     */
    def clearUUIDs(implicit trace: Trace): UIO[Unit] =
      bufferState.update(_.copy(uuids = List.empty))

    /**
     * Feeds the buffer with specified sequence of booleans. The first value in
     * the sequence will be the first to be taken. These values will be taken
     * before any values that were previously in the buffer.
     */
    def feedBooleans(booleans: Boolean*)(implicit trace: Trace): UIO[Unit] =
      bufferState.update(data => data.copy(booleans = booleans.toList ::: data.booleans))

    /**
     * Feeds the buffer with specified sequence of chunks of bytes. The first
     * value in the sequence will be the first to be taken. These values will be
     * taken before any values that were previously in the buffer.
     */
    def feedBytes(bytes: Chunk[Byte]*)(implicit trace: Trace): UIO[Unit] =
      bufferState.update(data => data.copy(bytes = bytes.toList.flatten ::: data.bytes))

    /**
     * Feeds the buffer with specified sequence of characters. The first value
     * in the sequence will be the first to be taken. These values will be taken
     * before any values that were previously in the buffer.
     */
    def feedChars(chars: Char*)(implicit trace: Trace): UIO[Unit] =
      bufferState.update(data => data.copy(chars = chars.toList ::: data.chars))

    /**
     * Feeds the buffer with specified sequence of doubles. The first value in
     * the sequence will be the first to be taken. These values will be taken
     * before any values that were previously in the buffer.
     */
    def feedDoubles(doubles: Double*)(implicit trace: Trace): UIO[Unit] =
      bufferState.update(data => data.copy(doubles = doubles.toList ::: data.doubles))

    /**
     * Feeds the buffer with specified sequence of floats. The first value in
     * the sequence will be the first to be taken. These values will be taken
     * before any values that were previously in the buffer.
     */
    def feedFloats(floats: Float*)(implicit trace: Trace): UIO[Unit] =
      bufferState.update(data => data.copy(floats = floats.toList ::: data.floats))

    /**
     * Feeds the buffer with specified sequence of integers. The first value in
     * the sequence will be the first to be taken. These values will be taken
     * before any values that were previously in the buffer.
     */
    def feedInts(ints: Int*)(implicit trace: Trace): UIO[Unit] =
      bufferState.update(data => data.copy(integers = ints.toList ::: data.integers))

    /**
     * Feeds the buffer with specified sequence of longs. The first value in the
     * sequence will be the first to be taken. These values will be taken before
     * any values that were previously in the buffer.
     */
    def feedLongs(longs: Long*)(implicit trace: Trace): UIO[Unit] =
      bufferState.update(data => data.copy(longs = longs.toList ::: data.longs))

    /**
     * Feeds the buffer with specified sequence of strings. The first value in
     * the sequence will be the first to be taken. These values will be taken
     * before any values that were previously in the buffer.
     */
    def feedStrings(strings: String*)(implicit trace: Trace): UIO[Unit] =
      bufferState.update(data => data.copy(strings = strings.toList ::: data.strings))

    /**
     * Feeds the buffer with specified sequence of UUIDs. The first value in the
     * sequence will be the first to be taken. These values will be taken before
     * any values that were previously in the buffer.
     */
    def feedUUIDs(uuids: UUID*)(implicit trace: Trace): UIO[Unit] =
      bufferState.update(data => data.copy(uuids = uuids.toList ::: data.uuids))

    /**
     * Gets the seed of this `TestRandom`.
     */
    def getSeed(implicit trace: Trace): UIO[Long] =
      randomState.get.map { case Data(seed1, seed2, _) =>
        ((seed1.toLong << 24) | seed2) ^ 0x5deece66dL
      }

    /**
     * Takes a boolean from the buffer if one exists or else generates a
     * pseudo-random boolean.
     */
    def nextBoolean(implicit trace: Trace): UIO[Boolean] =
      ZIO.succeed(unsafe.nextBoolean()(Unsafe.unsafe))

    /**
     * Takes a chunk of bytes from the buffer if one exists or else generates a
     * pseudo-random chunk of bytes of the specified length.
     */
    def nextBytes(length: => Int)(implicit trace: Trace): UIO[Chunk[Byte]] =
      ZIO.succeed(unsafe.nextBytes(length)(Unsafe.unsafe))

    /**
     * Takes a double from the buffer if one exists or else generates a
     * pseudo-random, uniformly distributed double between 0.0 and 1.0.
     */
    def nextDouble(implicit trace: Trace): UIO[Double] =
      ZIO.succeed(unsafe.nextDouble()(Unsafe.unsafe))

    /**
     * Takes a double from the buffer if one exists or else generates a
     * pseudo-random double in the specified range.
     */
    def nextDoubleBetween(minInclusive: => Double, maxExclusive: => Double)(implicit
      trace: Trace
    ): UIO[Double] =
      ZIO.succeed(unsafe.nextDoubleBetween(minInclusive, maxExclusive)(Unsafe.unsafe))

    /**
     * Takes a float from the buffer if one exists or else generates a
     * pseudo-random, uniformly distributed float between 0.0 and 1.0.
     */
    def nextFloat(implicit trace: Trace): UIO[Float] =
      ZIO.succeed(unsafe.nextFloat()(Unsafe.unsafe))

    /**
     * Takes a float from the buffer if one exists or else generates a
     * pseudo-random float in the specified range.
     */
    def nextFloatBetween(minInclusive: => Float, maxExclusive: => Float)(implicit trace: Trace): UIO[Float] =
      ZIO.succeed(unsafe.nextFloatBetween(minInclusive, maxExclusive)(Unsafe.unsafe))

    /**
     * Takes a double from the buffer if one exists or else generates a
     * pseudo-random double from a normal distribution with mean 0.0 and
     * standard deviation 1.0.
     */
    def nextGaussian(implicit trace: Trace): UIO[Double] =
      ZIO.succeed(unsafe.nextGaussian()(Unsafe.unsafe))

    /**
     * Takes an integer from the buffer if one exists or else generates a
     * pseudo-random integer.
     */
    def nextInt(implicit trace: Trace): UIO[Int] =
      ZIO.succeed(unsafe.nextInt()(Unsafe.unsafe))

    /**
     * Takes an integer from the buffer if one exists or else generates a
     * pseudo-random integer in the specified range.
     */
    def nextIntBetween(minInclusive: => Int, maxExclusive: => Int)(implicit trace: Trace): UIO[Int] =
      ZIO.succeed(unsafe.nextIntBetween(minInclusive, maxExclusive)(Unsafe.unsafe))

    /**
     * Takes an integer from the buffer if one exists or else generates a
     * pseudo-random integer between 0 (inclusive) and the specified value
     * (exclusive).
     */
    def nextIntBounded(n: => Int)(implicit trace: Trace): UIO[Int] =
      ZIO.succeed(unsafe.nextIntBounded(n)(Unsafe.unsafe))

    /**
     * Takes a long from the buffer if one exists or else generates a
     * pseudo-random long.
     */
    def nextLong(implicit trace: Trace): UIO[Long] =
      ZIO.succeed(unsafe.nextLong()(Unsafe.unsafe))

    /**
     * Takes a long from the buffer if one exists or else generates a
     * pseudo-random long in the specified range.
     */
    def nextLongBetween(minInclusive: => Long, maxExclusive: => Long)(implicit trace: Trace): UIO[Long] =
      ZIO.succeed(unsafe.nextLongBetween(minInclusive, maxExclusive)(Unsafe.unsafe))

    /**
     * Takes a long from the buffer if one exists or else generates a
     * pseudo-random long between 0 (inclusive) and the specified value
     * (exclusive).
     */
    def nextLongBounded(n: => Long)(implicit trace: Trace): UIO[Long] =
      ZIO.succeed(unsafe.nextLongBounded(n)(Unsafe.unsafe))

    /**
     * Takes a character from the buffer if one exists or else generates a
     * pseudo-random character from the ASCII range 33-126.
     */
    def nextPrintableChar(implicit trace: Trace): UIO[Char] =
      ZIO.succeed(unsafe.nextPrintableChar()(Unsafe.unsafe))

    /**
     * Takes a string from the buffer if one exists or else generates a
     * pseudo-random string of the specified length.
     */
    def nextString(length: => Int)(implicit trace: Trace): UIO[String] =
      ZIO.succeed(unsafe.nextString(length)(Unsafe.unsafe))

    /**
     * Takes a UUID from the buffer if one exists or else generates a
     * pseudo-random UUID.
     */
    def nextUUID(implicit trace: Trace): UIO[UUID] =
      ZIO.succeed(unsafe.nextUUID()(Unsafe.unsafe))

    /**
     * Saves the `TestRandom`'s current state in an effect which, when run, will
     * restore the `TestRandom` state to the saved state.
     */
    def save(implicit trace: Trace): UIO[UIO[Unit]] =
      for {
        randomData <- randomState.get
        bufferData <- bufferState.get
      } yield randomState.set(randomData) *> bufferState.set(bufferData)

    /**
     * Sets the seed of this `TestRandom` to the specified value.
     */
    def setSeed(seed: => Long)(implicit trace: Trace): UIO[Unit] =
      ZIO.succeed(unsafe.setSeed(seed)(Unsafe.unsafe))

    /**
     * Randomly shuffles the specified list.
     */
    def shuffle[A, Collection[+Element] <: Iterable[Element]](
      list: => Collection[A]
    )(implicit bf: BuildFrom[Collection[A], A, Collection[A]], trace: Trace): UIO[Collection[A]] =
      ZIO.succeed(unsafe.shuffle(list)(bf, Unsafe.unsafe))

    override val unsafe: UnsafeAPI =
      new UnsafeAPI {
        override def nextBoolean()(implicit unsafe: Unsafe): Boolean =
          getOrElse(bufferedBoolean)(randomBoolean)

        override def nextBytes(length: RuntimeFlags)(implicit unsafe: Unsafe): Chunk[Byte] =
          getOrElseChunk(length)(bufferedBytes)(randomBytes)

        override def nextDouble()(implicit unsafe: Unsafe): Double =
          getOrElse(bufferedDouble)(randomDouble)

        override def nextDoubleBetween(minInclusive: Double, maxExclusive: Double)(implicit unsafe: Unsafe): Double =
          getOrElse(bufferedDouble)(randomDoubleBetween(minInclusive, maxExclusive))

        override def nextFloat()(implicit unsafe: Unsafe): Float =
          getOrElse(bufferedFloat)(randomFloat)

        override def nextFloatBetween(minInclusive: Float, maxExclusive: Float)(implicit unsafe: Unsafe): Float =
          getOrElse(bufferedFloat)(randomFloatBetween(minInclusive, maxExclusive))

        override def nextGaussian()(implicit unsafe: Unsafe): Double =
          getOrElse(bufferedDouble)(randomGaussian)

        override def nextInt()(implicit unsafe: Unsafe): RuntimeFlags =
          getOrElse(bufferedInt)(randomInt)

        override def nextIntBetween(minInclusive: RuntimeFlags, maxExclusive: RuntimeFlags)(implicit
          unsafe: Unsafe
        ): RuntimeFlags =
          getOrElse(bufferedInt)(randomIntBetween(minInclusive, maxExclusive))

        override def nextIntBounded(n: RuntimeFlags)(implicit unsafe: Unsafe): RuntimeFlags =
          getOrElse(bufferedInt)(randomIntBounded(n))

        override def nextLong()(implicit unsafe: Unsafe): Long =
          getOrElse(bufferedLong)(randomLong)

        override def nextLongBetween(minInclusive: Long, maxExclusive: Long)(implicit unsafe: Unsafe): Long =
          getOrElse(bufferedLong)(randomLongBetween(minInclusive, maxExclusive))

        override def nextLongBounded(n: Long)(implicit unsafe: Unsafe): Long =
          getOrElse(bufferedLong)(randomLongBounded(n))

        override def nextPrintableChar()(implicit unsafe: Unsafe): Char =
          getOrElse(bufferedChar)(randomPrintableChar)

        override def nextString(length: RuntimeFlags)(implicit unsafe: Unsafe): String =
          getOrElse(bufferedString)(randomString(length))

        override def nextUUID()(implicit unsafe: Unsafe): UUID =
          getOrElse(bufferedUUID)(Random.nextUUIDWith(() => nextLong()))

        override def setSeed(seed: Long)(implicit unsafe: Unsafe): Unit =
          randomState.unsafe.set {
            val newSeed = (seed ^ 0x5deece66dL) & ((1L << 48) - 1)
            val seed1   = (newSeed >>> 24).toInt
            val seed2   = newSeed.toInt & ((1 << 24) - 1)
            Data(seed1, seed2, Queue.empty)
          }

        override def shuffle[A, Collection[+Element] <: Iterable[Element]](
          collection: Collection[A]
        )(implicit bf: zio.BuildFrom[Collection[A], A, Collection[A]], unsafe: Unsafe): Collection[A] =
          Random.shuffleWith(randomIntBounded, collection)

        private def getOrElse[A](buffer: Buffer => (Option[A], Buffer))(random: => A)(implicit unsafe: Unsafe): A =
          bufferState.unsafe.modify(buffer).getOrElse(random)

        private def getOrElseChunk[A](
          length: Int
        )(buffer: Int => Buffer => (Chunk[A], Buffer))(random: Int => Chunk[A])(implicit unsafe: Unsafe): Chunk[A] = {
          val buffered = bufferState.unsafe.modify(buffer(length))
          if (buffered.length == length) buffered
          else buffered ++ random(length - buffered.length)
        }

        private def randomBits(bits: Int)(implicit unsafe: Unsafe): Int =
          randomState.unsafe.modify { data =>
            val multiplier  = 0x5deece66dL
            val multiplier1 = (multiplier >>> 24).toInt
            val multiplier2 = multiplier.toInt & ((1 << 24) - 1)
            val product1    = data.seed2.toDouble * multiplier1.toDouble + data.seed1.toDouble * multiplier2.toDouble
            val product2    = data.seed2.toDouble * multiplier2.toDouble + 0xb
            val newSeed1    = (mostSignificantBits(product2) + leastSignificantBits(product1)) & ((1 << 24) - 1)
            val newSeed2    = leastSignificantBits(product2)
            val result      = (newSeed1 << 8) | (newSeed2 >> 16)
            (result >>> (32 - bits), Data(newSeed1, newSeed2, data.nextNextGaussians))
          }

        private def randomBoolean(implicit unsafe: Unsafe): Boolean =
          randomBits(1) != 0

        private def randomBytes(length: Int)(implicit unsafe: Unsafe): Chunk[Byte] = {
          //  Our RNG generates 32 bit integers so to maximize efficiency we want to
          //  pull 8 bit bytes from the current integer until it is exhausted
          //  before generating another random integer
          @tailrec
          def loop(i: Int, rnd: () => Int, n: Int, acc: List[Byte]): List[Byte] =
            if (i == length)
              acc.reverse
            else if (n > 0) {
              val r = rnd()
              loop(i + 1, () => r >> 8, n - 1, r.toByte :: acc)
            } else
              loop(i, () => randomInt, (length - i) min 4, acc)

          Chunk.fromIterable(loop(0, () => randomInt, length min 4, List.empty))
        }

        private def randomDouble(implicit unsafe: Unsafe): Double = {
          val i1 = randomBits(26)
          val i2 = randomBits(27)
          ((i1.toDouble * (1L << 27).toDouble) + i2.toDouble) / (1L << 53).toDouble
        }

        private def randomDoubleBetween(minInclusive: Double, maxExclusive: Double)(implicit
          unsafe: Unsafe
        ): Double =
          Random.nextDoubleBetweenWith(minInclusive, maxExclusive)(() => randomDouble)

        private def randomFloat(implicit unsafe: Unsafe): Float =
          (randomBits(24).toDouble / (1 << 24).toDouble).toFloat

        private def randomFloatBetween(minInclusive: Float, maxExclusive: Float)(implicit unsafe: Unsafe): Float =
          Random.nextFloatBetweenWith(minInclusive, maxExclusive)(() => randomFloat)

        private def randomGaussian(implicit unsafe: Unsafe): Double =
          //  The Box-Muller transform generates two normally distributed random
          //  doubles, so we store the second double in a queue and check the
          //  queue before computing a new pair of values to avoid wasted work.
          randomState.unsafe.modify { case Data(seed1, seed2, queue) =>
            queue.dequeueOption.fold((Option.empty[Double], Data(seed1, seed2, queue))) { case (d, queue) =>
              (Some(d), Data(seed1, seed2, queue))
            }
          } match {
            case Some(nextNextGaussian) => nextNextGaussian
            case None =>
              @tailrec
              def loop: (Double, Double, Double) = {
                val d1     = randomDouble
                val d2     = randomDouble
                val x      = 2 * d1 - 1
                val y      = 2 * d2 - 1
                val radius = x * x + y * y
                if (radius >= 1 || radius == 0) loop else (x, y, radius)
              }
              loop match {
                case (x, y, radius) =>
                  val c = sqrt(-2 * log(radius) / radius)
                  randomState.unsafe.modify { case Data(seed1, seed2, queue) =>
                    (x * c, Data(seed1, seed2, queue.enqueue(y * c)))
                  }
              }
          }

        private def randomInt(implicit unsafe: Unsafe): Int =
          randomBits(32)

        private def randomIntBounded(n: Int)(implicit unsafe: Unsafe): Int =
          if (n <= 0)
            throw new IllegalArgumentException("n must be positive")
          else if ((n & -n) == n)
            randomBits(31) >> Integer.numberOfLeadingZeros(n)
          else {
            @tailrec
            def loop: Int = {
              val i     = randomBits(31)
              val value = i % n
              if (i - value + (n - 1) < 0) loop
              else value
            }
            loop
          }

        private def randomIntBetween(minInclusive: Int, maxExclusive: Int)(implicit unsafe: Unsafe): Int =
          Random.nextIntBetweenWith(minInclusive, maxExclusive)(() => randomInt, randomIntBounded)

        private def randomLong(implicit unsafe: Unsafe): Long = {
          val i1 = randomBits(32)
          val i2 = randomBits(32)
          (i1.toLong << 32) + i2
        }

        private def randomLongBounded(n: Long)(implicit unsafe: Unsafe): Long =
          Random.nextLongBoundedWith(n)(() => randomLong)

        private def randomLongBetween(minInclusive: Long, maxExclusive: Long)(implicit unsafe: Unsafe): Long =
          Random.nextLongBetweenWith(minInclusive, maxExclusive)(() => randomLong, randomLongBounded)

        private def randomPrintableChar(implicit unsafe: Unsafe): Char =
          (randomIntBounded(127 - 33) + 33).toChar

        private def randomString(length: Int)(implicit unsafe: Unsafe): String =
          List.fill(length)((randomIntBounded(0xd800 - 1) + 1).toChar).mkString
      }

    private def bufferedBoolean(buffer: Buffer): (Option[Boolean], Buffer) =
      (
        buffer.booleans.headOption,
        buffer.copy(booleans = buffer.booleans.drop(1))
      )

    private def bufferedBytes(length: Int)(buffer: Buffer): (Chunk[Byte], Buffer) = {
      val (bufferedBytes, bytes) = buffer.bytes.splitAt(length)
      (Chunk.fromIterable(bufferedBytes), buffer.copy(bytes = bytes))
    }

    private def bufferedChar(buffer: Buffer): (Option[Char], Buffer) =
      (
        buffer.chars.headOption,
        buffer.copy(chars = buffer.chars.drop(1))
      )

    private def bufferedDouble(buffer: Buffer): (Option[Double], Buffer) =
      (
        buffer.doubles.headOption,
        buffer.copy(doubles = buffer.doubles.drop(1))
      )

    private def bufferedFloat(buffer: Buffer): (Option[Float], Buffer) =
      (
        buffer.floats.headOption,
        buffer.copy(floats = buffer.floats.drop(1))
      )

    private def bufferedInt(buffer: Buffer): (Option[Int], Buffer) =
      (
        buffer.integers.headOption,
        buffer.copy(integers = buffer.integers.drop(1))
      )

    private def bufferedLong(buffer: Buffer): (Option[Long], Buffer) =
      (
        buffer.longs.headOption,
        buffer.copy(longs = buffer.longs.drop(1))
      )

    private def bufferedString(buffer: Buffer): (Option[String], Buffer) =
      (
        buffer.strings.headOption,
        buffer.copy(strings = buffer.strings.drop(1))
      )

    private def bufferedUUID(buffer: Buffer): (Option[UUID], Buffer) =
      (
        buffer.uuids.headOption,
        buffer.copy(uuids = buffer.uuids.drop(1))
      )

    @inline
    private def leastSignificantBits(x: Double): Int =
      toInt(x) & ((1 << 24) - 1)

    @inline
    private def mostSignificantBits(x: Double): Int =
      toInt(x / (1 << 24).toDouble)

    @inline
    private def toInt(x: Double): Int =
      (x.asInstanceOf[Long] | 0.asInstanceOf[Long]).asInstanceOf[Int]
  }

  /**
   * An arbitrary initial seed for the `TestRandom`.
   */
  val DefaultData: Data = Data(1071905196, 1911589680)

  /**
   * The seed of the `TestRandom`.
   */
  final case class Data(
    seed1: Int,
    seed2: Int,
    private[TestRandom] val nextNextGaussians: Queue[Double] = Queue.empty
  )

  /**
   * Accesses a `TestRandom` instance in the environment and clears the buffer
   * of booleans.
   */
  def clearBooleans(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.clearBooleans)

  /**
   * Accesses a `TestRandom` instance in the environment and clears the buffer
   * of bytes.
   */
  def clearBytes(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.clearBytes)

  /**
   * Accesses a `TestRandom` instance in the environment and clears the buffer
   * of characters.
   */
  def clearChars(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.clearChars)

  /**
   * Accesses a `TestRandom` instance in the environment and clears the buffer
   * of doubles.
   */
  def clearDoubles(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.clearDoubles)

  /**
   * Accesses a `TestRandom` instance in the environment and clears the buffer
   * of floats.
   */
  def clearFloats(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.clearFloats)

  /**
   * Accesses a `TestRandom` instance in the environment and clears the buffer
   * of integers.
   */
  def clearInts(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.clearInts)

  /**
   * Accesses a `TestRandom` instance in the environment and clears the buffer
   * of longs.
   */
  def clearLongs(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.clearLongs)

  /**
   * Accesses a `TestRandom` instance in the environment and clears the buffer
   * of strings.
   */
  def clearStrings(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.clearStrings)

  /**
   * Accesses a `TestRandom` instance in the environment and clears the buffer
   * of UUIDs.
   */
  def clearUUIDs(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.clearUUIDs)

  /**
   * Accesses a `TestRandom` instance in the environment and feeds the buffer
   * with the specified sequence of booleans.
   */
  def feedBooleans(booleans: Boolean*)(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.feedBooleans(booleans: _*))

  /**
   * Accesses a `TestRandom` instance in the environment and feeds the buffer
   * with the specified sequence of chunks of bytes.
   */
  def feedBytes(bytes: Chunk[Byte]*)(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.feedBytes(bytes: _*))

  /**
   * Accesses a `TestRandom` instance in the environment and feeds the buffer
   * with the specified sequence of characters.
   */
  def feedChars(chars: Char*)(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.feedChars(chars: _*))

  /**
   * Accesses a `TestRandom` instance in the environment and feeds the buffer
   * with the specified sequence of doubles.
   */
  def feedDoubles(doubles: Double*)(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.feedDoubles(doubles: _*))

  /**
   * Accesses a `TestRandom` instance in the environment and feeds the buffer
   * with the specified sequence of floats.
   */
  def feedFloats(floats: Float*)(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.feedFloats(floats: _*))

  /**
   * Accesses a `TestRandom` instance in the environment and feeds the buffer
   * with the specified sequence of integers.
   */
  def feedInts(ints: Int*)(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.feedInts(ints: _*))

  /**
   * Accesses a `TestRandom` instance in the environment and feeds the buffer
   * with the specified sequence of longs.
   */
  def feedLongs(longs: Long*)(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.feedLongs(longs: _*))

  /**
   * Accesses a `TestRandom` instance in the environment and feeds the buffer
   * with the specified sequence of strings.
   */
  def feedStrings(strings: String*)(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.feedStrings(strings: _*))

  /**
   * Accesses a `TestRandom` instance in the environment and feeds the buffer
   * with the specified sequence of UUIDs.
   */
  def feedUUIDs(uuids: UUID*)(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.feedUUIDs(uuids: _*))

  /**
   * Accesses a `TestRandom` instance in the environment and gets the seed.
   */
  def getSeed(implicit trace: Trace): UIO[Long] =
    testRandomWith(_.getSeed)

  /**
   * Constructs a new `TestRandom` with the specified initial state. This can be
   * useful for providing the required environment to an effect that requires a
   * `Random`, such as with `ZIO#provide`.
   */
  def make(data: Data): Layer[Nothing, TestRandom] = {
    implicit val trace = Tracer.newTrace
    ZLayer.scoped {
      for {
        data   <- ZIO.succeed(Ref.unsafe.make(data)(Unsafe.unsafe))
        buffer <- ZIO.succeed(Ref.unsafe.make(Buffer())(Unsafe.unsafe))
        test    = Test(data, buffer)
        _      <- ZIO.withRandomScoped(test)
      } yield test
    }
  }

  val any: ZLayer[TestRandom, Nothing, TestRandom] =
    ZLayer.environment[TestRandom](Tracer.newTrace)

  val deterministic: Layer[Nothing, TestRandom] =
    make(DefaultData)

  val random: ZLayer[Clock, Nothing, TestRandom] = {
    implicit val trace = Tracer.newTrace
    (ZLayer.service[Clock] ++ deterministic) >>> ZLayer {
      for {
        random     <- ZIO.service[Random]
        testRandom <- ZIO.service[TestRandom]
        time       <- Clock.nanoTime
        _          <- TestRandom.setSeed(time)
      } yield testRandom
    }
  }

  /**
   * Constructs a new `Test` object that implements the `TestRandom` interface.
   * This can be useful for mixing in with implementations of other interfaces.
   */
  def makeTest(data: Data)(implicit trace: Trace): UIO[Test] =
    for {
      data   <- ZIO.succeed(Ref.unsafe.make(data)(Unsafe.unsafe))
      buffer <- ZIO.succeed(Ref.unsafe.make(Buffer())(Unsafe.unsafe))
    } yield Test(data, buffer)

  /**
   * Accesses a `TestRandom` instance in the environment and saves the random
   * state in an effect which, when run, will restore the `TestRandom` to the
   * saved state.
   */
  def save(implicit trace: Trace): UIO[UIO[Unit]] =
    testRandomWith(_.save)

  /**
   * Accesses a `TestRandom` instance in the environment and sets the seed to
   * the specified value.
   */
  def setSeed(seed: => Long)(implicit trace: Trace): UIO[Unit] =
    testRandomWith(_.setSeed(seed))

  /**
   * The buffer of the `TestRandom`.
   */
  final case class Buffer(
    booleans: List[Boolean] = List.empty,
    bytes: List[Byte] = List.empty,
    chars: List[Char] = List.empty,
    doubles: List[Double] = List.empty,
    floats: List[Float] = List.empty,
    integers: List[Int] = List.empty,
    longs: List[Long] = List.empty,
    strings: List[String] = List.empty,
    uuids: List[UUID] = List.empty
  )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy