Repository: 7mind/izumi
Pull request #799 (2019-12-26 20:01:37)

test envs revisited

codecov-io: # [Codecov](https://codecov.io/gh/7mind/izumi/pull/799?src=pr&el=h1) Report
> Merging [#799](https://codecov.io/gh/7mind/izumi/pull/799?src=pr&el=desc) into [develop](https://codecov.io/gh/7mind/izumi/commit/0133aba6bfcf2122e7cddf286c8530c5cd207a78?src=pr&el=desc) will **decrease** coverage by `7.51%`.
> The diff coverage is `89.28%`.

[![Impacted file tree graph](https://codecov.io/gh/7mind/izumi/pull/799/graphs/tree.svg?width=650&token=77pa3UU1Je&height=150&src=pr)](https://codecov.io/gh/7mind/izumi/pull/799?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##           develop     #799      +/-   ##
===========================================
- Coverage    70.16%   62.65%   -7.52%     
===========================================
  Files          551      545       -6     
  Lines        14073    12308    -1765     
  Branches       775      731      -44     
===========================================
- Hits          9875     7711    -2164     
- Misses        4198     4597     +399
```


| [Impacted Files](https://codecov.io/gh/7mind/izumi/pull/799?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [.../scala/izumi/distage/plugins/DebugProperties.scala](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLWV4dGVuc2lvbi1wbHVnaW5zL3NyYy9tYWluL3NjYWxhL2l6dW1pL2Rpc3RhZ2UvcGx1Z2lucy9EZWJ1Z1Byb3BlcnRpZXMuc2NhbGE=) | `0% <ø> (ø)` | |
| [...tage/testkit/services/dstest/DebugProperties.scala](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9kaXN0YWdlL3Rlc3RraXQvc2VydmljZXMvZHN0ZXN0L0RlYnVnUHJvcGVydGllcy5zY2FsYQ==) | `0% <0%> (ø)` | |
| [...s/scalatest/adapter/DistagePluginTestSupport.scala](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtbGVnYWN5L3NyYy9tYWluL3NjYWxhL2l6dW1pL2Rpc3RhZ2UvdGVzdGtpdC9zZXJ2aWNlcy9zY2FsYXRlc3QvYWRhcHRlci9EaXN0YWdlUGx1Z2luVGVzdFN1cHBvcnQuc2NhbGE=) | `90.9% <100%> (-2.2%)` | :arrow_down: |
| [...ain/scala/izumi/distage/plugins/PluginConfig.scala](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLWV4dGVuc2lvbi1wbHVnaW5zL3NyYy9tYWluL3NjYWxhL2l6dW1pL2Rpc3RhZ2UvcGx1Z2lucy9QbHVnaW5Db25maWcuc2NhbGE=) | `75% <100%> (+8.33%)` | :arrow_up: |
| [.../main/scala/izumi/distage/testkit/TestConfig.scala](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9kaXN0YWdlL3Rlc3RraXQvVGVzdENvbmZpZy5zY2FsYQ==) | `100% <100%> (ø)` | |
| [...calatest/dstest/DistageAbstractScalatestSpec.scala](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtc2NhbGF0ZXN0L3NyYy9tYWluL3NjYWxhL2l6dW1pL2Rpc3RhZ2UvdGVzdGtpdC9zZXJ2aWNlcy9zY2FsYXRlc3QvZHN0ZXN0L0Rpc3RhZ2VBYnN0cmFjdFNjYWxhdGVzdFNwZWMuc2NhbGE=) | `63.33% <50%> (-3.84%)` | :arrow_down: |
| [...stage/testkit/services/dstest/DistageTestEnv.scala](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9kaXN0YWdlL3Rlc3RraXQvc2VydmljZXMvZHN0ZXN0L0Rpc3RhZ2VUZXN0RW52LnNjYWxh) | `95% <95%> (ø)` | |
| [...la/izumi/fundamentals/collections/IzIterable.scala](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree#diff-ZnVuZGFtZW50YWxzL2Z1bmRhbWVudGFscy1jb2xsZWN0aW9ucy9zcmMvbWFpbi9zY2FsYS9penVtaS9mdW5kYW1lbnRhbHMvY29sbGVjdGlvbnMvSXpJdGVyYWJsZS5zY2FsYQ==) | `0% <0%> (-100%)` | :arrow_down: |
| [...alingua/translator/toscala/CompositeRenderer.scala](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree#diff-aWRlYWxpbmd1YS12MS9pZGVhbGluZ3VhLXYxLXRyYW5zcGlsZXJzL3NyYy9tYWluL3NjYWxhL2l6dW1pL2lkZWFsaW5ndWEvdHJhbnNsYXRvci90b3NjYWxhL0NvbXBvc2l0ZVJlbmRlcmVyLnNjYWxh) | `0% <0%> (-100%)` | :arrow_down: |
| [...slator/toscala/types/runtime/IDLRuntimeTypes.scala](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree#diff-aWRlYWxpbmd1YS12MS9pZGVhbGluZ3VhLXYxLXRyYW5zcGlsZXJzL3NyYy9tYWluL3NjYWxhL2l6dW1pL2lkZWFsaW5ndWEvdHJhbnNsYXRvci90b3NjYWxhL3R5cGVzL3J1bnRpbWUvSURMUnVudGltZVR5cGVzLnNjYWxh) | `0% <0%> (-100%)` | :arrow_down: |
| ... and [189 more](https://codecov.io/gh/7mind/izumi/pull/799/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/7mind/izumi/pull/799?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/7mind/izumi/pull/799?src=pr&el=footer). Last update [0133aba...4447148](https://codecov.io/gh/7mind/izumi/pull/799?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


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-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/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/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/test/scala/izumi/distage/testkit/distagesuite/DistageTestExample.scala b/distage/distage-testkit-scalatest/src/test/scala/izumi/distage/testkit/distagesuite/DistageTestExample.scala
index 08dc134fd..0c6854fa0 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,7 @@ 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]