Repository: 7mind/izumi
Pull request #822 (2020-01-14 18:01:37)

Fix running individual tests under IDEA / non-sbt ScalatestRunner – scan classpath for tests only under sbt

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

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

```diff
@@            Coverage Diff             @@
##           develop    #822      +/-   ##
==========================================
- Coverage    70.38%   62.9%   -7.49%     
==========================================
  Files          549     542       -7     
  Lines        14028   12295    -1733     
  Branches       725     747      +22     
==========================================
- Hits          9874    7734    -2140     
- Misses        4154    4561     +407
```


| [Impacted Files](https://codecov.io/gh/7mind/izumi/pull/822?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...stage/testkit/services/dstest/DistageTestEnv.scala](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9kaXN0YWdlL3Rlc3RraXQvc2VydmljZXMvZHN0ZXN0L0Rpc3RhZ2VUZXN0RW52LnNjYWxh) | `94.73% <ø> (ø)` | :arrow_up: |
| [...la/izumi/distage/planning/PlannerDefaultImpl.scala](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLWNvcmUvc3JjL21haW4vc2NhbGEvaXp1bWkvZGlzdGFnZS9wbGFubmluZy9QbGFubmVyRGVmYXVsdEltcGwuc2NhbGE=) | `96.03% <ø> (-0.04%)` | :arrow_down: |
| [.../scala/izumi/distage/testkit/DebugProperties.scala](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9kaXN0YWdlL3Rlc3RraXQvRGVidWdQcm9wZXJ0aWVzLnNjYWxh) | `0% <0%> (ø)` | |
| [.../izumi/distage/testkit/services/DISyntaxBase.scala](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9kaXN0YWdlL3Rlc3RraXQvc2VydmljZXMvRElTeW50YXhCYXNlLnNjYWxh) | `100% <100%> (ø)` | :arrow_up: |
| [...tage/testkit/services/dstest/SpecEnvironment.scala](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9kaXN0YWdlL3Rlc3RraXQvc2VydmljZXMvZHN0ZXN0L1NwZWNFbnZpcm9ubWVudC5zY2FsYQ==) | `100% <100%> (ø)` | :arrow_up: |
| [...alatest/dstest/DistageTestsRegistrySingleton.scala](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtc2NhbGF0ZXN0L3NyYy9tYWluL3NjYWxhL2l6dW1pL2Rpc3RhZ2UvdGVzdGtpdC9zZXJ2aWNlcy9zY2FsYXRlc3QvZHN0ZXN0L0Rpc3RhZ2VUZXN0c1JlZ2lzdHJ5U2luZ2xldG9uLnNjYWxh) | `100% <100%> (ø)` | :arrow_up: |
| [...umi/distage/testkit/services/DISyntaxBIOBase.scala](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9kaXN0YWdlL3Rlc3RraXQvc2VydmljZXMvRElTeW50YXhCSU9CYXNlLnNjYWxh) | `80% <100%> (-3.34%)` | :arrow_down: |
| [...rg/scalatest/DistageScalatestTestSuiteRunner.scala](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtc2NhbGF0ZXN0L3NyYy9tYWluL3NjYWxhL29yZy9zY2FsYXRlc3QvRGlzdGFnZVNjYWxhdGVzdFRlc3RTdWl0ZVJ1bm5lci5zY2FsYQ==) | `70.88% <100%> (-2.61%)` | :arrow_down: |
| [...calatest/dstest/DistageAbstractScalatestSpec.scala](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtc2NhbGF0ZXN0L3NyYy9tYWluL3NjYWxhL2l6dW1pL2Rpc3RhZ2UvdGVzdGtpdC9zZXJ2aWNlcy9zY2FsYXRlc3QvZHN0ZXN0L0Rpc3RhZ2VBYnN0cmFjdFNjYWxhdGVzdFNwZWMuc2NhbGE=) | `60.29% <21.73%> (-9.89%)` | :arrow_down: |
| [...stkit/services/scalatest/adapter/DISyntaxBIO.scala](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree#diff-ZGlzdGFnZS9kaXN0YWdlLXRlc3RraXQtbGVnYWN5L3NyYy9tYWluL3NjYWxhL2l6dW1pL2Rpc3RhZ2UvdGVzdGtpdC9zZXJ2aWNlcy9zY2FsYXRlc3QvYWRhcHRlci9ESVN5bnRheEJJTy5zY2FsYQ==) | `50% <50%> (-16.67%)` | :arrow_down: |
| ... and [188 more](https://codecov.io/gh/7mind/izumi/pull/822/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/7mind/izumi/pull/822?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/822?src=pr&el=footer). Last update [ea2d972...11d7235](https://codecov.io/gh/7mind/izumi/pull/822?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.gitignore b/.gitignore
index 5c293b9ca..f059220ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ secrets.tar
 build/project
 .DS_Store
 .metals
+project/metals.sbt
 .bloop
 .java-version
 
diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/PlannerDefaultImpl.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/PlannerDefaultImpl.scala
index 37583e4cd..f75a7cfb6 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/planning/PlannerDefaultImpl.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/planning/PlannerDefaultImpl.scala
@@ -11,7 +11,6 @@ import izumi.distage.model.plan.topology.PlanTopology
 import izumi.distage.model.planning._
 import izumi.distage.model.reflection.MirrorProvider
 import izumi.distage.model.reflection.universe.RuntimeDIUniverse.DIKey
-import izumi.distage.model.reflection.universe.RuntimeDIUniverse.Wiring.SingletonWiring
 import izumi.distage.model.{Planner, PlannerInput}
 import izumi.distage.planning.gc.TracingDIGC
 import izumi.functional.Value


Test patch:
diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala
index 02a39b0b9..df81301ce 100644
--- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala
+++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala
@@ -7,7 +7,6 @@ import izumi.distage.model.plan.GCMode
 import org.scalatest.WordSpec
 
 import scala.collection.immutable
-import scala.util.Try
 
 class GcBasicTestsJvm extends WordSpec with MkGcInjector {
   "Garbage-collecting injector" should {
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala
new file mode 100644
index 000000000..846c9331d
--- /dev/null
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala
@@ -0,0 +1,16 @@
+package izumi.distage.testkit
+
+import izumi.fundamentals.platform.logging
+
+/**
+  * Java properties that debug logging for distage-testkit and caching of distage-testkit classpath scans (when used)
+  *
+  * @see [[logging.DebugProperties]]
+  */
+object DebugProperties extends logging.DebugProperties {
+  /** Print debug messages */
+  final val `izumi.distage.testkit.debug` = "izumi.distage.testkit.debug"
+
+  /** Cache created [[izumi.distage.testkit.services.dstest.TestEnvironment]] */
+  final val `izumi.distage.testkit.environment.cache` = "izumi.distage.testkit.environment.cache"
+}
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/SpecConfig.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/SpecConfig.scala
index 5384394c4..288f1a6e6 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/SpecConfig.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/SpecConfig.scala
@@ -6,9 +6,9 @@ import izumi.distage.framework.config.PlanningOptions
 import izumi.logstage.api.Log
 
 final case class SpecConfig(
-                             contextOptions: PlanningOptions = PlanningOptions(),
-                             bootstrapOverrides: BootstrapModule = BootstrapModule.empty,
-                             moduleOverrides: ModuleBase = ModuleBase.empty,
+                             planningOptions: PlanningOptions = PlanningOptions(),
                              bootstrapLogLevel: Log.Level = Log.Level.Info,
-                             configOverrides: AppConfig => AppConfig = identity
+                             moduleOverrides: ModuleBase = ModuleBase.empty,
+                             bootstrapOverrides: BootstrapModule = BootstrapModule.empty,
+                             configOverrides: AppConfig => AppConfig = identity,
                            )
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxBIOBase.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxBIOBase.scala
index e2330e5af..60ea08cd9 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxBIOBase.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxBIOBase.scala
@@ -6,32 +6,24 @@ import izumi.distage.testkit.services.DISyntaxBIOBase.BIOBadBranch
 import izumi.functional.bio.BIOError
 import izumi.fundamentals.platform.language.CodePosition
 
-trait DISyntaxBIOBase[F[+_, +_]] {
-
+trait DISyntaxBIOBase[F[+_, +_]] extends DISyntaxBase[F[Throwable, ?]] {
   implicit def tagBIO: TagKK[F]
 
-  protected def takeAs1(fAsThrowable: ProviderMagnet[F[Throwable, _]], pos: CodePosition): Unit
-
-  protected final def take2(function: ProviderMagnet[F[_, _]], pos: CodePosition): Unit = {
+  protected final def takeBIO(function: ProviderMagnet[F[_, _]], pos: CodePosition): Unit = {
     val fAsThrowable: ProviderMagnet[F[Throwable, _]] = function
-      .zip(ProviderMagnet.identity[BIOError[F]])
-      .map[F[Throwable, _]] {
-        case (effect, bio) =>
-          bio.leftMap(effect) {
+      .map2(ProviderMagnet.identity[BIOError[F]]) {
+        (effect, F) =>
+          F.leftMap(effect) {
             case t: Throwable => t
             case otherError: Any => BIOBadBranch(otherError)
           }
       }
 
-    takeAs1(fAsThrowable, pos)
+    takeIO(fAsThrowable, pos)
   }
 
-  protected final def take2[T: Tag](function: T => F[_, _], pos: CodePosition): Unit = {
-    val providerMagnet: ProviderMagnet[F[_, _]] = {
-      x: T =>
-        function(x)
-    }
-    take2(providerMagnet, pos)
+  protected final def takeFunBIO[T: Tag](function: T => F[_, _], pos: CodePosition): Unit = {
+    takeBIO(function, pos)
   }
 
 }
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxBase.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxBase.scala
index 64d11a287..59676856c 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxBase.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/DISyntaxBase.scala
@@ -11,32 +11,20 @@ trait DISyntaxBase[F[_]] {
   protected def takeIO(function: ProviderMagnet[F[_]], pos: CodePosition): Unit
 
   protected final def takeAny(function: ProviderMagnet[Any], pos: CodePosition): Unit = {
-    val providerMagnet: ProviderMagnet[DIEffect[F]] = {
-      eff: DIEffect[F] =>
-        eff
-    }
-
-    val f: ProviderMagnet[F[Any]] = function.zip(providerMagnet).map {
-      case (u, e) =>
-        e.pure(u)
+    val f: ProviderMagnet[F[Any]] = function.flatAp {
+      F: DIEffect[F] =>
+        (a: Any) =>
+          F.pure(a)
     }
 
     takeIO(f, pos)
   }
 
   protected final def takeFunIO[T: Tag](function: T => F[_], pos: CodePosition): Unit = {
-    val providerMagnet: ProviderMagnet[F[_]] = {
-      x: T =>
-        function(x)
-    }
-    takeIO(providerMagnet, pos)
+    takeIO(function, pos)
   }
 
   protected final def takeFunAny[T: Tag](function: T => Any, pos: CodePosition): Unit = {
-    val providerMagnet: ProviderMagnet[Any] = {
-      x: T =>
-        function(x)
-    }
-    takeAny(providerMagnet, pos)
+    takeAny(function, pos)
   }
 }
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
deleted file mode 100644
index d7575df0e..000000000
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DebugProperties.scala
+++ /dev/null
@@ -1,13 +0,0 @@
-package izumi.distage.testkit.services.dstest
-
-import izumi.fundamentals.platform.logging
-
-/**
-  * Java properties that control caching of distage-testkit classpath scans (when used)
-  *
-  * @see [[logging.DebugProperties]]
-  */
-object DebugProperties extends logging.DebugProperties {
-  /** Cache created [[TestEnvironment]] */
-  final val `izumi.distage.testkit.environment.cache` = "izumi.distage.testkit.environment.cache"
-}
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
index b86da88e1..9b9a909d3 100644
--- 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
@@ -7,7 +7,7 @@ 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.distage.testkit.{DebugProperties, TestConfig}
 import izumi.fundamentals.platform.cache.SyncCache
 import izumi.fundamentals.platform.language.unused
 import izumi.logstage.api.IzLogger
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 ecaa639fe..5b2026ce3 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
@@ -14,7 +14,9 @@ import izumi.distage.model.effect.{DIEffect, DIEffectAsync, DIEffectRunner}
 import izumi.distage.model.exceptions.ProvisioningException
 import izumi.distage.model.plan.{OrderedPlan, TriSplittedPlan}
 import izumi.distage.model.providers.ProviderMagnet
+import izumi.distage.testkit.DebugProperties
 import izumi.distage.testkit.services.dstest.DistageTestRunner.{DistageTest, SuiteData, TestReporter, TestStatus}
+import izumi.fundamentals.platform.console.TrivialLogger
 import izumi.fundamentals.platform.functional.Identity
 import izumi.fundamentals.platform.integration.ResourceCheck
 import izumi.fundamentals.platform.language.CodePosition
@@ -35,7 +37,7 @@ class DistageTestRunner[F[_] : TagK]
     val groups = tests.groupBy(_.environment)
 
     val logger = runnerEnvironment.makeLogger()
-    val options = runnerEnvironment.contextOptions
+    val options = runnerEnvironment.planningOptions
     val loader = runnerEnvironment.makeConfigLoader(logger)
 
     val config = loader.buildConfig()
@@ -78,21 +80,28 @@ class DistageTestRunner[F[_] : TagK]
 
         logger.info(s"Memoized components in env: $sharedKeys")
 
-        val (strengthenKeys, strengthen) = appModule.drop(runtimeGcRoots).foldLeftWith(List.empty[DIKey]) {
+        val (strengthenedKeys, strengthenedAppModule) = appModule.drop(runtimeGcRoots).foldLeftWith(List.empty[DIKey]) {
           case (acc, b@SetElementBinding(key, r: ImplDef.ReferenceImpl, _, _)) if r.weak && wholeEnvKeys(key) =>
             (key :: acc) -> b.copy(implementation = r.copy(weak = false))
           case (acc, b) =>
             acc -> b
         }
 
-        logger.info(s"Strengthen weak components in env: $strengthenKeys")
+        logger.info(s"Strengthened weak components in env: $strengthenedKeys")
 
-        val shared = injector.trisectByKeys(strengthen, sharedKeys) {
+        val shared = injector.trisectByKeys(strengthenedAppModule, sharedKeys) {
           _.collectChildren[IntegrationCheck].map(_.target).toSet
         }
 
         checker.verify(runtimePlan)
 
+        debugLogger.log(
+          s"""Tests in env: ${tests.size}
+             |Integration plan: ${shared.side}
+             |Memoized plan: ${shared.shared}
+             |App plan: ${shared.primary}
+             |""".stripMargin)
+
         // first we produce our Monad's runtime
         injector.produceF[Identity](runtimePlan).use {
           runtimeLocator =>
@@ -173,7 +182,7 @@ class DistageTestRunner[F[_] : TagK]
                   val integrations = testplan.collectChildren[IntegrationCheck].map(_.target).toSet -- allSharedKeys
                   val newtestplan = testInjector.trisectByRoots(appmodule.drop(allSharedKeys), testplan.keys -- allSharedKeys, integrations)
 
-                  println(s"Test Id $id")
+                  debugLogger.log(s"Running, test Id=$id")
 
                   checker.verify(newtestplan.primary)
                   checker.verify(newtestplan.side)
@@ -253,6 +262,7 @@ class DistageTestRunner[F[_] : TagK]
     }
   }
 
+  lazy val debugLogger: TrivialLogger = TrivialLogger.make[DistageTestRunner[F]](DebugProperties.`izumi.distage.testkit.debug`)
 }
 
 object DistageTestRunner {
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/SpecEnvironment.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/SpecEnvironment.scala
index a5f671b0c..f035456fb 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/SpecEnvironment.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/SpecEnvironment.scala
@@ -18,7 +18,7 @@ trait SpecEnvironment {
   def bootstrapLogLevel: Log.Level
   def makeLogger(): IzLogger
 
-  def contextOptions: PlanningOptions
+  def planningOptions: PlanningOptions
 
   def configOverrides: AppConfig => AppConfig
   def makeConfigLoader(logger: IzLogger): ConfigLoader
@@ -30,7 +30,7 @@ object SpecEnvironment {
   class Impl[F[_]: TagK]
   (
     suiteClass: Class[_],
-    override val contextOptions: PlanningOptions,
+    override val planningOptions: PlanningOptions,
     override val bootstrapOverrides: BootstrapModule,
     override val moduleOverrides: ModuleBase,
     override val bootstrapLogLevel: Log.Level,
@@ -51,7 +51,7 @@ object SpecEnvironment {
         s"$classname-test" -> None,
         "application" -> None,
       )
-      new ConfigLoader.LocalFSImpl(logger, None, moreConfigs) map configOverrides
+      new ConfigLoader.LocalFSImpl(logger, None, moreConfigs).map(configOverrides)
     }
 
     override def makeModuleProvider(options: PlanningOptions, config: AppConfig, lateLogger: IzLogger, roles: RolesInfo, activationInfo: ActivationInfo, activation: Activation): ModuleProvider = {
diff --git a/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/DISyntaxBIO.scala b/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/DISyntaxBIO.scala
index 77cde8ca0..b842ae192 100644
--- a/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/DISyntaxBIO.scala
+++ b/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/DISyntaxBIO.scala
@@ -1,22 +1,19 @@
 package izumi.distage.testkit.services.scalatest.adapter
 
+import distage.Tag
 import izumi.distage.model.providers.ProviderMagnet
 import izumi.distage.testkit.services.DISyntaxBIOBase
-import distage.Tag
-import izumi.fundamentals.platform.language.{CodePosition, CodePositionMaterializer}
+import izumi.fundamentals.platform.language.CodePositionMaterializer
 
 @deprecated("Use dstest", "2019/Jul/18")
 trait DISyntaxBIO[F[+ _, + _]] extends DISyntaxBIOBase[F] {
-  this: DISyntax[F[Throwable, ?]] =>
-
-  override protected def takeAs1(fAsThrowable: ProviderMagnet[F[Throwable, _]], pos: CodePosition): Unit = dio(fAsThrowable)
 
   final def dio(function: ProviderMagnet[F[_, _]])(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
-    take2(function, pos.get)
+    takeBIO(function, pos.get)
   }
 
   final def dio[T: Tag](function: T => F[_, _])(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
-    take2(function, pos.get)
+    takeFunBIO(function, pos.get)
   }
 
 }
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..b05e8f055 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
@@ -4,5 +4,6 @@ import distage.TagK
 import izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec
 import org.scalatest.DistageScalatestTestSuiteRunner
 
-abstract class DistageSpecScalatest[F[_]]()(implicit val tagMonoIO: TagK[F])
-  extends DistageScalatestTestSuiteRunner[F] with DistageAbstractScalatestSpec[F]
+abstract class DistageSpecScalatest[F[_]](implicit override val tagMonoIO: TagK[F])
+  extends DistageScalatestTestSuiteRunner[F]
+    with DistageAbstractScalatestSpec[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 ec4e2b4ab..9ebca9da1 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
@@ -73,7 +73,7 @@ object DistageAbstractScalatestSpec {
                                        reg: TestRegistration[F],
                                        env: TestEnvironment,
                                      )(
-                                       implicit override val tagMonoIO: TagK[F]
+                                       implicit override val tagMonoIO: TagK[F],
                                      ) extends DISyntaxBase[F] {
 
     override protected def takeIO(function: ProviderMagnet[F[_]], pos: CodePosition): Unit = {
@@ -90,34 +90,48 @@ object DistageAbstractScalatestSpec {
       takeIO(function, pos.get)
     }
 
-    def in[T: Tag](function: T => F[_])(implicit pos: CodePositionMaterializer): Unit = {
-      takeFunIO(function, pos.get)
-    }
-
     def in(function: ProviderMagnet[_])(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
       takeAny(function, pos.get)
     }
 
+    def in[T: Tag](function: T => F[_])(implicit pos: CodePositionMaterializer): Unit = {
+      takeFunIO(function, pos.get)
+    }
+
     def in[T: Tag](function: T => _)(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
       takeFunAny(function, pos.get)
     }
 
+    def in(value: => F[_])(implicit pos: CodePositionMaterializer): Unit = {
+      takeIO(() => value, pos.get)
+    }
+
+    def in(value: => Any)(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
+      takeAny(() => value, pos.get)
+    }
+
     def skip(@unused function: ProviderMagnet[F[_]])(implicit pos: CodePositionMaterializer): Unit = {
-      takeIO((eff: DIEffect[F]) => cancel(eff), pos.get)
+      takeFunIO(cancel, pos.get)
+    }
+
+    def skip(@unused function: ProviderMagnet[_])(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
+      takeFunIO(cancel, pos.get)
     }
 
     def skip[T: Tag](@unused function: T => F[_])(implicit pos: CodePositionMaterializer): Unit = {
-      takeIO((eff: DIEffect[F]) => cancel(eff), pos.get)
+      takeFunIO(cancel, pos.get)
     }
 
-    def skip(function: ProviderMagnet[_])(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
-      Quirks.discard(function)
-      takeIO((eff: DIEffect[F]) => cancel(eff), pos.get)
+    def skip[T: Tag](@unused function: T => _)(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
+      takeFunIO(cancel, pos.get)
     }
 
-    def skip[T: Tag](function: T => _)(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
-      Quirks.discard(function)
-      takeFunAny((_: T) => cancelNow(), pos.get)
+    def skip(@unused value: => F[_])(implicit pos: CodePositionMaterializer): Unit = {
+      takeFunIO(cancel, pos.get)
+    }
+
+    def skip(@unused value: => Any)(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
+      takeFunIO(cancel, pos.get)
     }
 
     private def cancel(eff: DIEffect[F]): F[Nothing] = {
@@ -129,18 +143,19 @@ object DistageAbstractScalatestSpec {
     }
   }
 
-  class DSWordSpecStringWrapper2[F[+ _, + _]](
-                                               context: Option[SuiteContext],
-                                               suiteName: String,
-                                               suiteId: String,
-                                               testname: String,
-                                               reg: TestRegistration[F[Throwable, ?]],
-                                               env: TestEnvironment,
-                                             )(
-                                               implicit override val tagBIO: TagKK[F],
-                                             ) extends DISyntaxBIOBase[F] {
-
-    override protected def takeAs1(fAsThrowable: ProviderMagnet[F[Throwable, _]], pos: CodePosition): Unit = {
+  class DSWordSpecStringWrapper2[F[+_, +_]](
+                                             context: Option[SuiteContext],
+                                             suiteName: String,
+                                             suiteId: String,
+                                             testname: String,
+                                             reg: TestRegistration[F[Throwable, ?]],
+                                             env: TestEnvironment,
+                                           )(
+                                             implicit override val tagBIO: TagKK[F],
+                                           ) extends DISyntaxBIOBase[F] {
+    override val tagMonoIO: TagK[F[Throwable, ?]] = TagK[F[Throwable, ?]]
+
+    override protected def takeIO(fAsThrowable: ProviderMagnet[F[Throwable, _]], pos: CodePosition): Unit = {
       val id = TestId(
         context.map(_.toName(testname)).getOrElse(testname),
         suiteName,
@@ -151,23 +166,55 @@ object DistageAbstractScalatestSpec {
     }
 
     def in(function: ProviderMagnet[F[_, _]])(implicit pos: CodePositionMaterializer): Unit = {
-      take2(function, pos.get)
+      takeBIO(function, pos.get)
+    }
+
+    def in(function: ProviderMagnet[_])(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
+      takeAny(function, pos.get)
     }
 
     def in[T: Tag](function: T => F[_, _])(implicit pos: CodePositionMaterializer): Unit = {
-      take2(function, pos.get)
+      takeFunBIO(function, pos.get)
+    }
+
+    def in[T: Tag](function: T => _)(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
+      takeFunAny(function, pos.get)
+    }
+
+    def in(value: => F[_, _])(implicit pos: CodePositionMaterializer): Unit = {
+      takeBIO(() => value, pos.get)
+    }
+
+    def in(value: => Any)(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
+      takeAny(() => value, pos.get)
     }
 
     def skip(@unused function: ProviderMagnet[F[_, _]])(implicit pos: CodePositionMaterializer): Unit = {
-      takeAs1((eff: DIEffect[F[Throwable, ?]]) => cancel(eff), pos.get)
+      takeFunIO(cancel, pos.get)
+    }
+
+    def skip(@unused function: ProviderMagnet[_])(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
+      takeFunIO(cancel, pos.get)
     }
 
     def skip[T: Tag](@unused function: T => F[_, _])(implicit pos: CodePositionMaterializer): Unit = {
-      takeAs1((eff: DIEffect[F[Throwable, ?]]) => cancel(eff), pos.get)
+      takeFunIO(cancel, pos.get)
     }
 
-    private def cancel(eff: DIEffect[F[Throwable, ?]]): F[Throwable, Nothing] = {
-      eff.maybeSuspend(cancelNow())
+    def skip[T: Tag](@unused function: T => _)(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
+      takeFunIO(cancel, pos.get)
+    }
+
+    def skip(@unused value: => F[_, _])(implicit pos: CodePositionMaterializer): Unit = {
+      takeFunIO(cancel, pos.get)
+    }
+
+    def skip(@unused value: => Any)(implicit pos: CodePositionMaterializer, dummyImplicit: DummyImplicit): Unit = {
+      takeFunIO(cancel, pos.get)
+    }
+
+    private def cancel(F: DIEffect[F[Throwable, ?]]): F[Throwable, Nothing] = {
+      F.maybeSuspend(cancelNow())
     }
 
     private def cancelNow(): Nothing = {
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 75f9a3ab9..dcb850b85 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
@@ -12,11 +12,12 @@ import scala.collection.mutable
 
 object DistageTestsRegistrySingleton {
   protected[this] type Fake[T]
+  private[this] object Fake
   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, java.lang.Boolean]()
-  private[this] val knownSuites = new ConcurrentHashMap[(SafeType, String), java.lang.Boolean]()
+  private[this] val runTracker = new ConcurrentHashMap[SafeType, Fake.type]()
+  private[this] val knownSuites = new ConcurrentHashMap[(SafeType, String), Fake.type]()
   private[this] val registrationOpen = new AtomicBoolean(true)
 
   def disableRegistration(): Unit = {
@@ -25,7 +26,7 @@ object DistageTestsRegistrySingleton {
 
   def registerSuite[F[_]: TagK](suiteId: String): Boolean = synchronized {
     val tpe = SafeType.getK[F]
-    knownSuites.putIfAbsent((tpe, suiteId), true) eq null
+    knownSuites.putIfAbsent((tpe, suiteId), Fake) eq null
   }
 
   def register[F[_]: TagK](t: DistageTest[F]): Unit = synchronized {
@@ -36,14 +37,14 @@ object DistageTestsRegistrySingleton {
 
   def proceedWithTests[F[_]: TagK](): Option[Seq[DistageTest[F]]] = {
     val tpe = SafeType.getK[F]
-    if (runTracker.putIfAbsent(tpe, true) eq null) {
-      Some(list[F])
+    if (runTracker.putIfAbsent(tpe, Fake) eq null) {
+      Some(registeredTests[F])
     } else {
       None
     }
   }
 
-  def list[F[_]: TagK]: Seq[DistageTest[F]] = synchronized {
+  def registeredTests[F[_]: TagK]: Seq[DistageTest[F]] = synchronized {
     registry.getOrElseUpdate(SafeType.getK[F], mutable.ArrayBuffer.empty).map(_.asInstanceOf[DistageTest[F]]).toSeq
   }
 
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 111863618..c9973f703 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
@@ -5,10 +5,11 @@ import java.util.concurrent.atomic.AtomicBoolean
 import distage.TagK
 import io.github.classgraph.ClassGraph
 import izumi.distage.framework.services.IntegrationChecker
-import izumi.distage.testkit.SpecConfig
+import izumi.distage.testkit.{DebugProperties, SpecConfig}
 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.fundamentals.platform.console.TrivialLogger
 import izumi.fundamentals.platform.functional.Identity
 import izumi.logstage.api.{IzLogger, Log}
 import org.scalatest.exceptions.TestCanceledException
@@ -21,7 +22,7 @@ trait ScalatestInitWorkaround {
 }
 
 object ScalatestInitWorkaround {
-  def apply[F[_]](runner: DistageScalatestTestSuiteRunner[F]): ScalatestInitWorkaround = {
+  def scan[F[_]](runner: DistageScalatestTestSuiteRunner[F]): ScalatestInitWorkaround = {
     ScalatestInitWorkaroundImpl.doScan(runner)
 
     new ScalatestInitWorkaround {
@@ -65,22 +66,26 @@ object ScalatestInitWorkaround {
 }
 
 trait DistageScalatestTestSuiteRunner[F[_]] extends Suite with AbstractDistageSpec[F] {
-  protected[scalatest] val init = ScalatestInitWorkaround[F](this)
+  protected def specConfig: SpecConfig = SpecConfig()
 
   implicit def tagMonoIO: TagK[F]
+
+  // initialize status early, so that runner can set it to `true` even before this test is discovered
+  // by scalatest, if it was already executed by that time
+  private[this] val status: StatefulStatus = DistageTestsRegistrySingleton.registerStatus(suiteId)
+
   private[this] lazy val specEnv: SpecEnvironment = makeSpecEnvironment()
-  protected def specConfig: SpecConfig = SpecConfig()
   protected def makeSpecEnvironment(): SpecEnvironment = {
     val c = specConfig
     val clazz = this.getClass
 
     new SpecEnvironment.Impl[F](
       suiteClass = clazz,
-      contextOptions = c.contextOptions,
+      planningOptions = c.planningOptions,
       bootstrapOverrides = c.bootstrapOverrides,
       moduleOverrides = c.moduleOverrides,
       bootstrapLogLevel = c.bootstrapLogLevel,
-      configOverrides = c.configOverrides
+      configOverrides = c.configOverrides,
     )
   }
 
@@ -88,12 +93,20 @@ trait DistageScalatestTestSuiteRunner[F[_]] extends Suite with AbstractDistageSp
   override protected final def runTests(testName: Option[String], args: Args): Status = throw new UnsupportedOperationException
   override protected final def runTest(testName: String, args: Args): Status = throw new UnsupportedOperationException
 
-  // initialize status early, so that runner can set it to `true`
-  private[this] val status: StatefulStatus = DistageTestsRegistrySingleton.registerStatus(suiteId)
-
   override def run(testName: Option[String], args: Args): Status = {
     DistageTestsRegistrySingleton.registerTracker(suiteId)(args.tracker)
-    init.awaitTestsLoaded()
+    // If, we're running under sbt, scan the classpath manually to add all tests
+    // in neighboring packages before starting anything, because sbt runner
+    // instantiates & runs tests at the same time, so when `run` is called
+    // NOT all tests have been registered, so we must force all tests, otherwise
+    // we can't be sure.
+    //
+    // NON-sbt ScalatestRunner first instantiates ALL tests, THEN calls `.run` method,
+    // so for non-sbt runs we KNOW that all tests have already been registered, so we
+    // don't have to scan the classpath ourselves.
+    if (args.reporter.getClass.getName.contains("org.scalatest.tools.Framework")) {
+      ScalatestInitWorkaround.scan(this).awaitTestsLoaded()
+    }
 
     try {
       DistageTestsRegistrySingleton.proceedWithTests[F]() match {
@@ -115,7 +128,7 @@ trait DistageScalatestTestSuiteRunner[F[_]] extends Suite with AbstractDistageSp
   }
 
   override def testNames: Set[String] = {
-    val testsInThisTestClass = DistageTestsRegistrySingleton.list[F].filter(_.meta.id.suiteId == suiteId)
+    val testsInThisTestClass = DistageTestsRegistrySingleton.registeredTests[F].filter(_.meta.id.suiteId == suiteId)
     TreeSet[String](testsInThisTestClass.map(_.meta.id.name): _*)
   }
 
@@ -151,8 +164,8 @@ trait DistageScalatestTestSuiteRunner[F[_]] extends Suite with AbstractDistageSp
   }
 
   private def doRun(candidatesForThisRuntime: Seq[DistageTest[F]], testName: Option[String], args: Args): Unit = {
-    println(args)
-    println(
+    debugLogger.log(s"Scalatest Args: $args")
+    debugLogger.log(
       s"""tagsToInclude: ${args.filter.tagsToInclude}
          |tagsToExclude: ${args.filter.tagsToExclude}
          |dynaTags: ${args.filter.dynaTags}
@@ -181,7 +194,7 @@ trait DistageScalatestTestSuiteRunner[F[_]] extends Suite with AbstractDistageSp
     if (toRun.isEmpty) {
       return
     } else {
-      println(s"TORUN: ${toRun.map(_.meta.id.name)}")
+      debugLogger.log(s"GOING TO RUN TESTS in ${tagMonoIO.tag}: ${toRun.map(_.meta.id.name)}")
     }
 
     val runner = {
@@ -197,7 +210,6 @@ trait DistageScalatestTestSuiteRunner[F[_]] extends Suite with AbstractDistageSp
     }
   }
 
-  // FIXME: read scalatest runner configuration???
   protected def parallelTestsAlways: Boolean = true
 
   private def mkTestReporter(args: Args): TestReporter = {
@@ -206,7 +218,6 @@ trait DistageScalatestTestSuiteRunner[F[_]] extends Suite with AbstractDistageSp
   }
 
   override final val styleName: String = "DistageSuite"
-}
-
-
 
+  lazy val debugLogger: TrivialLogger = TrivialLogger.make[DistageScalatestTestSuiteRunner[F]](DebugProperties.`izumi.distage.testkit.debug`)
+}