diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f2001d7..0a60fb58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,25 +2,28 @@ name: CI on: pull_request: - branches: ["*"] + branches: ["main", "series/*"] push: - branches: ["main"] + branches: ["main", "series/*"] tags: ["v*"] +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + jobs: build: - name: Test ${{matrix.ceVersion}} ${{matrix.scalaVersion}} (${{matrix.scalaPlatform}}) + name: Test ${{matrix.scalaVersion}} (${{matrix.scalaPlatform}}) strategy: fail-fast: false matrix: os: [ubuntu-latest] - java: [adopt@1.8] + java: [8] scalaVersion: ["2_12", "2_13", "3"] - scalaPlatform: ["jvm", "js"] - ceVersion: ["CE2", "CE3"] + scalaPlatform: ["jvm", "js", "native"] runs-on: ${{ matrix.os }} env: - BUILD_KEY: ${{matrix.ceVersion}}_${{matrix.scalaVersion}}_${{matrix.scalaPlatform}} + BUILD_KEY: ${{matrix.scalaVersion}}_${{matrix.scalaPlatform}} steps: - name: Checkout current branch uses: actions/checkout@v2 @@ -30,10 +33,11 @@ jobs: with: extraKey: ${{ env.BUILD_KEY }} - - name: Setup Java and Scala - uses: olafurpg/setup-scala@v10 + - uses: actions/setup-java@v3 with: + distribution: 'temurin' java-version: ${{ matrix.java }} + cache: 'sbt' - name: Run tests run: | @@ -68,15 +72,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout current branch - uses: actions/checkout@v2 - - - name: Cache - uses: coursier/cache-action@v6 + uses: actions/checkout@v3 - - name: Setup Java and Scala - uses: olafurpg/setup-scala@v10 + - uses: actions/setup-java@v3 with: - java-version: adopt@1.8 + distribution: 'temurin' + java-version: '8' + cache: 'sbt' - name: Run mdoc run: sbt "docs/mdoc" @@ -84,21 +86,19 @@ jobs: publish: name: Publish needs: [documentation, build] - if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v')) + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || (github.ref == 'refs/heads/main')) runs-on: ubuntu-latest steps: - name: Checkout current branch (full) - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Setup Java and Scala - uses: olafurpg/setup-scala@v10 + - uses: actions/setup-java@v3 with: - java-version: adopt@1.8 - - - name: Cache - uses: coursier/cache-action@v6 + distribution: 'temurin' + java-version: '8' + cache: 'sbt' - name: Download compilation cache uses: actions/download-artifact@v2 @@ -110,8 +110,7 @@ jobs: - name: Publish ${{ github.ref }} run: | - echo $PGP_SECRET | base64 --decode | gpg --import --no-tty --batch --yes - sbt 'pullRemoteCache; release' + sbt 'pullRemoteCache; ci-release' env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} PGP_SECRET: ${{ secrets.PGP_SECRET }} @@ -125,7 +124,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout current branch (full) - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 lfs: true diff --git a/.sbtopts b/.sbtopts new file mode 100644 index 00000000..224cb189 --- /dev/null +++ b/.sbtopts @@ -0,0 +1,3 @@ +-J-Xms2g +-J-Xmx4g +-J-XX:MaxMetaspaceSize=512m diff --git a/README.md b/README.md index 1d5f029d..c843307e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ # Weaver-test A test-framework built on [cats-effect](https://github.com/typelevel/cats-effect) and -[fs2](https://github.com/functional-streams-for-scala/fs2), with [zio](https://zio.dev) and [monix](https://monix.io) interop. +[fs2](https://github.com/functional-streams-for-scala/fs2) ## Installation @@ -23,24 +23,8 @@ Refer yourself to the [releases](https://github.com/disneystreaming/weaver-test/ libraryDependencies += "com.disneystreaming" %% "weaver-cats" % "x.y.z" % Test testFrameworks += new TestFramework("weaver.framework.CatsEffect") -// optionally (for ZIO usage) -libraryDependencies += "com.disneystreaming" %% "weaver-zio" % "x.y.z" % Test -testFrameworks += new TestFramework("weaver.framework.ZIO") - -// optionally (for Monix usage) -libraryDependencies += "com.disneystreaming" %% "weaver-monix" % "x.y.z" % Test -testFrameworks += new TestFramework("weaver.framework.Monix") - -// optionally (for Monix BIO usage) -libraryDependencies += "com.disneystreaming" %% "weaver-monix-bio" % "x.y.z" % Test -testFrameworks += new TestFramework("weaver.framework.MonixBIO") - // optionally (for Scalacheck usage) libraryDependencies += "com.disneystreaming" %% "weaver-scalacheck" % "x.y.z" % Test - -// optionally (for specs2 interop) -libraryDependencies += "com.disneystreaming" %% "weaver-specs2" % "x.y.z" % Test - ``` ## Motivation @@ -132,20 +116,11 @@ object MySuite extends IOSuite { Weaver also includes support for -- `ZIO`-based suites via the optional `weaver-zio` dependency -- `Monix`-based suites via the optional `weaver-monix` dependency -- `Monix BIO`-based suites via the optional `weaver-monix-bio` dependency | Alias | Suite name | Provided by | Use case | | ----------------- | ------------------------ | ------------------ | --------------------------------------------- | | `SimpleIOSuite` | `SimpleMutableIOSuite` | `weaver-cats` | Each test is a standalone `IO` action | | `IOSuite` | `MutableIOSuite` | `weaver-cats` | Each test needs access to a shared `Resource` | -| `SimpleZIOSuite` | `SimpleMutableZIOSuite` | `weaver-zio` | Each test is a standalone `ZIO` action | -| `ZIOSuite[R]` | `MutableZIOSuite[R]` | `weaver-zio` | Each test needs access to a shared `ZLayer` | -| `SimpleTaskSuite` | `SimpleMutableTaskSuite` | `weaver-monix` | Each test is a standalone `Task` action | -| `TaskSuite` | `MutableTaskSuite` | `weaver-monix` | Each test needs access to a shared `Resource` | -| `SimpleIOSuite` | `SimpleMutableIOSuite` | `weaver-monix-bio` | Each test is a standalone `Task` action | -| `IOSuite` | `MutableIOSuite` | `weaver-monix-bio` | Each test needs access to a shared `Resource` | ### Expectations (assertions) diff --git a/build.sbt b/build.sbt index 8e9c00f8..228a25c8 100644 --- a/build.sbt +++ b/build.sbt @@ -38,27 +38,17 @@ sonatypeCredentialHost := "s01.oss.sonatype.org" val Version = new { object CE3 { - val fs2 = "3.2.12" - val cats = "3.3.14" - val zioInterop = "3.2.9.1" + val fs2 = "3.3.0" + val cats = "3.3.14" } - object CE2 { - val fs2 = "2.5.11" - val cats = "2.5.5" - val zioInterop = "2.5.1.0" - } - - val expecty = "0.15.4" + val expecty = "0.16.0" val portableReflect = "1.1.2" val junit = "4.13.2" val scalajsStubs = "1.1.0" - val specs2 = "4.16.1" val discipline = "1.5.1" val catsLaws = "2.8.0" val scalacheck = "1.16.0" - val monix = "3.4.1" - val monixBio = "1.2.0" val testInterface = "1.0" val scalaJavaTime = "2.4.0" val scalajsMacroTask = "1.0.0" @@ -73,7 +63,6 @@ lazy val allModules = Seq( core.projectRefs, framework.projectRefs, scalacheck.projectRefs, - specs2.projectRefs, discipline.projectRefs, intellijRunner.projectRefs, effectCores, @@ -82,18 +71,11 @@ lazy val allModules = Seq( def catsEffectDependencies(proj: Project): Project = { proj.settings( - libraryDependencies ++= { - if (virtualAxes.value.contains(CatsEffect2Axis)) - Seq( - "co.fs2" %%% "fs2-core" % Version.CE2.fs2, - "org.typelevel" %%% "cats-effect" % Version.CE2.cats - ) - else - Seq( - "co.fs2" %%% "fs2-core" % Version.CE3.fs2, - "org.typelevel" %%% "cats-effect" % Version.CE3.cats - ) - } + libraryDependencies ++= + Seq( + "co.fs2" %%% "fs2-core" % Version.CE3.fs2, + "org.typelevel" %%% "cats-effect" % Version.CE3.cats + ) ) } @@ -139,15 +121,15 @@ val allEffectCoresFilter: ScopeFilter = val allIntegrationsCoresFilter: ScopeFilter = ScopeFilter( inProjects( - (scalacheck.projectRefs ++ specs2.projectRefs ++ discipline.projectRefs): _*), + (scalacheck.projectRefs ++ discipline.projectRefs): _*), inConfigurations(Compile) ) lazy val docs = projectMatrix .in(file("modules/docs")) - .jvmPlatform(WeaverPlugin.supportedScala2Versions) + .jvmPlatform(Seq(WeaverPlugin.scala213)) .enablePlugins(DocusaurusPlugin, MdocPlugin) - .dependsOn(core, scalacheck, cats, zio, monix, monixBio, specs2, discipline) + .dependsOn(core, scalacheck, cats, discipline) .settings( moduleName := "docs", watchSources += (ThisBuild / baseDirectory).value / "docs", @@ -155,11 +137,11 @@ lazy val docs = projectMatrix "VERSION" -> version.value ), libraryDependencies ++= Seq( - "org.http4s" %% "http4s-dsl" % "0.21.0", - "org.http4s" %% "http4s-blaze-server" % "0.21.0", - "org.http4s" %% "http4s-blaze-client" % "0.21.0", + "org.http4s" %% "http4s-dsl" % "0.23.12", + "org.http4s" %% "http4s-blaze-server" % "0.23.12", + "org.http4s" %% "http4s-blaze-client" % "0.23.12", "com.lihaoyi" %% "fansi" % "0.2.7", - "org.typelevel" %% "cats-kernel-laws" % "2.4.2" + "org.typelevel" %% "cats-kernel-laws" % "2.8.0" ), Compile / sourceGenerators += Def.taskDyn { val filePath = @@ -175,17 +157,11 @@ lazy val docs = projectMatrix case a: VirtualAxis.ScalaVersionAxis => a }.get.scalaVersion - val CE = axes.collectFirst { - case CatsEffect2Axis => "CE2" - case CatsEffect3Axis => "CE3" - }.get - List( s"name = ${q(name)}", s"jvm = $isJVM", s"js = $isJS", s"scalaVersion = ${q(scalaVersion)}", - s"catsEffect = $CE", s"version = ${q(ver)}" ).mkString("Artifact(", ",", ")") }.mkString("List(", ",\n", ")") @@ -194,13 +170,7 @@ lazy val docs = projectMatrix val integrations = process(projectsWithAxes.all(allIntegrationsCoresFilter).value) - val artifactsCE2Version = (cats.finder( - VirtualAxis.jvm, - CatsEffect2Axis).apply(scala213) / version).value - - val artifactsCE3Version = (cats.finder( - VirtualAxis.jvm, - CatsEffect3Axis).apply(scala213) / version).value + val artifactsCE3Version = (cats.jvm(scala213) / version).value IO.write( filePath, @@ -209,7 +179,6 @@ lazy val docs = projectMatrix | | object BuildMatrix { | val catsEffect3Version = ${q(Version.CE3.cats)} - | val artifactsCE2Version = ${q(artifactsCE2Version)} | val artifactsCE3Version = ${q(artifactsCE3Version)} | val effects = $effects | val integrations = $integrations @@ -232,10 +201,15 @@ lazy val framework = projectMatrix "org.scala-sbt" % "test-interface" % Version.testInterface, "org.scala-js" %%% "scalajs-stubs" % Version.scalajsStubs % "provided" cross CrossVersion.for3Use2_13 ) - else + else if (virtualAxes.value.contains(VirtualAxis.js)) Seq( "org.scala-js" %% "scalajs-test-interface" % scalaJSVersion cross CrossVersion.for3Use2_13 ) + else if (virtualAxes.value.contains(VirtualAxis.native)) + Seq( + "org.scala-native" %%% "test-interface" % nativeVersion + ) + else Seq.empty } ++ Seq("junit" % "junit" % Version.junit) ) .settings(WeaverPlugin.simpleLayout) @@ -248,28 +222,13 @@ lazy val scalacheck = projectMatrix .settings( testFrameworks := Seq(new TestFramework("weaver.framework.CatsEffect")), libraryDependencies ++= Seq( - "org.scalacheck" %%% "scalacheck" % Version.scalacheck - ) ++ Seq( - "org.typelevel" %%% "cats-effect-testkit" % Version.CE3.cats % Test).filter( - _ => virtualAxes.value.contains(CatsEffect3Axis)) - ) - -lazy val specs2 = projectMatrix - .in(file("modules/specs2")) - .sparse(withCE3 = true, withJS = true, withScala3 = false) - .dependsOn(core, cats % "test->compile") - .settings( - name := "specs2", - testFrameworks := Seq(new TestFramework("weaver.framework.CatsEffect")), - libraryDependencies ++= Seq( - "org.specs2" %%% "specs2-matcher" % Version.specs2 - ) + "org.scalacheck" %%% "scalacheck" % Version.scalacheck, + "org.typelevel" %%% "cats-effect-testkit" % Version.CE3.cats % Test) ) - .settings(WeaverPlugin.simpleLayout) lazy val discipline = projectMatrix .in(file("modules/discipline")) - .sparse(withCE3 = true, withJS = true, withScala3 = true) + .sparse(withJS = true, withScala3 = true) .dependsOn(core, cats) .settings( name := "discipline", @@ -286,7 +245,7 @@ lazy val discipline = projectMatrix // ################################################################################################# lazy val effectCores: Seq[ProjectReference] = - coreCats.projectRefs ++ coreMonix.projectRefs ++ coreZio.projectRefs ++ coreMonixBio.projectRefs + coreCats.projectRefs lazy val coreCats = projectMatrix .in(file("modules/core/cats")) @@ -296,59 +255,11 @@ lazy val coreCats = projectMatrix .settings(scalaJSMacroTask) .settings(name := "cats-core") -lazy val coreMonix = projectMatrix - .in(file("modules/core/monix")) - .sparse(withCE3 = false, withJS = true, withScala3 = true) - .dependsOn(core) - .settings(WeaverPlugin.simpleLayout) - .settings( - name := "monix-core", - libraryDependencies ++= Seq( - "io.monix" %%% "monix" % Version.monix - ) - ) - -lazy val coreMonixBio = projectMatrix - .in(file("modules/core/monixBio")) - .sparse(withCE3 = false, withJS = true, withScala3 = true) - .dependsOn(core) - .settings(WeaverPlugin.simpleLayout) - .settings( - name := "monix-bio-core", - libraryDependencies ++= Seq( - "io.monix" %%% "monix-bio" % Version.monixBio - ) - ) - -lazy val coreZio = projectMatrix - .in(file("modules/core/zio")) - .sparse(withCE3 = true, withJS = true, withScala3 = false) - .dependsOn(core) - .settings(WeaverPlugin.simpleLayout) - .settings( - name := "zio-core", - libraryDependencies ++= { - if (virtualAxes.value.contains(CatsEffect3Axis)) - Seq( - "dev.zio" %%% "zio-interop-cats" % Version.CE3.zioInterop - ) - else - Seq( - "dev.zio" %%% "zio-interop-cats" % Version.CE2.zioInterop - ) - } - ) - // ################################################################################################# // Effect-specific frameworks // ################################################################################################# -lazy val effectFrameworks: Seq[ProjectReference] = Seq( - cats.projectRefs, - monix.projectRefs, - monixBio.projectRefs, - zio.projectRefs -).flatten +lazy val effectFrameworks: Seq[ProjectReference] = cats.projectRefs lazy val cats = projectMatrix .in(file("modules/framework/cats")) @@ -360,43 +271,12 @@ lazy val cats = projectMatrix testFrameworks := Seq(new TestFramework("weaver.framework.CatsEffect")) ) -lazy val monix = projectMatrix - .in(file("modules/framework/monix")) - .sparse(withCE3 = false, withJS = true, withScala3 = true) - .dependsOn(framework, coreMonix) - .settings(WeaverPlugin.simpleLayout) - .settings( - name := "monix", - testFrameworks := Seq(new TestFramework("weaver.framework.Monix")) - ) - -lazy val monixBio = projectMatrix - .in(file("modules/framework/monix-bio")) - .sparse(withCE3 = false, withJS = true, withScala3 = true) - .dependsOn(framework, coreMonixBio) - .settings(WeaverPlugin.simpleLayout) - .settings( - name := "monix-bio", - testFrameworks := Seq(new TestFramework("weaver.framework.MonixBIO")) - ) - -lazy val zio = projectMatrix - .in(file("modules/framework/zio")) - .sparse(withCE3 = true, withJS = true, withScala3 = false) - .dependsOn(framework, coreZio, scalacheck % "test->compile") - .settings(WeaverPlugin.simpleLayout) - .settings( - name := "zio", - testFrameworks := Seq(new TestFramework("weaver.framework.ZIO")), - libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % Version.scalaJavaTime % Test - ) - // ################################################################################################# // Intellij // ################################################################################################# lazy val intellijRunner = projectMatrix - .sparse(withCE3 = false, withJS = false, withScala3 = false) + .sparse(withJS = false, withScala3 = false) .in(file("modules/intellij-runner")) .dependsOn(core, framework, framework % "test->compile") .settings(WeaverPlugin.simpleLayout) diff --git a/docs/discipline.md b/docs/discipline.md index 02a83bf6..66423cee 100644 --- a/docs/discipline.md +++ b/docs/discipline.md @@ -39,5 +39,5 @@ object DisciplineTests extends FunSuite with Discipline { ``` ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(DisciplineTests).unsafeRunSync()) +println(weaver.docs.Output.runSuites(DisciplineTests)) ``` diff --git a/docs/expectations.md b/docs/expectations.md index 83b36108..622cbeff 100644 --- a/docs/expectations.md +++ b/docs/expectations.md @@ -198,7 +198,7 @@ object ExpectationsSuite extends SimpleIOSuite { ``` ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(ExpectationsSuite).unsafeRunSync()) +println(weaver.docs.Output.runSuites(ExpectationsSuite)) ``` ## Tracing locations of failed expectations @@ -223,5 +223,5 @@ object TracingSuite extends SimpleIOSuite { ``` ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(TracingSuite).unsafeRunSync()) +println(weaver.docs.Output.runSuites(TracingSuite)) ``` diff --git a/docs/funsuite.md b/docs/funsuite.md index 517375c0..7bb53084 100644 --- a/docs/funsuite.md +++ b/docs/funsuite.md @@ -21,21 +21,5 @@ object CatsFunSuite extends weaver.FunSuite { ``` ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(CatsFunSuite).unsafeRunSync()) -``` - -A `FunSuite` alias is provided in each of the frameworks supported by weaver: - -```scala mdoc -object MonixFunSuite extends weaver.monixcompat.FunSuite { - test("asserts") { expect(Some(5).contains(5)) } -} - -object MonixBIOFunSuite extends weaver.monixbiocompat.FunSuite { - test("asserts") { expect(Some(5).contains(5)) } -} - -object ZioBIOFunSuite extends weaver.ziocompat.FunSuite { - test("asserts") { expect(Some(5).contains(5)) } -} +println(weaver.docs.Output.runSuites(CatsFunSuite)) ``` diff --git a/docs/global_resources.md b/docs/global_resources.md index 8fec8043..27dfd0f5 100644 --- a/docs/global_resources.md +++ b/docs/global_resources.md @@ -18,11 +18,6 @@ NB : the implementations have to be static objects. ```scala mdoc import weaver._ -// The same API / developer experience is offered with any of the following imports : -// import weaver.monixcompat._ -// import weaver.monixbiocompat._ -// import weaver.ziocompat._ - import cats.effect.IO import cats.effect.Resource @@ -129,7 +124,7 @@ class MyOtherSuite(global: GlobalRead) extends IOSuite { def sharedResource: Resource[IO, String] = sharedResourceOrFallback(global) - test("oops, forgot something here") { sharedString => + test("oops, forgot something here") { sharedString => IO(expect(sharedString == "hello world!")) } } diff --git a/docs/installation.md b/docs/installation.md index 19a5919a..d8899736 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -3,9 +3,7 @@ id: installation title: Installation --- -All of the artifacts below are available for both **JVM and Scala.js**. - -Note, that artifacts that use Cats Effect 3 are published under a different version to those published for Cats Effect 2 (minor version bump), because they're binary incompatible. +The artifacts below are available for **Scala JVM, Scala.js, Scala-native**. ```scala mdoc:passthrough import weaver.docs._ @@ -14,11 +12,11 @@ import BuildMatrix._ val effectsTable = Table .create("Effect types", effects) - .render(catsEffect3Version, artifactsCE2Version, artifactsCE3Version) + .render(artifactsCE3Version) val integrationsTable = Table .create("Integrations", integrations) - .render(catsEffect3Version, artifactsCE2Version, artifactsCE3Version) + .render(artifactsCE3Version) println(effectsTable) println(integrationsTable) @@ -30,6 +28,3 @@ the effect-type library you've elected to use (or test against). Refer yourself to the library specific pages to get the correct configuration. - [cats](cats_effect_usage.md) -- [monix](monix_usage.md) -- [monix-bio](monix_bio_usage.md) -- [zio](zio_usage.md) diff --git a/docs/intellij.md b/docs/intellij.md index 129b0e8e..2e94a977 100644 --- a/docs/intellij.md +++ b/docs/intellij.md @@ -11,7 +11,7 @@ We (the maintainers) had tried to build an IntelliJ plugin. It worked but its ma ## Installation -Ensure the JUnit plugin is enabled in IntelliJ. Nothing else is needed (as long as weaver is declared correctly in your build). +Ensure the JUnit plugin is enabled in IntelliJ. Nothing else is needed (as long as weaver is declared correctly in your build). ## Usage @@ -46,7 +46,7 @@ object MyIgnoreSuite extends SimpleIOSuite { A `.only` extension method is provided on strings, and can be used when declaring tests. When at least one test is "tagged" as such in a suite, weaver will ignore all tests but the ones that have the "only" tag. Note: `.ignore` has precedence over `.only`. -```scala mdoc   +```scala mdoc import weaver._ import cats.effect._ diff --git a/docs/logging.md b/docs/logging.md index 49ce1dc2..1ee6c14e 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -35,5 +35,5 @@ object LoggedTests extends IOSuite { ``` ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(LoggedTests).unsafeRunSync()) +println(weaver.docs.Output.runSuites(LoggedTests)) ``` diff --git a/docs/monix_bio_usage.md b/docs/monix_bio_usage.md deleted file mode 100644 index 328569f1..00000000 --- a/docs/monix_bio_usage.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -id: monix_bio -title: Monix BIO usage ---- - -Testing Monix BIO programs is very similar to testing regular Monix programs. - -Tests must return `monix.bio.Task[Expectation]` instances. - -Note that in Monix BIO `Task[A]` [is an alias](https://bio.monix.io/docs/introduction) for the `IO[Throwable, A]` effect. - -## Installation - -You'll need to install an additional dependency in order to use weaver to test Monix BIO rograms. - -### SBT -```scala -libraryDependencies += "com.disneystreaming" %% "weaver-monix-bio" % "@VERSION@" % Test -testFrameworks += new TestFramework("weaver.framework.MonixBIO") -``` - -### Mill -```scala -object test extends Tests { - def ivyDeps = Agg( - ivy"com.disneystreaming::weaver-monix-bio:@VERSION@" - ) - def testFramework = "weaver.framework.MonixBIO" -} -``` - -## Usage - -For basic usage, simply extend `SimpleIOSuite`. Porting the example test, for instance: - -```scala mdoc -import monix.bio.Task -import weaver.monixbiocompat._ - -object MySuite extends SimpleIOSuite { - - val randomUUID = Task(java.util.UUID.randomUUID()) // Use of `Task` instead of `IO` - - test("hello side-effects") { - for { - x <- randomUUID - y <- randomUUID - } yield expect(x != y) - } - -} -``` - -## Usage with shared resources - -Monix BIO program tests make use of shared resorces in the same way as Cats Effect program tests. - -Extend `IOSuite`, implementing the `sharedResource` member. For example: - -```scala mdoc -import monix.bio.Task -import weaver.monixbiocompat._ -import cats.effect._ - -// Using http4s -import org.http4s.client.blaze._ -import org.http4s.client._ - -object HttpSuite extends IOSuite { - - // Sharing a single http client across all tests - override type Res = Client[Task] - override def sharedResource : Resource[Task, Res] = - BlazeClientBuilder[Task](scheduler).resource - - // The test receives the shared client as an argument - test("Good requests lead to good results") { httpClient => - for { - statusCode <- httpClient.get("https://httpbin.org/get"){ - response => Task.pure(response.status.code) - } - } yield expect(statusCode == 200) - } - - test("Bad requests lead to bad results") { httpClient => - for { - statusCode <- httpClient.get("https://httpbin.org/oops"){ - response => Task.pure(response.status.code) - } - } yield expect(statusCode == 404) - } - - -} -``` diff --git a/docs/monix_usage.md b/docs/monix_usage.md deleted file mode 100644 index 3a1fea3a..00000000 --- a/docs/monix_usage.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -id: monix -title: Monix usage ---- - -## Installation - -You'll need to install an additional dependency in order to use weaver to test Monix programs. - -### SBT -```scala -libraryDependencies += "com.disneystreaming" %% "weaver-monix" % "@VERSION@" % Test -testFrameworks += new TestFramework("weaver.framework.Monix") -``` - -### Mill -```scala -object test extends Tests { - def ivyDeps = Agg( - ivy"com.disneystreaming::weaver-monix:@VERSION@" - ) - def testFramework = "weaver.framework.Monix" -} -``` - -## Usage - -Testing Monix programs is practically the same as testing Cats Effect programs. - -For basic usage, simply extend `SimpleTaskSuite`. Porting the example test, for instance: - -```scala mdoc -import monix.eval.Task -import weaver.monixcompat._ - -object MySuite extends SimpleTaskSuite { - - val randomUUID = Task(java.util.UUID.randomUUID()) // Use of `Task` instead of `IO` - - test("hello side-effects") { - for { - x <- randomUUID - y <- randomUUID - } yield expect(x != y) - } - -} -``` - -## Usage with shared resources - -Monix program tests make use of shared resorces in the same way as Cats Effect program tests. - -Extend `TaskSuite`, implementing the `sharedResource` member. For example: - -```scala mdoc -import monix.eval.Task -import weaver.monixcompat._ -import cats.effect._ - -// Using http4s -import org.http4s.client.blaze._ -import org.http4s.client._ - -object HttpSuite extends TaskSuite { - - // Sharing a single http client across all tests - override type Res = Client[Task] - override def sharedResource : Resource[Task, Res] = - BlazeClientBuilder[Task](scheduler).resource - - // The test receives the shared client as an argument - test("Good requests lead to good results") { httpClient => - for { - statusCode <- httpClient.get("https://httpbin.org/get"){ - response => Task.pure(response.status.code) - } - } yield expect(statusCode == 200) - } - - test("Bad requests lead to bad results") { httpClient => - for { - statusCode <- httpClient.get("https://httpbin.org/oops"){ - response => Task.pure(response.status.code) - } - } yield expect(statusCode == 404) - } - - -} -``` diff --git a/docs/motivation.md b/docs/motivation.md index 705e9de5..745e14ab 100644 --- a/docs/motivation.md +++ b/docs/motivation.md @@ -5,7 +5,7 @@ title: Motivation ## A test framework for integration tests -Weaver was built for integration/end-to-end tests. It aims at making tests faster and make issues easier to debug, by treating effect types (IO/Task/ZIO) as first-class citizens. +Weaver was built for integration/end-to-end tests. It aims at making tests faster and make issues easier to debug, by treating effect types as first-class citizens. ## History diff --git a/docs/multiple_suites_failures.md b/docs/multiple_suites_failures.md index fd56aeb1..38776181 100644 --- a/docs/multiple_suites_failures.md +++ b/docs/multiple_suites_failures.md @@ -35,5 +35,5 @@ object MyAnotherSuite extends SimpleIOSuite { The report looks like this: ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(MySuite, MyAnotherSuite).unsafeRunSync()) +println(weaver.docs.Output.runSuites(MySuite, MyAnotherSuite)) ``` diff --git a/docs/multiple_suites_logging.md b/docs/multiple_suites_logging.md index af008a6d..3137ba00 100644 --- a/docs/multiple_suites_logging.md +++ b/docs/multiple_suites_logging.md @@ -26,14 +26,13 @@ object MySuite extends SimpleIOSuite { } object MyAnotherSuite extends SimpleIOSuite { - import java.util.concurrent.TimeUnit import scala.util.Random.alphanumeric val randomString = IO(alphanumeric.take(10).mkString("")) loggedTest("failure should print logs") { log => for { - currentTime <- timer.clock.realTime(TimeUnit.SECONDS) + currentTime <- IO.realTime.map(_.toSeconds) context = Map("time" -> currentTime.toString, "purpose" -> "docs") _ <- log.info("Starting the test...", context) x <- randomString @@ -46,5 +45,5 @@ object MyAnotherSuite extends SimpleIOSuite { The report would look something like this: ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(MySuite, MyAnotherSuite).unsafeRunSync()) +println(weaver.docs.Output.runSuites(MySuite, MyAnotherSuite)) ``` diff --git a/docs/multiple_suites_success.md b/docs/multiple_suites_success.md index b8f03f2d..873c9924 100644 --- a/docs/multiple_suites_success.md +++ b/docs/multiple_suites_success.md @@ -42,5 +42,5 @@ object MyAnotherSuite extends SimpleIOSuite { The report looks like this: ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(MySuite, MyAnotherSuite).unsafeRunSync()) +println(weaver.docs.Output.runSuites(MySuite, MyAnotherSuite)) ``` diff --git a/docs/other_effects.md b/docs/other_effects.md new file mode 100644 index 00000000..ca2d42f3 --- /dev/null +++ b/docs/other_effects.md @@ -0,0 +1,23 @@ +--- +id: other_effects + +title: Other effects +--- + +Starting with version 0.8.0, Weaver no longer offers out-of-the-box support for other effect types +than cats-effect. + +We (maintainers) are happy to keep the core of weaver effect-agnostic, in an effort to allow for third party +to resurrect support for the effect types they use in repository they control. + +You can read about the rationale for decision [here](https://github.com/disneystreaming/weaver-test/discussions/570). Feel free to ping us via a github discussion, if you seek to resurrect support for a given effect-type. + +If you are looking for documentation of the maintenance branch of weaver that did support other effect types, you can find it [over there](https://disneystreaming.github.io/weaver-test/docs/0.6.15/installation). + +We will try to fix critical bugs on the 0.6/0.7 series, as they get found. + +To summarise : + +* `0.8.x` and further -> CE3 only, active development, +* `0.7.x` -> CE3 + ZIO 1 support (maintenance mode, fixing critical bugs that not have workarounds) +* `0.6.x` -> CE2 + Monix/MonixBIO/ZIO 1 support (maintenance mode, fixing critical bugs that do not have workarounds) diff --git a/docs/resources.md b/docs/resources.md index eaeb0c5c..e7c98f98 100644 --- a/docs/resources.md +++ b/docs/resources.md @@ -13,16 +13,15 @@ import weaver._ import cats.effect._ // Using http4s -import org.http4s.client.blaze._ +import org.http4s.blaze.client._ import org.http4s.client._ -import scala.concurrent.ExecutionContext.global object HttpSuite extends IOSuite { // Sharing a single http client across all tests override type Res = Client[IO] override def sharedResource : Resource[IO, Res] = - BlazeClientBuilder[IO](global).resource + BlazeClientBuilder[IO].resource // The test receives the shared client as an argument test("Good requests lead to good results") { httpClient => @@ -77,7 +76,7 @@ object ResourceDemo extends IOSuite { ``` ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(ResourceDemo).unsafeRunSync()) +println(weaver.docs.Output.runSuites(ResourceDemo)) println("Contents of `order` are:\n") println("```") diff --git a/docs/scalacheck.md b/docs/scalacheck.md index a1afe8ce..b3c4a5a1 100644 --- a/docs/scalacheck.md +++ b/docs/scalacheck.md @@ -77,5 +77,5 @@ object ForallExamples extends SimpleIOSuite with Checkers { ``` ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(ForallExamples).unsafeRunSync()) +println(weaver.docs.Output.runSuites(ForallExamples)) ``` diff --git a/docs/specs2.md b/docs/specs2.md index 45346da5..420b957d 100644 --- a/docs/specs2.md +++ b/docs/specs2.md @@ -1,77 +1,6 @@ --- id: specs2 -title: specs2 integration +title: specs2 (discontinued) --- -Weaver comes with [specs2](http://specs2.org/) matchers interop, allowing for matcher style testing. - -## Installation - -You'll need to install an additional dependency in order to use specs2 matchers with Weaver. - -### SBT -```scala -libraryDependencies += "com.disneystreaming" %% "weaver-specs2" % "@VERSION@" % Test -``` - -### Mill -```scala -object test extends Tests { - def ivyDeps = Agg( - ivy"com.disneystreaming::weaver-specs2:@VERSION@" - ) -} -``` - -## Usage - -Add the `weaver.specs2compat.IOMatchers` mixin to use specs2 matchers within your test suite. - -```scala mdoc -import weaver.SimpleIOSuite - -import weaver.specs2compat.IOMatchers - -object MatchersSpec extends SimpleIOSuite with IOMatchers { - pureTest("pureTest { 1 must beEqualTo(1) }") { - 1 must beEqualTo(1) - } - - pureTest("pureTest { 1 must be_==(1) }") { - 1 must be_==(1) - } - - pureTest("pureTest { 1 mustEqual 1 }") { - 1 mustEqual 1 - } - - pureTest("pureTest { 1 === 1 }") { - 1 === 1 - } - - pureTest("pureTest { 1 must beEqualTo(1) }") { - 1 must beEqualTo(1) - } - - pureTest("pureTest { 1 must be_==(1) }") { - 1 must be_==(1) - } - - pureTest("pureTest { 1 mustEqual 1 }") { - 1 mustEqual 1 - } - - pureTest("pureTest { 1 === 1 }") { - 1 === 1 - } - - pureTest("failure example") { - 1 must beEqualTo(2) - } -} - -``` - -```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(MatchersSpec).unsafeRunSync()) -``` +The specs2-matchers integration has been dropped in weaver 0.8.0. If you want to test CE code with specs2 UX, please consider using [cats-effect-testing](https://github.com/typelevel/cats-effect-testing). diff --git a/docs/tagging.md b/docs/tagging.md index abd0b9cb..1a3f7295 100644 --- a/docs/tagging.md +++ b/docs/tagging.md @@ -34,5 +34,5 @@ object TaggingSuite extends SimpleIOSuite { ``` ```scala mdoc:passthrough -println(weaver.docs.Output.runSuites(TaggingSuite).unsafeRunSync()) +println(weaver.docs.Output.runSuites(TaggingSuite)) ``` diff --git a/docs/zio_usage.md b/docs/zio_usage.md deleted file mode 100644 index 10060aaa..00000000 --- a/docs/zio_usage.md +++ /dev/null @@ -1,118 +0,0 @@ ---- -id: zio -title: ZIO usage ---- - -## Installation - -You'll need to install an additional dependency in order to use weaver to test ZIO programs. - -### SBT -```scala -libraryDependencies += "com.disneystreaming" %% "weaver-zio" % "@VERSION@" % Test -testFrameworks += new TestFramework("weaver.framework.ZIO") -``` - -### Mill -```scala -object test extends Tests { - def ivyDeps = Agg( - ivy"com.disneystreaming::weaver-zio:@VERSION@" - ) - def testFramework = "weaver.framework.ZIO" -} -``` - -## Usage - -Start with the following imports : - -```scala mdoc -import weaver.ziocompat._ -``` - -Weaver tries to respect ZIO's idiomatic usage, and leverages the [ZLayer](https://zio.dev/docs/howto/howto_use_layers) construct to provide environment. `ZLayer` can be trivially constructed from a `Managed` (the zio counterpart to `cats.effect.Resource`), and therefore encompasses `beforeAndAfterAll` semantics. - -Assuming the following module : - -```scala mdoc -import zio._ -import org.http4s.client.blaze._ -import org.http4s.client._ - -object modules { - type Http = Has[Client[Task]] - object Http { - def get(uri : String) : RIO[modules.Http, Int] = - ZIO.accessM(_.get.get(uri)(response => UIO(response.status.code))) - } -} -import modules._ -``` - -declare a shared layer as such : - -```scala mdoc -// Needed to instantiate an http4s client against ZIO -import zio.interop.catz._ - -// ZIOSuite requires the type of the shared layer to be defined as a type -// parameter. That is because zio needs to implicitly derive some type-tag -// instance for its built-in dependency injection mechanism to work. -// -// NB, the default environment ZEnv is provided by default. -// -object HttpSuite extends ZIOSuite[Http] { - - // Sharing a single layer across all tests - override val sharedLayer : ZLayer[ZEnv, Throwable, Http] = - ZLayer.fromManaged { - val makeHttpClient = ZIO.runtime[Any].map { implicit rts => - val exec = rts.platform.executor.asEC - BlazeClientBuilder[Task](exec).resource.toManagedZIO - } - Managed.fromEffect(makeHttpClient).flatten - } - - test("Standard test") { - for { - statusCode <- Http.get("https://httpbin.org/get") - } yield expect(statusCode == 200) - } - - test("A log module is available") { - for { - _ <- log.info("Calling https://httpbin.org/oops") - statusCode <- Http.get("https://httpbin.org/oops") - } yield expect(statusCode == 404) - } - -} -``` - -## Usage without shared layer - -If you have no need for a shared layer, you can simply use the following. -Note that you can still use side effects provided by the default environment `ZEnv`  - -```scala mdoc -import zio.duration._ - -object SimpleExample extends SimpleZIOSuite { - - pureTest("no side effect"){ - expect("hello".size == 5) - } - - test("with side effects"){ - for { - before <- zio.clock.currentDateTime - _ <- zio.clock.sleep(2.second) - after <- zio.clock.currentDateTime - } yield expect(before != after) - } - -} -``` - - diff --git a/modules/core/cats/src-ce2-js/PlatformECCompat.scala b/modules/core/cats/src-ce2-js/PlatformECCompat.scala deleted file mode 100644 index 3efcf6bf..00000000 --- a/modules/core/cats/src-ce2-js/PlatformECCompat.scala +++ /dev/null @@ -1,9 +0,0 @@ -package weaver - -import scala.concurrent.ExecutionContext - -import org.scalajs.macrotaskexecutor.MacrotaskExecutor - -private[weaver] object PlatformECCompat { - val ec: ExecutionContext = MacrotaskExecutor -} diff --git a/modules/core/cats/src-ce2-jvm/PlatformECCompat.scala b/modules/core/cats/src-ce2-jvm/PlatformECCompat.scala deleted file mode 100644 index af27b861..00000000 --- a/modules/core/cats/src-ce2-jvm/PlatformECCompat.scala +++ /dev/null @@ -1,7 +0,0 @@ -package weaver - -import scala.concurrent.ExecutionContext - -private[weaver] object PlatformECCompat { - val ec: ExecutionContext = ExecutionContext.global -} diff --git a/modules/core/cats/src-ce2/weaver/BaseIOSuites.scala b/modules/core/cats/src-ce2/weaver/BaseIOSuites.scala deleted file mode 100644 index ab0a4976..00000000 --- a/modules/core/cats/src-ce2/weaver/BaseIOSuites.scala +++ /dev/null @@ -1,18 +0,0 @@ -package weaver - -import cats.effect.{ ContextShift, IO, Timer } - -trait BaseIOSuite extends BaseCatsSuite { self: RunnableSuite[IO] => - implicit protected def effectCompat: UnsafeRun[EffectType] = CatsUnsafeRun - def unsafeRun: UnsafeRun[EffectType] = CatsUnsafeRun - final implicit protected def contextShift: ContextShift[IO] = - effectCompat.contextShift - final implicit protected def timer: Timer[IO] = effectCompat.timer - def getSuite: EffectSuite[IO] = this -} - -trait BaseFunIOSuite extends FunSuiteF[IO] with BaseCatsSuite { - override implicit protected def effectCompat: UnsafeRun[EffectType] = - CatsUnsafeRun - def getSuite: EffectSuite[IO] = this -} diff --git a/modules/core/cats/src-ce2/weaver/CatsUnsafeRun.scala b/modules/core/cats/src-ce2/weaver/CatsUnsafeRun.scala deleted file mode 100644 index fdb8df61..00000000 --- a/modules/core/cats/src-ce2/weaver/CatsUnsafeRun.scala +++ /dev/null @@ -1,29 +0,0 @@ -package weaver - -import cats.effect.{ ContextShift, IO, Timer } - -object CatsUnsafeRun extends CatsUnsafeRun - -trait CatsUnsafeRun extends UnsafeRun[IO] { - - type CancelToken = IO[Unit] - - override implicit val contextShift: ContextShift[IO] = - IO.contextShift(PlatformECCompat.ec) - - override implicit val timer: Timer[IO] = - IO.timer(PlatformECCompat.ec) - - override implicit val effect = IO.ioConcurrentEffect(contextShift) - override implicit val parallel = IO.ioParallel(contextShift) - - def background(task: IO[Unit]): CancelToken = - task.unsafeRunCancelable { _ => () } - - def cancel(token: CancelToken): Unit = sync(token) - - def sync(task: IO[Unit]): Unit = task.unsafeRunSync() - - def async(task: IO[Unit]): Unit = task.unsafeRunAsyncAndForget() - -} diff --git a/modules/core/cats/src-ce3-js/weaver/CatsUnsafeRunPlatformCompat.scala b/modules/core/cats/src-js/weaver/CatsUnsafeRunPlatformCompat.scala similarity index 78% rename from modules/core/cats/src-ce3-js/weaver/CatsUnsafeRunPlatformCompat.scala rename to modules/core/cats/src-js/weaver/CatsUnsafeRunPlatformCompat.scala index 21b31954..4275e7de 100644 --- a/modules/core/cats/src-ce3-js/weaver/CatsUnsafeRunPlatformCompat.scala +++ b/modules/core/cats/src-js/weaver/CatsUnsafeRunPlatformCompat.scala @@ -5,7 +5,7 @@ import cats.effect.IO private[weaver] trait CatsUnsafeRunPlatformCompat { self: CatsUnsafeRun => - def sync(task: IO[Unit]): Unit = ??? + def unsafeRunSync(task: IO[Unit]): Unit = ??? def background(task: IO[Unit]): CancelToken = ??? diff --git a/modules/core/cats/src-ce3-jvm/weaver/CatsUnsafeRunPlatformCompat.scala b/modules/core/cats/src-jvm/weaver/CatsUnsafeRunPlatformCompat.scala similarity index 78% rename from modules/core/cats/src-ce3-jvm/weaver/CatsUnsafeRunPlatformCompat.scala rename to modules/core/cats/src-jvm/weaver/CatsUnsafeRunPlatformCompat.scala index 8937ab72..2c3ad99d 100644 --- a/modules/core/cats/src-ce3-jvm/weaver/CatsUnsafeRunPlatformCompat.scala +++ b/modules/core/cats/src-jvm/weaver/CatsUnsafeRunPlatformCompat.scala @@ -5,7 +5,7 @@ import cats.effect.unsafe.implicits.global private[weaver] trait CatsUnsafeRunPlatformCompat { self: CatsUnsafeRun => - def sync(task: IO[Unit]): Unit = task.unsafeRunSync() + def unsafeRunSync(task: IO[Unit]): Unit = task.unsafeRunSync() def background(task: IO[Unit]): CancelToken = task.start.unsafeRunSync() diff --git a/modules/core/cats/src-native/weaver/CatsUnsafeRunPlatformCompat.scala b/modules/core/cats/src-native/weaver/CatsUnsafeRunPlatformCompat.scala new file mode 100644 index 00000000..af8c30f1 --- /dev/null +++ b/modules/core/cats/src-native/weaver/CatsUnsafeRunPlatformCompat.scala @@ -0,0 +1,20 @@ +package weaver + +import scala.concurrent.Await +import scala.concurrent.duration._ + +import cats.effect.IO +import cats.effect.unsafe.implicits.global + +private[weaver] trait CatsUnsafeRunPlatformCompat { + self: CatsUnsafeRun => + + def unsafeRunSync(task: IO[Unit]): Unit = { + val future = task.unsafeToFuture() + scalanative.runtime.loop() + Await.result(future, 1.minute) + } + + def background(task: IO[Unit]): CancelToken = ??? + +} diff --git a/modules/core/cats/src-ce3/weaver/BaseIOSuite.scala b/modules/core/cats/src/weaver/BaseIOSuite.scala similarity index 100% rename from modules/core/cats/src-ce3/weaver/BaseIOSuite.scala rename to modules/core/cats/src/weaver/BaseIOSuite.scala diff --git a/modules/core/cats/src-ce3/weaver/CatsUnsafeRun.scala b/modules/core/cats/src/weaver/CatsUnsafeRun.scala similarity index 58% rename from modules/core/cats/src-ce3/weaver/CatsUnsafeRun.scala rename to modules/core/cats/src/weaver/CatsUnsafeRun.scala index cb7230c4..72b67a3d 100644 --- a/modules/core/cats/src-ce3/weaver/CatsUnsafeRun.scala +++ b/modules/core/cats/src/weaver/CatsUnsafeRun.scala @@ -1,5 +1,7 @@ package weaver +import scala.concurrent.Future + import cats.effect.unsafe.implicits.global import cats.effect.{ FiberIO, IO } @@ -12,8 +14,9 @@ trait CatsUnsafeRun extends UnsafeRun[IO] with CatsUnsafeRunPlatformCompat { override implicit val parallel = IO.parallelForIO override implicit val effect = IO.asyncForIO - def cancel(token: CancelToken): Unit = sync(token.cancel) + def cancel(token: CancelToken): Unit = unsafeRunSync(token.cancel) - def async(task: IO[Unit]): Unit = task.unsafeRunAndForget() + def unsafeRunAndForget(task: IO[Unit]): Unit = task.unsafeRunAndForget() + def unsafeRunToFuture(task: IO[Unit]): Future[Unit] = task.unsafeToFuture() } diff --git a/modules/core/monix/src-js/PlatformCompat.scala b/modules/core/monix/src-js/PlatformCompat.scala deleted file mode 100644 index 7e67bd31..00000000 --- a/modules/core/monix/src-js/PlatformCompat.scala +++ /dev/null @@ -1,12 +0,0 @@ -package weaver -package monixcompat - -import monix.execution.Scheduler - -object PlatformCompat { - def runSync(task: monix.eval.Task[Unit])(implicit scheduler: Scheduler) = { - val _ = scheduler - } - - def defaultScheduler: Scheduler = Scheduler.global -} diff --git a/modules/core/monix/src-jvm/PlatformCompat.scala b/modules/core/monix/src-jvm/PlatformCompat.scala deleted file mode 100644 index a5ae8d0f..00000000 --- a/modules/core/monix/src-jvm/PlatformCompat.scala +++ /dev/null @@ -1,14 +0,0 @@ -package weaver -package monixcompat - -import monix.execution.Scheduler - -object PlatformCompat { - def runSync(task: monix.eval.Task[Unit])(implicit scheduler: Scheduler) = - task.runSyncUnsafe() - - def defaultScheduler: Scheduler = Scheduler.fixedPool( - "weaver-monix", - java.lang.Runtime.getRuntime().availableProcessors() - 1) - -} diff --git a/modules/core/monix/src/weaver/monixcompat/MonixUnsafeRun.scala b/modules/core/monix/src/weaver/monixcompat/MonixUnsafeRun.scala deleted file mode 100644 index 092980c4..00000000 --- a/modules/core/monix/src/weaver/monixcompat/MonixUnsafeRun.scala +++ /dev/null @@ -1,32 +0,0 @@ -package weaver -package monixcompat - -import cats.effect.{ ContextShift, Timer } - -import monix.eval.Task -import monix.execution.{ Cancelable, Scheduler } - -object MonixUnsafeRun extends UnsafeRun[Task] { - - type CancelToken = Cancelable - - implicit val scheduler: Scheduler = PlatformCompat.defaultScheduler - - override implicit val contextShift: ContextShift[Task] = - Task.contextShift(scheduler) - override implicit val timer: Timer[Task] = - Task.timer(scheduler) - - override implicit val effect = Task.catsEffect(scheduler) - override implicit val parallel = Task.catsParallel - - def background(task: Task[Unit]): Cancelable = - task.runAsync { _ => () }(scheduler) - - def cancel(token: CancelToken): Unit = token.cancel() - - def sync(task: Task[Unit]): Unit = PlatformCompat.runSync(task) - - def async(task: Task[Unit]): Unit = task.runAsyncAndForget - -} diff --git a/modules/core/monix/src/weaver/monixcompat/TaskGlobalResource.scala b/modules/core/monix/src/weaver/monixcompat/TaskGlobalResource.scala deleted file mode 100644 index 48c9e563..00000000 --- a/modules/core/monix/src/weaver/monixcompat/TaskGlobalResource.scala +++ /dev/null @@ -1,10 +0,0 @@ -package weaver -package monixcompat - -import cats.effect.Resource - -import monix.eval.Task - -trait TaskGlobalResource extends GlobalResourceF[Task] { - def sharedResources(global: GlobalWrite): Resource[Task, Unit] -} diff --git a/modules/core/monix/src/weaver/monixcompat/package.scala b/modules/core/monix/src/weaver/monixcompat/package.scala deleted file mode 100644 index 42302d5c..00000000 --- a/modules/core/monix/src/weaver/monixcompat/package.scala +++ /dev/null @@ -1,12 +0,0 @@ -package weaver - -import monix.eval.Task - -package object monixcompat { - type TaskSuite = MutableTaskSuite - type SimpleTaskSuite = SimpleMutableTaskSuite - type FunSuite = FunTaskSuite - type GlobalResource = TaskGlobalResource - type GlobalRead = GlobalResourceF.Read[Task] - type GlobalWrite = GlobalResourceF.Write[Task] -} diff --git a/modules/core/monix/src/weaver/monixcompat/suites.scala b/modules/core/monix/src/weaver/monixcompat/suites.scala deleted file mode 100644 index b0f49f19..00000000 --- a/modules/core/monix/src/weaver/monixcompat/suites.scala +++ /dev/null @@ -1,51 +0,0 @@ -package weaver -package monixcompat - -import cats.effect.Resource - -import monix.eval.Task -import monix.execution.Scheduler - -trait BaseTaskSuite extends EffectSuite.Provider[Task] - -abstract class PureTaskSuite - extends EffectSuite[Task] - with BaseTaskSuite - with Expectations.Helpers { - - def pureTest(name: String)(run: => Expectations): Task[TestOutcome] = - Test[Task](name, Task(run)) - def simpleTest(name: String)(run: Task[Expectations]): Task[TestOutcome] = - Test[Task](name, run) - def loggedTest(name: String)( - run: Log[Task] => Task[Expectations]): Task[TestOutcome] = - Test[Task](name, run) - - def getSuite: EffectSuite[Task] = this -} - -abstract class MutableTaskSuite - extends MutableFSuite[Task] - with BaseTaskSuite - with Expectations.Helpers { - - implicit protected def effectCompat = MonixUnsafeRun - - final implicit protected def scheduler: Scheduler = MonixUnsafeRun.scheduler - def getSuite: EffectSuite[Task] = this -} - -trait SimpleMutableTaskSuite extends MutableTaskSuite { - type Res = Unit - def sharedResource: Resource[Task, Unit] = Resource.pure[Task, Unit](()) -} - -trait FunTaskSuite - extends FunSuiteF[Task] - with BaseTaskSuite - with Expectations.Helpers { - implicit protected def effectCompat = MonixUnsafeRun - - final implicit protected def scheduler: Scheduler = MonixUnsafeRun.scheduler - def getSuite: EffectSuite[Task] = this -} diff --git a/modules/core/monixBio/src-js/PlatformCompat.scala b/modules/core/monixBio/src-js/PlatformCompat.scala deleted file mode 100644 index 7e308b1a..00000000 --- a/modules/core/monixBio/src-js/PlatformCompat.scala +++ /dev/null @@ -1,13 +0,0 @@ -package weaver -package monixbiocompat - -import monix.execution.Scheduler - -object PlatformCompat { - def runSync(task: monix.bio.Task[Unit])(implicit scheduler: Scheduler) = { - val _ = scheduler - } - - def defaultScheduler: Scheduler = Scheduler.global - -} diff --git a/modules/core/monixBio/src-jvm/PlatformCompat.scala b/modules/core/monixBio/src-jvm/PlatformCompat.scala deleted file mode 100644 index 86fe9ed2..00000000 --- a/modules/core/monixBio/src-jvm/PlatformCompat.scala +++ /dev/null @@ -1,13 +0,0 @@ -package weaver -package monixbiocompat - -import monix.execution.Scheduler - -object PlatformCompat { - def runSync(task: monix.bio.Task[Unit])(implicit scheduler: Scheduler) = - task.runSyncUnsafe() - - def defaultScheduler: Scheduler = Scheduler.fixedPool( - "weaver-monix", - java.lang.Runtime.getRuntime().availableProcessors() - 1) -} diff --git a/modules/core/monixBio/src/weaver/monixbiocompat/IOGlobalResource.scala b/modules/core/monixBio/src/weaver/monixbiocompat/IOGlobalResource.scala deleted file mode 100644 index 2e4f4f4c..00000000 --- a/modules/core/monixBio/src/weaver/monixbiocompat/IOGlobalResource.scala +++ /dev/null @@ -1,10 +0,0 @@ -package weaver -package monixbiocompat - -import cats.effect.Resource - -import monix.bio.Task - -trait IOGlobalResource extends GlobalResourceF[Task] { - def sharedResources(global: GlobalWrite): Resource[Task, Unit] -} diff --git a/modules/core/monixBio/src/weaver/monixbiocompat/MonixBioUnsafeRun.scala b/modules/core/monixBio/src/weaver/monixbiocompat/MonixBioUnsafeRun.scala deleted file mode 100644 index c7ed35f4..00000000 --- a/modules/core/monixBio/src/weaver/monixbiocompat/MonixBioUnsafeRun.scala +++ /dev/null @@ -1,25 +0,0 @@ -package weaver -package monixbiocompat - -import cats.Parallel -import cats.effect.{ ContextShift, Timer } - -import monix.bio.IO -import monix.execution.{ Cancelable, Scheduler } - -object MonixBIOUnsafeRun extends UnsafeRun[monix.bio.Task] { - type CancelToken = Cancelable - implicit val scheduler: Scheduler = monix.execution.Scheduler.global - - implicit val effect = IO.catsEffect(scheduler) - implicit val parallel: Parallel[monix.bio.Task] = IO.catsParallel - implicit val contextShift: ContextShift[monix.bio.Task] = - IO.contextShift(scheduler) - implicit val timer: Timer[monix.bio.Task] = IO.timer(scheduler) - def background(task: monix.bio.Task[Unit]): CancelToken = { - task.runAsync { _ => () }(scheduler) - } - def sync(task: monix.bio.Task[Unit]): Unit = PlatformCompat.runSync(task) - def async(task: monix.bio.Task[Unit]): Unit = task.runAsyncAndForget - def cancel(token: CancelToken): Unit = token.cancel() -} diff --git a/modules/core/monixBio/src/weaver/monixbiocompat/package.scala b/modules/core/monixBio/src/weaver/monixbiocompat/package.scala deleted file mode 100644 index 95c81024..00000000 --- a/modules/core/monixBio/src/weaver/monixbiocompat/package.scala +++ /dev/null @@ -1,12 +0,0 @@ -package weaver - -import monix.bio.Task - -package object monixbiocompat { - type IOSuite = MutableIOSuite - type SimpleIOSuite = SimpleMutableIOSuite - type FunSuite = FunIOSuite - type GlobalResource = IOGlobalResource - type GlobalRead = GlobalResourceF.Read[Task] - type GlobalWrite = GlobalResourceF.Write[Task] -} diff --git a/modules/core/monixBio/src/weaver/monixbiocompat/suites.scala b/modules/core/monixBio/src/weaver/monixbiocompat/suites.scala deleted file mode 100644 index 7e914de4..00000000 --- a/modules/core/monixBio/src/weaver/monixbiocompat/suites.scala +++ /dev/null @@ -1,78 +0,0 @@ -package weaver -package monixbiocompat - -import scala.concurrent.duration.{ MILLISECONDS, _ } - -import cats.data.Chain -import cats.effect.{ Resource } -import cats.effect.concurrent.Ref - -import monix.bio.{ IO, Task } -import monix.execution.Scheduler - -trait BaseIOSuite extends EffectSuite.Provider[Task] - -/** - * Individual test runner for Monix BIO's `IO[Throwable, A]` that properly - * handles unexpected errors, - * i.e. errors that occur in another channel. - */ -object Test { - def apply( - name: String, - f: Log[Task] => Task[Expectations]): Task[TestOutcome] = - for { - ref <- Ref[Task].of(Chain.empty[Log.Entry]) - start <- ts - res <- IO - .defer(f(Log.collected[Task, Chain](ref, ts))) - .map(Result.fromAssertion) - .redeemCause(c => Result.from(c.toThrowable), identity) - end <- ts - logs <- ref.get - } yield TestOutcome(name, (end - start).millis, res, logs) - - private val ts = IO.clock.realTime(MILLISECONDS) - - def apply(name: String, f: Task[Expectations]): Task[TestOutcome] = - apply(name, (_: Log[Task]) => f) -} - -abstract class MutableIOSuite - extends MutableFSuite[Task] - with BaseIOSuite - with Expectations.Helpers { - - implicit protected def effectCompat = MonixBIOUnsafeRun - final implicit protected def scheduler: Scheduler = - MonixBIOUnsafeRun.scheduler - def getSuite: EffectSuite[Task] = this - - override def test(name: TestName): PartiallyAppliedTest = - new SubPartiallyAppliedTest(name) - - class SubPartiallyAppliedTest(name: TestName) - extends super.PartiallyAppliedTest(name) { - override def apply(run: => Task[Expectations]): Unit = - registerTest(name)(_ => Test(name.name, run)) - override def apply(run: Res => Task[Expectations]): Unit = - registerTest(name)(res => Test(name.name, run(res))) - override def apply(run: (Res, Log[Task]) => Task[Expectations]): Unit = - registerTest(name)(res => Test(name.name, log => run(res, log))) - } -} - -abstract class SimpleMutableIOSuite extends MutableIOSuite { - type Res = Unit - def sharedResource: Resource[Task, Unit] = Resource.pure[Task, Unit](()) -} - -trait FunIOSuite - extends FunSuiteF[Task] - with BaseIOSuite - with Expectations.Helpers { - implicit protected def effectCompat = MonixBIOUnsafeRun - final implicit protected def scheduler: Scheduler = - MonixBIOUnsafeRun.scheduler - def getSuite: EffectSuite[Task] = this -} diff --git a/modules/core/src-ce2-js/weaver/PlatformEffectCompat.scala b/modules/core/src-ce2-js/weaver/PlatformEffectCompat.scala deleted file mode 100644 index d1ccb01a..00000000 --- a/modules/core/src-ce2-js/weaver/PlatformEffectCompat.scala +++ /dev/null @@ -1,3 +0,0 @@ -package weaver - -trait PlatformEffectCompat[F[_]] { self: EffectCompat[F] => } diff --git a/modules/core/src-ce2-jvm/weaver/PlatformEffectCompat.scala b/modules/core/src-ce2-jvm/weaver/PlatformEffectCompat.scala deleted file mode 100644 index ee10df3b..00000000 --- a/modules/core/src-ce2-jvm/weaver/PlatformEffectCompat.scala +++ /dev/null @@ -1,14 +0,0 @@ -package weaver - -import cats.effect.{ Blocker, Resource } - -trait PlatformEffectCompat[F[_]] { self: EffectCompat[F] => - - private[weaver] def blocker[T]( - f: BlockerCompat[F] => T): Resource[F, T] = - Blocker[F].map(blocker => - new BlockerCompat[F] { - def block[A](thunk: => A): F[A] = blocker.delay(thunk) - }).map(f) - -} diff --git a/modules/core/src-ce2/weaver/CECompat.scala b/modules/core/src-ce2/weaver/CECompat.scala deleted file mode 100644 index aa5b634d..00000000 --- a/modules/core/src-ce2/weaver/CECompat.scala +++ /dev/null @@ -1,69 +0,0 @@ -package weaver - -import cats.Applicative -import cats.effect.ExitCase.{ Canceled, Completed } -import cats.effect.syntax.all._ -import cats.effect.{ Concurrent, ExitCase, Resource } -import cats.syntax.all._ - -private[weaver] object CECompat extends CECompat - -private[weaver] trait CECompat { - - private[weaver] type Effect[F[_]] = Concurrent[F] - private[weaver] type Ref[F[_], A] = cats.effect.concurrent.Ref[F, A] - private[weaver] val Ref = cats.effect.concurrent.Ref - - private[weaver] type Deferred[F[_], A] = cats.effect.concurrent.Deferred[F, A] - private[weaver] val Deferred = cats.effect.concurrent.Deferred - - private[weaver] type Semaphore[F[_]] = cats.effect.concurrent.Semaphore[F] - private[weaver] val Semaphore = cats.effect.concurrent.Semaphore - - private[weaver] def guaranteeCase[F[_]: Concurrent, A]( - fa: F[A])( - cancelled: => F[Unit], - completed: => F[Unit], - errored: Throwable => F[Unit]): F[A] = - Concurrent[F].guaranteeCase(fa) { - case Canceled => cancelled - case Completed => completed - case cats.effect.ExitCase.Error(e) => errored(e) - } - - private[weaver] def guarantee[F[_]: Concurrent, A]( - fa: F[A])(fin: F[Unit]): F[A] = - Concurrent[F].guarantee(fa)(fin) - - private[weaver] def onErrorEnsure[F[_]: Concurrent, A](r: Resource[F, A])( - f: Throwable => F[Unit]): Resource[F, A] = - r.onFinalizeCase { - case Canceled => Concurrent[F].unit - case Completed => Concurrent[F].unit - case ExitCase.Error(e) => f(e) - } - - private[weaver] def background[F[_]: Concurrent, A, B](fa: F[A], default: A)( - f: F[A] => F[B]): F[B] = - fa.background.use(f) - - private[weaver] def resourceLift[F[_]: Applicative, A]( - fa: F[A]): Resource[F, A] = Resource.eval(fa) - - private[weaver] trait Queue[F[_], A] { - protected def fs2Queue: fs2.concurrent.Queue[F, A] - - def enqueue(a: A): F[Unit] = fs2Queue.enqueue1(a) - def dequeueStream: fs2.Stream[F, A] = fs2Queue.dequeue - } - - private[weaver] object Queue { - def unbounded[F[_]: Concurrent, A] = - fs2.concurrent.Queue.unbounded[F, A].map { - q => - new Queue[F, A] { - override val fs2Queue = q - } - } - } -} diff --git a/modules/core/src-ce2/weaver/UnsafeRun.scala b/modules/core/src-ce2/weaver/UnsafeRun.scala deleted file mode 100644 index bad67cbd..00000000 --- a/modules/core/src-ce2/weaver/UnsafeRun.scala +++ /dev/null @@ -1,40 +0,0 @@ -package weaver - -import java.util.concurrent.TimeUnit - -import scala.concurrent.duration.FiniteDuration - -import cats.Parallel -import cats.effect.{ Async, Concurrent, ContextShift, Timer } - -trait EffectCompat[F[_]] extends PlatformEffectCompat[F] { - implicit def parallel: Parallel[F] - implicit def effect: Concurrent[F] - implicit def timer: Timer[F] - implicit def contextShift: ContextShift[F] - - def realTimeMillis: F[Long] = timer.clock.realTime(TimeUnit.MILLISECONDS) - def sleep(duration: FiniteDuration): F[Unit] = timer.sleep(duration) - def fromFuture[A](thunk: => scala.concurrent.Future[A]): F[A] = - Async.fromFuture(effect.delay(thunk)) - def async[A](cb: (Either[Throwable, A] => Unit) => Unit): F[A] = - effect.async(cb) -} - -/** - * Abstraction allowing for running IO constructs unsafely. - * - * This is meant to delegate to library-specific constructs for running effect - * types. - */ -trait UnsafeRun[F[_]] extends EffectCompat[F] { - - type CancelToken - - def background(task: F[Unit]): CancelToken - def cancel(token: CancelToken): Unit - - def sync(task: F[Unit]): Unit - def async(task: F[Unit]): Unit - -} diff --git a/modules/core/src-ce3/weaver/CECompat.scala b/modules/core/src-ce3/weaver/CECompat.scala deleted file mode 100644 index 8513263a..00000000 --- a/modules/core/src-ce3/weaver/CECompat.scala +++ /dev/null @@ -1,69 +0,0 @@ -package weaver - -import cats.Applicative -import cats.effect.kernel.GenConcurrent -import cats.effect.kernel.Resource.ExitCase.{ Canceled, Errored, Succeeded } -import cats.effect.syntax.all._ -import cats.effect.{ Async, Resource } -import cats.syntax.all._ - -private[weaver] object CECompat extends CECompat - -private[weaver] trait CECompat { - - private[weaver] type Effect[F[_]] = Async[F] - - private[weaver] type Ref[F[_], A] = cats.effect.kernel.Ref[F, A] - private[weaver] val Ref = cats.effect.kernel.Ref - - private[weaver] type Deferred[F[_], A] = cats.effect.kernel.Deferred[F, A] - private[weaver] val Deferred = cats.effect.kernel.Deferred - - private[weaver] type Semaphore[F[_]] = cats.effect.std.Semaphore[F] - private[weaver] val Semaphore = cats.effect.std.Semaphore - - private[weaver] def guaranteeCase[F[_]: Async, A]( - fa: F[A])( - cancelled: => F[Unit], - completed: => F[Unit], - errored: Throwable => F[Unit]): F[A] = - Async[F].guaranteeCase(fa)(_.fold(cancelled, errored, _ *> completed)) - - private[weaver] def guarantee[F[_]: Async, A]( - fa: F[A])(fin: F[Unit]): F[A] = - Async[F].guarantee(fa, fin) - - private[weaver] def onErrorEnsure[F[_]: Async, A](r: Resource[F, A])( - f: Throwable => F[Unit]): Resource[F, A] = - r.onFinalizeCase { - case Canceled => Async[F].unit - case Succeeded => Async[F].unit - case Errored(e) => f(e) - } - - private[weaver] def background[F[_]: Async, A, B](fa: F[A], default: A)( - f: F[A] => F[B]): F[B] = - fa.background.use(fOutcome => - f(fOutcome.flatMap(_.embed(onCancel = Async[F].pure(default))))) - - private[weaver] def resourceLift[F[_]: Applicative, A]( - fa: F[A]): Resource[F, A] = Resource.eval(fa) - - private[weaver] trait Queue[F[_], A] { - protected def ceQueue: cats.effect.std.Queue[F, A] - - def enqueue(a: A): F[Unit] = ceQueue.offer(a) - def dequeueStream: fs2.Stream[F, A] = fs2.Stream.repeatEval(ceQueue.take) - } - - object Queue { - def unbounded[F[_], A](implicit gc: GenConcurrent[F, _]) = - cats.effect.std.Queue.unbounded[F, A].map { - q => - new Queue[F, A] { - override val ceQueue = q - } - } - } - -} diff --git a/modules/core/src-native/org/junit/runner/RunWith.scala b/modules/core/src-native/org/junit/runner/RunWith.scala new file mode 100644 index 00000000..98c3708b --- /dev/null +++ b/modules/core/src-native/org/junit/runner/RunWith.scala @@ -0,0 +1,6 @@ +package org.junit.runner + +/** + * Stub used for cross-compilation + */ +class RunWith[T](cls: Class[T]) extends scala.annotation.StaticAnnotation diff --git a/modules/core/src-native/weaver/PlatformCompat.scala b/modules/core/src-native/weaver/PlatformCompat.scala new file mode 100644 index 00000000..f981fbf9 --- /dev/null +++ b/modules/core/src-native/weaver/PlatformCompat.scala @@ -0,0 +1,8 @@ +package weaver + +private[weaver] object PlatformCompat { + val platform: Platform = Platform.Native + + def getClassLoader(clazz: java.lang.Class[_]): ClassLoader = + new ClassLoader() {} +} diff --git a/modules/core/src-native/weaver/internals/Timestamp.scala b/modules/core/src-native/weaver/internals/Timestamp.scala new file mode 100644 index 00000000..f80174b8 --- /dev/null +++ b/modules/core/src-native/weaver/internals/Timestamp.scala @@ -0,0 +1,34 @@ +package weaver.internals + +import scala.scalanative.posix +import scala.scalanative.unsafe._ + +import posix.time +import posix.timeOps._ + +private[weaver] object Timestamp { + + def format(epochSecond: Long): String = { + val out = stackalloc[time.tm]() + val timePtr = stackalloc[time.time_t]() + !timePtr = epochSecond + val gmTime: Ptr[time.tm] = time.localtime_r(timePtr, out) + val hour = gmTime.tm_hour + val minutes = gmTime.tm_min + val seconds = gmTime.tm_sec + s"$hour:$minutes:$seconds" + } + + def localTime(hours: Int, minutes: Int, seconds: Int): Long = { + val out = stackalloc[time.tm]() + val timePtr = stackalloc[time.time_t]() + !timePtr = time.time(null) + val gmTime: Ptr[time.tm] = time.gmtime_r(timePtr, out) + + gmTime.tm_hour = hours + gmTime.tm_min = minutes + gmTime.tm_sec = seconds + gmTime.tm_isdst = -1; // Is DST on? 1 = yes, 0 = no, -1 = unknown + time.mktime(gmTime).longValue() + } +} diff --git a/modules/core/src-native/weaver/junit/WeaverRunner.scala b/modules/core/src-native/weaver/junit/WeaverRunner.scala new file mode 100644 index 00000000..f74d83df --- /dev/null +++ b/modules/core/src-native/weaver/junit/WeaverRunner.scala @@ -0,0 +1,6 @@ +package weaver.junit + +/** + * Stub used for cross-compilation + */ +class WeaverRunner() diff --git a/modules/core/src/weaver/GlobalResourceF.scala b/modules/core/src/weaver/GlobalResourceF.scala index 3a511f8e..81a1721a 100644 --- a/modules/core/src/weaver/GlobalResourceF.scala +++ b/modules/core/src/weaver/GlobalResourceF.scala @@ -9,8 +9,6 @@ import cats.syntax.all._ import org.portablescala.reflect.annotation.EnableReflectiveInstantiation -import CECompat.Ref - /** * Top-level instances of this trait are detected by the framework and used to * manage the lifecycle of shared resources. @@ -33,7 +31,7 @@ trait GlobalResourceF[F[_]] extends GlobalResourceBase { object GlobalResourceF { trait Write[F[_]] { - protected implicit def F: CECompat.Effect[F] + protected implicit def F: Async[F] protected def rawPut[A]( pureOrLazy: Either[A, Resource[F, A]], label: Option[String])(implicit rt: ResourceTag[A]): F[Unit] @@ -42,7 +40,7 @@ object GlobalResourceF { implicit rt: ResourceTag[A]): F[Unit] = rawPut(Left(value), label) def putR[A](value: A, label: Option[String] = None)( implicit rt: ResourceTag[A]): Resource[F, Unit] = - CECompat.resourceLift(put(value, label)) + Resource.eval(put(value, label)) /** * Memoises a resource so to optimise its sharing. The memoised resource @@ -66,7 +64,7 @@ object GlobalResourceF { resource: Resource[F, A], label: Option[String] = None)(implicit rt: ResourceTag[A]): Resource[F, Unit] = - CECompat.resourceLift(putLazy(resource, label)) + Resource.eval(putLazy(resource, label)) } trait Read[F[_]] { @@ -83,7 +81,7 @@ object GlobalResourceF { def getR[A](label: Option[String] = None)( implicit rt: ResourceTag[A]): Resource[F, Option[A]] = - CECompat.resourceLift { + Resource.eval { rawGet[A](label) }.flatMap { case Some(Left(value)) => Resource.pure(Some(value)) @@ -105,14 +103,14 @@ object GlobalResourceF { getR[A](label).flatMap { case Some(value) => Resource.pure[F, A](value) case None => - CECompat.resourceLift(F.raiseError(GlobalResourceF.ResourceNotFound( + Resource.eval(F.raiseError(GlobalResourceF.ResourceNotFound( label, rt.description))) } } object Read { - def empty[F[_]](effect: CECompat.Effect[F]): Read[F] = new Read[F] { + def empty[F[_]](effect: Async[F]): Read[F] = new Read[F] { implicit protected def F: MonadError[F, Throwable] = effect protected def rawGet[A](label: Option[String])(implicit @@ -121,7 +119,7 @@ object GlobalResourceF { } } - def createMap[F[_]: CECompat.Effect]: F[Read[F] with Write[F]] = + def createMap[F[_]: Async]: F[Read[F] with Write[F]] = Ref[F] .of(Map.empty[(Option[String], ResourceTag[_]), Either[Any, Resource[F, Any]]]) @@ -131,7 +129,7 @@ object GlobalResourceF { ref: Ref[ F, Map[(Option[String], ResourceTag[_]), Either[Any, Resource[F, Any]]]])( - implicit val F: CECompat.Effect[F]) + implicit val F: Async[F]) extends Read[F] with Write[F] { self => @@ -150,7 +148,7 @@ object GlobalResourceF { case None => F.raiseError(ResourceNotFound(label, rt.description)) case Some(value) => F.pure(value) - }.flatMap(CECompat.resourceLift(_)))) + }.flatMap(Resource.eval(_)))) } } diff --git a/modules/core/src/weaver/Log.scala b/modules/core/src/weaver/Log.scala index 9b233894..b8ffa2c5 100644 --- a/modules/core/src/weaver/Log.scala +++ b/modules/core/src/weaver/Log.scala @@ -1,10 +1,9 @@ package weaver +import cats.effect.Ref import cats.syntax.all._ import cats.{ Applicative, FlatMap, Monoid, MonoidK, Show, ~> } -import CECompat.Ref - abstract class Log[F[_]: FlatMap](timestamp: F[Long]) { self => def log(l: => Log.Entry): F[Unit] diff --git a/modules/core/src/weaver/MemoisedResource.scala b/modules/core/src/weaver/MemoisedResource.scala index 730ff7a4..fea049e5 100644 --- a/modules/core/src/weaver/MemoisedResource.scala +++ b/modules/core/src/weaver/MemoisedResource.scala @@ -3,8 +3,6 @@ package weaver import cats.effect._ import cats.syntax.all._ -import CECompat.{ Deferred, Ref } - object MemoisedResource { def apply[F[_]: Concurrent, A]( resource: Resource[F, A]): F[Resource[F, A]] = diff --git a/modules/core/src/weaver/Platform.scala b/modules/core/src/weaver/Platform.scala index 9c9d6b67..5c42b6c6 100644 --- a/modules/core/src/weaver/Platform.scala +++ b/modules/core/src/weaver/Platform.scala @@ -5,8 +5,10 @@ sealed abstract class Platform(val name: String) object Platform { def isJVM: Boolean = PlatformCompat.platform == JVM def isJS: Boolean = PlatformCompat.platform == JS + def isNative: Boolean = PlatformCompat.platform == Native def isScala3: Boolean = ScalaCompat.isScala3 - case object JS extends Platform("js") - case object JVM extends Platform("jvm") + case object JS extends Platform("js") + case object JVM extends Platform("jvm") + case object Native extends Platform("native") } diff --git a/modules/core/src/weaver/Runner.scala b/modules/core/src/weaver/Runner.scala index 95ec72cf..18b0e206 100644 --- a/modules/core/src/weaver/Runner.scala +++ b/modules/core/src/weaver/Runner.scala @@ -2,12 +2,14 @@ package weaver import cats.Monoid import cats.data.Chain +import cats.effect.std.Queue +import cats.effect.{ Async, Ref } import cats.syntax.all._ import TestOutcome.{ Summary, Verbose } import Colours._ -class Runner[F[_]: CECompat.Effect]( +class Runner[F[_]: Async]( args: List[String], maxConcurrentSuites: Int)( printLine: String => F[Unit]) { @@ -15,14 +17,16 @@ class Runner[F[_]: CECompat.Effect]( import Runner._ // Signaling option, because we need to detect completion - private type Channel[A] = CECompat.Queue[F, Option[A]] + private type Channel[A] = Queue[F, Option[A]] def run(suites: fs2.Stream[F, Suite[F]]): F[Outcome] = for { - buffer <- CECompat.Ref[F].of(Chain.empty[SpecEvent]) - channel <- CECompat.Queue.unbounded[F, Option[SpecEvent]] + buffer <- Ref[F].of(Chain.empty[SpecEvent]) + channel <- Queue.unbounded[F, Option[SpecEvent]] outcome <- - CECompat.background(consume(channel, buffer), Outcome.empty) { res => + Async[F].background(consume(channel, buffer)).use { outcome => + val res = outcome.flatMap(_.embed(onCancel = Outcome.empty.pure[F])) + suites .parEvalMap(math.max(1, maxConcurrentSuites)) { suite => suite @@ -38,16 +42,16 @@ class Runner[F[_]: CECompat.Effect]( } yield outcome private def produce(ch: Channel[SpecEvent])(event: SpecEvent): F[Unit] = - ch.enqueue(Some(event)) + ch.offer(Some(event)) private def complete(channel: Channel[SpecEvent]): F[Unit] = - channel.enqueue(None) // We are done ! + channel.offer(None) // We are done ! // Recursively consumes from a channel until a "None" gets produced, // indicating the end of the stream. private def consume( ch: Channel[SpecEvent], - buffer: CECompat.Ref[F, Chain[SpecEvent]]): F[Outcome] = { + buffer: Ref[F, Chain[SpecEvent]]): F[Outcome] = { val stars = "*************" @@ -73,21 +77,22 @@ class Runner[F[_]: CECompat.Effect]( } yield outcome } - ch.dequeueStream.unNoneTerminate.evalMap( - handle).compile.foldMonoid.flatMap { - outcome => - for { - failures <- buffer.get - _ <- (printLine(red(stars) + "FAILURES" + red(stars)) *> failures - .traverse[F, Unit] { specEvent => - printLine(cyan(specEvent.name)) *> - specEvent.events.traverse(printTestEvent(Verbose)) *> - newLine - } - .void).whenA(failures.nonEmpty) - _ <- printLine(outcome.formatted) - } yield outcome - } + fs2.Stream.repeatEval(ch.take) + .unNoneTerminate.evalMap(handle) + .compile.foldMonoid.flatMap { + outcome => + for { + failures <- buffer.get + _ <- (printLine(red(stars) + "FAILURES" + red(stars)) *> failures + .traverse[F, Unit] { specEvent => + printLine(cyan(specEvent.name)) *> + specEvent.events.traverse(printTestEvent(Verbose)) *> + newLine + } + .void).whenA(failures.nonEmpty) + _ <- printLine(outcome.formatted) + } yield outcome + } } } diff --git a/modules/core/src/weaver/Test.scala b/modules/core/src/weaver/Test.scala index 23be5a59..397ee715 100644 --- a/modules/core/src/weaver/Test.scala +++ b/modules/core/src/weaver/Test.scala @@ -5,10 +5,9 @@ import scala.util.{ Failure, Success, Try } import cats.Defer import cats.data.Chain +import cats.effect.Ref import cats.syntax.all._ -import CECompat.Ref - object Test { def apply[F[_]](name: String, f: Log[F] => F[Expectations])( diff --git a/modules/core/src-ce3/weaver/UnsafeRun.scala b/modules/core/src/weaver/UnsafeRun.scala similarity index 86% rename from modules/core/src-ce3/weaver/UnsafeRun.scala rename to modules/core/src/weaver/UnsafeRun.scala index 4b119bee..56767644 100644 --- a/modules/core/src-ce3/weaver/UnsafeRun.scala +++ b/modules/core/src/weaver/UnsafeRun.scala @@ -1,5 +1,6 @@ package weaver +import scala.concurrent.Future import scala.concurrent.duration.FiniteDuration import cats.Parallel @@ -37,7 +38,8 @@ trait UnsafeRun[F[_]] extends EffectCompat[F] { def background(task: F[Unit]): CancelToken def cancel(token: CancelToken): Unit - def sync(task: F[Unit]): Unit - def async(task: F[Unit]): Unit + def unsafeRunSync(task: F[Unit]): Unit + def unsafeRunAndForget(task: F[Unit]): Unit + def unsafeRunToFuture(task: F[Unit]): Future[Unit] } diff --git a/modules/core/src/weaver/suites.scala b/modules/core/src/weaver/suites.scala index 2b9a8143..323ec1ef 100644 --- a/modules/core/src/weaver/suites.scala +++ b/modules/core/src/weaver/suites.scala @@ -1,6 +1,6 @@ package weaver -import cats.effect.Resource +import cats.effect.{ Async, Resource } import cats.syntax.all._ import fs2.Stream @@ -19,7 +19,7 @@ trait Suite[F[_]] extends BaseSuiteClass { // A version of EffectSuite that has a type member instead of a type parameter. protected[weaver] trait EffectSuiteAux { type EffectType[A] - implicit protected def effect: CECompat.Effect[EffectType] + implicit protected def effect: Async[EffectType] } // format: off @@ -27,7 +27,7 @@ trait EffectSuite[F[_]] extends Suite[F] with EffectSuiteAux with SourceLocation final type EffectType[A] = F[A] implicit protected def effectCompat: EffectCompat[F] - implicit final protected def effect: CECompat.Effect[F] = effectCompat.effect + implicit final protected def effect: Async[F] = effectCompat.effect /** * Raise an error that leads to the running test being tagged as "cancelled". @@ -63,7 +63,7 @@ abstract class RunnableSuite[F[_]] extends EffectSuite[F] { private[weaver] def getEffectCompat: UnsafeRun[EffectType] = effectCompat def plan : List[TestName] private[weaver] def runUnsafe(args: List[String])(report: TestOutcome => Unit) : Unit = - effectCompat.sync(run(args)(outcome => effectCompat.effect.delay(report(outcome)))) + effectCompat.unsafeRunSync(run(args)(outcome => effectCompat.effect.delay(report(outcome)))) } abstract class MutableFSuite[F[_]] extends RunnableSuite[F] { @@ -164,4 +164,3 @@ abstract class FunSuiteF[F[_]] extends RunnableSuite[F] with FunSuiteAux { self private[weaver] object initError extends AssertionError( "Cannot define new tests after TestSuite was initialized" ) - diff --git a/modules/core/zio/src-ce2/weaver/ziocompat/ZIOUnsafeRun.scala b/modules/core/zio/src-ce2/weaver/ziocompat/ZIOUnsafeRun.scala deleted file mode 100644 index 3a146827..00000000 --- a/modules/core/zio/src-ce2/weaver/ziocompat/ZIOUnsafeRun.scala +++ /dev/null @@ -1,33 +0,0 @@ -package weaver -package ziocompat - -import cats.Parallel -import cats.effect.{ ConcurrentEffect, ContextShift, Timer } - -import zio._ -import zio.interop.catz - -object ZIOUnsafeRun extends UnsafeRun[T] { - - type CancelToken = Fiber.Id => Exit[Throwable, Unit] - - implicit val runtime = Runtime.default - - implicit def timer: Timer[T] = catz.zioTimer[ZEnv, Throwable] - implicit def effect: ConcurrentEffect[T] = catz.taskEffectInstance[ZEnv] - - implicit def parallel: Parallel[T] = - catz.core.parallelInstance[ZEnv, Throwable] - - implicit def contextShift: ContextShift[T] = - catz.zioContextShift[ZEnv, Throwable] - - def background(task: T[Unit]): CancelToken = - runtime.unsafeRunAsyncCancelable(task)(_ => ()) - def cancel(token: Fiber.Id => Exit[Throwable, Unit]): Unit = - discard[Exit[Throwable, Unit]](token(Fiber.Id.None)) - - def sync(task: T[Unit]): Unit = runtime.unsafeRun(task) - - def async(task: T[Unit]): Unit = runtime.unsafeRunAsync(task)(_ => ()) -} diff --git a/modules/core/zio/src-ce3/weaver/ziocompat/ZIOUnsafeRun.scala b/modules/core/zio/src-ce3/weaver/ziocompat/ZIOUnsafeRun.scala deleted file mode 100644 index 43ef0230..00000000 --- a/modules/core/zio/src-ce3/weaver/ziocompat/ZIOUnsafeRun.scala +++ /dev/null @@ -1,29 +0,0 @@ -package weaver -package ziocompat - -import cats.Parallel -import cats.effect.Async - -import zio._ -import zio.interop.catz - -object ZIOUnsafeRun extends UnsafeRun[T] { - - type CancelToken = Fiber.Id => Exit[Throwable, Unit] - - implicit val runtime = Runtime.default - - implicit def effect: Async[T] = catz.asyncInstance[ZEnv] - - implicit def parallel: Parallel[T] = - catz.core.parallelInstance[ZEnv, Throwable] - - def background(task: T[Unit]): CancelToken = - runtime.unsafeRunAsyncCancelable(task)(_ => ()) - def cancel(token: Fiber.Id => Exit[Throwable, Unit]): Unit = - discard[Exit[Throwable, Unit]](token(Fiber.Id.None)) - - def sync(task: T[Unit]): Unit = runtime.unsafeRun(task) - - def async(task: T[Unit]): Unit = runtime.unsafeRunAsync(task)(_ => ()) -} diff --git a/modules/core/zio/src/weaver/ziocompat/FiberRefLog.scala b/modules/core/zio/src/weaver/ziocompat/FiberRefLog.scala deleted file mode 100644 index ff8201a3..00000000 --- a/modules/core/zio/src/weaver/ziocompat/FiberRefLog.scala +++ /dev/null @@ -1,25 +0,0 @@ -package weaver.ziocompat - -import cats.data.Chain - -import weaver.Log - -import zio._ -import zio.clock.Clock - -class FiberRefLog(ref: FiberRef[Chain[Log.Entry]], clock: Clock.Service) - extends LogModule.Service(clock) { - self => - def log(l: => Log.Entry): UIO[Unit] = - ref.modify(current => ((), current.append(l))) - - def logs: UIO[Chain[Log.Entry]] = - ref.get -} - -object FiberRefLog { - def apply( - ref: FiberRef[Chain[Log.Entry]], - clock: Clock.Service): LogModule.Service = - new FiberRefLog(ref, clock) -} diff --git a/modules/core/zio/src/weaver/ziocompat/LTTResourceTag.scala b/modules/core/zio/src/weaver/ziocompat/LTTResourceTag.scala deleted file mode 100644 index 42f5992d..00000000 --- a/modules/core/zio/src/weaver/ziocompat/LTTResourceTag.scala +++ /dev/null @@ -1,15 +0,0 @@ -package weaver -package ziocompat - -import zio.LightTypeTag - -case class LTTResourceTag[A](tag: LightTypeTag) extends ResourceTag[A] { - def description: String = tag.repr - - def cast(obj: Any): Option[A] = { - try { Some(obj.asInstanceOf[A]) } - catch { - case _: Throwable => None - } - } -} diff --git a/modules/core/zio/src/weaver/ziocompat/Live.scala b/modules/core/zio/src/weaver/ziocompat/Live.scala deleted file mode 100644 index 94ae7c22..00000000 --- a/modules/core/zio/src/weaver/ziocompat/Live.scala +++ /dev/null @@ -1,24 +0,0 @@ -package weaver -package ziocompat - -import zio.{ IO, URLayer, ZEnv, ZIO } - -/** - * Service used for getting the real environment during tests. This is useful - * for timing or getting random values during tests. For example, getting a - * random port for starting servers or timing an action with the real clock. - * This pattern is inspired by ZIO-test - */ -object Live { - trait Service { - def live[E, A](zio: ZIO[ZEnv, E, A]): IO[E, A] - } - - def live[E, A](zio: ZIO[ZEnv, E, A]): ZIO[Live, E, A] = - ZIO.serviceWith[Service](_.live(zio)) - - def apply(): URLayer[ZEnv, Live] = ZIO.environment[ZEnv].map(env => - new Service { - override def live[E, A](zio: ZIO[ZEnv, E, A]): IO[E, A] = zio.provide(env) - }).toLayer -} diff --git a/modules/core/zio/src/weaver/ziocompat/Test.scala b/modules/core/zio/src/weaver/ziocompat/Test.scala deleted file mode 100644 index e903aa17..00000000 --- a/modules/core/zio/src/weaver/ziocompat/Test.scala +++ /dev/null @@ -1,27 +0,0 @@ -package weaver.ziocompat - -import java.util.concurrent.TimeUnit - -import scala.concurrent.duration._ -import scala.util.control.NonFatal - -import weaver.{ Expectations, Result, TestOutcome } - -import zio._ - -object Test { - - def apply[R <: Has[_]]( - name: String, - f: ZIO[Env[R], Throwable, Expectations] - ): ZIO[Env[R], Nothing, TestOutcome] = - for { - start <- Live.live(zio.clock.currentTime(TimeUnit.MILLISECONDS)) - res <- f - .unrefine { case NonFatal(e) => e } - .fold(Result.from, Result.fromAssertion) - end <- Live.live(zio.clock.currentTime(TimeUnit.MILLISECONDS)) - logs <- LogModule.logs - } yield TestOutcome(name, (end - start).millis, res, logs) - -} diff --git a/modules/core/zio/src/weaver/ziocompat/ZIOGlobalResource.scala b/modules/core/zio/src/weaver/ziocompat/ZIOGlobalResource.scala deleted file mode 100644 index ee3e3eeb..00000000 --- a/modules/core/zio/src/weaver/ziocompat/ZIOGlobalResource.scala +++ /dev/null @@ -1,36 +0,0 @@ -package weaver -package ziocompat - -import cats.effect.Resource - -import zio._ -import zio.interop.catz._ - -trait ZIOGlobalResource extends weaver.GlobalResourceF[T] { - - def share(global: GlobalWrite): RManaged[ZEnv, Unit] - - final def sharedResources( - global: weaver.GlobalResourceF.Write[T]): Resource[T, Unit] = - share(ZIOGlobalResource.toZIO(global)).toResourceZIO - -} - -object ZIOGlobalResource { - - trait Write { - def put[A: Tag](value: A, label: Option[String] = None): RIO[ZEnv, Unit] - - def putM[A: Tag]( - value: A, - label: Option[String] = None): RManaged[ZEnv, Unit] = - ZManaged.fromEffect(put(value, label)) - } - - private def toZIO(global: weaver.GlobalResourceF.Write[T]): Write = - new Write { - def put[A: Tag](value: A, label: Option[String]): RIO[ZEnv, Unit] = - global.put(value, label) - } - -} diff --git a/modules/core/zio/src/weaver/ziocompat/log.scala b/modules/core/zio/src/weaver/ziocompat/log.scala deleted file mode 100644 index a1ef9258..00000000 --- a/modules/core/zio/src/weaver/ziocompat/log.scala +++ /dev/null @@ -1,31 +0,0 @@ -package weaver -package ziocompat - -import zio._ - -object log { - - def info( - msg: => String, - ctx: Map[String, String] = Map.empty, - cause: Throwable = null)(implicit loc: SourceLocation) = - ZIO.accessM[LogModule](_.get[LogModule.Service].info(msg, ctx, cause)) - - def debug( - msg: => String, - ctx: Map[String, String] = Map.empty, - cause: Throwable = null)(implicit loc: SourceLocation) = - ZIO.accessM[LogModule](_.get[LogModule.Service].debug(msg, ctx, cause)) - - def warn( - msg: => String, - ctx: Map[String, String] = Map.empty, - cause: Throwable = null)(implicit loc: SourceLocation) = - ZIO.accessM[LogModule](_.get[LogModule.Service].warn(msg, ctx, cause)) - - def error( - msg: => String, - ctx: Map[String, String] = Map.empty, - cause: Throwable = null)(implicit loc: SourceLocation) = - ZIO.accessM[LogModule](_.get[LogModule.Service].error(msg, ctx, cause)) -} diff --git a/modules/core/zio/src/weaver/ziocompat/package.scala b/modules/core/zio/src/weaver/ziocompat/package.scala deleted file mode 100644 index 6187aff2..00000000 --- a/modules/core/zio/src/weaver/ziocompat/package.scala +++ /dev/null @@ -1,46 +0,0 @@ -package weaver - -import java.util.concurrent.TimeUnit - -import cats.data.Chain - -import zio._ -import zio.clock.Clock -import zio.interop.catz._ - -package object ziocompat { - - object LogModule { - abstract class Service(clock: Clock.Service) - extends Log[UIO](clock.currentTime(TimeUnit.MILLISECONDS)) { - def logs: UIO[Chain[Log.Entry]] - } - def logs: URIO[LogModule, Chain[Log.Entry]] = ZIO.accessM(_.get.logs) - } - type LogModule = Has[LogModule.Service] - type Live = Has[Live.Service] - - type T[A] = RIO[ZEnv, A] - type Env[R <: Has[_]] = ZEnv with Live with R with LogModule - - val unitTag = implicitly[Tag[Unit]] - type ZIOSuite[R <: Has[_]] = MutableZIOSuite[R] - type SimpleZIOSuite = SimpleMutableZIOSuite - type GlobalResource = ZIOGlobalResource - type GlobalRead = GlobalResourceF.Read[T] - type GlobalWrite = ZIOGlobalResource.Write - type FunSuite = FunZIOSuite - - implicit class GlobalReadExt(private val read: GlobalRead) extends AnyVal { - def getManaged[A](label: Option[String] = None)( - implicit rt: ResourceTag[A]): RManaged[ZEnv, A] = - ZManaged.fromEffect(read.getOrFail[A](label)) - def getLayer[A](label: Option[String] = None)( - implicit rt: Tag[A]): RLayer[ZEnv, Has[A]] = - ZLayer.fromEffect(read.getOrFail[A](label)) - } - - implicit def resourceTagFromTag[A](implicit A: Tag[A]): ResourceTag[A] = - LTTResourceTag(A.tag) - -} diff --git a/modules/core/zio/src/weaver/ziocompat/suites.scala b/modules/core/zio/src/weaver/ziocompat/suites.scala deleted file mode 100644 index b8856279..00000000 --- a/modules/core/zio/src/weaver/ziocompat/suites.scala +++ /dev/null @@ -1,112 +0,0 @@ -package weaver -package ziocompat - -import scala.util.Try - -import cats.data.Chain - -import fs2._ -import zio._ -import zio.clock.Clock -import zio.interop.catz._ - -trait BaseZIOSuite extends RunnableSuite[T] with EffectSuite.Provider[T] - -abstract class BaseMutableZIOSuite[Res <: Has[_]](implicit tag: Tag[Res]) - extends BaseZIOSuite { - - override implicit protected def effectCompat = ZIOUnsafeRun - - val sharedLayer: ZLayer[ZEnv with LogModule, Throwable, Res] - - def maxParallelism: Int = 10000 - - private[this] type Test = ZIO[Env[Res], Nothing, TestOutcome] - - protected def registerTest(name: TestName)(test: Test): Unit = - synchronized { - if (isInitialized) throw initError() - testSeq = testSeq :+ ((name, test)) - } - - def getSuite: EffectSuite[T] = this - - def plan: List[TestName] = testSeq.map(_._1).toList - - def pureTest(name: TestName)(run: => Expectations): Unit = - registerTest(name)(Test(name.name, ZIO(run))) - - def test(name: TestName)( - run: => ZIO[Env[Res], Throwable, Expectations]): Unit = - registerTest(name)(Test(name.name, ZIO.fromTry(Try { run }).flatten)) - - override def spec(args: List[String]): Stream[T, TestOutcome] = - synchronized { - if (!isInitialized) isInitialized = true - val argsFilter = Filters.filterTests(this.name)(args) - val filteredTests = testSeq.collect { - case (name, test) if argsFilter(name) => test - } - if (filteredTests.isEmpty) Stream.empty // no need to allocate resources - else { - for { - ref <- Stream.eval(FiberRef.make(Chain.empty[Log.Entry])) - testLayer: RLayer[ZEnv, Live with LogModule with ZEnv] = - ZEnv.any >+> Live() ++ ZLayer.fromService[ - Clock.Service, - LogModule.Service](FiberRefLog(ref, _)) - suiteLayer = - (testLayer >+> sharedLayer).passthrough - resource <- Stream.resource(suiteLayer.build.toResourceZIO) - result <- - if (maxParallelism > 1) { - Stream - .emits(filteredTests) - .lift[Task] - .parEvalMap(math.max(1, maxParallelism))(_.provide(resource)) - } else { - // Forcing a fork in `evalMap` to trigger a fiber ref copy. - Stream.emits(filteredTests) - .lift[Task] - .evalMap { t => - for { - before <- ref.get - fiber <- t.provide(resource).fork - result <- fiber.join - _ <- ref.set(before) - } yield result - } - } - } yield result - } - } - - private[this] var testSeq = Seq.empty[(TestName, Test)] - private[this] var isInitialized = false - - private[this] def initError() = - new AssertionError( - "Cannot define new tests after TestSuite was initialized" - ) - - override protected def adaptRunError: PartialFunction[Throwable, Throwable] = { - case FiberFailure(cause) => cause.asInstanceOf[Cause[Throwable]].squash - } -} - -abstract class MutableZIOSuite[Res <: Has[_]](implicit tag: Tag[Res]) - extends BaseMutableZIOSuite()(tag) - with Expectations.Helpers - -abstract class SimpleMutableZIOSuite extends MutableZIOSuite[Has[Unit]] { - override val sharedLayer: zio.ZLayer[ZEnv, Throwable, Has[Unit]] = - ZLayer.fromEffect(UIO.unit) -} - -trait FunZIOSuite - extends FunSuiteF[T] - with BaseZIOSuite - with Expectations.Helpers { - override implicit protected def effectCompat = ZIOUnsafeRun - def getSuite: EffectSuite[T] = this -} diff --git a/modules/docs/src/main/scala/weaver/MatrixRendering.scala b/modules/docs/src/main/scala/weaver/MatrixRendering.scala index 7e0c9ae4..d65e83b3 100644 --- a/modules/docs/src/main/scala/weaver/MatrixRendering.scala +++ b/modules/docs/src/main/scala/weaver/MatrixRendering.scala @@ -1,15 +1,10 @@ package weaver.docs -sealed trait CatsEffect -case object CE2 extends CatsEffect -case object CE3 extends CatsEffect - case class Artifact( name: String, jvm: Boolean, js: Boolean, scalaVersion: String, - catsEffect: CatsEffect, version: String ) @@ -21,8 +16,7 @@ case class Cell( case class Row( name: String, - ce2: Option[Cell], - ce3: Option[Cell] + ce: Option[Cell] ) case class Table( @@ -53,22 +47,18 @@ case class Table( } } - def render( - catsEffect3Version: String, - ce2ArtifactsVersion: String, - ce3ArtifactsVersion: String) = { + def render(version: String) = { val sb = new StringBuilder sb.append(_row( Seq( name, - s"Cats Effect 2

Weaver version: `$ce2ArtifactsVersion`", - s"Cats Effect $catsEffect3Version

Weaver version: `$ce3ArtifactsVersion`" + s"Cats Effect 3

Weaver version: `$version`" ), header = true )) - rows.map { case Row(name, ce2, ce3) => - sb.append(_row(Seq(name, _cell(ce2), _cell(ce3)))) + rows.map { case Row(name, ce) => + sb.append(_row(Seq(name, _cell(ce)))) } sb.result() } @@ -77,11 +67,7 @@ case class Table( object Table { def row_name(artif: String) = artif match { case "cats" => "Cats-Effect" - case "zio" => "ZIO" - case "monix" => "Monix" - case "monix-bio" => "Monix BIO" case "scalacheck" => "ScalaCheck" - case "specs2" => "Specs2 matchers" case "discipline" => "Discipline law testing" case _ => throw new RuntimeException("Not another effect type!") } @@ -107,12 +93,7 @@ object Table { case (name, artifacts) => val rowName = row_name(name) - val ce2Artifacts = artifacts.filter(_.catsEffect == CE2) - val ce3Artifacts = artifacts.filter(_.catsEffect == CE3) - - Row(rowName, - artifactsToCell(ce2Artifacts), - artifactsToCell(ce3Artifacts)) + Row(rowName, artifactsToCell(artifacts)) } Table( diff --git a/modules/docs/src/main/scala/weaver/Output.scala b/modules/docs/src/main/scala/weaver/Output.scala index f555ba52..81138bd8 100644 --- a/modules/docs/src/main/scala/weaver/Output.scala +++ b/modules/docs/src/main/scala/weaver/Output.scala @@ -2,13 +2,10 @@ package weaver.docs import weaver._ import cats.effect._ -import cats.effect.concurrent.Ref +import cats.effect.Ref import cats.data.NonEmptyChain -import scala.concurrent.ExecutionContext.Implicits.global object Output { - implicit val cs = IO.contextShift(global) - def format(s: String) = { Ansi2Html(removeTrailingNewLine( removeTrailingNewLine( @@ -20,11 +17,12 @@ object Output { if (s.endsWith("\n")) s.substring(0, s.length - 2) else s } - def runSuites(s: Suite[IO]*): IO[String] = { + def runSuites(s: Suite[IO]*): String = { + import cats.effect.unsafe.implicits.global val header = "
"
     val footer = "
" - for { + val program = for { buf <- Ref.of[IO, NonEmptyChain[String]](NonEmptyChain(header)) printLine = (s: String) => buf.update(_.append(format(s))) runner = new Runner[IO](Nil, 10)(s => printLine(s)) @@ -33,6 +31,8 @@ object Output { _ <- printLine(footer) value <- buf.get } yield value.reduceLeft(_ + "\n" + _) + + program.unsafeRunSync() } } diff --git a/modules/framework/cats/src/weaver/framework/CatsFramework.scala b/modules/framework/cats/src/weaver/framework/CatsFramework.scala index 7dd9b718..403c8020 100644 --- a/modules/framework/cats/src/weaver/framework/CatsFramework.scala +++ b/modules/framework/cats/src/weaver/framework/CatsFramework.scala @@ -10,7 +10,9 @@ class CatsEffect(errorStream: PrintStream) CatsFingerprints, CatsUnsafeRun, errorStream) { - def this() = this(System.err) + def this() = { + this(System.err) + } } object CatsFingerprints diff --git a/modules/framework/cats/test/src-jvm/DogFoodTestsJVM.scala b/modules/framework/cats/test/src-jvm/DogFoodTestsJVM.scala index 16eb72b4..ed3cc4f3 100644 --- a/modules/framework/cats/test/src-jvm/DogFoodTestsJVM.scala +++ b/modules/framework/cats/test/src-jvm/DogFoodTestsJVM.scala @@ -13,6 +13,28 @@ object DogFoodTestsJVM extends IOSuite { def sharedResource: Resource[IO, DogFood[IO]] = DogFood.make(new CatsEffect) + def logState(logger: weaver.Log[IO])(st: DogFood.State): IO[Unit] = { + val (logs, events) = st + import weaver.framework.LoggedEvent._ + + val dumpLogs = logs.traverse { le => + le match { + case Debug(msg) => logger.debug(s"LOG: $msg") + case Warn(msg) => logger.debug(s"LOG: $msg") + case Trace(msg) => logger.debug(s"TRACE: $msg") + case Info(msg) => logger.debug(s"LOG: $msg") + case Error(msg) => logger.error(s"LOG $msg") + } + }.void + + val dumpOutcomes = events.traverse { ev => + val t = if (ev.throwable().isDefined()) ev.throwable.get() else null + logger.info(s"SBT EVENT: ${ev.status()}", cause = t) + }.void + + dumpLogs *> dumpOutcomes + } + // This tests the global resource sharing mechanism by running a suite that // acquires a temporary file that gets created during global resource initialisation. // The suite contains a test which logs the location of the file and fails to ensure logs @@ -20,11 +42,11 @@ object DogFoodTestsJVM extends IOSuite { // We then recover the location of the file, which happens after the dogfooding framework finishes // its run. At this point, the file should have been deleted by the global resource initialisation // mechanism, which we test for. - test("global sharing suites") { dogfood => + test("global sharing suites") { (dogfood, log) => import dogfood._ runSuites(moduleSuite(Meta.MutableSuiteTest), sharingSuite[MetaJVM.TmpFileSuite], - globalInit(MetaJVM.GlobalStub)).flatMap { + globalInit(MetaJVM.GlobalStub)).flatTap(logState(log)).flatMap { case (logs, events) => val file = logs.collectFirst { case LoggedEvent.Error(msg) if msg.contains("file:") => @@ -44,14 +66,14 @@ object DogFoodTestsJVM extends IOSuite { } } - test("global lazy resources (parallel)") { dogfood => + test("global lazy resources (parallel)") { (dogfood, log) => import dogfood._ runSuites( globalInit(MetaJVM.LazyGlobal), sharingSuite[MetaJVM.LazyAccessParallel], sharingSuite[MetaJVM.LazyAccessParallel], sharingSuite[MetaJVM.LazyAccessParallel] - ).map { + ).flatTap(logState(log)).map { case (_, events) => val successCount = events.toList.map(_.status()).count { case Status.Success => true; case _ => false @@ -61,7 +83,7 @@ object DogFoodTestsJVM extends IOSuite { } - test("global lazy resources (sequential)") { dogfood => + test("global lazy resources (sequential)") { (dogfood, log) => import dogfood._ runSuites( Seq( @@ -71,7 +93,7 @@ object DogFoodTestsJVM extends IOSuite { sharingSuite[MetaJVM.LazyAccessSequential2] ), maxParallelism = 1 - ).map { + ).flatTap(logState(log)).map { case (_, events) => val successCount = events.toList.map(_.status()).count { case Status.Success => true; case _ => false diff --git a/modules/framework/cats/test/src-jvm/MetaJVM.scala b/modules/framework/cats/test/src-jvm/MetaJVM.scala index 98afb0b7..d21d8dcc 100644 --- a/modules/framework/cats/test/src-jvm/MetaJVM.scala +++ b/modules/framework/cats/test/src-jvm/MetaJVM.scala @@ -4,9 +4,9 @@ package test import java.io.File -import scala.concurrent.duration._ - import cats.effect._ +import cats.effect.std.{ CyclicBarrier, Semaphore } +import cats.syntax.all._ // The build tool will only detect and run top-level test suites. We can however nest objects // that contain failing tests, to allow for testing the framework without failing the build @@ -50,9 +50,12 @@ object MetaJVM { class LazyState( initialised: IO[Int], finalised: IO[Int], - totalUses: CECompat.Ref[IO, Int], - uses: CECompat.Ref[IO, Int]) { - val getState: IO[(Int, Int, Int, Int)] = for { + totalUses: Ref[IO, Int], + uses: Ref[IO, Int], + latch: IO[Unit] + ) { + def getState(parallelWait: Boolean): IO[(Int, Int, Int, Int)] = for { + _ <- latch.whenA(parallelWait) i <- initialised f <- finalised t <- totalUses.updateAndGet(_ + 1) @@ -60,20 +63,52 @@ object MetaJVM { } yield (i, f, t, u) } + case class SequentialAccess(permit: Resource[IO, Unit]) + object LazyGlobal extends GlobalResource { def sharedResources(global: weaver.GlobalWrite): Resource[IO, Unit] = - CECompat.resourceLift { + Resource.eval { for { - initialised <- CECompat.Ref[IO].of(0) - finalised <- CECompat.Ref[IO].of(0) - totalUses <- CECompat.Ref[IO].of(0) + initialised <- Ref[IO].of(0) + finalised <- Ref[IO].of(0) + totalUses <- Ref[IO].of(0) + /** + * NOTE: the number 3 refers to the current number of instantiated + * suites in the DogFoodTestsJVM spec, tests involving "global lazy + * resources" + * + * If you do either of the following: + * + * - Change the number of LazyAccessParallel suites in "global lazy + * resources (parallel)" test + * + * - Change the number below + * + * - Add another test to the LazyAccessParallel spec below + * + * You are very likely to face a very confusing non-deterministic + * behaviour. Those numbers need to be kept in sync. + */ + parallelLatch <- CyclicBarrier[IO](3) resource = - CECompat.resourceLift(CECompat.Ref[IO].of(0)).flatMap { uses => - Resource.make(initialised.update(_ + 1))(_ => - finalised.update(_ + 1)).map(_ => - new LazyState(initialised.get, finalised.get, totalUses, uses)) + Resource.eval(Ref[IO].of(0)).flatMap { uses => + val resourceInitialisation = + Resource.make(initialised.update(_ + 1))(_ => + finalised.update(_ + 1)) + + val synchronisation = + parallelLatch.await + + resourceInitialisation.as(new LazyState(initialised.get, + finalised.get, + totalUses, + uses, + synchronisation)) } + sequential <- + Semaphore[IO](1).map(_.permit).map(SequentialAccess.apply) _ <- global.putLazy(resource) + _ <- global.put(sequential) } yield () } } @@ -83,7 +118,7 @@ object MetaJVM { def sharedResource: Resource[IO, Res] = global.getOrFailR[LazyState]() test("Lazy resources should be instantiated only once") { state => - IO.sleep(100.millis) *> state.getState.map { + state.getState(parallelWait = true).map { case (initialised, finalised, totalUses, localUses) => expect.all( initialised == 1, // resource is initialised only once and uses in parallel @@ -100,12 +135,16 @@ object MetaJVM { abstract class LazyAccessSequential(global: GlobalRead, index: Int) extends IOSuite { type Res = LazyState - def sharedResource: Resource[IO, Res] = - CECompat.resourceLift(IO.sleep(index * 500.millis)).flatMap(_ => - global.getOrFailR[LazyState]()) + def sharedResource: Resource[IO, Res] = { + global.getOrFailR[SequentialAccess]().flatMap { semRes => + semRes.permit.flatMap { _ => + global.getOrFailR[LazyState]() + } + } + } test("Lazy resources should be instantiated several times") { state => - state.getState.map { + state.getState(parallelWait = false).map { case (initialised, finalised, totalUses, localUses) => expect.all( initialised == totalUses, // lazy resource will get initialised for each suite diff --git a/modules/framework/cats/test/src/MemoisedResourceTests.scala b/modules/framework/cats/test/src/MemoisedResourceTests.scala index a6bff9e9..112643e4 100644 --- a/modules/framework/cats/test/src/MemoisedResourceTests.scala +++ b/modules/framework/cats/test/src/MemoisedResourceTests.scala @@ -7,8 +7,6 @@ import scala.concurrent.duration._ import cats.effect._ import cats.syntax.all._ -import CECompat.Ref - object MemoisedResourceTests extends SimpleIOSuite { test("""|Memoised resources should be: diff --git a/modules/framework/monix-bio/src/weaver/framework/MonixBIOFramework.scala b/modules/framework/monix-bio/src/weaver/framework/MonixBIOFramework.scala deleted file mode 100644 index 20d0469a..00000000 --- a/modules/framework/monix-bio/src/weaver/framework/MonixBIOFramework.scala +++ /dev/null @@ -1,23 +0,0 @@ -package weaver -package framework - -import java.io.PrintStream - -import weaver.monixbiocompat.{ - BaseIOSuite, - IOGlobalResource, - MonixBIOUnsafeRun -} - -import monix.bio.Task - -class MonixBIO(errorStream: PrintStream) - extends WeaverFramework("monix-bio", - MonixBIOFingerprints, - MonixBIOUnsafeRun, - errorStream) { - def this() = this(System.err) -} - -object MonixBIOFingerprints - extends WeaverFingerprints.Mixin[Task, BaseIOSuite, IOGlobalResource] diff --git a/modules/framework/monix-bio/test/src/weaver/monixbiocompat/FunSuiteTest.scala b/modules/framework/monix-bio/test/src/weaver/monixbiocompat/FunSuiteTest.scala deleted file mode 100644 index f0f6a76a..00000000 --- a/modules/framework/monix-bio/test/src/weaver/monixbiocompat/FunSuiteTest.scala +++ /dev/null @@ -1,16 +0,0 @@ -package weaver.monixbiocompat - -object FunSuiteTest extends FunSuite { - - test("and") { - expect(1 == 1) and expect(2 == 2) && not(expect(1 == 2)) - } - - test("forall (success)") { - forEach(List(true, true))(value => expect(value == true)) - } - - test("forall (failure)") { - not(forEach(List(true, false))(value => expect(value == true))) - } -} diff --git a/modules/framework/monix-bio/test/src/weaver/monixbiocompat/IOSuiteTest.scala b/modules/framework/monix-bio/test/src/weaver/monixbiocompat/IOSuiteTest.scala deleted file mode 100644 index c4cd5372..00000000 --- a/modules/framework/monix-bio/test/src/weaver/monixbiocompat/IOSuiteTest.scala +++ /dev/null @@ -1,69 +0,0 @@ -package weaver.monixbiocompat - -import cats.effect.Resource - -import weaver.framework.{ DogFood, MonixBIO } - -import monix.bio.Task -import sbt.testing.Status.{ Error, Failure } - -object IOSuiteTest extends MutableIOSuite { - override type Res = DogFood[Task] - override def sharedResource: Resource[monix.bio.Task, Res] = - DogFood.make(new MonixBIO) - - List( - TestWithExceptionInTest, - TestWithExceptionInExpectation, - TestWithExceptionInInitialisation - ).foreach { testSuite => - test(s"fail properly in ${testSuite.getClass.getSimpleName}") { dogfood => - dogfood.runSuite(testSuite).map { case (_, events) => - val maybeEvent = events.headOption - val maybeThrowable = maybeEvent.flatMap { event => - if (event.throwable().isDefined()) Some(event.throwable().get()) - else None - } - val maybeStatus = maybeEvent.map(_.status()) - expect(maybeStatus.contains(Error)) && - expect(maybeThrowable.map(_.getMessage).contains("oh no")) - } - } - } - - test("fail properly on failed expectations") { dogfood => - dogfood.runSuite(TestWithFailedExpectation).map { case (_, events) => - val maybeEvent = events.headOption - val maybeStatus = maybeEvent.map(_.status()) - expect(maybeStatus.contains(Failure)) - } - } - - object TestWithExceptionInTest extends SimpleIOSuite { - test("example test") { - Task.raiseError(new RuntimeException("oh no")) - } - } - - object TestWithExceptionInExpectation extends SimpleIOSuite { - test("example test") { - for { - _ <- Task.unit - } yield throw new RuntimeException("oh no") - } - } - - object TestWithExceptionInInitialisation extends SimpleIOSuite { - test("example test") { _ => - throw new RuntimeException("oh no") - } - } - - object TestWithFailedExpectation extends SimpleIOSuite { - test("example test") { _ => - for { - _ <- Task.unit - } yield expect(false) - } - } -} diff --git a/modules/framework/monix/src/weaver/framework/MonixFramework.scala b/modules/framework/monix/src/weaver/framework/MonixFramework.scala deleted file mode 100644 index 06c0122a..00000000 --- a/modules/framework/monix/src/weaver/framework/MonixFramework.scala +++ /dev/null @@ -1,19 +0,0 @@ -package weaver -package framework - -import java.io.PrintStream - -import weaver.monixcompat.{ BaseTaskSuite, MonixUnsafeRun, TaskGlobalResource } - -import monix.eval.Task - -class Monix(errorStream: PrintStream) - extends WeaverFramework("monix", - MonixFingerprints, - MonixUnsafeRun, - errorStream) { - def this() = this(System.err) -} - -object MonixFingerprints - extends WeaverFingerprints.Mixin[Task, BaseTaskSuite, TaskGlobalResource] diff --git a/modules/framework/monix/test/src/weaver/monixcompat/FunSuiteTest.scala b/modules/framework/monix/test/src/weaver/monixcompat/FunSuiteTest.scala deleted file mode 100644 index 5f45d304..00000000 --- a/modules/framework/monix/test/src/weaver/monixcompat/FunSuiteTest.scala +++ /dev/null @@ -1,17 +0,0 @@ -package weaver -package monixcompat - -object FunSuiteTest extends FunSuite { - - test("and") { - expect(1 == 1) and expect(2 == 2) && not(expect(1 == 2)) - } - - test("forall (success)") { - forEach(List(true, true))(value => expect(value == true)) - } - - test("forall (failure)") { - not(forEach(List(true, false))(value => expect(value == true))) - } -} diff --git a/modules/framework/monix/test/src/weaver/monixcompat/TaskSuiteTest.scala b/modules/framework/monix/test/src/weaver/monixcompat/TaskSuiteTest.scala deleted file mode 100644 index 9543ad4d..00000000 --- a/modules/framework/monix/test/src/weaver/monixcompat/TaskSuiteTest.scala +++ /dev/null @@ -1,74 +0,0 @@ -package weaver.monixcompat - -import cats.effect.Resource - -import weaver.framework.{ DogFood, Monix } - -import monix.eval.Task -import sbt.testing.Status.{ Error, Failure } - -object TaskSuiteTest extends MutableTaskSuite { - - type Res = DogFood[Task] - def sharedResource: Resource[Task, DogFood[Task]] = - DogFood.make(new Monix) - - List( - TestWithExceptionInTest, - TestWithExceptionInExpectation, - TestWithExceptionInInitialisation - ).foreach { testSuite => - test(s"fail properly in ${testSuite.getClass.getSimpleName}") { dogfood => - dogfood.runSuite(testSuite).map { case (_, events) => - val maybeEvent = events.headOption - val maybeThrowable = maybeEvent.flatMap { event => - if (event.throwable().isDefined()) Some(event.throwable().get()) - else None - } - val maybeStatus = maybeEvent.map(_.status()) - - expect.all( - maybeStatus.contains(Error), - maybeThrowable.map(_.getMessage).contains("oh no") - ) - - } - } - } - - test("fail properly on failed expectations") { dogfood => - dogfood.runSuite(TestWithFailedExpectation).map { case (_, events) => - val maybeEvent = events.headOption - val maybeStatus = maybeEvent.map(_.status()) - expect(maybeStatus.contains(Failure)) - } - } - - object TestWithExceptionInTest extends SimpleTaskSuite { - test("example test") { - Task.raiseError(new RuntimeException("oh no")) - } - } - - object TestWithExceptionInExpectation extends SimpleTaskSuite { - test("example test") { - for { - _ <- Task.unit - } yield throw new RuntimeException("oh no") - } - } - - object TestWithExceptionInInitialisation extends SimpleTaskSuite { - test("example test") { _ => - throw new RuntimeException("oh no") - } - } - - object TestWithFailedExpectation extends SimpleTaskSuite { - test("example test") { _ => - for { - _ <- Task.unit - } yield expect(false) - } - } -} diff --git a/modules/framework/src-js-native/AsyncTask.scala b/modules/framework/src-js-native/AsyncTask.scala new file mode 100644 index 00000000..0fe83915 --- /dev/null +++ b/modules/framework/src-js-native/AsyncTask.scala @@ -0,0 +1,11 @@ +package weaver + +import scala.concurrent.Future + +import sbt.testing.{ EventHandler, Logger, Task } + +private[weaver] trait AsyncTask extends Task { + def executeFuture( + eventHandler: EventHandler, + loggers: Array[Logger]): Future[Unit] +} diff --git a/modules/framework/src-js/DogFoodCompat.scala b/modules/framework/src-js-native/DogFoodCompat.scala similarity index 81% rename from modules/framework/src-js/DogFoodCompat.scala rename to modules/framework/src-js-native/DogFoodCompat.scala index 48f1cdfb..dc6b7d97 100644 --- a/modules/framework/src-js/DogFoodCompat.scala +++ b/modules/framework/src-js-native/DogFoodCompat.scala @@ -17,9 +17,8 @@ private[weaver] trait DogFoodCompat[F[_]] { self: DogFood[F] => logger: sbt.testing.Logger, maxParallelism: Int)(tasks: List[sbt.testing.Task]): F[Unit] = { tasks.traverse { task => - self.framework.unsafeRun.async { - (cb: (Either[Throwable, Unit] => Unit)) => - task.execute(eventHandler, Array(logger), _ => cb(Right(()))) + self.framework.unsafeRun.fromFuture { + task.asInstanceOf[AsyncTask].executeFuture(eventHandler, Array(logger)) } }.map { _ => Reporter.logRunFinished(Array(logger))( @@ -34,7 +33,7 @@ private[weaver] trait DogFoodCompat[F[_]] { self: DogFood[F] => private[weaver] trait DogFoodCompanion { def make[F[_]](framework: WeaverFramework[F]): Resource[F, DogFood[F]] = { import framework.unsafeRun.effect - CECompat.resourceLift(effect.delay(new DogFood(framework) { + Resource.eval(effect.delay(new DogFood(framework) { def blocker = new BlockerCompat[F] { // can't block on javascript obviously def block[A](thunk: => A): F[A] = effect.delay(thunk) diff --git a/modules/framework/src-js/RunnerCompat.scala b/modules/framework/src-js-native/RunnerCompat.scala similarity index 62% rename from modules/framework/src-js/RunnerCompat.scala rename to modules/framework/src-js-native/RunnerCompat.scala index f08230c1..a37921d6 100644 --- a/modules/framework/src-js/RunnerCompat.scala +++ b/modules/framework/src-js-native/RunnerCompat.scala @@ -1,19 +1,19 @@ package weaver package framework +import java.nio.ByteBuffer + import scala.collection.mutable.ListBuffer +import scala.concurrent.Future import scala.concurrent.duration._ -import scala.scalajs.js -import scala.scalajs.js.JSON import cats.data.Chain -import cats.effect.Sync +import cats.effect.kernel.Async +import cats.effect.{ Ref, Sync } import cats.syntax.all._ import sbt.testing.{ EventHandler, Logger, Task, TaskDef } -import CECompat.Ref - trait RunnerCompat[F[_]] { self: sbt.testing.Runner => protected val args: Array[String] protected val suiteLoader: SuiteLoader[F] @@ -24,15 +24,22 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => private[weaver] val failedTests = ListBuffer.empty[(SuiteName, TestOutcome)] - def reportDone(out: TestOutcomeJS): Unit = { - val serialised = JSON.stringify(out, null) + def reportDone(out: TestOutcomeNative): Unit = { + val serialised = ReadWriter.writer { p => + p.writeString(out.suiteName) + p.writeString(out.testName) + p.writeDouble(out.durationMs) + p.writeString(out.verboseFormatting) + () + } channel match { case Some(send) => send(serialised) - case None => failedTests.append(TestOutcomeJS.rehydrate(out)) + case None => failedTests.append(TestOutcomeNative.rehydrate(out)) } } - def reportDoneF(out: TestOutcomeJS): F[Unit] = Sync[F].delay(reportDone(out)) + def reportDoneF(out: TestOutcomeNative): F[Unit] = + Sync[F].delay(reportDone(out)) override def deserializeTask( task: String, @@ -65,7 +72,17 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => } override def receiveMessage(msg: String): Option[String] = { - val outcome = JSON.parse(msg).asInstanceOf[TestOutcomeJS] + val outcome = ReadWriter.reader(msg) { p => + val suite = p.readString() + val test = p.readString() + val dur = p.readDouble() + val verb = p.readString() + + new TestOutcomeNative(suiteName = suite, + testName = test, + durationMs = dur, + verboseFormatting = verb) + } reportDone(outcome) None } @@ -81,18 +98,12 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => } private case class SbtTask(td: TaskDef, loader: Option[suiteLoader.SuiteRef]) - extends Task { + extends PlatformTask { override def tags(): Array[String] = Array() - override def execute( + def executeFuture( eventHandler: EventHandler, - loggers: Array[Logger]): Array[Task] = Array() - - override def execute( - eventHandler: EventHandler, - loggers: Array[Logger], - continuation: Array[Task] => Unit): Unit = { - + loggers: Array[Logger]): Future[Unit] = { val fqn = taskDef().fullyQualifiedName() def reportTest(outcome: TestOutcome) = @@ -117,9 +128,9 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => failedF.flatMap { case c if c.isEmpty => effect.unit case failed => { - val ots: Chain[TestOutcomeJS] = + val ots: Chain[TestOutcomeNative] = failed.map { case (SuiteName(name), to) => - TestOutcomeJS.from(name)(to) + TestOutcomeNative.from(name)(to) } ots.traverse(reportDoneF).void @@ -135,26 +146,28 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => 0.seconds, Result.from(error), Chain.empty) - reportTest(outcome) - .productR(reportDoneF(TestOutcomeJS.from(fqn)(outcome))) + reportTest(outcome).productR( + reportDoneF(TestOutcomeNative.from(fqn)(outcome))) } val action = loader match { case None => effect.unit case Some(loader) => for { outcomes <- Ref.of(Chain.empty[TestOutcome]) - _ <- CECompat.guaranteeCase(loader.suite - .flatMap(runSuite(fqn, _, outcomes)))( - cancelled = effect.unit, - completed = finaliseCompleted(outcomes), - errored = finaliseError(outcomes) - ) + loadAndRun = loader.suite.flatMap(runSuite(fqn, _, outcomes)) + _ <- Async[F].background(loadAndRun).use { + _.flatMap { + _.fold( + canceled = effect.unit, + completed = _ *> finaliseCompleted(outcomes), + errored = finaliseError(outcomes) + ) + } + } } yield () } - unsafeRun.async(action.attempt.map { exc => - continuation(Array()) - }) + unsafeRun.unsafeRunToFuture(action) } override def taskDef(): TaskDef = td @@ -163,34 +176,61 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => } -class TestOutcomeJS( - val suiteName: String, - val testName: String, - val durationMs: Double, - val verboseFormatting: String -) extends js.Object {} +private[weaver] object ReadWriter { + class Reader(bytes: ByteBuffer, private var pt: Int) { + def readString() = { + val stringSize = bytes.getInt() + val ar = new Array[Byte](stringSize) + bytes.get(ar) + + new String(ar) + } -object TestOutcomeJS { - def from(suiteName: String)(outcome: TestOutcome): TestOutcomeJS = { - TestOutcomeJS( + def readDouble() = bytes.getDouble + } + + class Writer(bb: ByteBuffer) { + + def writeString(s: String) = { + bb.putInt(s.getBytes.size) + bb.put(s.getBytes()) + } + + def writeDouble(d: Double) = bb.putDouble(d) + } + + def reader[A](s: String)(f: Reader => A) = { + val buf = ByteBuffer.wrap(s.getBytes) + f(new Reader(buf, 0)) + } + + def writer(f: Writer => Unit): String = { + val buf = ByteBuffer.allocate(2048) + + f(new Writer(buf)) + + new String(buf.array()) + + } +} + +case class TestOutcomeNative( + suiteName: String, + testName: String, + durationMs: Double, + verboseFormatting: String +) + +object TestOutcomeNative { + def from(suiteName: String)(outcome: TestOutcome): TestOutcomeNative = { + new TestOutcomeNative( suiteName, outcome.name, outcome.duration.toMillis.toDouble, outcome.formatted(TestOutcome.Verbose)) } - def apply( - suiteName: String, - testName: String, - durationMs: Double, - verboseFormatting: String): TestOutcomeJS = - js.Dynamic.literal( - suiteName = suiteName, - testName = testName, - durationMs = durationMs, - verboseFormatting = verboseFormatting).asInstanceOf[TestOutcomeJS] - - def rehydrate(t: TestOutcomeJS): (SuiteName, TestOutcome) = { + def rehydrate(t: TestOutcomeNative): (SuiteName, TestOutcome) = { SuiteName(t.suiteName) -> DecodedOutcome( t.testName, t.durationMs.millis, diff --git a/modules/framework/src-js/TaskCompat.scala b/modules/framework/src-js-native/TaskCompat.scala similarity index 100% rename from modules/framework/src-js/TaskCompat.scala rename to modules/framework/src-js-native/TaskCompat.scala diff --git a/modules/framework/src-js/PlatformTask.scala b/modules/framework/src-js/PlatformTask.scala new file mode 100644 index 00000000..2f3f81b0 --- /dev/null +++ b/modules/framework/src-js/PlatformTask.scala @@ -0,0 +1,19 @@ +package weaver + +import sbt.testing.{ EventHandler, Logger, Task } + +private[weaver] trait PlatformTask extends AsyncTask { + + override def execute( + eventHandler: EventHandler, + loggers: Array[Logger], + continuation: Array[Task] => Unit): Unit = { + val _ = executeFuture(eventHandler, loggers).map(_ => + continuation(Array.empty[Task]))( + scala.scalajs.concurrent.JSExecutionContext.queue) + } + + override def execute( + eventHandler: EventHandler, + loggers: Array[Logger]): Array[Task] = Array.empty[Task] +} diff --git a/modules/framework/src-jvm/RunnerCompat.scala b/modules/framework/src-jvm/RunnerCompat.scala index a95f8010..8494d466 100644 --- a/modules/framework/src-jvm/RunnerCompat.scala +++ b/modules/framework/src-jvm/RunnerCompat.scala @@ -10,14 +10,12 @@ import scala.concurrent.{ ExecutionContext, Promise } import scala.util.Try import cats.data.Chain -import cats.effect.{ Sync, _ } +import cats.effect.std.Semaphore +import cats.effect.{ Ref, Sync, _ } import cats.syntax.all._ import sbt.testing.{ Task, TaskDef } -import CECompat.Ref -import CECompat.Semaphore - trait RunnerCompat[F[_]] { self: sbt.testing.Runner => protected val suiteLoader: SuiteLoader[F] @@ -142,6 +140,16 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => tasks(Array(deserializer(task))).head } + private[this] def onErrorEnsure[A](r: Resource[F, A])( + f: Throwable => F[Unit]): Resource[F, A] = { + import Resource.ExitCase._ + r.onFinalizeCase { + case Canceled => Async[F].unit + case Succeeded => Async[F].unit + case Errored(e) => f(e) + } + } + private def run( globalResources: List[GlobalResourceF[F]], waitForResourcesShutdown: java.util.concurrent.Semaphore, @@ -149,7 +157,7 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => gate: Promise[Unit]): F[Unit] = { def preventDeadlock[A](resource: Resource[F, A]) = { - CECompat.onErrorEnsure(resource) { + onErrorEnsure(resource) { error => effect.delay(isDone.set(true)) *> effect.delay(error.printStackTrace(errorStream)) *> @@ -187,7 +195,7 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => private def resourceMap( globalResources: List[GlobalResourceF[F]] ): Resource[F, GlobalResourceF.Read[F]] = - CECompat.resourceLift(GlobalResourceF.createMap[F]).flatTap { map => + Resource.eval(GlobalResourceF.createMap[F]).flatTap { map => globalResources.traverse(_.sharedResources(map)).void } @@ -217,9 +225,9 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => val finalizer = maybePublish.productR(broker.send(SuiteFinished(SuiteName(fqn)))) - CECompat.guaranteeCase(runSuite)( - completed = finalizer, - cancelled = finalizer, + Async[F].guaranteeCase(runSuite)(_.fold( + completed = _ *> finalizer, + canceled = finalizer, errored = { (error: Throwable) => val outcome = TestOutcome("Unexpected failure", @@ -227,11 +235,12 @@ trait RunnerCompat[F[_]] { self: sbt.testing.Runner => Result.from(error), Chain.empty) - CECompat.guarantee(outcomes - .update(_.append(SuiteName(fqn) -> outcome)) - .productR(broker.send(TestFinished(outcome))))(finalizer) + Async[F].guarantee(outcomes + .update(_.append(SuiteName(fqn) -> outcome)) + .productR(broker.send(TestFinished(outcome))), + finalizer) } - ).handleErrorWith { case scala.util.control.NonFatal(_) => + )).handleErrorWith { case scala.util.control.NonFatal(_) => effect.unit // avoid non-fatal errors propagating up } } diff --git a/modules/framework/src-native/PlatformTask.scala b/modules/framework/src-native/PlatformTask.scala new file mode 100644 index 00000000..cddf4970 --- /dev/null +++ b/modules/framework/src-native/PlatformTask.scala @@ -0,0 +1,18 @@ +package weaver + +import scala.concurrent.Await +import scala.concurrent.duration._ + +import sbt.testing.{ EventHandler, Logger, Task } + +private[weaver] trait PlatformTask extends AsyncTask { + + override def execute( + eventHandler: EventHandler, + loggers: Array[Logger]): Array[Task] = { + val future = executeFuture(eventHandler, loggers) + scalanative.runtime.loop() + Await.result(future, 5.minutes) + Array.empty[Task] + } +} diff --git a/modules/framework/src/weaver/framework/DogFood.scala b/modules/framework/src/weaver/framework/DogFood.scala index 030e08c7..67ae7d41 100644 --- a/modules/framework/src/weaver/framework/DogFood.scala +++ b/modules/framework/src/weaver/framework/DogFood.scala @@ -25,11 +25,11 @@ abstract class DogFood[F[_]]( import DogFood.State // ScalaJS executes asynchronously, therefore we need to wait - // for some time before getting the logs back. On JVM platform + // for some time before getting the logs back. On JVM/Native platform // we do not need to wait, since the suite will run synchronously private val patience: Option[FiniteDuration] = PlatformCompat.platform match { - case JS => 2.seconds.some - case JVM => none + case JS => 2.seconds.some + case JVM | Native => none } def runSuites( diff --git a/modules/framework/src/weaver/framework/Fingerprints.scala b/modules/framework/src/weaver/framework/Fingerprints.scala index c78024cd..29bdf3fd 100644 --- a/modules/framework/src/weaver/framework/Fingerprints.scala +++ b/modules/framework/src/weaver/framework/Fingerprints.scala @@ -68,7 +68,7 @@ abstract class WeaverFingerprints[F[_]](implicit F: Sync[F]) { * [[weaver.EffectSuite]]. */ object SuiteFingerprint extends WeaverFingerprint { - val isModule = true + def isModule() = true def requireNoArgConstructor(): Boolean = true def superclassName(): String = SuiteClass.runtimeClass.getName } @@ -79,13 +79,13 @@ abstract class WeaverFingerprints[F[_]](implicit F: Sync[F]) { * [[weaver.GlobalResources.Read]] parameter. */ object ResourceSharingSuiteFingerprint extends WeaverFingerprint { - val isModule = false + def isModule() = false def requireNoArgConstructor(): Boolean = false def superclassName(): String = SuiteClass.runtimeClass.getName } object GlobalResourcesFingerprint extends WeaverFingerprint { - val isModule = true + def isModule() = true def requireNoArgConstructor(): Boolean = true def superclassName(): String = GlobalResourcesInitClass.runtimeClass.getName } diff --git a/modules/framework/src/weaver/framework/WeaverRunner.scala b/modules/framework/src/weaver/framework/WeaverRunner.scala index eecf7fdd..5c2d89ea 100644 --- a/modules/framework/src/weaver/framework/WeaverRunner.scala +++ b/modules/framework/src/weaver/framework/WeaverRunner.scala @@ -7,12 +7,14 @@ import sbt.testing._ class WeaverRunner[F[_]]( val args: Array[String], - val remoteArgs: Array[String], + val rmArgs: Array[String], val suiteLoader: SuiteLoader[F], val unsafeRun: UnsafeRun[F], val channel: Option[String => Unit], val errorStream: PrintStream ) extends Runner - with RunnerCompat[F] + with RunnerCompat[F] { + override def remoteArgs(): Array[String] = rmArgs +} final case class SuiteName(name: String) extends AnyVal diff --git a/modules/framework/zio/src/weaver/framework/ZIO.scala b/modules/framework/zio/src/weaver/framework/ZIO.scala deleted file mode 100644 index 324339fd..00000000 --- a/modules/framework/zio/src/weaver/framework/ZIO.scala +++ /dev/null @@ -1,16 +0,0 @@ -package weaver -package framework - -import java.io.PrintStream - -import weaver.ziocompat.{ BaseZIOSuite, T, ZIOGlobalResource, ZIOUnsafeRun } - -import ZIOUnsafeRun.effect - -class ZIO(errorStream: PrintStream) - extends WeaverFramework("zio", ZIOFingerprints, ZIOUnsafeRun, errorStream) { - def this() = this(System.err) -} - -object ZIOFingerprints - extends WeaverFingerprints.Mixin[T, BaseZIOSuite, ZIOGlobalResource] diff --git a/modules/framework/zio/test/src-jvm/Global.scala b/modules/framework/zio/test/src-jvm/Global.scala deleted file mode 100644 index 789c1793..00000000 --- a/modules/framework/zio/test/src-jvm/Global.scala +++ /dev/null @@ -1,33 +0,0 @@ -package weaver -package ziocompat - -import zio._ - -object SharedResources extends ZIOGlobalResource { - def share(global: GlobalWrite): RManaged[ZEnv, Unit] = - for { - foo <- ZManaged.succeed("hello world!") - _ <- global.putM(foo) - } yield () -} - -class ResourceSharingSuite(global: GlobalRead) extends ZIOSuite[Has[String]] { - - val sharedLayer: RLayer[ZEnv, Has[String]] = - global.getLayer[String]() - - test("a stranger, from the outside ! ooooh") { - ZIO.access[Has[String]](_.get).map(s => expect(s == "hello world!")) - } -} - -class OtherResourceSharingSuite(global: GlobalRead) - extends ZIOSuite[Has[Option[Int]]] { - val sharedLayer: RLayer[ZEnv, Has[Option[Int]]] = - ZLayer.fromEffect(global.get[Int]()) - - test("oops, forgot something here") { - ZIO.access[Has[Option[Int]]](_.get).map(o => expect(o.isEmpty)) - } - -} diff --git a/modules/framework/zio/test/src/weaver/ziocompat/CheckersTest.scala b/modules/framework/zio/test/src/weaver/ziocompat/CheckersTest.scala deleted file mode 100644 index 0731b2fe..00000000 --- a/modules/framework/zio/test/src/weaver/ziocompat/CheckersTest.scala +++ /dev/null @@ -1,67 +0,0 @@ -package weaver.ziocompat - -import weaver.scalacheck._ - -import org.scalacheck.Gen -import zio.duration._ - -object CheckersTest extends SimpleZIOSuite with Checkers { - - override def checkConfig: CheckConfig = - super.checkConfig.copy(perPropertyParallelism = 100) - - test("universal") { - forall(Gen.posNum[Int]) { a => - expect(a > 0) - } - } - - test("form 1") { - forall { (a: Int) => - expect(a * 2 == 2 * a) - } - } - - test("form 2") { - forall { (a1: Int, a2: Int) => - expect(a1 * a2 == a2 * a1) - } - } - - test("form 3") { - forall { (a1: Int, a2: Int, a3: Int) => - expect(a1 * a2 * a3 == a3 * a2 * a1) - } - } - - test("form 4") { - forall { (a1: Int, a2: Int, a3: Int, a4: Int) => - expect(a1 * a2 * a3 * a4 == a4 * a3 * a2 * a1) - } - } - - test("form 5") { - forall { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int) => - expect(a1 * a2 * a3 * a4 * a5 == a5 * a4 * a3 * a2 * a1) - } - } - - test("form 6") { - forall { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int) => - expect(a1 * a2 * a3 * a4 * a5 * a6 == a6 * a5 * a4 * a3 * a2 * a1) - } - } - - test("IO form (1)") { - forall { (a1: Int) => - zio.clock.sleep(100.millis).map(_ => expect(a1 * 2 == a1 + a1)) - } - } - - test("IO form (2)") { - forall { (a1: Int, a2: Int) => - zio.clock.sleep(1.second).map(_ => expect(a1 + a2 == a2 + a1)) - } - } - -} diff --git a/modules/framework/zio/test/src/weaver/ziocompat/FunSuiteTest.scala b/modules/framework/zio/test/src/weaver/ziocompat/FunSuiteTest.scala deleted file mode 100644 index 58cb717c..00000000 --- a/modules/framework/zio/test/src/weaver/ziocompat/FunSuiteTest.scala +++ /dev/null @@ -1,16 +0,0 @@ -package weaver.ziocompat - -object FunSuiteTest extends FunSuite { - - test("and") { - expect(1 == 1) and expect(2 == 2) && not(expect(1 == 2)) - } - - test("forall (success)") { - forEach(List(true, true))(value => expect(value == true)) - } - - test("forall (failure)") { - not(forEach(List(true, false))(value => expect(value == true))) - } -} diff --git a/modules/framework/zio/test/src/weaver/ziocompat/MutableZIOSuiteTest.scala b/modules/framework/zio/test/src/weaver/ziocompat/MutableZIOSuiteTest.scala deleted file mode 100644 index 88f79c36..00000000 --- a/modules/framework/zio/test/src/weaver/ziocompat/MutableZIOSuiteTest.scala +++ /dev/null @@ -1,291 +0,0 @@ -package weaver.ziocompat - -import java.time.{ DateTimeException, OffsetDateTime } -import java.util.concurrent.TimeUnit - -import weaver.Log -import weaver.framework.DogFood -import weaver.ziocompat.modules._ - -import sbt.testing.Status -import zio._ -import zio.clock.Clock -import zio.duration._ -import zio.interop.catz._ - -object ZIOSuiteTest extends ZIOSuite[KVStore with DogFoodz] { - - override def maxParallelism: Int = 1 - - override val sharedLayer: ZLayer[ZEnv, Throwable, KVStore with DogFoodz] = { - val kvstore: ZLayer[ZEnv, Throwable, KVStore] = ZLayer.fromEffect { - Ref - .make(Map.empty[String, String]) - .map(new KVStore.RefBased(_)) - } - val dogfood: ZLayer[ZEnv, Throwable, DogFoodz] = { - ZLayer.fromManaged(DogFood.make(new weaver.framework.ZIO()).toManagedZIO) - } - - dogfood ++ kvstore - } - - // Don't do this at home, kids - test("setting some value in a shared store") { - for { - _ <- zio.clock.sleep(1.seconds) - _ <- KVStore.put("hello", "world") - helloValue <- KVStore.get("hello") - _ <- log.info(helloValue.getOrElse("empty")) - } yield expect(List(1, 2, 3).size == 3) - } - - test("getting the value set in a previous test") { - for { - previous <- KVStore.delete("hello") - now <- KVStore.get("hello") - } yield expect(previous == Some("world")) and expect(now == None) - } - - List( - TestWithExceptionInTest, - TestWithExceptionInExpectation, - TestWithExceptionInInitialisation, - TestWithEventualDiedSharedLayer, - TestWithFailedSharedLayer - ).foreach { testSuite => - test(s"fail properly in ${testSuite.getClass.getSimpleName}") { - for { - (_, events) <- DogFoodz.runSuite(testSuite) - } yield { - val maybeEvent = events.headOption - val maybeThrowable = maybeEvent.flatMap { event => - if (event.throwable().isDefined()) Some(event.throwable().get()) - else None - } - val maybeStatus = maybeEvent.map(_.status()) - expect(maybeStatus.contains(Status.Error)) && - expect(maybeThrowable.map(_.getMessage).contains("oh no")) - } - } - } - - test("fail properly on failed expectations") { - for { - (_, events) <- DogFoodz.runSuite(TestWithFailedExpectation) - } yield { - val maybeEvent = events.headOption - val maybeStatus = maybeEvent.map(_.status()) - expect(maybeStatus.contains(Status.Failure)) - } - } - - test("logs contain only the logs from each test (parallelism = 3)") { - new FiberRefLogTest(3).spec(List.empty) - .map(outcome => - expect(!outcome.status.isFailed) and expect(outcome.log.size == 1)) - .compile - .foldMonoid - } - - test("logs contain only the logs from each test (parallelism = 1)") { - new FiberRefLogTest(1).spec(List.empty) - .map(outcome => - expect(!outcome.status.isFailed) and expect(outcome.log.size == 1)) - .compile - .foldMonoid - } - - test("logs can use adapter to give logs from app") { - LogAdapterTest.spec(List.empty).map(outcome => - expect(!outcome.status.isFailed) and - expect(outcome.log.map(_.msg) == cats.data.Chain( - "one", - "two", - "three", - "four"))).compile.foldMonoid - } - - test("live clock") { - TestLiveSharedLayer.spec(List.empty).map(outcome => - expect(!outcome.status.isFailed) and - expect(outcome.duration.length > 0)).compile.foldMonoid - } - - object LogAdapterTest extends ZIOSuite[Has[SomeApp.Service]] { - - val loggingAdapterLayer: RLayer[LogModule, Has[SomeLogger.Service]] = - ZLayer.fromService[LogModule.Service, SomeLogger.Service] { - weaverLogger => - new SomeLogger.Service { - override def log(msg: String): Task[Unit] = - weaverLogger.log(Log.Entry( - 0L, - msg, - Map.empty, - Log.debug, - None, - weaver.SourceLocation("", "", 0))) - } - } - - val sharedLayer: ZLayer[ZEnv with LogModule, Throwable, Has[SomeApp.Service]] = - loggingAdapterLayer >>> SomeApp.program - - test("can run and log") { - for { - _ <- SomeApp.run() - logs <- LogModule.logs - } yield expect(logs.size == 4) - } - - } - - object SomeLogger { - trait Service { - def log(msg: String): Task[Unit] - } - def log(msg: String): RIO[Has[Service], Unit] = - ZIO.accessM[Has[Service]](_.get.log(msg)) - } - - object SomeApp { - class Program(logger: SomeLogger.Service) extends SomeApp.Service { - def run(): Task[Unit] = for { - _ <- logger.log("one") - _ <- logger.log("two") - _ <- logger.log("three") - _ <- logger.log("four") - } yield () - } - - val program: URLayer[Has[SomeLogger.Service], Has[SomeApp.Service]] = - ZLayer.fromService[SomeLogger.Service, SomeApp.Service](new Program(_)) - - trait Service { - def run(): Task[Unit] - } - def run(): RIO[Has[Service], Unit] = - ZIO.accessM[Has[Service]](_.get.run()) - } - - class FiberRefLogTest(override val maxParallelism: Int) - extends SimpleZIOSuite { - test("debug log") { - (log.debug("a log") *> LogModule.logs).map(logs => expect(logs.size == 1)) - } - - test("error log") { - (log.error("a log") *> LogModule.logs).map(logs => expect(logs.size == 1)) - } - - test("info log") { - (log.info("a log") *> LogModule.logs).map(logs => expect(logs.size == 1)) - } - - test("warning log") { - (log.warn("a log") *> LogModule.logs).map(logs => expect(logs.size == 1)) - } - } - - object TestWithExceptionInTest extends SimpleZIOSuite { - test("example test") { - Task.fail(new RuntimeException("oh no")) - } - } - - object TestWithExceptionInExpectation extends SimpleZIOSuite { - test("example test") { - for { - _ <- Task.succeed(()) - } yield throw new RuntimeException("oh no") - } - } - - object TestWithExceptionInInitialisation extends SimpleZIOSuite { - test("example test") { - throw new RuntimeException("oh no") - } - } - - object TestWithFailedExpectation extends SimpleZIOSuite { - test("example test") { - for { - _ <- Task.succeed(()) - } yield expect(false) - } - } - - object TestWithFailedSharedLayer extends MutableZIOSuite[Has[Unit]] { - override val sharedLayer: ZLayer[zio.ZEnv, Throwable, Has[Unit]] = - ZLayer.fail(new RuntimeException("oh no")) - - test("example test") { - ZIO.succeed(expect(true)) - } - } - - object TestWithEventualDiedSharedLayer extends MutableZIOSuite[Has[Unit]] { - override val sharedLayer: ZLayer[zio.ZEnv, Throwable, Has[Unit]] = - ZLayer.fromEffect(ZIO.effect(throw new RuntimeException("oh no"))) - - test("example test") { - ZIO.succeed(expect(true)) - } - } - object TestLiveSharedLayer extends MutableZIOSuite[Clock] { - override val sharedLayer: ZLayer[zio.ZEnv, Throwable, Clock] = - ZLayer.succeed(new Clock.Service { - def currentTime(unit: TimeUnit) = ??? - def currentDateTime: IO[DateTimeException, OffsetDateTime] = ??? - def nanoTime: UIO[Long] = UIO(42) - def sleep(duration: Duration): UIO[Unit] = ??? - }) - - test("can allow overriding of the clock whilst still having correct test timings") { - // Ensure the test duration is longer than 0ms so test duration can be asserted when the suite is run - Live.live(clock.sleep(1.millis)) *> clock.nanoTime.map(time => - expect(time == 42)) - } - } -} - -object modules { - - type DogFoodz = Has[DogFood[T]] - type KVStore = Has[KVStore.Service] - - object DogFoodz { - def runSuite(suite: BaseZIOSuite): RIO[ZEnv with DogFoodz, DogFood.State] = - ZIO.accessM(_.get[DogFood[T]].runSuite(suite)) - } - - object KVStore { - - // boileplate usually written via macro - def put(k: String, v: String): RIO[KVStore, Unit] = - ZIO.accessM(_.get.put(k, v)) - def get(k: String): RIO[KVStore, Option[String]] = - ZIO.accessM(_.get.get(k)) - def delete(k: String): RIO[KVStore, Option[String]] = - ZIO.accessM(_.get.delete(k)) - - trait Service { - def put(k: String, v: String): UIO[Unit] - def get(k: String): UIO[Option[String]] - def delete(k: String): UIO[Option[String]] - } - - class RefBased(ref: Ref[Map[String, String]]) extends Service { - def put(k: String, v: String): zio.UIO[Unit] = ref.update(_ + (k -> v)) - - def get(k: String): zio.UIO[Option[String]] = ref.get.map(_.get(k)) - - def delete(k: String): zio.UIO[Option[String]] = - ref.getAndUpdate(_ - k).map(_.get(k)) - - } - - } - -} diff --git a/project/WeaverPlugin.scala b/project/WeaverPlugin.scala index da5124b4..763102f1 100644 --- a/project/WeaverPlugin.scala +++ b/project/WeaverPlugin.scala @@ -8,6 +8,7 @@ import sbtprojectmatrix.ProjectMatrixKeys.virtualAxes import sbt.internal.ProjectMatrix import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.scalaJSLinkerConfig +import scala.scalanative.sbtplugin.ScalaNativePlugin import org.scalajs.linker.interface.ModuleKind import org.scalajs.sbtplugin.ScalaJSPlugin import scala.collection.immutable.Nil @@ -17,30 +18,23 @@ import sbt.VirtualAxis.ScalaVersionAxis import _root_.scalafix.sbt.ScalafixPlugin import org.scalafmt.sbt.ScalafmtPlugin -case class CatsEffectAxis(idSuffix: String, directorySuffix: String) - extends VirtualAxis.WeakAxis - /** * Common project settings. */ object WeaverPlugin extends AutoPlugin { - val CatsEffect2Axis = CatsEffectAxis("_CE2", "ce2") - val CatsEffect3Axis = CatsEffectAxis("_CE3", "ce3") - implicit final class ProjectMatrixOps(pmx: ProjectMatrix) { type ConfigureX = ProjectMatrix => ProjectMatrix type Configure = Project => Project val defaults = Seq[VirtualAxis]( - CatsEffect2Axis, VirtualAxis.jvm, VirtualAxis.scalaVersionAxis(WeaverPlugin.scala213, "2.13")) def addOne( scalaVersion: String, - platform: VirtualAxis.PlatformAxis, - catsEffectAxis: CatsEffectAxis): ConfigureX = { + platform: VirtualAxis.PlatformAxis + ): ConfigureX = { projectMatrix => val addScalafix: Configure = if (scalaVersion == scala213) @@ -55,47 +49,47 @@ object WeaverPlugin extends AutoPlugin { val scalaJSSettings: Configure = if (platform == VirtualAxis.js) configureScalaJSProject else identity - val ce3VersionOverride: Configure = - if (catsEffectAxis == CatsEffect3Axis) - _.settings(versionOverrideForCE3) + val scalaNativeSettings: Configure = + if (platform == VirtualAxis.native) configureScalaNativeProject else identity + val ce3VersionOverride: Configure = + _.settings(versionOverrideForCE3) + val configureProject = - addScalafix andThen addScalafmt andThen scalaJSSettings andThen ce3VersionOverride + addScalafix andThen addScalafmt andThen scalaJSSettings andThen scalaNativeSettings andThen ce3VersionOverride projectMatrix.defaultAxes(defaults: _*).customRow( scalaVersions = List(scalaVersion), - axisValues = Seq(catsEffectAxis, platform), + axisValues = Seq(platform), configureProject ) } def add( scalaVersions: Iterable[String], - platform: VirtualAxis.PlatformAxis, - catsEffectAxis: CatsEffectAxis): ConfigureX = { - scalaVersions.map(addOne(_, platform, catsEffectAxis)).reduce(_ andThen _) + platform: VirtualAxis.PlatformAxis + ): ConfigureX = { + scalaVersions.map(addOne(_, platform)).reduce(_ andThen _) } def full = sparse(true, true, true) def sparse( - withCE3: Boolean, - withJS: Boolean, - withScala3: Boolean + withJS: Boolean = false, + withNative: Boolean = false, + withScala3: Boolean = false ): ProjectMatrix = { val defaultScalaVersions = supportedScala2Versions val defaultPlatform = List(VirtualAxis.jvm) - val defaultCE = List(CatsEffect2Axis) val addJs = if (withJS) List(VirtualAxis.js) else Nil + val addNative = if (withNative) List(VirtualAxis.native) else Nil val addScala3 = if (withScala3) List(scala3) else Nil - val addCE3 = if (withCE3) List(CatsEffect3Axis) else Nil val configurators = for { scalaVersion <- defaultScalaVersions ++ addScala3 - platform <- defaultPlatform ++ addJs - catsEffect <- defaultCE ++ addCE3 - } yield addOne(scalaVersion, platform, catsEffect) + platform <- defaultPlatform ++ addJs ++ addNative + } yield addOne(scalaVersion, platform) val configure: ConfigureX = configurators.reduce(_ andThen _) @@ -112,8 +106,11 @@ object WeaverPlugin extends AutoPlugin { original match { case regex(major, minor, patch) => - original.replaceFirst(s"$major.$minor.$patch", - s"$major.${minor.toInt + 1}.$patch") + if (minor == "6") + original.replaceFirst(s"$major.$minor.$patch", + s"$major.${minor.toInt + 1}.$patch") + else + original case _ => throw new RuntimeException( s"Version $original doesn't match SemVer format") @@ -145,6 +142,13 @@ object WeaverPlugin extends AutoPlugin { ) } + def configureScalaNativeProject(proj: Project): Project = { + proj.enablePlugins(ScalaNativePlugin) + .settings( + Test / fork := false + ) + } + override def requires = plugins.JvmPlugin override def trigger = allRequirements @@ -308,10 +312,12 @@ object WeaverPlugin extends AutoPlugin { Def.setting((Compile / scalaSource).value.getParentFile().getParentFile().getParentFile()) def suffixes(axes: Seq[VirtualAxis]) = axes.collect { - case VirtualAxis.js => List("", "-js") - case VirtualAxis.jvm => List("", "-jvm") - case CatsEffect3Axis => List("", "-ce3") - case CatsEffect2Axis => List("", "-ce2") + case VirtualAxis.js => + List("", "-js", "-jvm-js", "-js-native") + case VirtualAxis.jvm => + List("", "-jvm", "-jvm-js", "-jvm-native") + case VirtualAxis.native => + List("", "-native", "-jvm-native", "-js-native") case ScalaVersionAxis(ver, _) => if (ver.startsWith("3.")) List("", "-scala-3") else List("", "-scala-2") @@ -369,41 +375,25 @@ object WeaverPlugin extends AutoPlugin { email = "anton.sviridov@disneystreaming.com", url = url("https://github.com/keynmol") ) - ), - credentials ++= - sys.env - .get("SONATYPE_USER") - .zip(sys.env.get("SONATYPE_PASSWORD")) - .map { - case (username, password) => - Credentials( - "Sonatype Nexus Repository Manager", - "oss.sonatype.org", - username, - password - ) - } - .toSeq + ) ) def createBuildCommands(projects: Seq[ProjectReference]) = { - case class Triplet(ce: String, scala: String, platform: String) + case class Duplet(scala: String, platform: String) val scala3Suffix = VirtualAxis.scalaABIVersion(scala3).idSuffix val scala213Suffix = VirtualAxis.scalaABIVersion(scala213).idSuffix val scala212Suffix = VirtualAxis.scalaABIVersion(scala212).idSuffix val jsSuffix = VirtualAxis.js.idSuffix - val ce3Suffix = CatsEffect3Axis.idSuffix - val ce2Suffix = CatsEffect2Axis.idSuffix + val nativeSuffix = VirtualAxis.native.idSuffix - val all: List[(Triplet, Seq[String])] = + val all: List[(Duplet, Seq[String])] = projects.collect { case lp: LocalProject => var projectId = lp.project val scalaAxis = - if (projectId.endsWith(scala3Suffix) && !projectId.endsWith( - ce3Suffix)) { + if (projectId.endsWith(scala3Suffix)) { projectId = projectId.dropRight(scala3Suffix.length) "3" } else if (projectId.endsWith(scala212Suffix)) { @@ -415,25 +405,21 @@ object WeaverPlugin extends AutoPlugin { val platformAxis = if (projectId.endsWith(jsSuffix)) { projectId = projectId.dropRight(jsSuffix.length) - "js" + } else if (projectId.endsWith(nativeSuffix)) { + projectId = projectId.dropRight(nativeSuffix.length) + "native" } else "jvm" - val ceAxis = - if (projectId.endsWith(ce3Suffix)) { - projectId = projectId.dropRight(ce3Suffix.length) - "CE3" - } else "CE2" - - Triplet(ceAxis, scalaAxis, platformAxis) -> lp.project + Duplet(scalaAxis, platformAxis) -> lp.project }.groupBy(_._1).mapValues(_.map(_._2)).toList // some commands, like test and compile, are setup for all modules - val any = (t: Triplet) => true + val any = (t: Duplet) => true // things like scalafix and scalafmt are only enabled on jvm 2.13 projects - val jvm2_13 = (t: Triplet) => t.scala == "2_13" && t.platform == "jvm" + val jvm2_13 = (t: Duplet) => t.scala == "2_13" && t.platform == "jvm" - val desiredCommands: Map[String, (String, Triplet => Boolean)] = Map( + val desiredCommands: Map[String, (String, Duplet => Boolean)] = Map( "test" -> ("test", any), "compile" -> ("compile", any), "publishLocal" -> ("publishLocal", any), @@ -444,10 +430,10 @@ object WeaverPlugin extends AutoPlugin { ) val cmds = all.flatMap { - case (triplet, projects) => - desiredCommands.filter(_._2._2(triplet)).map { case (name, (cmd, _)) => + case (duplet, projects) => + desiredCommands.filter(_._2._2(duplet)).map { case (name, (cmd, _)) => Command.command( - s"${name}_${triplet.ce}_${triplet.scala}_${triplet.platform}") { + s"${name}_${duplet.scala}_${duplet.platform}") { state => projects.foldLeft(state) { case (st, proj) => s"$proj/$cmd" :: st diff --git a/project/build.properties b/project/build.properties index c8fcab54..22af2628 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.6.2 +sbt.version=1.7.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index 2e9e91b8..b66bf23b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,10 +1,9 @@ // format: off addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.1") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.7") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.1.1") -addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.13") +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.3") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") diff --git a/website/sidebars.json b/website/sidebars.json index c38d4834..b1842ffb 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -6,9 +6,7 @@ ], "Effect types": [ "cats", - "monix", - "monix_bio", - "zio" + "other_effects" ], "Features": [ "expectations", @@ -17,7 +15,6 @@ "logging", "tagging", "scalacheck", - "specs2", "discipline", "parallelism", "funsuite",