Skip to content

Commit

Permalink
distage-testkit: Run tests in parallel, add DIEffectAsync
Browse files Browse the repository at this point in the history
  • Loading branch information
neko-kai committed Nov 17, 2019
1 parent d8c87f9 commit 499f765
Show file tree
Hide file tree
Showing 23 changed files with 313 additions and 196 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class PlanInterpreterDefaultRuntimeImpl
, resourceStrategy: ResourceStrategy

, failureHandler: ProvisioningFailureInterceptor
, verifier: ProvisionOperationVerifier
, verifier: ProvisionOperationVerifier,
) extends PlanInterpreter
with OperationExecutor
with WiringExecutor {
Expand Down Expand Up @@ -108,22 +108,20 @@ class PlanInterpreterDefaultRuntimeImpl
}
}

def processSteps(steps: Vector[ExecutableOp]): F[Unit] = F.traverse_(steps)(processStep)

val (imports, otherSteps) = plan.steps.partition {
case _: ImportDependency => true
case _ => false
}

for {
_ <- processSteps(imports)
_ <- F.traverse_(imports)(processStep)
_ <- verifyEffectType[F](otherSteps, addFailure = f => F.maybeSuspend(mutFailures += f))

failedImportsOrEffects <- F.maybeSuspend(mutFailures.nonEmpty)
res <- if (failedImportsOrEffects) {
F.maybeSuspend(Left(FailedProvision[F](mutProvisioningContext.toImmutable, plan, parentContext, mutFailures.toVector))): F[Either[FailedProvision[F], LocatorDefaultImpl[F]]]
} else {
processSteps(otherSteps)
F.traverse_(otherSteps)(processStep)
.flatMap { _ =>
F.maybeSuspend {
val context = mutProvisioningContext.toImmutable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package izumi.distage.impl
import java.io.ByteArrayInputStream

import izumi.distage.model.definition.DIResource
import izumi.distage.model.monadic.{DIEffect, FromCats}
import izumi.distage.model.monadic.{DIEffect, LowPriorityDIEffectInstances}
import izumi.functional.bio.{BIO, BIOAsync}
import izumi.fundamentals.platform.functional.Identity
import org.scalatest.{GivenWhenThen, WordSpec}
Expand Down Expand Up @@ -33,10 +33,10 @@ class OptionalDependencyTest extends WordSpec with GivenWhenThen {
assertTypeError("implicitly[BIO[Either]]")

And("`No More Orphans` type provider is inacessible")
FromCats.discard()
LowPriorityDIEffectInstances.discard()
assertTypeError(
"""
def y[R[_[_]]: FromCats._Sync]() = ()
def y[R[_[_]]: LowPriorityDIEffectInstances._Sync]() = ()
y()
""")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,18 @@ import izumi.fundamentals.collections.IzCollections._
import scala.collection.immutable.ListSet

trait ModuleBase {
def bindings: Set[Binding]

type Self <: ModuleBase
def bindings: Set[Binding]

final def keys: Set[DIKey] = bindings.map(_.key)

override final def hashCode(): Int = bindings.hashCode()

override final def equals(obj: Any): Boolean = obj match {
case m: ModuleBase =>
m.bindings == this.bindings
case _ =>
false
}

override final def toString: String = bindings.toString()
}

Expand Down Expand Up @@ -147,7 +144,12 @@ object ModuleBase {
out
}

/** Optional instance via https://blog.7mind.io/no-more-orphans.html */
/**
* This instance uses 'no more orphans' trick to provide an Optional instance
* only IFF you have cats-effect as a dependency without REQUIRING a cats-effect dependency.
*
* Optional instance via https://blog.7mind.io/no-more-orphans.html
*/
implicit def optionalCatsPartialOrderHashForModuleBase[T <: ModuleBase, K[_] : CatsPartialOrderHash]: K[T] = {
import cats.instances.set._

Expand All @@ -158,7 +160,12 @@ object ModuleBase {
}.asInstanceOf[K[T]]
}

/** Optional instance via https://blog.7mind.io/no-more-orphans.html */
/**
* This instance uses 'no more orphans' trick to provide an Optional instance
* only IFF you have cats-effect as a dependency without REQUIRING a cats-effect dependency.
*
* Optional instance via https://blog.7mind.io/no-more-orphans.html
*/
implicit def optionalCatsSemilatticeForModuleBase[T <: ModuleBase.Aux[T] : ModuleMake, K[_] : CatsBoundedSemilattice]: K[T] =
new ModuleBaseSemilattice[T].asInstanceOf[K[T]]

Expand All @@ -168,12 +175,10 @@ private object ModuleBaseInstances {

final class ModuleBaseSemilattice[T <: ModuleBase.Aux[T] : ModuleMake] extends BoundedSemilattice[T] {
def empty: T = ModuleMake[T].empty

def combine(x: T, y: T): T = x ++ y
}

sealed abstract class CatsBoundedSemilattice[K[_]]

object CatsBoundedSemilattice {
@inline implicit final def get: CatsBoundedSemilattice[BoundedSemilattice] = null
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package izumi.distage.model.monadic

import cats.effect.ExitCase
import izumi.distage.model.monadic.FromCats._Sync
import izumi.distage.model.monadic.LowPriorityDIEffectInstances._Sync
import izumi.functional.bio.{BIO, BIOExit}
import izumi.fundamentals.platform.functional.Identity
import izumi.fundamentals.platform.language.Quirks._
Expand Down Expand Up @@ -45,9 +45,7 @@ trait DIEffect[F[_]] {
}
}

object DIEffect
extends FromCats {

object DIEffect extends LowPriorityDIEffectInstances {
def apply[F[_] : DIEffect]: DIEffect[F] = implicitly

object syntax {
Expand Down Expand Up @@ -132,7 +130,7 @@ object DIEffect
}
}

trait FromCats {
private[monadic] sealed trait LowPriorityDIEffectInstances {

/**
* This instance uses 'no more orphans' trick to provide an Optional instance
Expand Down Expand Up @@ -175,12 +173,12 @@ trait FromCats {

}

object FromCats {
object LowPriorityDIEffectInstances {
/**
* 'No more orphans' trick. Late-bind the type used in implicit to let cats-effect be an Optional dependency, but
* _still_ provide _non-orphan_ instances if it's on classpath
* This instance uses 'no more orphans' trick to provide an Optional instance
* only IFF you have cats-effect as a dependency without REQUIRING a cats-effect dependency.
*
* @see https://blog.7mind.io/no-more-orphans.html
* Optional instance via https://blog.7mind.io/no-more-orphans.html
*/
sealed abstract class _Sync[R[_[_]]]
object _Sync {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package izumi.distage.model.monadic

import cats.Parallel
import cats.effect.Timer
import izumi.distage.model.monadic.LowPriorityDIEffectAsyncInstances.{_Parallel, _Timer}
import izumi.functional.bio.{BIOAsync, F}
import izumi.fundamentals.platform.functional.Identity

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{ExecutionContext, Future}

trait DIEffectAsync[F[_]] {
def parTraverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit]
def sleep(duration: FiniteDuration): F[Unit]
}

object DIEffectAsync extends LowPriorityDIEffectAsyncInstances {
def apply[F[_]: DIEffectAsync]: DIEffectAsync[F] = implicitly

implicit val diEffectParIdentity: DIEffectAsync[Identity] = {
new DIEffectAsync[Identity] {
override def parTraverse_[A](l: Iterable[A])(f: A => Unit): Unit = {
l.foreach(a => Future(f(a))(ExecutionContext.global))
}
override def sleep(duration: FiniteDuration): Identity[Unit] = {
Thread.sleep(duration.toMillis)
}
}
}

implicit def fromBIOAsync[F[+_, +_]: BIOAsync]: DIEffectAsync[F[Throwable, ?]] = {
new DIEffectAsync[F[Throwable, ?]] {
override def parTraverse_[A](l: Iterable[A])(f: A => F[Throwable, Unit]): F[Throwable, Unit] = {
F.parTraverseN(100)(l)(f).void
}
override def sleep(duration: FiniteDuration): F[Throwable, Unit] = {
F.sleep(duration)
}
}
}

}

private[monadic] sealed trait LowPriorityDIEffectAsyncInstances {
/**
* This instance uses 'no more orphans' trick to provide an Optional instance
* only IFF you have cats-effect as a dependency without REQUIRING a cats-effect dependency.
*
* Optional instance via https://blog.7mind.io/no-more-orphans.html
*/
implicit final def fromParallelTimer[F[_], P[_[_]]: _Parallel, T[_[_]]: _Timer](implicit P: P[F], T: T[F]): DIEffectAsync[F] = {
new DIEffectAsync[F] {
override def parTraverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit] = {
Parallel.parTraverse_(l.toList)(f)(cats.instances.list.catsStdInstancesForList, P.asInstanceOf[Parallel[F]])
}
override def sleep(duration: FiniteDuration): F[Unit] = {
T.asInstanceOf[Timer[F]].sleep(duration)
}
}
}
}

private object LowPriorityDIEffectAsyncInstances {
sealed trait _Parallel[K[_[_]]]
object _Parallel {
@inline implicit final def get: _Parallel[cats.Parallel] = null
}

sealed trait _Timer[K[_[_]]]
object _Timer {
@inline implicit final def get: _Timer[cats.effect.Timer] = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ package izumi.distage.model

package object monadic {
type DIEffect2[F[_, _]] = DIEffect[F[Throwable, ?]]
type DIEffectAsync2[F[_, _]] = DIEffectAsync[F[Throwable, ?]]
type DIEffectRunner2[F[_, _]] = DIEffectRunner[F[Throwable, ?]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,12 @@ final case class SemiPlan(definition: ModuleBase, steps: Vector[ExecutableOp], g

object SemiPlan {

// implicit def catsKernelStdHashForSemiPlan: Hash[SemiPlan] =
// new Hash[SemiPlan] {
// override def hash(x: SemiPlan): Int = x.hashCode()
//
// override def eqv(x: SemiPlan, y: SemiPlan): Boolean = x == y
// }

/** Optional instance via https://blog.7mind.io/no-more-orphans.html */
/**
* This instance uses 'no more orphans' trick to provide an Optional instance
* only IFF you have cats-effect as a dependency without REQUIRING a cats-effect dependency.
*
* Optional instance via https://blog.7mind.io/no-more-orphans.html
*/
implicit def optionalCatsMonoidForSemiplan[K[_] : CatsMonoid]: K[SemiPlan] =
new Monoid[SemiPlan] {
override def empty: SemiPlan = SemiPlan(ModuleBase.empty, Vector.empty, GCMode.NoGC)
Expand Down Expand Up @@ -285,7 +283,7 @@ private object SemiPlanOrderedPlanInstances {
* only IFF you have cats-effect as a dependency without REQUIRING a cats-effect dependency.
*
* Optional instance via https://blog.7mind.io/no-more-orphans.html
* */
*/
sealed abstract class CatsMonoid[K[_]]
object CatsMonoid {
@inline implicit final def get: CatsMonoid[Monoid] = null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,46 @@
package izumi.distage.monadic.modules

import cats.effect.IO
import cats.effect.{Async, Bracket, Concurrent, ConcurrentEffect, ContextShift, Effect, ExitCode, IO, IOApp, Sync, Timer}
import cats.{Applicative, Functor, Monad, MonadError, Parallel}
import izumi.distage.model.definition.ModuleDef
import izumi.distage.model.monadic.{DIEffect, DIEffectRunner}
import izumi.distage.model.monadic.{DIEffect, DIEffectAsync, DIEffectRunner}
import izumi.distage.monadic.modules.CatsDIEffectModule.PublicIOApp

trait CatsDIEffectModule extends ModuleDef {
addImplicit[DIEffectRunner[IO]]
addImplicit[DIEffect[IO]]
make[DIEffectAsync[IO]].from {
(P0: Parallel[IO], T0: Timer[IO]) =>
implicit val P = P0
implicit val T = T0
DIEffectAsync[IO]
}

addImplicit[Functor[IO]]
addImplicit[Applicative[IO]]
addImplicit[Monad[IO]]
addImplicit[MonadError[IO, Throwable]]
addImplicit[Bracket[IO, Throwable]]
addImplicit[Sync[IO]]
addImplicit[Async[IO]]
addImplicit[Effect[IO]]

make[Parallel[IO]].from(IO.ioParallel(_: ContextShift[IO]))
bind[ConcurrentEffect[IO]](IO.ioConcurrentEffect(_: ContextShift[IO]))
.to[Concurrent[IO]]

make[ContextShift[IO]].from((_: PublicIOApp).contextShift)
make[Timer[IO]].from((_: PublicIOApp).timer)

make[PublicIOApp].from {
new PublicIOApp { override def run(args: List[String]): IO[ExitCode] = IO.pure(ExitCode(0)) }
}
}

object CatsDIEffectModule {
// extract default ContextShift & Timer from IOApp
trait PublicIOApp extends IOApp {
override def contextShift: ContextShift[IO] = super.contextShift
override def timer: Timer[IO] = super.timer
}
}
Loading

0 comments on commit 499f765

Please sign in to comment.