Repository: 7mind/izumi
Pull request #1080 (2020-05-24 19:38:49)

distage-testkit-scalatest: Add ability to alter test discovery configuration in SBT runner


Patch:
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ActivationInfoExtractor.scala b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ActivationInfoExtractor.scala
index 5c9d14d4b..8b080d7fd 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ActivationInfoExtractor.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ActivationInfoExtractor.scala
@@ -5,25 +5,23 @@ import izumi.distage.framework.model.ActivationInfo
 import izumi.distage.model.definition.{Axis, BindingTag}
 import izumi.distage.roles.model.exceptions.DIAppBootstrapException
 import izumi.fundamentals.platform.strings.IzString._
-import izumi.logstage.api.IzLogger
 
 object ActivationInfoExtractor {
-  def findAvailableChoices(logger: IzLogger, module: ModuleBase): ActivationInfo = {
-    findAvailableChoices(module) match {
+  def findAvailableChoices(module: ModuleBase): ActivationInfo = {
+    findAvailableChoicesDetailed(module) match {
       case Left(badAxis) =>
         val conflicts = badAxis.map {
           case (name, axes) =>
-            s"$name: ${axes.niceList().shift(2)}"
+            s"$name: ${axes.map(a => a.getClass -> a.hashCode()).niceList().shift(2)}"
         }
-        logger.crit(s"Conflicting axis ${conflicts.niceList() -> "names"}")
-        throw new DIAppBootstrapException(s"Conflicting axis: $conflicts")
+        throw new DIAppBootstrapException(s"Conflicting axis names: ${conflicts.niceList()}")
 
       case Right(value) => value
     }
   }
 
   /** @return Either conflicting axis names or a set of axis choices present in the bindings */
-  def findAvailableChoices(module: ModuleBase): Either[Map[String, Set[Axis]], ActivationInfo] = {
+  def findAvailableChoicesDetailed(module: ModuleBase): Either[Map[String, Set[Axis]], ActivationInfo] = {
     val allChoices = module.bindings.flatMap(_.tags).collect { case BindingTag.AxisTag(choice) => choice }
     val allAxis = allChoices.map(_.axis).groupBy(_.name)
     val badAxis = allAxis.filter(_._2.size > 1)
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala b/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala
index ee537f6db..db7471346 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala
@@ -218,8 +218,8 @@ abstract class RoleAppLauncherImpl[F[_]: TagK] extends RoleAppLauncher {
     (maybeGlobalConfig, roleConfigs.toMap)
   }
 
-  protected def parseActivationInfo(lateLogger: IzLogger, appModule: ModuleBase): ActivationInfo = {
-    ActivationInfoExtractor.findAvailableChoices(lateLogger, appModule)
+  protected def parseActivationInfo(@unused lateLogger: IzLogger, appModule: ModuleBase): ActivationInfo = {
+    ActivationInfoExtractor.findAvailableChoices(appModule)
   }
 
   /** Note, besides overriding this method, activation parsing strategy can also be changed by using bootstrap modules or plugins
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/staticinjector/plugins/macros/StaticPluginCheckerMacro.scala b/distage/distage-framework/src/main/scala/izumi/distage/staticinjector/plugins/macros/StaticPluginCheckerMacro.scala
index e9023426d..637583774 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/staticinjector/plugins/macros/StaticPluginCheckerMacro.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/staticinjector/plugins/macros/StaticPluginCheckerMacro.scala
@@ -139,7 +139,7 @@ object StaticPluginCheckerMacro {
     val logger = IzLogger.NullLogger
 
     val activation = {
-      val activationInfo = ActivationInfoExtractor.findAvailableChoices(logger, module)
+      val activationInfo = ActivationInfoExtractor.findAvailableChoices(module)
       new RoleAppActivationParser.Impl(logger).parseActivation(activations.map(_.split2(':')), activationInfo)
     }
 


Test patch:
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 90982b4a8..51cc6ea0e 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
@@ -11,16 +11,14 @@ import izumi.distage.plugins.merge.{PluginMergeStrategy, SimplePluginMergeStrate
 import izumi.distage.roles.model.meta.RolesInfo
 import izumi.distage.testkit.{DebugProperties, TestConfig}
 import izumi.fundamentals.platform.cache.SyncCache
-import izumi.fundamentals.platform.language.unused
-import izumi.logstage.api.IzLogger
 
 trait DistageTestEnv {
-  private[distage] def loadEnvironment(logger: IzLogger, testConfig: TestConfig): TestEnvironment = {
-    val roles = loadRoles(logger)
-    val mergeStrategy = makeMergeStrategy(logger)
-    val pluginLoader = makePluginloader(logger)
+  private[distage] def loadEnvironment(testConfig: TestConfig): TestEnvironment = {
+    val roles = loadRoles()
+    val mergeStrategy = makeMergeStrategy()
+    val pluginLoader = makePluginloader()
     def doMake(): TestEnvironment = {
-      makeEnv(logger, testConfig, pluginLoader, roles, mergeStrategy)
+      makeEnv(testConfig, pluginLoader, roles, mergeStrategy)
     }
 
     if (DebugProperties.`izumi.distage.testkit.environment.cache`.asBoolean(true)) {
@@ -31,7 +29,6 @@ trait DistageTestEnv {
   }
 
   private[distage] def makeEnv(
-    logger: IzLogger,
     testConfig: TestConfig,
     pluginLoader: PluginLoader,
     roles: RolesInfo,
@@ -41,7 +38,7 @@ trait DistageTestEnv {
     val bsPlugins = pluginLoader.load(testConfig.bootstrapPluginConfig)
     val appModule = mergeStrategy.merge(appPlugins) overridenBy testConfig.moduleOverrides
     val bootstrapModule = mergeStrategy.merge(bsPlugins) overridenBy testConfig.bootstrapOverrides
-    val availableActivations = ActivationInfoExtractor.findAvailableChoices(logger, appModule)
+    val availableActivations = ActivationInfoExtractor.findAvailableChoices(appModule)
     val activation = testConfig.activation
 
     val bsModule = bootstrapModule overridenBy new BootstrapModuleDef {
@@ -70,23 +67,23 @@ trait DistageTestEnv {
     )
   }
 
-  protected def loadRoles(@unused logger: IzLogger): RolesInfo = {
+  protected def loadRoles(): 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 logger: IzLogger): PluginMergeStrategy = {
+  protected def makeMergeStrategy(): PluginMergeStrategy = {
     SimplePluginMergeStrategy
   }
 
-  protected def makePluginloader(@unused logger: IzLogger): PluginLoader = {
+  protected def makePluginloader(): PluginLoader = {
     new PluginLoaderDefaultImpl()
   }
 
 }
 
 object DistageTestEnv {
-  private[distage] lazy val cache = new SyncCache[EnvCacheKey, TestEnvironment]
+  private[distage] val cache = new SyncCache[EnvCacheKey, TestEnvironment]
 
   final case class EnvCacheKey(config: TestConfig, rolesInfo: RolesInfo, mergeStrategy: PluginMergeStrategy)
 }
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 7ccede03a..4b7f0d242 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
@@ -12,7 +12,6 @@ import izumi.distage.testkit.services.{DISyntaxBIOBase, DISyntaxBase}
 import izumi.functional.bio.BIOLocal
 import izumi.fundamentals.platform.language.{CodePosition, CodePositionMaterializer, unused}
 import izumi.reflect.TagK3
-import izumi.logstage.api.{IzLogger, Log}
 import org.scalactic.source
 import org.scalatest.Assertion
 import org.scalatest.distage.TestCancellation
@@ -31,40 +30,34 @@ trait WithSingletonTestRegistration[F[_]] extends AbstractDistageSpec[F] {
 }
 
 @org.scalatest.Finders(value = Array("org.scalatest.finders.WordSpecFinder"))
-trait DistageAbstractScalatestSpec[F[_]]
-  extends ShouldVerb with MustVerb with CanVerb
-    with DistageTestEnv
-    with WithSingletonTestRegistration[F] {
+trait DistageAbstractScalatestSpec[F[_]] extends ShouldVerb with MustVerb with CanVerb with DistageTestEnv with WithSingletonTestRegistration[F] {
   this: AbstractDistageSpec[F] =>
 
-  final protected lazy val testEnv: TestEnvironment = makeTestEnv()
-
-  protected def testEnvLogger: IzLogger = IzLogger(Log.Level.Info)("phase" -> "loader")
-
   protected def config: TestConfig = TestConfig.forSuite(this.getClass)
-  protected def makeTestEnv(): TestEnvironment = loadEnvironment(testEnvLogger, config)
 
-  protected def distageSuiteName: String = getSimpleNameOfAnObjectsClass(this)
-  protected def distageSuiteId: String = this.getClass.getName
+  final protected[this] lazy val testEnv: TestEnvironment = makeTestEnv()
+  protected[this] def makeTestEnv(): TestEnvironment = loadEnvironment(config)
 
-  //
-  private[distage] var context: Option[SuiteContext] = None
+  protected[this] def distageSuiteName: String = getSimpleNameOfAnObjectsClass(this)
+  protected[this] def distageSuiteId: String = this.getClass.getName
 
-  implicit val subjectRegistrationFunction1: StringVerbBlockRegistration = new StringVerbBlockRegistration {
+  protected implicit val subjectRegistrationFunction1: StringVerbBlockRegistration = new StringVerbBlockRegistration {
     override def apply(left: String, verb: String, @unused pos: source.Position, f: () => Unit): Unit = {
       registerBranch(left, verb, f)
     }
   }
 
-  protected def registerBranch(description: String, verb: String, fun: () => Unit): Unit = {
-    this.context = Some(SuiteContext(description, verb))
-    fun()
-    this.context = None
-  }
-
   protected implicit def convertToWordSpecStringWrapperDS(s: String): DSWordSpecStringWrapper[F] = {
     new DSWordSpecStringWrapper(context, distageSuiteName, distageSuiteId, s, this, testEnv)
   }
+
+  protected[this] def registerBranch(description: String, verb: String, fun: () => Unit): Unit = {
+    context = Some(SuiteContext(description, verb))
+    fun()
+    context = None
+  }
+
+  private[distage] var context: Option[SuiteContext] = None
 }
 
 object DistageAbstractScalatestSpec {
@@ -105,15 +98,15 @@ object DistageAbstractScalatestSpec {
   }
 
   class DSWordSpecStringWrapper[F[_]](
-                                       context: Option[SuiteContext],
-                                       suiteName: String,
-                                       suiteId: String,
-                                       testname: String,
-                                       reg: TestRegistration[F],
-                                       env: TestEnvironment,
-                                     )(
-                                       implicit override val tagMonoIO: TagK[F],
-                                     ) extends DISyntaxBase[F] with LowPriorityIdentityOverloads[F] {
+    context: Option[SuiteContext],
+    suiteName: String,
+    suiteId: String,
+    testname: String,
+    reg: TestRegistration[F],
+    env: TestEnvironment,
+  )(implicit override val tagMonoIO: TagK[F]
+  ) extends DISyntaxBase[F]
+    with LowPriorityIdentityOverloads[F] {
 
     override protected def takeIO(function: ProviderMagnet[F[_]], pos: CodePosition): Unit = {
       val id = TestId(
@@ -143,16 +136,16 @@ 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],
-                                             implicit override val tagMonoIO: TagK[F[Throwable, ?]],
-                                           ) extends DISyntaxBIOBase[F] with LowPriorityIdentityOverloads[F[Throwable, ?]] {
+    context: Option[SuiteContext],
+    suiteName: String,
+    suiteId: String,
+    testname: String,
+    reg: TestRegistration[F[Throwable, ?]],
+    env: TestEnvironment,
+  )(implicit override val tagBIO: TagKK[F],
+    implicit override val tagMonoIO: TagK[F[Throwable, ?]],
+  ) extends DISyntaxBIOBase[F]
+    with LowPriorityIdentityOverloads[F[Throwable, ?]] {
 
     override protected def takeIO(fAsThrowable: ProviderMagnet[F[Throwable, _]], pos: CodePosition): Unit = {
       val id = TestId(
@@ -182,16 +175,16 @@ object DistageAbstractScalatestSpec {
   }
 
   class DSWordSpecStringWrapper3[F[-_, +_, +_]: TagK3](
-                                             context: Option[SuiteContext],
-                                             suiteName: String,
-                                             suiteId: String,
-                                             testname: String,
-                                             reg: TestRegistration[F[Any, Throwable, ?]],
-                                             env: TestEnvironment,
-                                           )(
-                                             implicit override val tagBIO: TagKK[F[Any, ?, ?]],
-                                             implicit override val tagMonoIO: TagK[F[Any, Throwable, ?]]
-                                           ) extends DISyntaxBIOBase[F[Any, +?, +?]] with LowPriorityIdentityOverloads[F[Any, Throwable, ?]] {
+    context: Option[SuiteContext],
+    suiteName: String,
+    suiteId: String,
+    testname: String,
+    reg: TestRegistration[F[Any, Throwable, ?]],
+    env: TestEnvironment,
+  )(implicit override val tagBIO: TagKK[F[Any, ?, ?]],
+    implicit override val tagMonoIO: TagK[F[Any, Throwable, ?]],
+  ) extends DISyntaxBIOBase[F[Any, +?, +?]]
+    with LowPriorityIdentityOverloads[F[Any, Throwable, ?]] {
 
     override protected def takeIO(fAsThrowable: ProviderMagnet[F[Any, Throwable, _]], pos: CodePosition): Unit = {
       val id = TestId(
@@ -204,15 +197,21 @@ object DistageAbstractScalatestSpec {
     }
 
     def in[R: HasConstructor](function: ProviderMagnet[F[R, _, Unit]])(implicit pos: CodePositionMaterializer): Unit = {
-      takeBIO(function.zip(HasConstructor[R]).map2(ProviderMagnet.identity[BIOLocal[F]]) {
-        case ((eff, r), f) => f.provide(eff)(r)
-      }, pos.get)
+      takeBIO(
+        function.zip(HasConstructor[R]).map2(ProviderMagnet.identity[BIOLocal[F]]) {
+          case ((eff, r), f) => f.provide(eff)(r)
+        },
+        pos.get,
+      )
     }
 
     def in[R: HasConstructor](function: ProviderMagnet[F[R, _, Assertion]])(implicit pos: CodePositionMaterializer, d1: DummyImplicit): Unit = {
-      takeBIO(function.zip(HasConstructor[R]).map2(ProviderMagnet.identity[BIOLocal[F]]) {
-        case ((eff, r), f) => f.provide(eff)(r)
-      }, pos.get)
+      takeBIO(
+        function.zip(HasConstructor[R]).map2(ProviderMagnet.identity[BIOLocal[F]]) {
+          case ((eff, r), f) => f.provide(eff)(r)
+        },
+        pos.get,
+      )
     }
 
     def in[R: HasConstructor](value: => F[R, _, Unit])(implicit pos: CodePositionMaterializer): Unit = {
@@ -240,10 +239,6 @@ object DistageAbstractScalatestSpec {
     }
   }
 
-//  abstract class StringVerbBlockRegistration {
-//    def apply(string: String, verb: String, pos: source.Position, block: () => Unit): Unit
-//  }
-
   def getSimpleNameOfAnObjectsClass(o: AnyRef): String = stripDollars(parseSimpleName(o.getClass.getName))
 
   // [bv: this is a good example of the expression type refactor. I moved this from SuiteClassNameListCellRenderer]
diff --git a/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/distage/DistageScalatestTestSuiteRunner.scala b/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/distage/DistageScalatestTestSuiteRunner.scala
index 659ab3cdc..65ae7d49a 100644
--- a/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/distage/DistageScalatestTestSuiteRunner.scala
+++ b/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/distage/DistageScalatestTestSuiteRunner.scala
@@ -71,6 +71,13 @@ object ScalatestInitWorkaround {
 
 abstract class DistageScalatestTestSuiteRunner[F[_]](implicit override val tagMonoIO: TagK[F]) extends TestSuite with AbstractDistageSpec[F] {
 
+  /** Modify test discovery options for SBT test runner only.
+    * Overriding this with [[withWhitelistJarsOnly]] will slightly boost test start-up speed,
+    * but will disable the ability to discover tests that inherit [[izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec]]
+    * indirectly through a different library JAR. (this does not affect local sbt modules) */
+  protected def modifyClasspathScan: ClassGraph => ClassGraph = identity
+  protected final def withWhitelistJarsOnly: ClassGraph => ClassGraph = _.whitelistJars("distage-testkit-scalatest*")
+
   // 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[F](suiteId)
@@ -194,5 +201,4 @@ abstract class DistageScalatestTestSuiteRunner[F[_]](implicit override val tagMo
     new SafeTestReporter(scalatestReporter)
   }
 
-  override final val styleName: String = "DistageSuite"
 }