diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dae36a36e..6b76ad755 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - scala: [2.13.2] + scala: [2.12.12, 2.13.3, 3.0.0-M2, 3.0.0-M3] java: [adopt@1.8] runs-on: ${{ matrix.os }} steps: @@ -65,7 +65,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.12.12] + scala: [2.13.3] java: [adopt@1.8] runs-on: ${{ matrix.os }} steps: diff --git a/build.sbt b/build.sbt index 197dff2a9..7496aa2ef 100644 --- a/build.sbt +++ b/build.sbt @@ -11,11 +11,12 @@ ThisBuild / versionIntroduced := Map( "3.0.0-M3" -> "0.15.0" ) +ThisBuild / crossScalaVersions := Seq("2.12.12", "2.13.3", "3.0.0-M2", "3.0.0-M3") +ThisBuild / scalaVersion := crossScalaVersions.value.filter(_.startsWith("2.")).last + lazy val commonSettings = Seq( description := "NIO Framework for Scala", - crossScalaVersions := Seq("2.11.12", "2.12.12", "2.13.3"), - scalaVersion := crossScalaVersions.value.filter(_.startsWith("2.")).last, - scalacOptions in Test ~= (_.filterNot(Set("-Ywarn-dead-code", "-Wdead-code", "-Xfatal-warnings"))), // because mockito + scalacOptions in Test ~= (_.filterNot(Set("-Ywarn-dead-code", "-Wdead-code"))), // because mockito scalacOptions in (Compile, doc) += "-no-link-warnings", unmanagedSourceDirectories in Compile ++= { (unmanagedSourceDirectories in Compile).value.map { dir => @@ -69,8 +70,8 @@ lazy val core = Project("blaze-core", file("core")) .settings( libraryDependencies ++= Seq(log4s), libraryDependencies ++= Seq( - specs2, - specs2Mock, + specs2.withDottyCompat(scalaVersion.value), + specs2Mock.withDottyCompat(scalaVersion.value), logbackClassic ).map(_ % Test), buildInfoPackage := "org.http4s.blaze", @@ -98,8 +99,8 @@ lazy val http = Project("blaze-http", file("http")) // Test Dependencies libraryDependencies ++= Seq( asyncHttpClient, - scalacheck, - specs2Scalacheck + scalacheck.withDottyCompat(scalaVersion.value), + specs2Scalacheck.withDottyCompat(scalaVersion.value) ).map(_ % Test), mimaBinaryIssueFilters ++= Seq( ProblemFilters.exclude[MissingClassProblem]("org.http4s.blaze.http.http2.PingManager$PingState"), diff --git a/core/src/main/scala/org/http4s/blaze/channel/nio1/NIO1SocketServerGroup.scala b/core/src/main/scala/org/http4s/blaze/channel/nio1/NIO1SocketServerGroup.scala index e77f96a50..5e4847587 100644 --- a/core/src/main/scala/org/http4s/blaze/channel/nio1/NIO1SocketServerGroup.scala +++ b/core/src/main/scala/org/http4s/blaze/channel/nio1/NIO1SocketServerGroup.scala @@ -208,14 +208,14 @@ private final class NIO1SocketServerGroup private ( // in case we were closed because the event loop was closed, // we need to be ready to handle a `RejectedExecutionException`. def doClose(): Unit = { - logger.info(s"Closing NIO1 channel $socketAddress") + this.logger.info(s"Closing NIO1 channel $socketAddress") closed = true listeningSet.synchronized { listeningSet.remove(this) } try selectableChannel.close() catch { - case NonFatal(t) => logger.warn(t)("Failure during channel close.") + case NonFatal(t) => this.logger.warn(t)("Failure during channel close.") } finally connections.close() // allow the acceptor thread through } @@ -227,7 +227,7 @@ private final class NIO1SocketServerGroup private ( }) catch { case _: RejectedExecutionException => - logger.info("Selector loop closed. Closing in local thread.") + this.logger.info("Selector loop closed. Closing in local thread.") doClose() } } diff --git a/core/src/main/scala/org/http4s/blaze/pipeline/stages/SSLStage.scala b/core/src/main/scala/org/http4s/blaze/pipeline/stages/SSLStage.scala index 890845465..b3341e982 100644 --- a/core/src/main/scala/org/http4s/blaze/pipeline/stages/SSLStage.scala +++ b/core/src/main/scala/org/http4s/blaze/pipeline/stages/SSLStage.scala @@ -236,9 +236,6 @@ final class SSLStage(engine: SSLEngine, maxWrite: Int = 1024 * 1024) case DelayedRead(sz, p) => doRead(sz, p) case DelayedWrite(d, p) => doWrite(d, p) } - - case status => - handshakeFailure(util.bug(s"Unexpected status: ${status}")) } val start = System.nanoTime diff --git a/core/src/test/scala/org/http4s/blaze/channel/EchoStage.scala b/core/src/test/scala/org/http4s/blaze/channel/EchoStage.scala index 0003055ed..3c1c47ac3 100644 --- a/core/src/test/scala/org/http4s/blaze/channel/EchoStage.scala +++ b/core/src/test/scala/org/http4s/blaze/channel/EchoStage.scala @@ -19,7 +19,7 @@ package channel import org.http4s.blaze.pipeline.TailStage import java.nio.ByteBuffer - +import scala.concurrent.ExecutionContext import scala.util.{Failure, Success} import org.http4s.blaze.pipeline.Command.EOF @@ -28,7 +28,7 @@ class EchoStage extends TailStage[ByteBuffer] { val msg = "echo: ".getBytes - private implicit def ec = util.Execution.trampoline + private implicit def ec: ExecutionContext = util.Execution.trampoline final override def stageStartup(): Unit = channelRead().onComplete { diff --git a/core/src/test/scala/org/http4s/blaze/pipeline/PipelineSpec.scala b/core/src/test/scala/org/http4s/blaze/pipeline/PipelineSpec.scala index 1bd26d2f3..2b5caa0b9 100644 --- a/core/src/test/scala/org/http4s/blaze/pipeline/PipelineSpec.scala +++ b/core/src/test/scala/org/http4s/blaze/pipeline/PipelineSpec.scala @@ -20,9 +20,10 @@ import org.http4s.blaze.util.{Execution, FutureUnit} import org.specs2.mutable._ import scala.concurrent.{Await, Future} import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext class PipelineSpec extends Specification { - private implicit def ec = Execution.trampoline + private implicit def ec: ExecutionContext = Execution.trampoline class IntHead extends HeadStage[Int] { def name = "IntHead" diff --git a/core/src/test/scala/org/http4s/blaze/pipeline/stages/SSLStageSpec.scala b/core/src/test/scala/org/http4s/blaze/pipeline/stages/SSLStageSpec.scala index 0ff1f0e47..e906c2a43 100644 --- a/core/src/test/scala/org/http4s/blaze/pipeline/stages/SSLStageSpec.scala +++ b/core/src/test/scala/org/http4s/blaze/pipeline/stages/SSLStageSpec.scala @@ -31,7 +31,7 @@ import scala.concurrent._ import scala.util.control.NonFatal class SSLStageSpec extends Specification { - implicit def ec = Execution.trampoline + implicit def ec: ExecutionContext = Execution.trampoline def debug = false diff --git a/http/src/main/scala/org/http4s/blaze/http/http2/PriorKnowledgeHandshaker.scala b/http/src/main/scala/org/http4s/blaze/http/http2/PriorKnowledgeHandshaker.scala index 48a46e165..3a02b598c 100644 --- a/http/src/main/scala/org/http4s/blaze/http/http2/PriorKnowledgeHandshaker.scala +++ b/http/src/main/scala/org/http4s/blaze/http/http2/PriorKnowledgeHandshaker.scala @@ -21,13 +21,12 @@ import java.nio.ByteBuffer import org.http4s.blaze.http.http2.SettingsDecoder.SettingsFrame import org.http4s.blaze.pipeline.TailStage import org.http4s.blaze.util.{BufferTools, Execution} - -import scala.concurrent.Future +import scala.concurrent.{ExecutionContext, Future} /** Base type for performing the HTTP/2 prior knowledge handshake */ abstract class PriorKnowledgeHandshaker[T](localSettings: ImmutableHttp2Settings) extends TailStage[ByteBuffer] { - final protected implicit def ec = Execution.trampoline + final protected implicit def ec: ExecutionContext = Execution.trampoline override def name: String = s"${getClass.getSimpleName}($localSettings)" @@ -112,6 +111,9 @@ abstract class PriorKnowledgeHandshaker[T](localSettings: ImmutableHttp2Settings case Left(http2Exception) => sendGoAway(http2Exception) } + + case None => + sendGoAway(Http2Exception.INTERNAL_ERROR.goaway("Could not read frame size")) } } diff --git a/http/src/test/scala/org/http4s/blaze/http/HeaderNamesSpec.scala b/http/src/test/scala/org/http4s/blaze/http/HeaderNamesSpec.scala index f4211440c..f6fc11377 100644 --- a/http/src/test/scala/org/http4s/blaze/http/HeaderNamesSpec.scala +++ b/http/src/test/scala/org/http4s/blaze/http/HeaderNamesSpec.scala @@ -41,11 +41,11 @@ class HeaderNamesSpec extends Specification { } "Accept a header with numbers" in { - HeaderNames.validH2HeaderKey('0' to '9' mkString) must_== true + HeaderNames.validH2HeaderKey(('0' to '9').mkString) must_== true } "Accept a header with lower case letters" in { - HeaderNames.validH2HeaderKey('a' to 'z' mkString) must_== true + HeaderNames.validH2HeaderKey(('a' to 'z').mkString) must_== true } "Accept a header with non-delimiters" in { diff --git a/http/src/test/scala/org/http4s/blaze/http/http2/FrameDecoderSpec.scala b/http/src/test/scala/org/http4s/blaze/http/http2/FrameDecoderSpec.scala index d8b3df94b..a57f5231a 100644 --- a/http/src/test/scala/org/http4s/blaze/http/http2/FrameDecoderSpec.scala +++ b/http/src/test/scala/org/http4s/blaze/http/http2/FrameDecoderSpec.scala @@ -1434,12 +1434,11 @@ class FrameDecoderSpec extends Specification { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) val listener = new MockFrameListener(false) + var data: ByteBuffer = null + var code: Option[Byte] = None + var flags: Option[Byte] = None + var streamId: Option[Int] = None val dec = new FrameDecoder(Http2Settings.default, listener) { - var data: ByteBuffer = null - var code: Option[Byte] = None - var flags: Option[Byte] = None - var streamId: Option[Int] = None - override def onExtensionFrame( _code: Byte, _streamId: Int, @@ -1454,10 +1453,10 @@ class FrameDecoderSpec extends Specification { } dec.decodeBuffer(testData) must_== Continue - dec.data must_== ByteBuffer.wrap(new Array[Byte](8)) - dec.code must_== Some(0x16) - dec.flags must_== Some(0x00) - dec.streamId must_== Some(0) + data must_== ByteBuffer.wrap(new Array[Byte](8)) + code must_== Some(0x16) + flags must_== Some(0x00) + streamId must_== Some(0) } } } diff --git a/http/src/test/scala/org/http4s/blaze/http/http2/FrameSerializerSpec.scala b/http/src/test/scala/org/http4s/blaze/http/http2/FrameSerializerSpec.scala index 55ea5d8ca..fd40b2b8d 100644 --- a/http/src/test/scala/org/http4s/blaze/http/http2/FrameSerializerSpec.scala +++ b/http/src/test/scala/org/http4s/blaze/http/http2/FrameSerializerSpec.scala @@ -70,7 +70,7 @@ class FrameSerializerSpec extends Specification with ScalaCheck { } }) - "roundtrip" >> prop { dataFrame: DataFrame => + "roundtrip" >> prop { (dataFrame: DataFrame) => val frame = joinBuffers( FrameSerializer.mkDataFrame( dataFrame.streamId, @@ -81,7 +81,7 @@ class FrameSerializerSpec extends Specification with ScalaCheck { } "Decode padded buffers" in { - forall(0 to 256) { i: Int => + forall(0 to 256) { (i: Int) => def dat = mkData(20) val frame = joinBuffers(FrameSerializer.mkDataFrame(3, false, i, dat)) dec(DataFrame(3, false, dat, i)).decodeBuffer(frame) must_== Continue @@ -134,7 +134,7 @@ class FrameSerializerSpec extends Specification with ScalaCheck { } }) - "roundtrip" >> prop { headerFrame: HeadersFrame => + "roundtrip" >> prop { (headerFrame: HeadersFrame) => val buff1 = joinBuffers( FrameSerializer.mkHeaderFrame( headerFrame.streamId, @@ -191,14 +191,14 @@ class FrameSerializerSpec extends Specification with ScalaCheck { } }) - implicit lazy val arbPriority = Arbitrary( + implicit lazy val arbPriority: Arbitrary[PriorityFrame] = Arbitrary( for { streamId <- Gen.posNum[Int] p <- genPriority.filter(_.dependentStreamId != streamId) } yield PriorityFrame(streamId, p) ) - "roundtrip" >> prop { p: PriorityFrame => + "roundtrip" >> prop { (p: PriorityFrame) => val buff1 = FrameSerializer.mkPriorityFrame(p.streamId, p.priority) dec(p).decodeBuffer(buff1) must_== Continue buff1.remaining() must_== 0 @@ -237,7 +237,7 @@ class FrameSerializerSpec extends Specification with ScalaCheck { } }) - "roundtrip" >> prop { rst: RstFrame => + "roundtrip" >> prop { (rst: RstFrame) => val buff1 = FrameSerializer.mkRstStreamFrame(rst.streamId, rst.code) dec(rst).decodeBuffer(buff1) must_== Continue buff1.remaining() must_== 0 @@ -257,7 +257,7 @@ class FrameSerializerSpec extends Specification with ScalaCheck { } }) - "roundtrip" >> prop { ack: Boolean => + "roundtrip" >> prop { (ack: Boolean) => val settings = if (ack) None else Some((0 until 100).map(i => Setting(i, i + 3))) val buff1 = settings match { @@ -289,7 +289,7 @@ class FrameSerializerSpec extends Specification with ScalaCheck { } }) - "make a simple round trip" >> prop { pingFrame: PingFrame => + "make a simple round trip" >> prop { (pingFrame: PingFrame) => val pingBuffer = FrameSerializer.mkPingFrame(pingFrame.ack, pingFrame.data) dec(pingFrame).decodeBuffer(pingBuffer) must_== Continue } @@ -320,7 +320,7 @@ class FrameSerializerSpec extends Specification with ScalaCheck { } }) - "roundtrip" >> prop { goAway: GoAwayFrame => + "roundtrip" >> prop { (goAway: GoAwayFrame) => val encodedGoAway = FrameSerializer.mkGoAwayFrame(goAway.lastStream, goAway.err, goAway.data) dec(goAway).decodeBuffer(encodedGoAway) must_== Continue } @@ -345,7 +345,7 @@ class FrameSerializerSpec extends Specification with ScalaCheck { } }) - "roundtrip" >> prop { updateFrame: WindowUpdateFrame => + "roundtrip" >> prop { (updateFrame: WindowUpdateFrame) => val updateBuffer = FrameSerializer.mkWindowUpdateFrame(updateFrame.streamId, updateFrame.increment) dec(updateFrame).decodeBuffer(updateBuffer) must_== Continue diff --git a/http/src/test/scala/org/http4s/blaze/http/http2/SessionFrameListenerSpec.scala b/http/src/test/scala/org/http4s/blaze/http/http2/SessionFrameListenerSpec.scala index ceae7a8b7..197a6f2b0 100644 --- a/http/src/test/scala/org/http4s/blaze/http/http2/SessionFrameListenerSpec.scala +++ b/http/src/test/scala/org/http4s/blaze/http/http2/SessionFrameListenerSpec.scala @@ -67,7 +67,7 @@ class SessionFrameListenerSpec extends Specification { "initiate a new stream for idle inbound stream (server)" >> { val head = new BasicTail[StreamFrame]("") val tools = new MockTools(isClient = false) { - override lazy val newInboundStream = Some { _: Int => LeafBuilder(head) } + override lazy val newInboundStream = Some((_: Int) => LeafBuilder(head)) } tools.streamManager.get(1) must beNone @@ -110,9 +110,9 @@ class SessionFrameListenerSpec extends Specification { } "delegates to StreamManager" >> { + var sId, pId = -1 + var hss: Headers = Nil val tools = new MockTools(isClient = true) { - var sId, pId = -1 - var hss: Headers = Nil override lazy val streamManager = new MockStreamManager() { override def handlePushPromise( streamId: Int, @@ -128,9 +128,9 @@ class SessionFrameListenerSpec extends Specification { } tools.frameListener.onCompletePushPromiseFrame(1, 2, hs) must_== Continue - tools.sId must_== 1 - tools.pId must_== 2 - tools.hss must_== hs + sId must_== 1 + pId must_== 2 + hss must_== hs } } @@ -194,8 +194,8 @@ class SessionFrameListenerSpec extends Specification { "on RST_STREAM" >> { "delegates to the StreamManager" >> { + var observedCause: Option[Http2StreamException] = None val tools = new MockTools(true) { - var observedCause: Option[Http2StreamException] = None override lazy val streamManager = new MockStreamManager() { override def rstStream(cause: Http2StreamException) = { observedCause = Some(cause) @@ -205,7 +205,7 @@ class SessionFrameListenerSpec extends Specification { } tools.frameListener.onRstStreamFrame(1, STREAM_CLOSED.code) must_== Continue - tools.observedCause must beLike { case Some(ex) => + observedCause must beLike { case Some(ex) => ex.stream must_== 1 ex.code must_== STREAM_CLOSED.code } @@ -214,8 +214,8 @@ class SessionFrameListenerSpec extends Specification { "on WINDOW_UPDATE" >> { "delegates to the StreamManager" >> { + var observedIncrement: Option[(Int, Int)] = None val tools = new MockTools(true) { - var observedIncrement: Option[(Int, Int)] = None override lazy val streamManager = new MockStreamManager() { override def flowWindowUpdate(streamId: Int, sizeIncrement: Int) = { observedIncrement = Some(streamId -> sizeIncrement) @@ -225,7 +225,7 @@ class SessionFrameListenerSpec extends Specification { } tools.frameListener.onWindowUpdateFrame(1, 2) must_== Continue - tools.observedIncrement must beLike { case Some((1, 2)) => + observedIncrement must beLike { case Some((1, 2)) => ok } } @@ -242,8 +242,8 @@ class SessionFrameListenerSpec extends Specification { } "pass ping ACK's to the PingManager" >> { + var observedAck: Option[Array[Byte]] = None val tools = new MockTools(true) { - var observedAck: Option[Array[Byte]] = None override lazy val pingManager = new PingManager(this) { override def pingAckReceived(data: Array[Byte]): Unit = observedAck = Some(data) @@ -251,7 +251,7 @@ class SessionFrameListenerSpec extends Specification { } val data = (0 until 8).map(_.toByte).toArray tools.frameListener.onPingFrame(true, data) must_== Continue - tools.observedAck must_== Some(data) + observedAck must_== Some(data) } } @@ -272,8 +272,8 @@ class SessionFrameListenerSpec extends Specification { "on GOAWAY frame" >> { "delegates to the sessions goAway logic" >> { + var observedGoAway: Option[(Int, Http2SessionException)] = None val tools = new MockTools(true) { - var observedGoAway: Option[(Int, Http2SessionException)] = None override def invokeGoAway( lastHandledOutboundStream: Int, reason: Http2SessionException @@ -286,8 +286,8 @@ class SessionFrameListenerSpec extends Specification { NO_ERROR.code, "lol".getBytes(StandardCharsets.UTF_8)) must_== Continue - tools.observedGoAway must beLike { - case Some((1, Http2SessionException(NO_ERROR.code, "lol"))) => ok + observedGoAway must beLike { case Some((1, Http2SessionException(NO_ERROR.code, "lol"))) => + ok } } } diff --git a/http/src/test/scala/org/http4s/blaze/http/parser/Benchmarks.scala b/http/src/test/scala/org/http4s/blaze/http/parser/Benchmarks.scala index 3a693aaaf..2a2a4a8e4 100644 --- a/http/src/test/scala/org/http4s/blaze/http/parser/Benchmarks.scala +++ b/http/src/test/scala/org/http4s/blaze/http/parser/Benchmarks.scala @@ -67,8 +67,8 @@ class Benchmarks extends Specification { } def checkingBenchmark(iterations: Int): Unit = { + val sb = new StringBuilder val p = new BenchParser() { - val sb = new StringBuilder override def parsecontent(s: ByteBuffer): ByteBuffer = { val b = super.parsecontent(s) @@ -118,9 +118,9 @@ class Benchmarks extends Specification { p.parsecontent(b) assert(p.contentComplete()) // println(p.sb.result()) - assert(p.sb.result() == reconstructed) + assert(sb.result() == reconstructed) - p.sb.clear() + sb.clear() p.reset() assert(!p.requestLineComplete()) @@ -156,19 +156,18 @@ class Benchmarks extends Specification { } def headerCounterBenchmark(iterations: Int): Unit = { + val headers = new ListBuffer[(String, String)] val p = new BenchParser() { - val headers = new ListBuffer[(String, String)] - override def headerComplete(name: String, value: String): Boolean = { headers += ((name, value)) false } - - def clear(): Unit = { - headers.clear() - super.reset() - } } + def clear(): Unit = { + headers.clear() + p.reset() + } + val b = ByteBuffer.wrap(mockChunked.getBytes(StandardCharsets.UTF_8)) def iteration(remaining: Int): Unit = @@ -178,8 +177,8 @@ class Benchmarks extends Specification { assert(p.parseLine(b)) assert(p.parseheaders(b)) p.parsecontent(b) - assert(p.headers.length == 5) - p.clear() + assert(headers.length == 5) + clear() assert(!p.requestLineComplete()) } diff --git a/http/src/test/scala/org/http4s/blaze/http/parser/HttpTokensSpec.scala b/http/src/test/scala/org/http4s/blaze/http/parser/HttpTokensSpec.scala index f8017f749..e453cdbc3 100644 --- a/http/src/test/scala/org/http4s/blaze/http/parser/HttpTokensSpec.scala +++ b/http/src/test/scala/org/http4s/blaze/http/parser/HttpTokensSpec.scala @@ -27,9 +27,9 @@ class HttpTokensSpec extends Specification { "HttpTokens" should { "parse hex chars to ints" in { - smalChrs.map(c => HttpTokens.hexCharToInt(c)) should_== (0 until 16 toList) + smalChrs.map(c => HttpTokens.hexCharToInt(c)) should_== ((0 until 16).toList) - bigChrs.map(c => HttpTokens.hexCharToInt(c)) should_== (0 until 16 toList) + bigChrs.map(c => HttpTokens.hexCharToInt(c)) should_== ((0 until 16).toList) HttpTokens.hexCharToInt('x') should throwA[BadMessage] } diff --git a/http/src/test/scala/org/http4s/blaze/http/parser/ServerParserSpec.scala b/http/src/test/scala/org/http4s/blaze/http/parser/ServerParserSpec.scala index d8140fb81..7d164ddee 100644 --- a/http/src/test/scala/org/http4s/blaze/http/parser/ServerParserSpec.scala +++ b/http/src/test/scala/org/http4s/blaze/http/parser/ServerParserSpec.scala @@ -26,7 +26,8 @@ import org.http4s.blaze.http.parser.BaseExceptions.{BadMessage, InvalidState} import scala.collection.mutable.ListBuffer class ServerParserSpec extends Specification { - implicit def strToBuffer(str: String) = ByteBuffer.wrap(str.getBytes(StandardCharsets.ISO_8859_1)) + implicit def strToBuffer(str: String): ByteBuffer = + ByteBuffer.wrap(str.getBytes(StandardCharsets.ISO_8859_1)) class Parser(maxReq: Int = 1034, maxHeader: Int = 1024) extends Http1ServerParser(maxReq, maxHeader, 1) { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 785a7fe43..ea8111ac5 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -4,7 +4,7 @@ object Dependencies { lazy val logbackClassic = "ch.qos.logback" % "logback-classic" % "1.2.3" lazy val twitterHPACK = "com.twitter" % "hpack" % "1.0.2" lazy val asyncHttpClient = "org.asynchttpclient" % "async-http-client" % "2.12.2" - lazy val log4s = "org.log4s" %% "log4s" % "1.9.0" + lazy val log4s = "org.log4s" %% "log4s" % "1.10.0-M4" lazy val scalacheck = "org.scalacheck" %% "scalacheck" % "1.15.2" lazy val specs2 = "org.specs2" %% "specs2-core" % "4.10.6" lazy val specs2Mock = "org.specs2" %% "specs2-mock" % specs2.revision