Skip to content

Commit

Permalink
Cross-compile to Scala 3 (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
Z1kkurat committed Nov 17, 2022
1 parent fdd2843 commit 746a87f
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 19 deletions.
16 changes: 9 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ jobs:
strategy:
matrix:
scala:
- 2.13.5
- 2.12.13
- 2.13.10
- 2.12.17
- 3.2.1

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- uses: coursier/cache-action@v5
- uses: coursier/cache-action@v6

- name: scala
uses: olafurpg/setup-scala@v10
uses: olafurpg/setup-scala@v13
with:
java-version: openjdk@1.11

Expand All @@ -29,8 +30,9 @@ jobs:
- name: test coverage
if: success()
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
run: sbt ++${{ matrix.scala }} coverageReport coverageAggregate coveralls
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_FLAG_NAME: Scala ${{ matrix.scala }}
run: sbt ++${{ matrix.scala }} coverageReport coveralls

- name: slack
uses: homoluctus/slatify@master
Expand Down
8 changes: 4 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ startYear := Some(2018)

organizationName := "Evolution"

organizationHomepage := Some(url("http://evolutiongaming.com"))
organizationHomepage := Some(url("https://evolution.com"))

scalaVersion := crossScalaVersions.value.head

crossScalaVersions := Seq("2.13.5", "2.12.13")
crossScalaVersions := Seq("2.13.10", "2.12.17", "3.2.1")

publishTo := Some(Resolver.evolutionReleases)

libraryDependencies ++= Seq(
"com.evolutiongaming" %% "executor-tools" % "1.0.2",
"org.scalatest" %% "scalatest" % "3.0.8" % Test)
"com.evolutiongaming" %% "executor-tools" % "1.0.4",
"org.scalatest" %% "scalatest" % "3.2.14" % Test)

licenses := Seq(("MIT", url("https://opensource.org/licenses/MIT")))

Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.5.1
sbt.version=1.8.0
6 changes: 3 additions & 3 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.7.1")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.5")

addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.7")
addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.1")

addSbtPlugin("com.github.sbt" % "sbt-release" % "1.0.15")
addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0")

addSbtPlugin("com.evolution" % "sbt-scalac-opts-plugin" % "0.0.9")

Expand Down
99 changes: 99 additions & 0 deletions src/main/scala-3/com/evolutiongaming/concurrent/FutureHelper.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.evolutiongaming.concurrent

import scala.collection.{BuildFrom, immutable}
import scala.collection.immutable.Seq
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.control.NonFatal
import scala.util.{Failure, Try}

