diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..9a4d2bb4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +name: Release +on: + push: + tags: ["*"] +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-java@v4 + with: + java-version: 8 + distribution: 'adopt' + cache: sbt + - uses: sbt/setup-sbt@v1 + - run: sbt ci-release + env: + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + PGP_SECRET: ${{ secrets.PGP_SECRET }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} \ No newline at end of file diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index eb1dc355..fce8bd7e 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -8,15 +8,14 @@ on: jobs: scala_3: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 11 distribution: 'adopt' + - uses: sbt/setup-sbt@v1 - name: Run tests run: sbt ++3.4.2 test diff --git a/build.sbt b/build.sbt index 94157f0d..af6431ba 100644 --- a/build.sbt +++ b/build.sbt @@ -32,3 +32,18 @@ lazy val examples = project in file("examples") settings( specs2.value % Test ) ) dependsOn scalamock.jvm + +inThisBuild( + List( + organization := "org.scalamock", + homepage := Some(url("http://scalamock.org/")), + licenses := List( + "MIT" -> url("https://opensource.org/licenses/MIT") + ), + developers := List( + Developer("paulbutcher", "Paul Butcher", "", url("http://paulbutcher.com/")), + Developer("barkhorn", "Philipp Meyerhoefer", "", url("https://github.com/barkhorn")), + Developer("goshacodes", "Georgii Kovalev", "", url("https://github.com/goshacodes")) + ) + ) +) diff --git a/jvm/src/test/scala/org/scalamock/test/specs2/ConcurrencyTest.scala b/jvm/src/test/scala/org/scalamock/test/specs2/ConcurrencyTest.scala index e20d17e7..c3bb37d4 100644 --- a/jvm/src/test/scala/org/scalamock/test/specs2/ConcurrencyTest.scala +++ b/jvm/src/test/scala/org/scalamock/test/specs2/ConcurrencyTest.scala @@ -20,26 +20,24 @@ package org.scalamock.test.specs2 -import scala.concurrent.Await +import scala.concurrent.{Await, Future} import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future -import scala.concurrent.duration.Duration -import scala.concurrent.duration.SECONDS - -import org.scalamock.specs2.MockContext +import scala.concurrent.duration.{Duration, SECONDS} +import org.scalamock.specs2.IsolatedMockFactory import org.scalamock.test.mockable.TestTrait import org.specs2.mutable.Specification -class ConcurrencyTest extends Specification { +class ConcurrencyTest extends Specification with IsolatedMockFactory { - "Futures should work" in new MockContext { + "Futures should work" in { val s = stubFunction[Int] s.when().returns(1) Await.result(Future { s() }, Duration(10, SECONDS)) must be_==(1) s.verify().once() + success } - "Concurrent mock access should work" in new MockContext { + "Concurrent mock access should work" in { val m = mock[TestTrait] (m.oneParamMethod _).expects(42).repeated(500000).returning("a") @@ -48,5 +46,49 @@ class ConcurrencyTest extends Specification { } futures.foreach(future => Await.result(future, Duration(10, SECONDS))) + success } + + case class MyClass(i: Int) + + trait SlowTestTrait { + def oneParamMethod(param: MyClass): String + def otherMethod(): String + } + + val m1 = stub[SlowTestTrait] + // This test fails flakily, so rerun it several times to confirm. + (1 to 10).foreach(i => + s"Concurrent mock access should work ($i)" in { + val len = 500 + val args = (0 to len).toList + (m1.otherMethod _).when().returns("ok") + args.foreach(i => (m1.oneParamMethod _).when(MyClass(i)).returns(i.toString)) + + val futures = args.map { i => + Future { + m1.oneParamMethod(MyClass(i)) + } + } + + futures.foreach(future => Await.result(future, Duration(10, SECONDS))) + args.foreach { i => + Future { + m1.otherMethod() + } + } + args.foreach { i => + Future { + m1.oneParamMethod(MyClass(i)) + } + } + + eventually { + (1 to len).foreach { i => + (m1.oneParamMethod _).verify(MyClass(i)).atLeastOnce() + } + success + } + }) + } diff --git a/project/build.properties b/project/build.properties index abbbce5d..73df629a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.8 +sbt.version=1.10.7 diff --git a/project/plugins.sbt b/project/plugins.sbt index b26f0b4b..ecd712d7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ - +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.12.2") -addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1") +addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") addSbtPlugin("com.github.sbt" % "sbt-git" % "2.1.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") diff --git a/publishing.sbt b/publishing.sbt deleted file mode 100644 index 4d939aad..00000000 --- a/publishing.sbt +++ /dev/null @@ -1,37 +0,0 @@ -ThisBuild / organization := "org.scalamock" -ThisBuild / licenses := Seq("MIT" -> url("https://opensource.org/licenses/MIT")) -ThisBuild / scmInfo := Some( - ScmInfo(url("https://github.com/paulbutcher/ScalaMock"), "scm:git:git@github.com:paulbutcher/ScalaMock.git") -) -ThisBuild / developers := List( - Developer("paulbutcher", "Paul Butcher", "", url("http://paulbutcher.com/")), - Developer("barkhorn", "Philipp Meyerhoefer", "", url("https://github.com/barkhorn")) -) -ThisBuild / homepage := Some(url("http://scalamock.org/")) -//ThisBuild / pomIncludeRepository := { _ => false } - -ThisBuild / version := { - val Snapshot = """(\d+)\.(\d+)\.(\d+)-\d+.*?""".r - git.gitDescribedVersion.value.getOrElse("0.0.0-1")match { - case Snapshot(maj, min, _) => s"$maj.${min.toInt + 1}.0-SNAPSHOT" - case v => v - } -} - -ThisBuild / isSnapshot := version.value.endsWith("-SNAPSHOT") - -ThisBuild / publishTo := { - if (isSnapshot.value) Opts.resolver.sonatypeOssSnapshots.headOption else Some(Opts.resolver.sonatypeStaging) -} -ThisBuild / publishConfiguration := publishConfiguration.value.withOverwrite(true) -ThisBuild / publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true) - -addCommandAlias("ci-all", ";+clean ;+compile ;+test ;+package") -addCommandAlias("release", ";+scalamockJVM/publishSigned ;+scalamockJS/publishSigned ;sonatypeReleaseAll") - -credentials ++= ( - for { - u <- Option(System.getenv().get("SONATYPE_USERNAME")) - p <- Option(System.getenv().get("SONATYPE_PASSWORD")) - } yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", u, p) - ).toSeq diff --git a/shared/src/main/scala/org/scalamock/context/CallLog.scala b/shared/src/main/scala/org/scalamock/context/CallLog.scala index e43551db..d5919115 100644 --- a/shared/src/main/scala/org/scalamock/context/CallLog.scala +++ b/shared/src/main/scala/org/scalamock/context/CallLog.scala @@ -34,9 +34,9 @@ private[scalamock] class CallLog { log += call } - def foreach(f: Call => Unit) = log foreach f + def foreach(f: Call => Unit) = this.synchronized { log foreach f } - override def toString = log mkString(" ", "\n ", "") + override def toString = this.synchronized { log mkString(" ", "\n ", "") } private val log = new ListBuffer[Call] }