From a93d5c2def84a9158808d15fe93f846b30887f26 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 3 Feb 2023 15:54:35 +0100 Subject: [PATCH] feat: Cross build for Scala 3 (#30) * also enabled compiler warnings * switched to commons-text since other was deprecated --- .github/workflows/build-test.yml | 57 ++++++------------- .../akka/diagnostics/ConfigChecker.scala | 25 ++++---- .../akka/diagnostics/StarvationDetector.scala | 37 ++++++------ .../scala/akka/diagnostics/AkkaSpec.scala | 32 +++++------ .../akka/diagnostics/ConfigCheckerSpec.scala | 6 +- .../diagnostics/StarvationDetectorSpec.scala | 25 ++++---- build.sbt | 26 ++++++++- project/Dependencies.scala | 12 ++-- 8 files changed, 117 insertions(+), 103 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 11d45f9..48460f1 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -27,47 +27,27 @@ jobs: - name: Cache Coursier cache uses: coursier/cache-action@v6.4.0 - - name: Set up JDK 17 - uses: coursier/setup-action@v1.3.0 - with: - jvm: temurin:1.17.0 - - - name: Code style check and binary-compatibility check - run: |- - cp .jvmopts-ci .jvmopts - sbt scalafmtCheckAll scalafmtSbtCheck headerCheck - compile-with-scala-2_12: - name: Compile with Scala 2.12 - runs-on: ubuntu-22.04 - if: github.repository == 'akka/akka-diagnostics' - steps: - - name: Checkout - uses: actions/checkout@v3.1.0 - with: - # we don't know what commit the last tag was it's safer to get entire repo so previousStableVersion resolves - fetch-depth: 0 - - name: Checkout GitHub merge - if: github.event.pull_request - run: |- - git fetch origin pull/${{ github.event.pull_request.number }}/merge:scratch - git checkout scratch - - name: Cache Coursier cache - uses: coursier/cache-action@v6.4.0 - - name: Set up JDK 8 uses: coursier/setup-action@v1.3.0 with: jvm: temurin:1.8.0 - - name: Code style check and binary-compatibility check + - name: Code style check, compilation and binary-compatibility check run: |- cp .jvmopts-ci .jvmopts - sbt ++2.12 compile Test/compile + sbt scalafmtCheckAll scalafmtSbtCheck headerCheck +Test/compile - test-diagnostics-11: - name: Run tests 11 + test: + name: Run tests runs-on: ubuntu-22.04 if: github.repository == 'akka/akka-diagnostics' + strategy: + fail-fast: false + matrix: + include: + - { jdkVersion: "1.8.0", jvmName: "temurin:1.8.0" } + - { jdkVersion: "1.11.0", jvmName: "temurin:1.11.0" } + - { jdkVersion: "1.17.0", jvmName: "temurin:1.17.0" } steps: - name: Checkout uses: actions/checkout@v3.1.0 @@ -82,18 +62,18 @@ jobs: - name: Cache Coursier cache uses: coursier/cache-action@v6.4.0 - - name: Set up JDK 11 + - name: Set up JDK ${{ matrix.jdkVersion }} uses: coursier/setup-action@v1.3.0 with: - jvm: temurin:1.11.0 + jvm: ${{ matrix.jvmName }} - name: sbt test run: |- cp .jvmopts-ci .jvmopts sbt test - test-diagnostics-17: - name: Run tests 17 + check-docs: + name: Check Docs runs-on: ubuntu-22.04 if: github.repository == 'akka/akka-diagnostics' steps: @@ -107,6 +87,7 @@ jobs: run: |- git fetch origin pull/${{ github.event.pull_request.number }}/merge:scratch git checkout scratch + - name: Cache Coursier cache uses: coursier/cache-action@v6.4.0 @@ -115,7 +96,5 @@ jobs: with: jvm: temurin:1.17.0 - - name: sbt test - run: |- - cp .jvmopts-ci .jvmopts - sbt test + - name: Create all API docs for artifacts/website and all reference docs + run: sbt "unidoc; docs/paradox" diff --git a/akka-diagnostics/src/main/scala/akka/diagnostics/ConfigChecker.scala b/akka-diagnostics/src/main/scala/akka/diagnostics/ConfigChecker.scala index 66a5d28..301687b 100644 --- a/akka-diagnostics/src/main/scala/akka/diagnostics/ConfigChecker.scala +++ b/akka-diagnostics/src/main/scala/akka/diagnostics/ConfigChecker.scala @@ -7,17 +7,21 @@ package akka.diagnostics import java.net.InetAddress import java.util.Locale import java.util.concurrent.TimeUnit.MILLISECONDS -import akka.actor.{ ActorSystem, ExtendedActorSystem } -import akka.dispatch.ThreadPoolConfig -import akka.event.Logging -import com.typesafe.config._ -import org.apache.commons.lang3.StringUtils import scala.collection.JavaConverters._ import scala.collection.immutable import scala.collection.immutable.VectorBuilder import scala.concurrent.duration._ -import scala.util.{ Failure, Success, Try } +import scala.util.Failure +import scala.util.Success +import scala.util.Try + +import akka.actor.ActorSystem +import akka.actor.ExtendedActorSystem +import akka.dispatch.ThreadPoolConfig +import akka.event.Logging +import com.typesafe.config._ +import org.apache.commons.text.similarity.LevenshteinDistance object ConfigChecker { @@ -221,12 +225,14 @@ class ConfigChecker(system: ExtendedActorSystem, config: Config, reference: Conf collectLeaves("akka", reference.getConfig("akka").root) } + private val maxSimilarDistance = 5 private val maxSimilarItems = 3 + private lazy val levenshteinDistance = new LevenshteinDistance(maxSimilarDistance) private def similar(name: String): Seq[String] = knownSettings .map { case (key, path) => - (key, path, StringUtils.getLevenshteinDistance(key, name, maxSimilarDistance)) + (key, path, levenshteinDistance.apply(key, name)) } .filter(_._3 >= 0) .sortBy(_._3) @@ -483,7 +489,7 @@ class ConfigChecker(system: ExtendedActorSystem, config: Config, reference: Conf checkProvider() ++ checkJvmExitOnFatalError() ++ checkDefaultDispatcherSize() ++ - checkInternalDispatcherSize ++ + checkInternalDispatcherSize() ++ checkDefaultDispatcherType() ++ checkDispatcherThroughput(defaultDispatcherPath, config.getConfig(defaultDispatcherPath)) } @@ -685,7 +691,7 @@ class ConfigChecker(system: ExtendedActorSystem, config: Config, reference: Conf checkCreateActorRemotely() ++ checkPreferClusterToRemote() ++ checkRemoteDispatcherSize() ++ - checkArteryNotEnabled + checkArteryNotEnabled() } else Vector.empty[ConfigWarning] private def checkRemoteDispatcher(): List[ConfigWarning] = @@ -1029,7 +1035,6 @@ class ConfigChecker(system: ExtendedActorSystem, config: Config, reference: Conf private def checkSplitBrainResolver(): List[ConfigWarning] = ifEnabled("split-brain-resolver") { checkerKey => - val downingProviderPath = "akka.cluster.downing-provider-class" val sbrStrategyPath = "akka.cluster.split-brain-resolver.active-strategy" val sbrActive = isClusterConfigAvailable && isSplitBrainResolverConfigAvailable && config.getString(sbrStrategyPath).toLowerCase(Locale.ROOT) != "off" diff --git a/akka-diagnostics/src/main/scala/akka/diagnostics/StarvationDetector.scala b/akka-diagnostics/src/main/scala/akka/diagnostics/StarvationDetector.scala index d8c33bd..2d9f32b 100644 --- a/akka-diagnostics/src/main/scala/akka/diagnostics/StarvationDetector.scala +++ b/akka-diagnostics/src/main/scala/akka/diagnostics/StarvationDetector.scala @@ -31,12 +31,12 @@ import scala.util.Try import scala.util.control.NoStackTrace import scala.util.control.NonFatal -abstract class StarvationDetectorSettings { _: StarvationDetectorSettings.StarvationDetectorSettingsImpl => - def checkInterval: FiniteDuration - def initialDelay: FiniteDuration - def maxDelayWarningThreshold: FiniteDuration - def warningInterval: FiniteDuration - def threadTraceLimit: Int +final class StarvationDetectorSettings( + val checkInterval: FiniteDuration, + val initialDelay: FiniteDuration, + val maxDelayWarningThreshold: FiniteDuration, + val warningInterval: FiniteDuration, + val threadTraceLimit: Int) { def withCheckInterval(newCheckInterval: FiniteDuration): StarvationDetectorSettings = copy(checkInterval = newCheckInterval) @@ -52,7 +52,21 @@ abstract class StarvationDetectorSettings { _: StarvationDetectorSettings.Starva copy(threadTraceLimit = Integer.MAX_VALUE) def isEnabled: Boolean = checkInterval > Duration.Zero + + private def copy( + checkInterval: FiniteDuration = checkInterval, + initialDelay: FiniteDuration = initialDelay, + maxDelayWarningThreshold: FiniteDuration = maxDelayWarningThreshold, + warningInterval: FiniteDuration = warningInterval, + threadTraceLimit: Int = threadTraceLimit): StarvationDetectorSettings = + new StarvationDetectorSettings( + checkInterval, + initialDelay, + maxDelayWarningThreshold, + warningInterval, + threadTraceLimit) } + object StarvationDetectorSettings { def apply( checkInterval: FiniteDuration, @@ -67,7 +81,7 @@ object StarvationDetectorSettings { maxDelayWarningThreshold: FiniteDuration, warningInterval: FiniteDuration, threadTraceLimit: Int): StarvationDetectorSettings = - StarvationDetectorSettingsImpl( + new StarvationDetectorSettings( checkInterval, initialDelay, maxDelayWarningThreshold, @@ -109,15 +123,6 @@ object StarvationDetectorSettings { }) } - /** INTERNAL API */ - @InternalApi - private[StarvationDetectorSettings] final case class StarvationDetectorSettingsImpl( - checkInterval: FiniteDuration, - initialDelay: FiniteDuration, - maxDelayWarningThreshold: FiniteDuration, - warningInterval: FiniteDuration, - threadTraceLimit: Int) - extends StarvationDetectorSettings {} } object StarvationDetector { diff --git a/akka-diagnostics/src/test/scala/akka/diagnostics/AkkaSpec.scala b/akka-diagnostics/src/test/scala/akka/diagnostics/AkkaSpec.scala index 338df97..645a45d 100644 --- a/akka-diagnostics/src/test/scala/akka/diagnostics/AkkaSpec.scala +++ b/akka-diagnostics/src/test/scala/akka/diagnostics/AkkaSpec.scala @@ -4,25 +4,25 @@ package akka.diagnostics +import java.lang.reflect.Modifier + +import scala.concurrent.Future +import scala.concurrent.duration._ + import akka.actor.ActorSystem import akka.dispatch.Dispatchers import akka.event.Logging import akka.event.LoggingAdapter -import akka.testkit.TestEvent.Mute import akka.testkit.DeadLettersFilter +import akka.testkit.TestEvent.Mute import akka.testkit.TestKit import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import org.scalactic.CanEqual -import org.scalatest.concurrent.ScalaFutures import org.scalatest.BeforeAndAfterAll -import org.scalatest.Matchers -import org.scalatest.WordSpecLike - -import java.lang.reflect.Modifier -import scala.concurrent.Future -import scala.concurrent.duration._ -import scala.language.postfixOps +import org.scalatest.concurrent.ScalaFutures +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike object AkkaSpec { val testConf: Config = ConfigFactory.parseString(""" @@ -96,12 +96,12 @@ object AkkaSpec { abstract class AkkaSpec(_system: ActorSystem) extends TestKit(_system) - with WordSpecLike + with AnyWordSpecLike with Matchers with BeforeAndAfterAll with ScalaFutures { - implicit val patience = PatienceConfig(testKitSettings.DefaultTimeout.duration) + implicit val patience: PatienceConfig = PatienceConfig(testKitSettings.DefaultTimeout.duration) def this(config: Config) = this( ActorSystem( @@ -114,7 +114,7 @@ abstract class AkkaSpec(_system: ActorSystem) def this() = this(ActorSystem(AkkaSpec.testNameFromCallStack(classOf[AkkaSpec]), AkkaSpec.testConf)) - val log: LoggingAdapter = Logging(system, this.getClass) + val log: LoggingAdapter = Logging(system, Logging.simpleName(this)) override val invokeBeforeAllAndAfterAllEvenIfNoTestsAreExpected = true @@ -130,16 +130,16 @@ abstract class AkkaSpec(_system: ActorSystem) // TODO not published stopCoroner() } - protected def atStartup() = {} + protected def atStartup(): Unit = {} - protected def beforeTermination() = {} + protected def beforeTermination(): Unit = {} - protected def afterTermination() = {} + protected def afterTermination(): Unit = {} def spawn(dispatcherId: String = Dispatchers.DefaultDispatcherId)(body: => Unit): Unit = Future(body)(system.dispatchers.lookup(dispatcherId)) - def expectedTestDuration: FiniteDuration = 60 seconds + def expectedTestDuration: FiniteDuration = 60.seconds def muteDeadLetters(messageClasses: Class[_]*)(sys: ActorSystem = system): Unit = if (!sys.log.isDebugEnabled) { diff --git a/akka-diagnostics/src/test/scala/akka/diagnostics/ConfigCheckerSpec.scala b/akka-diagnostics/src/test/scala/akka/diagnostics/ConfigCheckerSpec.scala index dacdd3e..19b5953 100644 --- a/akka-diagnostics/src/test/scala/akka/diagnostics/ConfigCheckerSpec.scala +++ b/akka-diagnostics/src/test/scala/akka/diagnostics/ConfigCheckerSpec.scala @@ -54,7 +54,7 @@ class ConfigCheckerSpec extends AkkaSpec { akka.diagnostics.checker.disabled-checks = [${allDisabledCheckerKeys.mkString(",")}] """) .withFallback(c) - new ConfigChecker(extSys, disabled, reference).check.warnings should ===(Nil) + new ConfigChecker(extSys, disabled, reference).check().warnings should ===(Nil) } def assertCheckerKey(warnings: immutable.Seq[ConfigWarning], expectedCheckerKeys: String*): Unit = @@ -269,12 +269,12 @@ class ConfigCheckerSpec extends AkkaSpec { "check internal-dispatcher as default-dispatcher is find" in { val c = ConfigFactory - .parseString(""" + .parseString(s""" |akka.actor.default-dispatcher = { | type = "Dispatcher" | # ... | } - |akka.actor.internal-dispatcher = ${akka.actor.default-dispatcher} """.stripMargin) + |akka.actor.internal-dispatcher = $${akka.actor.default-dispatcher} """.stripMargin) .resolve() .withFallback(reference) diff --git a/akka-diagnostics/src/test/scala/akka/diagnostics/StarvationDetectorSpec.scala b/akka-diagnostics/src/test/scala/akka/diagnostics/StarvationDetectorSpec.scala index 89c246f..e93039d 100644 --- a/akka-diagnostics/src/test/scala/akka/diagnostics/StarvationDetectorSpec.scala +++ b/akka-diagnostics/src/test/scala/akka/diagnostics/StarvationDetectorSpec.scala @@ -12,18 +12,17 @@ import java.util.concurrent.CompletableFuture import java.util.concurrent.Executors import java.util.concurrent.ThreadLocalRandom import java.util.concurrent.TimeUnit -import akka.dispatch.ExecutionContexts -import akka.testkit.EventFilter import scala.concurrent.Await +import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.concurrent.duration._ import scala.util.Try -import StarvationDetectorSpec._ -import akka.event.Logging -import akka.diagnostics.StarvationDetector.StarvationDetectorThread -import scala.concurrent.ExecutionContext +import akka.diagnostics.StarvationDetector.StarvationDetectorThread +import akka.dispatch.ExecutionContexts +import akka.event.Logging +import akka.testkit.EventFilter class StarvationDetectorSpec extends AkkaSpec(s""" akka.diagnostics.starvation-detector.check-interval = 200ms # check more often @@ -34,8 +33,8 @@ class StarvationDetectorSpec extends AkkaSpec(s""" type = Dispatcher executor = "fork-join-executor" fork-join-executor { - parallelism-min = $numThreads - parallelism-max = $numThreads + parallelism-min = ${StarvationDetectorSpec.numThreads} + parallelism-max = ${StarvationDetectorSpec.numThreads} } } custom-affinity-dispatcher { @@ -48,13 +47,15 @@ class StarvationDetectorSpec extends AkkaSpec(s""" type = Dispatcher executor = "thread-pool-executor" thread-pool-executor { - fixed-pool-size = $numThreads + fixed-pool-size = ${StarvationDetectorSpec.numThreads} } } """) { + import akka.diagnostics.StarvationDetectorSpec._ + "The StarvationDetector" should { def testsExecutor(dispatcherId: String): Unit = s"support $dispatcherId" should { - implicit val dispatcher = system.dispatchers.lookup(dispatcherId) + implicit val dispatcher: ExecutionContext = system.dispatchers.lookup(dispatcherId) // default dispatcher is already checked out of the box if (dispatcherId != DefaultDispatcherId) { StarvationDetector.checkExecutionContext( @@ -110,7 +111,7 @@ class StarvationDetectorSpec extends AkkaSpec(s""" } } "not log a warning if the dispatcher is busy for an amount of small non-blocking tasks" in { - def runThunks(remaining: Int)(implicit ec: ExecutionContext): Future[Unit] = + def runThunks(remaining: Int): Future[Unit] = if (remaining > 0) Future { () @@ -148,7 +149,7 @@ class StarvationDetectorSpec extends AkkaSpec(s""" case class AntiPattern(name: String, expectedIssueDescription: String, block: () => Unit) def antiPattern(name: String, expectedIssueDescription: String)(block: => Unit): AntiPattern = - AntiPattern(name, expectedIssueDescription, block _) + AntiPattern(name, expectedIssueDescription, () => block) // A collection of blocking AntiPatterns to test, each should take ~ 100ms lazy val AntiPatterns: Seq[AntiPattern] = Seq( diff --git a/build.sbt b/build.sbt index c388c3a..3828650 100644 --- a/build.sbt +++ b/build.sbt @@ -32,14 +32,31 @@ inThisBuild( lazy val common: Seq[Setting[_]] = Seq( - crossScalaVersions := Seq(Dependencies.Scala213, Dependencies.Scala212), - scalaVersion := Dependencies.Scala213, + crossScalaVersions := Dependencies.CrossScalaVersions, + scalaVersion := Dependencies.CrossScalaVersions.head, crossVersion := CrossVersion.binary, scalafmtOnCompile := true, sonatypeProfileName := "com.lightbend", + headerLicense := Some(HeaderLicense.Custom("""Copyright (C) 2022 Lightbend Inc. """)), // Setting javac options in common allows IntelliJ IDEA to import them automatically Compile / javacOptions ++= Seq("-encoding", "UTF-8", "-source", "1.8", "-target", "1.8"), - headerLicense := Some(HeaderLicense.Custom("""Copyright (C) 2022 Lightbend Inc. """)), + scalacOptions ++= { + var scalacOptionsBase = Seq("-encoding", "UTF-8", "-feature", "-unchecked", "-deprecation") + if (scalaVersion.value == Dependencies.Scala212) + scalacOptionsBase ++: Seq("-Xfuture", "-Xfatal-warnings", "-Xlint", "-Ywarn-dead-code") + else if (scalaVersion.value == Dependencies.Scala213) + scalacOptionsBase ++: Seq("-Xfatal-warnings", "-Xlint", "-Ywarn-dead-code", "-Wconf:cat=deprecation:info") + else + scalacOptionsBase + }, + javacOptions ++= ( + if (isJdk8) Seq.empty + else Seq("--release", "8") + ), + scalacOptions ++= ( + if (isJdk8 || scalaVersion.value == Dependencies.Scala212) Seq.empty + else Seq("--release", "8") + ), Test / logBuffered := false, Test / parallelExecution := false, // show full stack traces and test case durations @@ -111,3 +128,6 @@ lazy val docs = (project in file("docs")) publishRsyncHost := "akkarepo@gustav.akka.io") lazy val dontPublish = Seq(publish / skip := true, Compile / publishArtifact := false) + +lazy val isJdk8 = + VersionNumber(sys.props("java.specification.version")).matchesSemVer(SemanticSelector(s"=1.8")) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 1137ff5..cb39f9e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -6,11 +6,15 @@ import sbt._ object Dependencies { val Scala212 = "2.12.17" val Scala213 = "2.13.10" + val Scala3 = "3.1.3" + val CrossScalaVersions = Seq(Scala213, Scala212, Scala3) + val AkkaVersion = "2.7.0" val AkkaVersionInDocs = AkkaVersion.take(3) val AkkaHttpVersionInDocs = "10.4.0" - val buildScalaVersion = System.getProperty("akka.build.scalaVersion", Scala213) - val commonsLang = "org.apache.commons" % "commons-lang3" % "3.12.0" // ApacheV2 + val ScalaTestVersion = "3.2.15" + + val commonsText = "org.apache.commons" % "commons-text" % "1.10.0" // ApacheV2 object Compile { val akkaActor = "com.typesafe.akka" %% "akka-actor" % AkkaVersion @@ -20,8 +24,8 @@ object Dependencies { val akkaRemoting = "com.typesafe.akka" %% "akka-remote" % AkkaVersion val akkaClusterMetrics = "com.typesafe.akka" %% "akka-cluster-metrics" % AkkaVersion val akkaStreamTestKit = "com.typesafe.akka" %% "akka-stream-testkit" % AkkaVersion + val scalaTest = "org.scalatest" %% "scalatest" % ScalaTestVersion val akkaPersistenceTestKit = "com.typesafe.akka" %% "akka-persistence-testkit" % AkkaVersion - val scalaTest = "org.scalatest" %% "scalatest" % "3.0.8" val junit = "junit" % "junit" % "4.13.2" // Common Public License 1.0 val all = Seq( akkaRemoting % Test, @@ -36,6 +40,6 @@ object Dependencies { import Compile._ val akkaDiagnostics = Seq( - commonsLang, // for levenshtein distance impl + commonsText, // for levenshtein distance impl akkaActor) ++ TestDeps.all }