From 7a995670e7b4ee03e6ab3f11bfc4b02a16d3cff7 Mon Sep 17 00:00:00 2001 From: skelantros Date: Thu, 11 Apr 2024 19:14:10 +0300 Subject: [PATCH 1/2] fixed zio.Tag's usage in ZIO's WithRun implicit --- .../core/src/main/scala/tofu/zioInstances/ZioInstances.scala | 4 +--- .../core/src/main/scala/tofu/zioInstances/implicits.scala | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/ZioInstances.scala b/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/ZioInstances.scala index 079a65d01..cd50f9eee 100644 --- a/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/ZioInstances.scala +++ b/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/ZioInstances.scala @@ -46,9 +46,7 @@ private[zioInstances] class ZioInstances { private[this] val zioTofuInstanceAny: ZioTofuInstance[Any, Any] = new ZioTofuInstance final def zioTofuInstance[R, E]: ZioTofuInstance[R, E] = zioTofuInstanceAny.asInstanceOf[ZioTofuInstance[R, E]] - private[this] val zioTofuWithRunInstanceAny = new ZioTofuWithRunInstance[Any, Any] - final def zioTofuWithRunInstance[R, E]: ZioTofuWithRunInstance[R, E] = - zioTofuWithRunInstanceAny.asInstanceOf[ZioTofuWithRunInstance[R, E]] + final def zioTofuWithRunInstance[R: Tag, E]: ZioTofuWithRunInstance[R, E] = new ZioTofuWithRunInstance final def zioTofuUnliftManyInstance[R, E, R1: Tag]: ZioTofuUnliftManyInstance[R, E, R1] = new ZioTofuUnliftManyInstance diff --git a/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/implicits.scala b/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/implicits.scala index 5ab8384c5..f3978caf2 100644 --- a/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/implicits.scala +++ b/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/implicits.scala @@ -39,7 +39,7 @@ private[zioInstances] trait ZioTofuImplicits2 extends ZioTofuImplicits3 { @inline final implicit def zioTofuErrorsToImplicit[R, E]: ZioTofuErrorsToInstance[R, E, Nothing] = zioTofuErrorsToInstance @inline final implicit def zioTofuImplicit[R, E]: ZioTofuInstance[R, E] = zioTofuInstance - @inline final implicit def zioTofuWithRunImplicit[R, E]: ZioTofuWithRunInstance[R, E] = zioTofuWithRunInstance + @inline final implicit def zioTofuWithRunImplicit[R: Tag, E]: ZioTofuWithRunInstance[R, E] = zioTofuWithRunInstance @inline final implicit def zioTofuBlockingImplicit[R, E]: ZioTofuBlockingInstance[R, E] = zioTofuBlockingInstance[R, E] } From ef35537a7e477144aaf0a80ba23179b1259be543 Mon Sep 17 00:00:00 2001 From: Vladislav Ugryumov Date: Thu, 16 May 2024 02:25:58 +0300 Subject: [PATCH 2/2] Fix bug with zioTofuBiInstance, add tests for zio2-core module, add logging mid test with bug related function for zio2 logging. --- build.sbt | 7 +- .../src/main/scala/tofu/zioFunctions.scala | 12 +- .../tofu/zioInstances/ZioInstances.scala | 3 +- .../scala/tofu/zioInstances/implicits.scala | 2 +- .../scala/tofu/zioInstances/RunZioSpec.scala | 27 ++++ .../tofu/logging/zlogs/ZLogBiMidSpec.scala | 137 ++++++++++++++++++ .../tofu/logging/zlogs/DerivedData.scala | 11 +- .../scala/tofu/logging/zlogs/ZTestLog.scala | 20 +++ .../tofu/logging/derivation/package.scala | 28 ++-- 9 files changed, 211 insertions(+), 36 deletions(-) create mode 100644 modules/interop/zio2/core/src/test/scala/tofu/zioInstances/RunZioSpec.scala create mode 100644 modules/interop/zio2/logging/src/test/scala-2/tofu/logging/zlogs/ZLogBiMidSpec.scala create mode 100644 modules/interop/zio2/logging/src/test/scala/tofu/logging/zlogs/ZTestLog.scala diff --git a/build.sbt b/build.sbt index 8c3f772bd..854f36939 100644 --- a/build.sbt +++ b/build.sbt @@ -343,7 +343,8 @@ lazy val zio2Core = projectMatrix .settings( defaultSettings, scala3MigratedModuleOptions, - libraryDependencies ++= List(zio2, zio2Cats), + libraryDependencies ++= List(zio2, zio2Cats, zio2Test, zio2TestSbt), + testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), name := "tofu-zio2-core" ) .jvmPlatform(scalaVersions = scala2And3Versions) @@ -357,7 +358,7 @@ lazy val zio1Logging = projectMatrix name := "tofu-zio-logging" ) .jvmPlatform(scala2Versions) - .dependsOn(loggingStr, loggingDer % "test", zio1Core % Test) + .dependsOn(loggingStr, loggingDer % Test, zio1Core % Test) lazy val zio2Logging = projectMatrix .in(modules / "interop" / "zio2" / "logging") @@ -369,7 +370,7 @@ lazy val zio2Logging = projectMatrix testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") ) .jvmPlatform(scalaVersions = scala2And3Versions) - .dependsOn(loggingStr, loggingDer % "test") + .dependsOn(loggingStr, loggingDer % Test, zio2Core % Test) val interop = modules / "interop" diff --git a/modules/interop/zio2/core/src/main/scala/tofu/zioFunctions.scala b/modules/interop/zio2/core/src/main/scala/tofu/zioFunctions.scala index d8ce29ed9..be6bb1d92 100644 --- a/modules/interop/zio2/core/src/main/scala/tofu/zioFunctions.scala +++ b/modules/interop/zio2/core/src/main/scala/tofu/zioFunctions.scala @@ -1,15 +1,13 @@ package tofu -import izumi.reflect.Tag import tofu.higherKind.bi.{EmbedBK, FunctorBK} import tofu.syntax.functorbk._ import tofu.zioInstances.implicits._ -import zio.{IO, ZIO, Tag => ZTag} - -import scala.annotation.nowarn +import zio.{IO, Tag, ZIO} object zioFunctions { - @nowarn - def expose[U[_[_, _]]: EmbedBK: FunctorBK: Tag.auto.T]: U[ZIO[U[IO], +_, +_]] = - EmbedBK.of[ZIO[U[IO], +_, +_], U](ZIO.environmentWith(_.get[U[IO]](ZTag[U[IO]]).widenb)) + + def expose[U[bf[_, _]]: EmbedBK: FunctorBK](implicit ev: Tag[U[IO]]): U[ZIO[U[IO], +_, +_]] = + EmbedBK.of[ZIO[U[IO], +_, +_], U](ZIO.environmentWith(_.get[U[IO]].widenb)) + } diff --git a/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/ZioInstances.scala b/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/ZioInstances.scala index cd50f9eee..ed7466f91 100644 --- a/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/ZioInstances.scala +++ b/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/ZioInstances.scala @@ -61,6 +61,5 @@ private[zioInstances] class ZioInstances { final def rioTofuBlockingInstance[R]: RioTofuBlockingInstance[R] = rioTofuBlockingInstanceAny.asInstanceOf[RioTofuBlockingInstance[R]] - private[this] val zioTofuBiInstanceAny = new ZioTofuBiInstance[Any] - final def zioTofuBiInstance[R]: ZioTofuBiInstance[R] = zioTofuBiInstanceAny.asInstanceOf[ZioTofuBiInstance[R]] + final def zioTofuBiInstance[R: Tag]: ZioTofuBiInstance[R] = new ZioTofuBiInstance } diff --git a/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/implicits.scala b/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/implicits.scala index f3978caf2..a44ff3a7c 100644 --- a/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/implicits.scala +++ b/modules/interop/zio2/core/src/main/scala/tofu/zioInstances/implicits.scala @@ -33,7 +33,7 @@ private[zioInstances] class ZioTofuImplicits1 extends ZioTofuImplicits2 { @inline final implicit def rioTofuBlockingImplicit[R]: RioTofuBlockingInstance[R] = rioTofuBlockingInstance[R] - @inline final implicit def zioTofuBiImplicit[R]: ZioTofuBiInstance[R] = zioTofuBiInstance[R] + @inline final implicit def zioTofuBiImplicit[R: Tag]: ZioTofuBiInstance[R] = zioTofuBiInstance[R] } private[zioInstances] trait ZioTofuImplicits2 extends ZioTofuImplicits3 { @inline final implicit def zioTofuErrorsToImplicit[R, E]: ZioTofuErrorsToInstance[R, E, Nothing] = diff --git a/modules/interop/zio2/core/src/test/scala/tofu/zioInstances/RunZioSpec.scala b/modules/interop/zio2/core/src/test/scala/tofu/zioInstances/RunZioSpec.scala new file mode 100644 index 000000000..4ee3cdcad --- /dev/null +++ b/modules/interop/zio2/core/src/test/scala/tofu/zioInstances/RunZioSpec.scala @@ -0,0 +1,27 @@ +package tofu.zioInstances + +import tofu.WithRun +import tofu.zioInstances.implicits._ +import zio._ +import zio.test._ +import tofu.bi.BiRun + +object RunZioSpec extends ZIOSpecDefault { + case class Context(x: Int) + private val app: RIO[Context, Int] = ZIO.serviceWith[Context](_.x) + private val someResult = 111 + + override def spec = + suite("RunZioSpec")( + test("should summon and run WithRun instance without errors") { + for { + result <- WithRun[RIO[Context, _], Task, Context].runContext(app)(Context(someResult)) + } yield assertTrue(result == someResult) + }, + test("should summon and run BiRun instance without errors") { + for { + result <- BiRun[ZIO[Context, +_, +_], IO[+_, +_], Context, Context].runLeft(app)(Context(someResult)) + } yield assertTrue(result == someResult) + } + ) +} diff --git a/modules/interop/zio2/logging/src/test/scala-2/tofu/logging/zlogs/ZLogBiMidSpec.scala b/modules/interop/zio2/logging/src/test/scala-2/tofu/logging/zlogs/ZLogBiMidSpec.scala new file mode 100644 index 000000000..1f5f1dbe0 --- /dev/null +++ b/modules/interop/zio2/logging/src/test/scala-2/tofu/logging/zlogs/ZLogBiMidSpec.scala @@ -0,0 +1,137 @@ +package tofu.logging +package zlogs + +import derevo.derive +import tofu.logging.derivation.{loggable, loggingBiMid} +import zio._ +import zio.URLayer +import tofu.logging.bi.LoggingBiCompanion +import tofu.higherKind.bi.BiTemplate +import tofu.zioInstances.implicits._ +import tofu.zioFunctions +import zio.test.ZIOSpecDefault +import zio.test._ +import tofu.higherKind.bi.RepresentableB +import tofu.higherKind +import tofu.higherKind.bi.FunBK + +object ZLogBiMidSpec extends ZIOSpecDefault { + import tofu.logging.zlogs.Lurker.Point + val cls = s"<${classOf[Lurker[Any]].getName()}>" + + def go[E, A](z: ZIO[Lurker.Dep, E, A]): ZIO[Any, Nothing, (Either[E, A], Vector[String])] = + (for { + ref <- Ref.make(Vector[String]()) + // TODO: remake to true ZIO style + e <- z.provide(ZTestLog.layer, Lurker.attachLog, ZLayer.succeed(ref)).either + logs <- ref.get + } yield (e, logs)) + + override def spec = + suite("ZLogBiMidSpec")( + test("simple walk") { + for { + (res, logs) <- go(Lurker.DO.move(Point(20, 3))) + } yield assertTrue( + logs == Vector( + s"[Debug] $cls entering move (target = Point{x=20,y=3})", + s"[Debug] $cls leaving move (target = Point{x=20,y=3}) result is Point{x=5,y=3}", + ), + res == Right(Point(5, 3)) + ) + }, + test("attack") { + for { + (res, logs) <- go(Lurker.DO.attack(Point(20, 3))) + } yield assertTrue( + res == Left(Lurker.Normal), + logs == Vector( + s"[Debug] $cls entering attack (target = Point{x=20,y=3})", + s"[Error] $cls error during attack (target = Point{x=20,y=3}) error is cant do this operation in Normal state", + ) + ) + }, + test("burrow and attack") { + for { + (res, logs) <- go(Lurker.DO.burrowChange *> Lurker.DO.attack(Point(2, 3))) + } yield assertTrue( + res == Right(true), + logs == Vector( + s"[Debug] $cls entering burrowChange ()", + s"[Debug] $cls leaving burrowChange () result is ()", + s"[Debug] $cls entering attack (target = Point{x=2,y=3})", + s"[Debug] $cls leaving attack (target = Point{x=2,y=3}) result is true", + ) + ) + } + ) +} + +@derive(loggingBiMid) +trait Lurker[F[_, _]] extends BiTemplate[F] { + import Lurker._ + def move(target: Point): F[State, Point] + def burrowChange: FS[Unit] + def attack(target: Point): F[State, Boolean] +} + +object Lurker extends LoggingBiCompanion[Lurker] { + // TODO: use `@derive(representableB)` from derivation module when it is ready + implicit def reprBInstance: RepresentableB[Lurker] = new RepresentableB[Lurker] { + override def bitabulate[F[_, _]](repr: FunBK[higherKind.bi.RepBK[Lurker, _, _], F]): Lurker[F] = + new Lurker[F] { + def move(target: Point): F[State, Point] = + repr(higherKind.bi.RepBK[Lurker](_.move(target))) + def burrowChange: FS[Unit] = + repr(higherKind.bi.RepBK[Lurker](_.burrowChange)) + def attack(target: Point): F[State, Boolean] = + repr(higherKind.bi.RepBK[Lurker](_.attack(target))) + } + } + + type Dep = Lurker[IO] + + sealed trait State + case object Normal extends State + case object Buried extends State + implicit val wrongStateLoggable: Loggable[State] = + Loggable[String].contramap(st => s"cant do this operation in $st state") + + lazy val DO: Lurker[ZIO[Dep, +_, +_]] = zioFunctions.expose[Lurker] + + @derive(loggable) + final case class Point(x: Long, y: Long) + + class Test(ref: Ref.Synchronized[(State, Point)], maxDistance: Long = 5) extends Lurker[IO] { + + private def moveOne(cur: Long, target: Long) = { + val shift = target - cur + val dist = shift.abs.min(maxDistance) + if (shift >= 0) cur + dist else cur - dist + } + def move(target: Point): IO[State, Point] = + ref.modifyZIO { + case (Normal, pt) => + val res = Point(moveOne(pt.x, target.x), moveOne(pt.y, target.y)) + ZIO.succeed((res, (Normal, res))) + case (Buried, _) => ZIO.fail(Buried) + } + + def burrowChange: IO[Nothing, Unit] = ref.update { + case (Normal, pt) => (Buried, pt) + case (Buried, pt) => (Normal, pt) + } + + def attack(target: Point): IO[State, Boolean] = ref.get.flatMap { + case (Normal, _) => + ZIO.fail(Normal) + case (Buried, pt) => + ZIO.succeed((pt.x - target.x).abs < maxDistance && (pt.y - target.x).abs < maxDistance) + } + } + + val make: UIO[Lurker[IO]] = Ref.Synchronized.make((Normal: State, Point(0, 0))).map(new Test(_)) + + val attachLog: URLayer[ZLogging.Make, Dep] = + ZLayer.fromZIO(ZIO.serviceWithZIO[ZLogging.Make](implicit logs => make.map(_.attachLogs))) +} diff --git a/modules/interop/zio2/logging/src/test/scala-3/tofu/logging/zlogs/DerivedData.scala b/modules/interop/zio2/logging/src/test/scala-3/tofu/logging/zlogs/DerivedData.scala index 73c0e401f..0821375a4 100644 --- a/modules/interop/zio2/logging/src/test/scala-3/tofu/logging/zlogs/DerivedData.scala +++ b/modules/interop/zio2/logging/src/test/scala-3/tofu/logging/zlogs/DerivedData.scala @@ -2,12 +2,7 @@ package tofu.logging.zlogs import tofu.logging.Loggable import tofu.logging.derivation.loggable +import tofu.logging.derivation._ -object DerivedData { - - final case class User(name: String) - - object User { - implicit val userLoggable: Loggable[User] = loggable.instance - } -} +object DerivedData: + final case class User(name: String) derives Loggable diff --git a/modules/interop/zio2/logging/src/test/scala/tofu/logging/zlogs/ZTestLog.scala b/modules/interop/zio2/logging/src/test/scala/tofu/logging/zlogs/ZTestLog.scala new file mode 100644 index 000000000..0b71e2a50 --- /dev/null +++ b/modules/interop/zio2/logging/src/test/scala/tofu/logging/zlogs/ZTestLog.scala @@ -0,0 +1,20 @@ +package tofu.logging +package zlogs + +import zio._ +import org.slf4j.helpers.MessageFormatter + +class ZTestLog(svc: String, val ref: Ref[Vector[String]]) extends Logging[UIO] { + override def write(level: Logging.Level, message: String, values: LoggedValue*): UIO[Unit] = { + val formatted = MessageFormatter.arrayFormat(message, values.toArray).getMessage() + ref.update(_ :+ s"[$level] <$svc> $formatted") + } +} + +object ZTestLog { + def make(): URIO[Ref[Vector[String]], Logging.Make[UIO]] = + ZIO.serviceWith[Ref[Vector[String]]](ref => (svc: String) => new ZTestLog(svc, ref)) + + val layer: URLayer[Ref[Vector[String]], ZLogging.Make] = + ZLayer.fromZIO(make()) +} diff --git a/modules/logging/derivation/src/main/scala-3/tofu/logging/derivation/package.scala b/modules/logging/derivation/src/main/scala-3/tofu/logging/derivation/package.scala index cc888365e..188316b38 100644 --- a/modules/logging/derivation/src/main/scala-3/tofu/logging/derivation/package.scala +++ b/modules/logging/derivation/src/main/scala-3/tofu/logging/derivation/package.scala @@ -1,23 +1,21 @@ -package tofu.logging +package tofu.logging.derivation import magnolia1.TypeInfo import scala.collection.compat._ import scala.deriving.Mirror +import tofu.logging.Loggable -package object derivation { +extension (x: Loggable.type) inline def derived[A](using Mirror.Of[A]): Loggable[A] = loggable.derived[A] - extension (x: Loggable.type) inline def derived[A](using Mirror.Of[A]): Loggable[A] = loggable.derived[A] +private[derivation] def strJoin(typeName: String, strings: IterableOnce[String]): String = + if (strings.iterator.isEmpty) typeName else strings.iterator.mkString(s"$typeName{", ",", "}") - private[derivation] def strJoin(typeName: String, strings: IterableOnce[String]): String = - if (strings.iterator.isEmpty) typeName else strings.iterator.mkString(s"$typeName{", ",", "}") +private[derivation] def calcTypeName(typeName: TypeInfo, seen: Set[TypeInfo] = Set()): String = + if (seen(typeName)) "#" + else { + val args = typeName.typeParams + val name = typeName.full - private[derivation] def calcTypeName(typeName: TypeInfo, seen: Set[TypeInfo] = Set()): String = - if (seen(typeName)) "#" - else { - val args = typeName.typeParams - val name = typeName.full - - if (args.isEmpty) name - else args.iterator.map(calcTypeName(_, seen + typeName)).mkString(name + "[", ",", "]") - } -} + if (args.isEmpty) name + else args.iterator.map(calcTypeName(_, seen + typeName)).mkString(name + "[", ",", "]") + }