diff --git a/build.sbt b/build.sbt index 220ac30514..f1711095da 100644 --- a/build.sbt +++ b/build.sbt @@ -18,6 +18,7 @@ import java.io.File import com.typesafe.tools.mima.core._ import org.openqa.selenium.firefox.FirefoxOptions +import org.openqa.selenium.firefox.FirefoxProfile import org.scalajs.jsenv.selenium.SeleniumJSEnv ThisBuild / baseVersion := "3.1" @@ -70,7 +71,8 @@ ThisBuild / githubWorkflowBuild := Seq( WorkflowStep.Sbt(List("${{ matrix.ci }}")), WorkflowStep.Sbt( List("docs/mdoc"), - cond = Some(s"(matrix.scala == '$Scala213' || matrix.scala == '$Scala3') && matrix.ci == 'ciJVM'")), + cond = Some( + s"(matrix.scala == '$Scala213' || matrix.scala == '$Scala3') && matrix.ci == 'ciJVM'")), WorkflowStep.Sbt( List("exampleJVM/compile"), cond = Some(s"matrix.ci == 'ciJVM' && matrix.os == '$PrimaryOS'")), @@ -128,7 +130,10 @@ ThisBuild / Test / jsEnv := { val old = (Test / jsEnv).value if (useFirefoxEnv.value) { + val profile = new FirefoxProfile() + profile.setPreference("privacy.file_unique_origin", false) val options = new FirefoxOptions() + options.setProfile(profile) options.addArguments("-headless") new SeleniumJSEnv(options) } else { @@ -166,13 +171,22 @@ addCommandAlias("ciJS", "; project rootJS; headerCheck; scalafmtCheck; clean; te // we do the firefox ci *only* on core because we're only really interested in IO here addCommandAlias( "ciFirefox", - "; set Global / useFirefoxEnv := true; project rootJS; headerCheck; scalafmtCheck; clean; testsJS/test; set Global / useFirefoxEnv := false" + "; set Global / useFirefoxEnv := true; project rootJS; headerCheck; scalafmtCheck; clean; testsJS/test; webWorkerTests/test; set Global / useFirefoxEnv := false" ) addCommandAlias("prePR", "; root/clean; +root/scalafmtAll; +root/headerCreate") val jsProjects: Seq[ProjectReference] = - Seq(kernel.js, kernelTestkit.js, laws.js, core.js, testkit.js, tests.js, std.js, example.js) + Seq( + kernel.js, + kernelTestkit.js, + laws.js, + core.js, + testkit.js, + tests.js, + webWorkerTests, + std.js, + example.js) val undocumentedRefs = jsProjects ++ Seq[ProjectReference](benchmarks, example.jvm) @@ -222,14 +236,14 @@ lazy val kernel = crossProject(JSPlatform, JVMPlatform) name := "cats-effect-kernel", libraryDependencies ++= Seq( ("org.specs2" %%% "specs2-core" % Specs2Version % Test).cross(CrossVersion.for3Use2_13), - "org.typelevel" %%% "cats-core" % CatsVersion)) - .jsSettings( - Compile / doc / sources := { - if (isDotty.value) - Seq() - else - (Compile / doc / sources).value - }) + "org.typelevel" %%% "cats-core" % CatsVersion) + ) + .jsSettings(Compile / doc / sources := { + if (isDotty.value) + Seq() + else + (Compile / doc / sources).value + }) /** * Reference implementations (including a pure ConcurrentBracket), generic ScalaCheck @@ -278,16 +292,21 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) ProblemFilters.exclude[MissingClassProblem]("cats.effect.AsyncPropagateCancelation$"), // introduced by #1913, striped fiber callback hashtable, changes to package private code ProblemFilters.exclude[MissingClassProblem]("cats.effect.unsafe.FiberErrorHashtable"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("cats.effect.unsafe.IORuntime.fiberErrorCbs"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]( + "cats.effect.unsafe.IORuntime.fiberErrorCbs"), ProblemFilters.exclude[IncompatibleMethTypeProblem]("cats.effect.unsafe.IORuntime.this"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("cats.effect.unsafe.IORuntime.$default$6"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]( + "cats.effect.unsafe.IORuntime.$default$6"), // introduced by #1928, wake up a worker thread before spawning a helper thread when blocking // changes to `cats.effect.unsafe` package private code - ProblemFilters.exclude[IncompatibleResultTypeProblem]("cats.effect.unsafe.WorkStealingThreadPool.notifyParked"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]( + "cats.effect.unsafe.WorkStealingThreadPool.notifyParked"), // introduced by #2041, Rewrite and improve `ThreadSafeHashtable` // changes to `cats.effect.unsafe` package private code - ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.unsafe.ThreadSafeHashtable.hashtable"), - ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.unsafe.ThreadSafeHashtable.hashtable_="), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "cats.effect.unsafe.ThreadSafeHashtable.hashtable"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "cats.effect.unsafe.ThreadSafeHashtable.hashtable_="), // introduced by #2051, Tracing // changes to package private code ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#Blocking.apply"), @@ -299,7 +318,8 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#FlatMap.apply"), ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#FlatMap.copy"), ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#FlatMap.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#HandleErrorWith.apply"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "cats.effect.IO#HandleErrorWith.apply"), ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#HandleErrorWith.copy"), ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#HandleErrorWith.this"), ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#Map.apply"), @@ -312,7 +332,9 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) ProblemFilters.exclude[MissingClassProblem]("cats.effect.SyncIO$Delay"), ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#IOCont.apply"), ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#IOCont.copy"), - ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#IOCont.this"))) + ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#IOCont.this") + ) + ) .jvmSettings( javacOptions ++= Seq("-source", "1.8", "-target", "1.8") ) @@ -345,6 +367,20 @@ lazy val tests = crossProject(JSPlatform, JVMPlatform) Test / fork := true, Test / javaOptions += s"-Dsbt.classpath=${(Test / fullClasspath).value.map(_.data.getAbsolutePath).mkString(File.pathSeparator)}") +lazy val webWorkerTests = project + .in(file("webworker-tests")) + .dependsOn(tests.js % "compile->test") + .enablePlugins(ScalaJSPlugin, BuildInfoPlugin, NoPublishPlugin) + .settings( + name := "cats-effect-webworker-tests", + scalaJSUseMainModuleInitializer := true, + libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "1.1.0") + .cross(CrossVersion.for3Use2_13), + (Test / test) := (Test / test).dependsOn(Compile / fastOptJS).value, + buildInfoKeys := Seq[BuildInfoKey](scalaVersion, baseDirectory), + buildInfoPackage := "cats.effect" + ) + /** * Implementations lof standard functionality (e.g. Semaphore, Console, Queue) * purely in terms of the typeclasses, with no dependency on IO. In most cases, @@ -388,7 +424,4 @@ lazy val benchmarks = project .settings(name := "cats-effect-benchmarks") .enablePlugins(NoPublishPlugin, JmhPlugin) -lazy val docs = project - .in(file("site-docs")) - .dependsOn(core.jvm) - .enablePlugins(MdocPlugin) +lazy val docs = project.in(file("site-docs")).dependsOn(core.jvm).enablePlugins(MdocPlugin) diff --git a/core/js/src/main/scala/cats/effect/unsafe/PolyfillExecutionContext.scala b/core/js/src/main/scala/cats/effect/unsafe/PolyfillExecutionContext.scala index e9bc9e3d74..816d850273 100644 --- a/core/js/src/main/scala/cats/effect/unsafe/PolyfillExecutionContext.scala +++ b/core/js/src/main/scala/cats/effect/unsafe/PolyfillExecutionContext.scala @@ -113,6 +113,21 @@ private[unsafe] object PolyfillExecutionContext extends ExecutionContext { js.Dynamic.global.postMessage(messagePrefix + handle, "*") () } + } else if (js.typeOf(js.Dynamic.global.MessageChannel) != Undefined) { + val channel = js.Dynamic.newInstance(js.Dynamic.global.MessageChannel)() + + channel.port1.onmessage = { event: js.Dynamic => + runIfPresent(event.data.asInstanceOf[Int]) + } + + { k => + val handle = nextHandle + nextHandle += 1 + + tasksByHandle += (handle -> k) + channel.port2.postMessage(handle) + () + } } else { // we don't try to look for process.nextTick since scalajs doesn't support old node // we're also not going to bother fast-pathing for IE6; just fall through diff --git a/project/plugins.sbt b/project/plugins.sbt index 26fa3c8185..97417c72cb 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,3 +7,4 @@ addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.22") addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") diff --git a/webworker-tests/src/main/scala/cats/effect/IOSpecRunner.scala b/webworker-tests/src/main/scala/cats/effect/IOSpecRunner.scala new file mode 100644 index 0000000000..4a187f7f7f --- /dev/null +++ b/webworker-tests/src/main/scala/cats/effect/IOSpecRunner.scala @@ -0,0 +1,53 @@ +/* + * Copyright 2020-2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect + +import org.scalajs.dom.webworkers.DedicatedWorkerGlobalScope +import org.specs2.control.ExecuteActions +import org.specs2.reporter.BufferedLineLogger +import org.specs2.runner.ClassRunner +import org.specs2.runner.Runner +import org.specs2.specification.core.Env + +import scala.scalajs.js + +object IOSpecRunner extends IOApp.Simple with ClassRunner { + + def postMessage(msg: js.Any): Unit = DedicatedWorkerGlobalScope.self.postMessage(msg) + + override def run: IO[Unit] = IO.fromFuture { + IO { + val spec = new IOSpec + val env = Env(lineLogger = new BufferedLineLogger { + override def infoLine(msg: String): Unit = postMessage(s"[info] $msg") + override def failureLine(msg: String): Unit = postMessage(s"[error] $msg") + override def errorLine(msg: String): Unit = postMessage(s"[error] $msg") + override def warnLine(msg: String): Unit = postMessage(s"[warn] $msg") + }) + val loader = new ClassLoader() {} + val action = for { + printers <- createPrinters(env.arguments, loader).toAction + stats <- Runner.runSpecStructure(spec.structure(env), env, loader, printers) + // TODO I have no idea how to suspend effects in this + _ = postMessage(stats.toString) + _ = postMessage(stats.isSuccess) + } yield () + ExecuteActions.runActionFuture(action)(env.executionEnv) + } + } + +} diff --git a/webworker-tests/src/test/scala/cats/effect/WebWorkerIOSpec.scala b/webworker-tests/src/test/scala/cats/effect/WebWorkerIOSpec.scala new file mode 100644 index 0000000000..a3778b98a9 --- /dev/null +++ b/webworker-tests/src/test/scala/cats/effect/WebWorkerIOSpec.scala @@ -0,0 +1,56 @@ +/* + * Copyright 2020-2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect + +import org.scalajs.dom.webworkers.Worker +import org.scalajs.dom.window + +import scala.concurrent.duration._ +import scala.util.Try + +class WebWorkerIOSpec extends BaseSpec { + + override def executionTimeout = 5.minutes // This is to run the _entire_ IOSpec + + def scalaVersion = if (BuildInfo.scalaVersion.startsWith("2")) + BuildInfo.scalaVersion.split("\\.").init.mkString(".") + else + BuildInfo.scalaVersion + + def targetDir = s"${BuildInfo.baseDirectory}/target/scala-${scalaVersion}" + + Try(window).toOption.foreach { _ => + "io on webworker" should { + "pass the spec" in real { + for { + worker <- IO( + new Worker(s"file://${targetDir}/cats-effect-webworker-tests-fastopt/main.js")) + success <- IO.async_[Boolean] { cb => + worker.onmessage = { event => + event.data match { + case log: String => println(log) + case success: Boolean => cb(Right(success)) + case _ => () + } + } + } + } yield success mustEqual true + } + } + } + +} diff --git a/webworker-tests/src/test/scala/java/io/File.scala b/webworker-tests/src/test/scala/java/io/File.scala new file mode 100644 index 0000000000..b03c6cc7b3 --- /dev/null +++ b/webworker-tests/src/test/scala/java/io/File.scala @@ -0,0 +1,22 @@ +/* + * Copyright 2020-2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.io + +// hack hack buildinfo hack +class File(path: String) { + override def toString() = path +}