Repository: 7mind/izumi
Pull request #800 (2019-12-27 00:41:29)

Fix Scalatest test output
Now tests are reported normally in Intellij, they're grouped into distinct suites, not under one suite:
<img width="521" alt="Screenshot 2019-12-26 at 23 08 21" src="https://user-images.githubusercontent.com/450507/71494771-953cda80-2841-11ea-9fc1-45cd89e267f3.png">



Patch:
diff --git a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala
index 02830ab96..cbc49f380 100644
--- a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala
+++ b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala
@@ -9,5 +9,11 @@ final case class PluginConfig(
 object PluginConfig {
   def empty: PluginConfig = PluginConfig(Nil, Nil, cachePackages = false, debug = false)
   def packages(packagesEnabled: Seq[String]): PluginConfig = PluginConfig(packagesEnabled, Nil, cachePackages = true, debug = false)
-  def cached(packagesEnabled: Seq[String]): PluginConfig = PluginConfig(packagesEnabled, Nil, cachePackages = true, debug = false)
+  def cached(packagesEnabled: Seq[String]): PluginConfig = {
+    import izumi.fundamentals.platform.strings.IzString._
+    val cacheEnabled = System
+      .getProperty(DebugProperties.`izumi.distage.testkit.plugins.memoize`)
+      .asBoolean(true)
+    PluginConfig(packagesEnabled, Nil, cachePackages = cacheEnabled, debug = false)
+  }
 }


Test patch:
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala
similarity index 89%
rename from distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala
rename to distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala
index 2a4cb1914..09490cab1 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala
+++ b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala
@@ -1,4 +1,4 @@
-package izumi.distage.testkit
+package izumi.distage.plugins
 
 import izumi.fundamentals.platform.logging.DebugProperties
 
diff --git a/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/DistageTestDockerBIO.scala b/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/DistageTestDockerBIO.scala
index 5acdc3e6a..434150969 100644
--- a/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/DistageTestDockerBIO.scala
+++ b/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/DistageTestDockerBIO.scala
@@ -31,7 +31,7 @@ final class DistageTestDockerBIO extends DistageBIOSpecScalatest[IO] {
   }
 
   override protected def config: TestConfig = {
-    TestConfig(
+    super.config.copy(
       memoizationRoots = Set(
         DIKey.get[DynamoDocker.Container],
         DIKey.get[PostgresDocker.Container],
diff --git a/distage/distage-framework/src/test/scala/izumi/distage/roles/test/fixtures/Fixture.scala b/distage/distage-framework/src/test/scala/izumi/distage/roles/test/fixtures/Fixture.scala
index 61bff0365..b635b84f7 100644
--- a/distage/distage-framework/src/test/scala/izumi/distage/roles/test/fixtures/Fixture.scala
+++ b/distage/distage-framework/src/test/scala/izumi/distage/roles/test/fixtures/Fixture.scala
@@ -2,8 +2,9 @@ package izumi.distage.roles.test.fixtures
 
 import izumi.distage.framework.model.IntegrationCheck
 import izumi.fundamentals.platform.integration.ResourceCheck
+import izumi.fundamentals.platform.language.Quirks._
 
-import scala.collection.{immutable, mutable}
+import scala.collection.mutable
 
 object Fixture {
   trait Dummy
@@ -25,9 +26,9 @@ object Fixture {
     private val closedCloseables: mutable.ArrayBuffer[AutoCloseable] = mutable.ArrayBuffer()
     private val checkedResources: mutable.ArrayBuffer[IntegrationCheck] = mutable.ArrayBuffer()
 
-    def onStart(c: AutoCloseable): Unit = this.synchronized(startedCloseables += c)
-    def onClose(c: AutoCloseable): Unit = this.synchronized(closedCloseables += c)
-    def onCheck(c: IntegrationCheck): Unit = this.synchronized(checkedResources += c)
+    def onStart(c: AutoCloseable): Unit = this.synchronized(startedCloseables += c).discard()
+    def onClose(c: AutoCloseable): Unit = this.synchronized(closedCloseables += c).discard()
+    def onCheck(c: IntegrationCheck): Unit = this.synchronized(checkedResources += c).discard()
 
     def getStartedCloseables(): Seq[AutoCloseable] = this.synchronized(startedCloseables.toList)
     def getClosedCloseables(): Seq[AutoCloseable] = this.synchronized(closedCloseables.toList)
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/TestConfig.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/TestConfig.scala
index 28c8bbd2d..4f37ac389 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/TestConfig.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/TestConfig.scala
@@ -1,11 +1,11 @@
 package izumi.distage.testkit
 
-import distage.{BootstrapModule, DIKey, Module, StandardAxis, Activation}
+import distage.{Activation, BootstrapModule, DIKey, Module, StandardAxis}
 import izumi.distage.framework.model.PluginSource
+import izumi.distage.plugins.PluginConfig
 
 /**
-  * @param pluginSource       If `None`, recursively scans packages of the test class itself
-  *                           each pluginSource creates a distinct memoization group, i.e.
+  * @param pluginSource       Each pluginSource creates a distinct memoization group, i.e.
   *                           objects will be memoized only between tests with the same plugins
   * @param activation         Chosen configurations. Different Activations have distinct memoization groups
   * @param memoizationRoots   Every distinct set of `memoizationRoots` will have a distinct memoization group
@@ -16,9 +16,13 @@ import izumi.distage.framework.model.PluginSource
   * @param bootstrapOverrides Same as [[moduleOverrides]], but for [[BootstrapModule]]
   */
 final case class TestConfig(
-                             pluginSource: Option[PluginSource] = None,
+                             pluginSource: PluginSource,
                              activation: Activation = StandardAxis.testProdActivation,
                              memoizationRoots: Set[DIKey] = Set.empty,
                              moduleOverrides: Module = Module.empty,
                              bootstrapOverrides: BootstrapModule = BootstrapModule.empty,
                            )
+
+object TestConfig {
+  def forSuite(clazz: Class[_]): TestConfig = TestConfig(PluginSource(PluginConfig.cached(Seq(this.getClass.getPackage.getName)), None))
+}
\ No newline at end of file
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/PluginsCache.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/PluginsCache.scala
deleted file mode 100644
index 4167b5ade..000000000
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/PluginsCache.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-package izumi.distage.testkit.services
-
-import izumi.distage.framework.model.{AllLoadedPlugins, BootstrapConfig, PluginSource}
-import izumi.fundamentals.platform.cache.SyncCache
-
-object PluginsCache {
-  final case class CacheKey(config: BootstrapConfig)
-  final case class CacheValue(plugins: AllLoadedPlugins)
-
-  // sbt in nofork mode runs each module in it's own classloader thus we have separate cache per module per run
-  object Instance extends SyncCache[CacheKey, CacheValue]
-
-  def cachePluginSource(pluginSource: PluginSource): PluginSource = {
-    pluginSource.replaceLoaders {
-      case p@PluginSource.Load(_, _, Some(bootstrapConfig)) =>
-        PluginSource(Instance.getOrCompute(CacheKey(bootstrapConfig), CacheValue(p.load())).plugins)
-      case p => p
-    }
-  }
-}
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DebugProperties.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DebugProperties.scala
new file mode 100644
index 000000000..5b116330a
--- /dev/null
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DebugProperties.scala
@@ -0,0 +1,8 @@
+package izumi.distage.testkit.services.dstest
+
+import izumi.fundamentals.platform.logging.DebugProperties
+
+object DebugProperties extends DebugProperties {
+  /** Scan classpath only once per test run */
+  final val `izumi.distage.testkit.environment.memoize` = "izumi.distage.testkit.environment.memoize"
+}
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestEnv.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestEnv.scala
new file mode 100644
index 000000000..cd7a62f1d
--- /dev/null
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestEnv.scala
@@ -0,0 +1,74 @@
+package izumi.distage.testkit.services.dstest
+
+import izumi.distage.framework.activation.PruningPlanMergingPolicyLoggedImpl
+import izumi.distage.framework.model.ActivationInfo
+import izumi.distage.framework.services.ActivationInfoExtractor
+import izumi.distage.model.definition.{Activation, BootstrapModuleDef}
+import izumi.distage.model.planning.PlanMergingPolicy
+import izumi.distage.plugins.merge.{PluginMergeStrategy, SimplePluginMergeStrategy}
+import izumi.distage.roles.model.meta.RolesInfo
+import izumi.distage.testkit.TestConfig
+import izumi.fundamentals.platform.cache.SyncCache
+import izumi.fundamentals.platform.language.unused
+import izumi.logstage.api.IzLogger
+
+
+trait DistageTestEnv {
+  protected[distage] def loadEnvironment(logger: IzLogger, testConfig: TestConfig): TestEnvironment = {
+    val roles = loadRoles(logger)
+    val mergeStrategy = makeMergeStrategy(logger)
+
+    import izumi.fundamentals.platform.strings.IzString._
+    def doMake(): TestEnvironment = {
+      makeEnv(logger, testConfig, roles, mergeStrategy)
+    }
+
+    if (DebugProperties.`izumi.distage.testkit.environment.memoize`.asBoolean(true)) {
+      DistageTestEnv.cache.getOrCompute(DistageTestEnv.EnvCacheKey(testConfig, roles, mergeStrategy), doMake())
+    } else {
+      doMake()
+    }
+  }
+
+  private def makeEnv(logger: IzLogger, testConfig: TestConfig, roles: RolesInfo, mergeStrategy: PluginMergeStrategy): TestEnvironment = {
+    val plugins = testConfig.pluginSource.load()
+    val appModule = mergeStrategy.merge(plugins.app)
+    val bootstrapModule = mergeStrategy.merge(plugins.bootstrap)
+    val availableActivations = ActivationInfoExtractor.findAvailableChoices(logger, appModule)
+
+    val baseEnv = TestEnvironment(
+      bsModule = bootstrapModule,
+      appModule = appModule,
+      roles = roles,
+      activationInfo = availableActivations,
+      activation = testConfig.activation,
+      memoizedKeys = Set.empty,
+    )
+
+    val bsModule = baseEnv.bsModule overridenBy new BootstrapModuleDef {
+      make[PlanMergingPolicy].from[PruningPlanMergingPolicyLoggedImpl]
+      make[ActivationInfo].fromValue(baseEnv.activationInfo)
+      make[Activation].fromValue(baseEnv.activation)
+    }
+
+    baseEnv.copy(bsModule = bsModule)
+  }
+
+  protected def loadRoles(@unused logger: IzLogger): RolesInfo = {
+    // For all normal scenarios we don't need roles to setup a test
+    RolesInfo(Set.empty, Seq.empty, Seq.empty, Seq.empty, Set.empty)
+  }
+
+  protected def makeMergeStrategy(@unused lateLogger: IzLogger): PluginMergeStrategy = {
+    SimplePluginMergeStrategy
+  }
+
+}
+
+object DistageTestEnv {
+
+  private lazy val cache = new SyncCache[EnvCacheKey, TestEnvironment]
+
+  case class EnvCacheKey(config: TestConfig, rolesInfo: RolesInfo, mergeStrategy: PluginMergeStrategy)
+
+}
\ No newline at end of file
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestRunner.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestRunner.scala
index 362f10ce3..79e5c0d30 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestRunner.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestRunner.scala
@@ -256,7 +256,7 @@ object DistageTestRunner {
 
   sealed trait TestStatus
   object TestStatus {
-    case object Scheduled extends TestStatus
+//    case object Scheduled extends TestStatus
     case object Running extends TestStatus
 
     sealed trait Done extends TestStatus
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/TestEnvironment.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/TestEnvironment.scala
index ef86a1ea1..29180dfb3 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/TestEnvironment.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/TestEnvironment.scala
@@ -1,10 +1,8 @@
 package izumi.distage.testkit.services.dstest
 
 import distage._
-import izumi.distage.framework.activation.PruningPlanMergingPolicyLoggedImpl
 import izumi.distage.framework.model.ActivationInfo
 import izumi.distage.model.definition.Activation
-import izumi.distage.model.planning.PlanMergingPolicy
 import izumi.distage.roles.model.meta.RolesInfo
 
 final case class TestEnvironment(
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/TestEnvironmentProvider.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/TestEnvironmentProvider.scala
deleted file mode 100644
index 7be9371d2..000000000
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/TestEnvironmentProvider.scala
+++ /dev/null
@@ -1,75 +0,0 @@
-package izumi.distage.testkit.services.dstest
-
-import distage.DIKey
-import izumi.distage.framework.model.{ActivationInfo, PluginSource}
-import izumi.distage.framework.services.ActivationInfoExtractor
-import izumi.distage.model.definition.{Activation, BootstrapModule, ModuleBase}
-import izumi.distage.plugins.merge.{PluginMergeStrategy, SimplePluginMergeStrategy}
-import izumi.distage.roles.model.meta.RolesInfo
-import izumi.distage.testkit.DebugProperties
-import izumi.distage.testkit.services.PluginsCache
-import izumi.fundamentals.platform.language.unused
-import izumi.logstage.api.IzLogger
-
-trait TestEnvironmentProvider {
-  /** Merge strategy will be applied only once for all the tests with the same BootstrapConfig, if memoization is on */
-  def loadEnvironment(logger: IzLogger): TestEnvironment
-}
-
-object TestEnvironmentProvider {
-
-  class Impl
-  (
-    protected val pluginSource: PluginSource,
-    protected val activation: Activation,
-    protected val memoizedKeys: Set[DIKey],
-    protected val bootstrapOverrides: BootstrapModule,
-    protected val moduleOverrides: ModuleBase,
-  ) extends TestEnvironmentProvider {
-
-    override final def loadEnvironment(logger: IzLogger): TestEnvironment = {
-      val cachedSource = if (memoizePlugins) {
-        PluginsCache.cachePluginSource(pluginSource)
-      } else pluginSource
-
-      val plugins = cachedSource.load()
-      val mergeStrategy = makeMergeStrategy(logger)
-      val appModule = mergeStrategy.merge(plugins.app)
-      val bootstrapModule = mergeStrategy.merge(plugins.bootstrap)
-      val availableActivations = ActivationInfoExtractor.findAvailableChoices(logger, appModule)
-
-      doLoad(logger, appModule, bootstrapModule, availableActivations)
-    }
-
-    protected final def doLoad(logger: IzLogger, appModule: ModuleBase, bootstrapModule: ModuleBase, availableActivations: ActivationInfo): TestEnvironment = {
-      val roles = loadRoles(logger)
-      TestEnvironment(
-        bsModule = bootstrapModule overridenBy bootstrapOverrides,
-        appModule = appModule overridenBy moduleOverrides,
-        roles = roles,
-        activationInfo = availableActivations,
-        activation = activation,
-        memoizedKeys = memoizedKeys,
-      )
-    }
-
-    protected def memoizePlugins: Boolean = {
-      import izumi.fundamentals.platform.strings.IzString._
-
-      System
-        .getProperty(DebugProperties.`izumi.distage.testkit.plugins.memoize`)
-        .asBoolean(true)
-    }
-
-    protected def loadRoles(@unused logger: IzLogger): RolesInfo = {
-      // For all normal scenarios we don't need roles to setup a test
-      RolesInfo(Set.empty, Seq.empty, Seq.empty, Seq.empty, Set.empty)
-    }
-
-    protected def makeMergeStrategy(@unused lateLogger: IzLogger): PluginMergeStrategy = {
-      SimplePluginMergeStrategy
-    }
-
-  }
-
-}
diff --git a/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/DistagePluginTestSupport.scala b/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/DistagePluginTestSupport.scala
index af3ff2898..a979cadb6 100644
--- a/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/DistagePluginTestSupport.scala
+++ b/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/DistagePluginTestSupport.scala
@@ -1,24 +1,19 @@
 package izumi.distage.testkit.services.scalatest.adapter
 
 import distage.SafeType
-import izumi.distage.framework.activation.PruningPlanMergingPolicyLoggedImpl
-import izumi.distage.framework.model.{ActivationInfo, BootstrapConfig, PluginSource}
-import izumi.distage.framework.services.ActivationInfoExtractor
+import izumi.distage.framework.model.{BootstrapConfig, PluginSource}
+import izumi.distage.model.definition.Activation
 import izumi.distage.model.definition.StandardAxis._
-import izumi.distage.model.definition.{Activation, BootstrapModuleDef, ModuleBase}
-import izumi.distage.model.planning.PlanMergingPolicy
 import izumi.distage.plugins.PluginConfig
-import izumi.distage.plugins.merge.{PluginMergeStrategy, SimplePluginMergeStrategy}
-import izumi.distage.roles.model.meta.RolesInfo
-import izumi.distage.testkit.DebugProperties
-import izumi.distage.testkit.services.PluginsCache
-import izumi.distage.testkit.services.dstest.TestEnvironment
-import izumi.fundamentals.platform.language.unused
+import izumi.distage.testkit.TestConfig
+import izumi.distage.testkit.services.dstest.{DistageTestEnv, TestEnvironment}
 import izumi.fundamentals.reflection.Tags.TagK
 import izumi.logstage.api.IzLogger
 
+
+
 @deprecated("Use dstest", "2019/Jul/18")
-abstract class DistagePluginTestSupport[F[_] : TagK] extends DistageTestSupport[F] {
+abstract class DistagePluginTestSupport[F[_] : TagK] extends DistageTestSupport[F] with DistageTestEnv {
 
   /**
     * This may be used as an implementation of [[pluginPackages]] in simple cases.
@@ -36,47 +31,8 @@ abstract class DistagePluginTestSupport[F[_] : TagK] extends DistageTestSupport[
 
   override final def loadEnvironment(logger: IzLogger): TestEnvironment = {
     val pluginSource = PluginSource(bootstrapConfig)
-    val cachedSource = if (memoizePlugins) {
-      PluginsCache.cachePluginSource(pluginSource)
-    } else pluginSource
-
-    val plugins = cachedSource.load()
-    val mergeStrategy = makeMergeStrategy(logger)
-    val appModule = mergeStrategy.merge(plugins.app)
-    val bootstrapModule = mergeStrategy.merge(plugins.bootstrap)
-    val availableActivations = ActivationInfoExtractor.findAvailableChoices(logger, appModule)
-
-    doLoad(logger, appModule, bootstrapModule, availableActivations)
-  }
-
-  protected final def doLoad(logger: IzLogger, appModule: ModuleBase, bootstrapModule: ModuleBase, availableActivations: ActivationInfo): TestEnvironment = {
-    val roles = loadRoles(logger)
-    val bsModule = bootstrapModule overridenBy new BootstrapModuleDef {
-      make[PlanMergingPolicy].from[PruningPlanMergingPolicyLoggedImpl]
-      make[ActivationInfo].fromValue(availableActivations)
-      make[Activation].fromValue(activation)
-    }
-    TestEnvironment(
-      bsModule = bsModule,
-      appModule = appModule,
-      roles = roles,
-      activationInfo = availableActivations,
-      activation = activation,
-      memoizedKeys = Set.empty,
-    )
-  }
-
-  protected def memoizePlugins: Boolean = {
-    import izumi.fundamentals.platform.strings.IzString._
-
-    System
-      .getProperty(DebugProperties.`izumi.distage.testkit.plugins.memoize`)
-      .asBoolean(true)
-  }
-
-  protected def loadRoles(@unused logger: IzLogger): RolesInfo = {
-    // For all normal scenarios we don't need roles to setup a test
-    RolesInfo(Set.empty, Seq.empty, Seq.empty, Seq.empty, Set.empty)
+    val config = TestConfig(pluginSource, activation)
+    loadEnvironment(logger, config)
   }
 
   protected def activation: Activation = {
@@ -87,10 +43,6 @@ abstract class DistagePluginTestSupport[F[_] : TagK] extends DistageTestSupport[
     MemoizationContextId.PerRuntimeAndActivationAndBsconfig[F](bootstrapConfig, activation, SafeType.getK[F])
   }
 
-  protected def makeMergeStrategy(@unused lateLogger: IzLogger): PluginMergeStrategy = {
-    SimplePluginMergeStrategy
-  }
-
   protected def bootstrapConfig: BootstrapConfig = {
     BootstrapConfig(
       PluginConfig.cached(pluginPackages),
diff --git a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/DistageBIOSpecScalatest.scala b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/DistageBIOSpecScalatest.scala
index 442200532..4ae5011b2 100644
--- a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/DistageBIOSpecScalatest.scala
+++ b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/DistageBIOSpecScalatest.scala
@@ -1,7 +1,7 @@
 package izumi.distage.testkit.scalatest
 
 import distage.{TagK, TagKK}
-import izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec
+import izumi.distage.testkit.services.scalatest.dstest.{DistageAbstractScalatestSpec, DistageTestsRegistrySingleton}
 import izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec.DSWordSpecStringWrapper2
 import org.scalatest.DistageScalatestTestSuiteRunner
 
diff --git a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/DistageSpecScalatest.scala b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/DistageSpecScalatest.scala
index 088afb708..41b9b6cc9 100644
--- a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/DistageSpecScalatest.scala
+++ b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/scalatest/DistageSpecScalatest.scala
@@ -1,7 +1,7 @@
 package izumi.distage.testkit.scalatest
 
 import distage.TagK
-import izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec
+import izumi.distage.testkit.services.scalatest.dstest.{DistageAbstractScalatestSpec, DistageTestsRegistrySingleton}
 import org.scalatest.DistageScalatestTestSuiteRunner
 
 abstract class DistageSpecScalatest[F[_]]()(implicit val tagMonoIO: TagK[F])
diff --git a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageAbstractScalatestSpec.scala b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageAbstractScalatestSpec.scala
index b376757a5..454a79e79 100644
--- a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageAbstractScalatestSpec.scala
+++ b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageAbstractScalatestSpec.scala
@@ -1,10 +1,8 @@
 package izumi.distage.testkit.services.scalatest.dstest
 
 import distage.{Tag, TagK, TagKK}
-import izumi.distage.framework.model.PluginSource
 import izumi.distage.model.effect.DIEffect
 import izumi.distage.model.providers.ProviderMagnet
-import izumi.distage.plugins.PluginConfig
 import izumi.distage.testkit.TestConfig
 import izumi.distage.testkit.services.dstest.DistageTestRunner.{DistageTest, TestId, TestMeta}
 import izumi.distage.testkit.services.dstest._
@@ -23,31 +21,19 @@ trait WithSingletonTestRegistration[F[_]] extends AbstractDistageSpec[F] {
   }
 }
 
-trait DistageAbstractScalatestSpec[F[_]] extends ScalatestWords with WithSingletonTestRegistration[F] {
+trait DistageAbstractScalatestSpec[F[_]] extends ScalatestWords with WithSingletonTestRegistration[F] with DistageTestEnv {
   this: AbstractDistageSpec[F] =>
 
-  private[this] lazy val testEnvProvider: TestEnvironmentProvider = makeTestEnvProvider()
   final protected lazy val testEnv: TestEnvironment = makeTestEnv()
 
-  protected def makeTestEnvProvider(): TestEnvironmentProvider = {
-    val conf = config
-    val pluginSource = conf.pluginSource
-      .getOrElse(PluginSource(PluginConfig.cached(Seq(this.getClass.getPackage.getName)), None))
-
-    new TestEnvironmentProvider.Impl(
-      pluginSource,
-      conf.activation,
-      conf.memoizationRoots,
-      conf.bootstrapOverrides,
-      conf.moduleOverrides,
-    )
+  protected def makeTestEnv(): TestEnvironment = {
+    loadEnvironment(logger, config)
   }
-  protected def makeTestEnv(): TestEnvironment = testEnvProvider.loadEnvironment(logger)
 
   protected def distageSuiteName: String = getSimpleNameOfAnObjectsClass(this)
   protected def distageSuiteId: String = this.getClass.getName
 
-  protected def config: TestConfig = TestConfig()
+  protected def config: TestConfig = TestConfig.forSuite(this.getClass)
 
   protected def logger: IzLogger = IzLogger(Log.Level.Debug)("phase" -> "test")
 
diff --git a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageTestsRegistrySingleton.scala b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageTestsRegistrySingleton.scala
index 611f8270d..4fc9ce147 100644
--- a/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageTestsRegistrySingleton.scala
+++ b/distage/distage-testkit-scalatest/src/main/scala/izumi/distage/testkit/services/scalatest/dstest/DistageTestsRegistrySingleton.scala
@@ -5,12 +5,15 @@ import java.util.concurrent.ConcurrentHashMap
 import distage.{SafeType, TagK}
 import izumi.distage.testkit.services.dstest.DistageTestRunner.DistageTest
 import izumi.fundamentals.platform.language.Quirks._
+import org.scalatest.{StatefulStatus, Tracker}
 
 import scala.collection.mutable
 
 object DistageTestsRegistrySingleton {
   private[DistageTestsRegistrySingleton] type Fake[T]
   private[this] val registry = new mutable.HashMap[SafeType, mutable.ArrayBuffer[DistageTest[Fake]]]()
+  private[this] val statuses = new mutable.HashMap[String, StatefulStatus]()
+  private[this] val trackers = new mutable.HashMap[String, Either[mutable.ArrayBuffer[Tracker => Unit], Tracker]]()
   private[this] val runTracker = new ConcurrentHashMap[SafeType, Boolean]()
 
   def list[F[_]: TagK]: Seq[DistageTest[F]] = synchronized {
@@ -25,4 +28,31 @@ object DistageTestsRegistrySingleton {
     val tpe = SafeType.getK[F]
     !runTracker.putIfAbsent(tpe, true)
   }
+
+  def registerStatus(suiteId: String, status: StatefulStatus): Unit = synchronized {
+    statuses(suiteId) = status
+  }
+
+  def completeStatuses(suiteIds: Set[String]): Unit = synchronized {
+    suiteIds.foreach(statuses.get(_).foreach(_.setCompleted()))
+  }
+
+  def runReport(suiteId: String)(f: Tracker => Unit): Unit = synchronized {
+    trackers.getOrElseUpdate(suiteId, Left(mutable.ArrayBuffer.empty)) match {
+      case Left(reports) =>
+        reports += f
+      case Right(tracker) =>
+        f(tracker)
+    }
+    ()
+  }
+
+  def registerTracker(suiteId: String)(tracker: Tracker): Unit = synchronized {
+    trackers.getOrElseUpdate(suiteId, Right(tracker)) match {
+      case Left(reports) =>
+        trackers(suiteId) = Right(tracker)
+        reports.foreach(_.apply(tracker))
+      case Right(_) =>
+    }
+  }
 }
diff --git a/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/DistageScalatestTestSuiteRunner.scala b/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/DistageScalatestTestSuiteRunner.scala
index 98bece712..3d197c0d2 100644
--- a/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/DistageScalatestTestSuiteRunner.scala
+++ b/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/DistageScalatestTestSuiteRunner.scala
@@ -7,7 +7,6 @@ import izumi.distage.testkit.services.dstest.DistageTestRunner._
 import izumi.distage.testkit.services.dstest.{AbstractDistageSpec, DistageTestRunner, SpecEnvironment}
 import izumi.distage.testkit.services.scalatest.dstest.DistageTestsRegistrySingleton
 import izumi.logstage.api.{IzLogger, Log}
-import org.scalatest.events._
 import org.scalatest.exceptions.TestCanceledException
 
 import scala.collection.immutable.TreeSet
@@ -57,25 +56,20 @@ trait DistageScalatestTestSuiteRunner[F[_]] extends Suite with AbstractDistageSp
     DistageTestsRegistrySingleton.list[F]
   }
 
-  override def run(testName: Option[String], args: Args): Status = {
-    val status = new StatefulStatus
+  private[this] val status = new StatefulStatus
+  DistageTestsRegistrySingleton.registerStatus(suiteId, status)
 
+  override def run(testName: Option[String], args: Args): Status = {
+    DistageTestsRegistrySingleton.registerTracker(suiteId)(args.tracker)
     try {
       if (DistageTestsRegistrySingleton.ticketToProceed[F]()) {
         doRun(testName, args)
-      } else {
-        addStub(args, None)
       }
     } catch {
       case t: Throwable =>
-        // IDEA reporter is insane
-        //status.setFailedWith(t)
-        addStub(args, Some(t))
-
-    } finally {
-      status.setCompleted()
+        status.setFailedWith(t)
+        status.setCompleted()
     }
-
     status
   }
 
@@ -91,17 +85,17 @@ trait DistageScalatestTestSuiteRunner[F[_]] extends Suite with AbstractDistageSp
     val testTags: Set[String] = Set.empty
 
     new TestData {
-      val configMap: ConfigMap = theConfigMap
-      val name: String = testName
-      val scopes: Vector[Nothing] = Vector.empty
-      val text: String = testName
-      val tags: Set[String] = Set.empty ++ suiteTags ++ testTags
-      val pos: None.type = None
+      override val configMap: ConfigMap = theConfigMap
+      override val name: String = testName
+      override val scopes: Vector[Nothing] = Vector.empty
+      override val text: String = testName
+      override val tags: Set[String] = Set.empty ++ suiteTags ++ testTags
+      override val pos: None.type = None
     }
   }
 
   private def doRun(testName: Option[String], args: Args): Unit = {
-    val dreporter = mkTestReporter(args)
+    val testReporter = mkTestReporter(args)
 
     val toRun = testName match {
       case None =>
@@ -134,54 +128,24 @@ trait DistageScalatestTestSuiteRunner[F[_]] extends Suite with AbstractDistageSp
     val runner = {
       val logger = IzLogger(Log.Level.Debug)("phase" -> "test")
       val checker = new IntegrationChecker.Impl(logger)
-      new DistageTestRunner[F](dreporter, checker, specEnv, toRun, _.isInstanceOf[TestCanceledException], parallelTestsAlways)
+      new DistageTestRunner[F](testReporter, checker, specEnv, toRun, _.isInstanceOf[TestCanceledException], parallelTestsAlways)
     }
 
-    runner.run()
+    try {
+      runner.run()
+    } finally {
+      DistageTestsRegistrySingleton.completeStatuses(testsInThisMonad.map(_.meta.id.suiteId).toSet)
+    }
   }
 
   // FIXME: read scalatest runner configuration???
   protected def parallelTestsAlways: Boolean = true
 
   private def mkTestReporter(args: Args): TestReporter = {
-    val scalatestReporter = new ScalatestReporter(args, suiteName, suiteId)
+    val scalatestReporter = new ScalatestReporter(args.reporter)
     new SafeTestReporter(scalatestReporter)
   }
 
-  private def addStub(args: Args, failure: Option[Throwable]): Unit = {
-    val tracker = args.tracker
-    val FUCK_SCALATEST = "Scalatest and IDEA aren't so nice"
-    val SUITE_FAILED = "Whole suite failed :/"
-
-    failure match {
-      case Some(_) =>
-        args.reporter(TestStarting(
-          tracker.nextOrdinal(),
-          suiteName, suiteId, Some(suiteId),
-          SUITE_FAILED,
-          SUITE_FAILED,
-        ))
-        args.reporter(TestFailed(
-          tracker.nextOrdinal(),
-          s"suite failed",
-          suiteName, suiteId, Some(suiteId),
-          SUITE_FAILED,
-          SUITE_FAILED,
-          scala.collection.immutable.IndexedSeq.empty[RecordableEvent],
-          throwable = failure
-        ))
-      case None =>
-        args.reporter(TestCanceled(
-          tracker.nextOrdinal(), FUCK_SCALATEST,
-          suiteName, suiteId, Some(suiteId),
-          FUCK_SCALATEST,
-          FUCK_SCALATEST,
-          Vector.empty,
-        ))
-    }
-
-  }
-
   override final val styleName: String = "DistageSuite"
 }
 
diff --git a/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/SafeTestReporter.scala b/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/SafeTestReporter.scala
index 60a54f3b5..707e3407f 100644
--- a/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/SafeTestReporter.scala
+++ b/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/SafeTestReporter.scala
@@ -1,15 +1,15 @@
 package org.scalatest
 
-import izumi.distage.testkit.services.dstest.DistageTestRunner.TestStatus.{Done, Finished, Ignored}
+import izumi.distage.testkit.services.dstest.DistageTestRunner.TestStatus.{Done, Finished, Ignored, Running}
 import izumi.distage.testkit.services.dstest.DistageTestRunner.{SuiteData, TestMeta, TestReporter, TestStatus}
-import izumi.fundamentals.platform.language.Quirks
+import izumi.fundamentals.platform.language.Quirks._
+import izumi.fundamentals.platform.language.unused
 
 import scala.collection.mutable
 
 class SafeTestReporter(underlying: TestReporter) extends TestReporter {
-  private val delayedReports = mutable.LinkedHashMap.empty[TestMeta, TestStatus]
-  private var signalled: Option[TestMeta] = None
-  private var openSuite: Option[SuiteData] = None
+  private val delayedReports = new mutable.LinkedHashMap[TestMeta, TestStatus]()
+  private var runningTest: Option[TestMeta] = None
 
   override def onFailure(f: Throwable): Unit = synchronized {
     endAll()
@@ -18,45 +18,41 @@ class SafeTestReporter(underlying: TestReporter) extends TestReporter {
 
   override def endAll(): Unit = synchronized {
     finish()
-    finishOpen()
     underlying.endAll()
   }
 
-  override def beginSuite(id: SuiteData): Unit = synchronized {
-    Quirks.discard(id)
-  }
+  override def beginSuite(@unused id: SuiteData): Unit = {}
 
-  override def endSuite(id: SuiteData): Unit = synchronized {
-    Quirks.discard(id)
-  }
+  override def endSuite(@unused id: SuiteData): Unit = {}
 
   override def testStatus(test: TestMeta, testStatus: TestStatus): Unit = synchronized {
     testStatus match {
-      case TestStatus.Scheduled =>
-        reportStatus(test, testStatus)
+//      case TestStatus.Scheduled => reportStatus(test, testStatus)
 
       case TestStatus.Running =>
-        signalled match {
+        runningTest match {
           case Some(_) =>
+            delayedReports.put(test, testStatus)
           case None =>
             reportStatus(test, testStatus)
-            signalled = Some(test)
+            runningTest = Some(test)
         }
 
       case _: Ignored =>
-        signalled match {
+        runningTest match {
           case None =>
             reportStatus(test, TestStatus.Running)
             reportStatus(test, testStatus)
           case Some(_) =>
+            // Ignores happen before running, so an ignored test cannot be the same as `signalled`
             delayedReports.put(test, testStatus)
         }
 
       case _: Finished =>
-        signalled match {
+        runningTest match {
           case Some(value) if value == test =>
             finishTest(test, testStatus)
-            signalled = None
+            runningTest = None
             finish()
           case _ =>
             delayedReports.put(test, testStatus)
@@ -66,38 +62,25 @@ class SafeTestReporter(underlying: TestReporter) extends TestReporter {
   }
 
   private def reportStatus(test: TestMeta, testStatus: TestStatus): Unit = synchronized {
-    val fakeSuite = SuiteData(test.id.suiteName, test.id.suiteId, test.id.suiteClassName)
-
-    nextSuite(fakeSuite)
     underlying.testStatus(test, testStatus)
   }
 
-  private val fakeSuites = false
-  private def nextSuite(fakeSuite: SuiteData): Unit = {
-    if (fakeSuites) {
-      if (!openSuite.contains(fakeSuite)) {
-        finishOpen()
-        underlying.beginSuite(fakeSuite)
-        openSuite = Some(fakeSuite)
-      }
-    }
-  }
-
-  private def finishOpen(): Unit = {
-    if (fakeSuites) {
-      openSuite.foreach(underlying.endSuite)
-    }
-  }
-
   private def finish(): Unit = {
     val finishedTests = delayedReports.collect { case (t, s: Done) => (t, s) }
 
+    // report all finished tests
     finishedTests
       .foreach {
         case (t, s) =>
           reportStatus(t, TestStatus.Running)
           finishTest(t, s)
       }
+    // switch to another test if it's already running
+    delayedReports.collectFirst {
+      case (t, Running) =>
+        reportStatus(t, TestStatus.Running)
+        runningTest = Some(t)
+    }.discard()
   }
 
   private def finishTest(test: TestMeta, testStatus: TestStatus): Option[TestStatus] = {
diff --git a/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/ScalatestReporter.scala b/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/ScalatestReporter.scala
index 22188877c..c35dea0bc 100644
--- a/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/ScalatestReporter.scala
+++ b/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/ScalatestReporter.scala
@@ -1,12 +1,11 @@
 package org.scalatest
 
 import izumi.distage.testkit.services.dstest.DistageTestRunner.{SuiteData, TestMeta, TestReporter, TestStatus}
-import izumi.fundamentals.platform.language.Quirks
-import org.scalatest.events._
+import izumi.distage.testkit.services.scalatest.dstest.DistageTestsRegistrySingleton
 import izumi.fundamentals.platform.strings.IzString._
+import org.scalatest.events._
 
-class ScalatestReporter(args: Args, suiteName: String, suiteId: String) extends TestReporter {
-  private val tracker = args.tracker
+class ScalatestReporter(reporter: Reporter) extends TestReporter {
 
   override def onFailure(f: Throwable): Unit = {
     System.err.println("Test runner failed")
@@ -16,107 +15,98 @@ class ScalatestReporter(args: Args, suiteName: String, suiteId: String) extends
   override def endAll(): Unit = {}
 
   override def beginSuite(id: SuiteData): Unit = {
-    args.reporter.apply(TestStarting(
-      tracker.nextOrdinal(),
-      suiteName, suiteId, Some(suiteId),
-      id.suiteName,
-      id.suiteName,
-    ))
+//    doReport(id.suiteId)(TestStarting(
+//      _,
+//      id.suiteName, id.suiteId, Some(id.suiteClassName),
+//      id.suiteName,
+//      id.suiteName,
+//    ))
   }
 
   override def endSuite(id: SuiteData): Unit = {
-    args.reporter.apply(TestSucceeded(
-      tracker.nextOrdinal(),
-      suiteName, suiteId, Some(suiteId),
-      id.suiteName,
-      id.suiteName,
-      scala.collection.immutable.IndexedSeq.empty[RecordableEvent],
-    ))
+//    doReport(id.suiteId)(TestSucceeded(
+//      _,
+//      id.suiteName, id.suiteId, Some(id.suiteClassName),
+//      id.suiteName,
+//      id.suiteName,
+//      Vector.empty,
+//    ))
   }
 
   override def testStatus(test: TestMeta, testStatus: TestStatus): Unit = {
-    val suiteName1 = suiteName
-    val suiteId1 = suiteId
+    val suiteName1 = test.id.suiteName
+    val suiteId1 = test.id.suiteId
+    val suiteClassName1 = test.id.suiteId
     val testName = mkName(test)
 
     testStatus match {
-      case TestStatus.Scheduled =>
+//      case TestStatus.Scheduled =>
 
       case TestStatus.Running =>
-        args.reporter.apply(TestStarting(
-          ord(test),
-          suiteName1, suiteId1, Some(suiteId1),
+        doReport(suiteId1)(TestStarting(
+          _,
+          suiteName1, suiteId1, Some(suiteClassName1),
           testName,
           testName,
           location = Some(LineInFile(test.pos.position.line, test.pos.position.file, None)),
+          rerunner = Some(test.id.suiteClassName),
         ))
       case TestStatus.Succeed(duration) =>
-        args.reporter.apply(TestSucceeded(
-          ord(test),
-          suiteName1, suiteId1, Some(suiteId1),
+        doReport(suiteId1)(TestSucceeded(
+          _,
+          suiteName1, suiteId1, Some(suiteClassName1),
           testName,
           testName,
-          scala.collection.immutable.IndexedSeq.empty[RecordableEvent],
-          location = Some(LineInFile(test.pos.position.line, test.pos.position.file, None)),
+          recordedEvents = Vector.empty,
           duration = Some(duration.toMillis),
+          location = Some(LineInFile(test.pos.position.line, test.pos.position.file, None)),
           rerunner = Some(test.id.suiteClassName),
         ))
       case TestStatus.Failed(t, duration) =>
-        args.reporter.apply(TestFailed(
-          ord(test),
+        doReport(suiteId1)(TestFailed(
+          _,
           "Test failed",
-          suiteName1, suiteId1, Some(suiteId1),
+          suiteName1, suiteId1, Some(suiteClassName1),
           testName,
           testName,
-          scala.collection.immutable.IndexedSeq.empty[RecordableEvent],
-          location = Some(LineInFile(test.pos.position.line, test.pos.position.file, None)),
+          recordedEvents = Vector.empty,
           throwable = Some(t),
           duration = Some(duration.toMillis),
+          location = Some(LineInFile(test.pos.position.line, test.pos.position.file, None)),
           rerunner = Some(test.id.suiteClassName),
         ))
       case TestStatus.Cancelled(clue, duration) =>
-        args.reporter.apply(TestCanceled(
-          ord(test),
+        doReport(suiteId1)(TestCanceled(
+          _,
           s"cancelled: $clue",
-          suiteName1, suiteId1, Some(suiteId1),
+          suiteName1, suiteId1, Some(suiteClassName1),
           testName,
           testName,
-          scala.collection.immutable.IndexedSeq.empty[RecordableEvent],
-          location = Some(LineInFile(test.pos.position.line, test.pos.position.file, None)),
+          recordedEvents = Vector.empty,
           duration = Some(duration.toMillis),
+          location = Some(LineInFile(test.pos.position.line, test.pos.position.file, None)),
           rerunner = Some(test.id.suiteClassName),
         ))
       case TestStatus.Ignored(checks) =>
-//              args.reporter.apply(TestIgnored(
-//                ord(test),
-//                suiteName1, suiteId1, Some(suiteId1),
-//                testName,
-//                testName,
-//                None,
-//                location = Some(LineInFile(test.pos.position.line, test.pos.position.file, None)),
-//                payload = None,
-//                threadName = "",
-//                timeStamp = 0,
-//              ))
-        args.reporter.apply(TestCanceled(
-          ord(test),
+        doReport(suiteId1)(TestCanceled(
+          _,
           s"ignored: ${checks.niceList()}",
-          suiteName1, suiteId1, Some(suiteId1),
+          suiteName1, suiteId1, Some(suiteClassName1),
           testName,
           testName,
-          scala.collection.immutable.IndexedSeq.empty[RecordableEvent],
+          recordedEvents = Vector.empty,
           location = Some(LineInFile(test.pos.position.line, test.pos.position.file, None)),
           rerunner = Some(test.id.suiteClassName),
         ))
     }
   }
 
-  private def mkName(test: TestMeta) = {
+  private def mkName(test: TestMeta): String = {
     s"${test.id.suiteName}: ${test.id.name}"
   }
 
-  private def ord(testId: TestMeta): Ordinal = {
-    Quirks.discard(testId)
-    tracker.nextOrdinal()
+  @inline private[this] def doReport(suiteId: String)(f: Ordinal => Event): Unit = {
+    DistageTestsRegistrySingleton.runReport(suiteId)(tracker => reporter(f(tracker.nextOrdinal())))
   }
+
 }
diff --git a/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/DistageTestExample.scala b/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/DistageTestExample.scala
index 08dc134fd..2d3abe974 100644
--- a/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/DistageTestExample.scala
+++ b/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/DistageTestExample.scala
@@ -2,8 +2,10 @@ package izumi.distage.testkit.distagesuite
 
 import cats.effect.{IO => CIO}
 import distage._
+import izumi.distage.framework.model.PluginSource
 import izumi.distage.model.effect.DIEffect
 import izumi.distage.model.effect.DIEffect.syntax._
+import izumi.distage.plugins.PluginConfig
 import izumi.distage.testkit.TestConfig
 import izumi.distage.testkit.distagesuite.fixtures.{ApplePaymentProvider, MockCache, MockCachedUserService, MockUserRepository}
 import izumi.distage.testkit.scalatest.{DistageBIOSpecScalatest, DistageSpecScalatest}
@@ -11,9 +13,10 @@ import izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestS
 import izumi.fundamentals.platform.functional.Identity
 import zio.Task
 
-trait DistageMemoizeExample[F[_]] { this: DistageAbstractScalatestSpec[F] =>
+trait DistageMemoizeExample[F[_]] {
+  this: DistageAbstractScalatestSpec[F] =>
   override protected def config: TestConfig = {
-    TestConfig(
+    TestConfig.forSuite(this.getClass).copy(
       memoizationRoots = Set(
         DIKey.get[MockCache[CIO]],
         DIKey.get[MockCache[Task]],
@@ -34,7 +37,12 @@ class DistageTestExampleBIO extends DistageBIOSpecScalatest[zio.IO] with Distage
 
 }
 
-abstract class DistageTestExampleBase[F[_]: TagK](implicit F: DIEffect[F]) extends DistageSpecScalatest[F] with DistageMemoizeExample[F] {
+abstract class DistageTestExampleBase[F[_] : TagK](implicit F: DIEffect[F]) extends DistageSpecScalatest[F] with DistageMemoizeExample[F] {
+
+  override protected def config: TestConfig = super.config.copy(
+    pluginSource = super.config.pluginSource ++ PluginSource(PluginConfig.cached(Seq("xxx")))
+  )
+
 
   "distage test custom runner" should {
     "test 1" in {
@@ -65,7 +73,7 @@ abstract class DistageTestExampleBase[F[_]: TagK](implicit F: DIEffect[F]) exten
 
     "test 5 (should be ignored)" skip {
       _: MockCachedUserService[F] =>
-       ???
+        ???
     }
 
     "test 6 (should be ignored)" in {
@@ -77,5 +85,8 @@ abstract class DistageTestExampleBase[F[_]: TagK](implicit F: DIEffect[F]) exten
 }
 
 final class DistageTestExampleId extends DistageTestExampleBase[Identity]
+
 final class DistageTestExampleCIO extends DistageTestExampleBase[CIO]
+
 final class DistageTestExampleZIO extends DistageTestExampleBase[Task]
+final class DistageTestExampleZIO2 extends DistageTestExampleBase[Task]