object FutureHelper {
private val futureUnit = ().future
private val futureNone = Option.empty.future
private val futureSeq = Seq.empty.future
private val futureNil = Nil.future
private val futureTrue = true.future
private val futureFalse = false.future


implicit class FutureObjOps(val self: Future.type) extends AnyVal {

def unit: Future[Unit] = futureUnit

def none[T]: Future[Option[T]] = futureNone

def seq[T]: Future[Seq[T]] = futureSeq

def nil[T]: Future[List[T]] = futureNil

def `true`: Future[Boolean] = futureTrue

def `false`: Future[Boolean] = futureFalse

@deprecated("use `foldUnit1` instead", "1.0.5")
def foldUnit[T](iter: Iterable[Future[T]]): Future[Unit] = {
self.foldUnit1(iter)(CurrentThreadExecutionContext)
}

def foldUnit1[T](iter: Iterable[Future[T]])(implicit executor: ExecutionContext): Future[Unit] = {
Future.foldLeft(iter.toList)(()) { (_, _) => () }
}

def foldLeft[T, S](iter: immutable.Iterable[Future[T]])(s: S)(f: (S, T) => S)(implicit ec: ExecutionContext): Future[S] = {
val iterator = iter.iterator

def foldLeft(s: S): Future[S] = {
if (iterator.isEmpty) s.future
else iterator.next().flatMap { value => foldLeft(f(s, value)) }
}

foldLeft(s)
}

def sequenceSuccessful[A, M[X] <: IterableOnce[X]](in: M[Future[A]])(implicit cbf: BuildFrom[M[Future[A]], A, M[A]], executor: ExecutionContext): Future[M[A]] = {
in.iterator.foldLeft(Future.successful(cbf.newBuilder(in))) {
(acc, f) => acc.flatMap(acc => f.map(acc += _).recover { case _ => acc })
}.map(_.result())(CurrentThreadExecutionContext)
}
}


implicit class FutureOps[T](val self: Future[T]) extends AnyVal {

@deprecated("use `as` instead", "1.0.4")
def mapVal[TT](value: TT): Future[TT] = as(value)

def as[TT](value: TT): Future[TT] = self.map(_ => value)(CurrentThreadExecutionContext)

def unit: Future[Unit] = as(())

def flatten[TT](implicit ev: T <:< Future[TT]): Future[TT] = self.flatMap(ev)(CurrentThreadExecutionContext)

def transform[TT](f: Try[T] => Try[TT])(implicit ec: ExecutionContext): Future[TT] = {
val p = Promise[TT]()
self.onComplete { result => p.complete(try f(result) catch { case NonFatal(t) => Failure(t) }) }
p.future
}
}


implicit class AnyFutureOps[T](val self: T) extends AnyVal {

def future: Future[T] = Future.successful(self)

def traverseSequentially[A, B, M[X] <: IterableOnce[X]](in: M[A])(f: A => Future[B])
(implicit buildFrom: BuildFrom[M[A], B, M[B]]): Future[M[B]] = {

implicit val ec = CurrentThreadExecutionContext

val builder = buildFrom.newBuilder(in)
builder sizeHint in.iterator.size

in.iterator.foldLeft(Future successful builder) { (prev, next) =>
for {
prev <- prev
next <- f(next)
} yield prev += next
}.map { builder => builder.result() }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.evolutiongaming.concurrent

import com.evolutiongaming.concurrent.FutureHelper._
import org.scalatest.{FunSuite, Matchers}
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should._

import scala.concurrent.duration._
import scala.concurrent.{Await, Future, Promise, TimeoutException}
import scala.util.Success

class FutureHelperSpec extends FunSuite with Matchers {
class FutureHelperSpec extends AnyFunSuite with Matchers {
import FutureHelperSpec._

test("traverseSequentially") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.evolutiongaming.concurrent

import com.evolutiongaming.concurrent.FutureHelper._
import org.scalatest.{FunSuite, Matchers}
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should._

import scala.concurrent.duration._
import scala.concurrent.{Await, Future, Promise, TimeoutException}
import scala.util.Success

class FutureHelperSpec extends FunSuite with Matchers {
class FutureHelperSpec extends AnyFunSuite with Matchers {
import FutureHelperSpec._

test("traverseSequentially") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.evolutiongaming.concurrent

import com.evolutiongaming.concurrent.FutureHelper._
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should._

import scala.concurrent.duration._
import scala.concurrent.{Await, Future, Promise, TimeoutException}
import scala.util.Success

class FutureHelperSpec extends AnyFunSuite with Matchers {
import FutureHelperSpec._

test("traverseSequentially") {

val promise = Promise[Int]()

val futures = List(Future.successful(1), promise.future, Future.successful(3))
val future = Future.traverseSequentially(futures)(identity)
the[TimeoutException] thrownBy future.await(100.millis)

promise.success(2)

Await.result(future, 3.seconds) shouldEqual List(1, 2, 3)
}

test("sequenceSuccessful") {
implicit val ec = CurrentThreadExecutionContext
val futures = List(Future.successful(1), Future.failed(new RuntimeException()), Future.successful(3))
Future.sequenceSuccessful(futures).await() shouldEqual List(1, 3)
}

test("as") {
Future.successful(()).as("").await() shouldEqual ""
}

test("true") {
Future.`true`.await() shouldEqual true
}

test("false") {
Future.`false`.await() shouldEqual false
}

test("nil") {
Future.nil[Int].await() shouldEqual Nil
}

test("none") {
Future.none[Int].await() shouldEqual None
}

test("unit") {
Future.unit.value shouldEqual Some(Success(()))
Future.nil.unit.value shouldEqual Some(Success(()))
}

test("foldLeft") {
val futures = List(Future.successful(1), Future.successful(2))
val ops = FutureHelper.FutureObjOps(Future)
ops.foldLeft(futures)(List.empty[Int]) { (s, a) => a :: s }(CurrentThreadExecutionContext).await() shouldEqual List(2, 1)
}
}

object FutureHelperSpec {

implicit class FutureHelperSpecFutureOps[A](val future: Future[A]) extends AnyVal {
def await(timeout: FiniteDuration = 3.seconds): A = Await.result(future, timeout)
}
}

0 comments on commit 746a87f

Please sign in to comment.