From b2631483722dfb521a338ea18abb645857e1445f Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 20 Jan 2024 12:01:05 +0100 Subject: [PATCH 1/6] Removed ZIO1 and CE2 modules, updated all dependencies --- build.sbt | 78 +-- project/build.properties | 2 +- project/plugins.sbt | 13 +- .../scala/io/github/vigoo/prox/ProxFS2.scala | 71 +- .../prox/tests/fs2/InterpolatorSpecs.scala | 18 +- .../prox/tests/fs2/ProcessGroupSpecs.scala | 58 +- .../vigoo/prox/tests/fs2/ProcessSpecs.scala | 153 +++-- .../prox/tests/fs2/ProxSpecHelpers.scala | 25 +- .../scala/io/github/vigoo/prox/ProxFS2.scala | 122 ---- .../prox/tests/fs2/InterpolatorSpecs.scala | 85 --- .../prox/tests/fs2/ProcessGroupSpecs.scala | 586 ----------------- .../vigoo/prox/tests/fs2/ProcessSpecs.scala | 617 ------------------ .../prox/tests/fs2/ProxSpecHelpers.scala | 25 - .../prox/tests/zstream/ProcessSpecs.scala | 8 +- .../io/github/vigoo/prox/ProxZStream.scala | 147 ----- .../tests/zstream/ProcessGroupSpecs.scala | 494 -------------- .../prox/tests/zstream/ProcessSpecs.scala | 451 ------------- .../prox/tests/zstream/ProxSpecHelpers.scala | 18 - 18 files changed, 216 insertions(+), 2755 deletions(-) delete mode 100644 prox-fs2/src/main/scala/io/github/vigoo/prox/ProxFS2.scala delete mode 100644 prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala delete mode 100644 prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala delete mode 100644 prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala delete mode 100644 prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala delete mode 100644 prox-zstream/src/main/scala/io/github/vigoo/prox/ProxZStream.scala delete mode 100644 prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala delete mode 100644 prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala delete mode 100644 prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProxSpecHelpers.scala diff --git a/build.sbt b/build.sbt index 021ec45d..2d2a9a29 100644 --- a/build.sbt +++ b/build.sbt @@ -2,21 +2,19 @@ val scala212 = "2.12.18" val scala213 = "2.13.12" val scala3 = "3.3.1" -val zioVersion = "1.0.16" -val zio2Version = "2.0.2" +val zio2Version = "2.0.21" -val scalacOptions212 = Seq("-Ypartial-unification", "-deprecation", "-target:jvm-1.8") -val scalacOptions213 = Seq("-deprecation", "-target:jvm-1.8") +def scalacOptions212(jdk: Int) = Seq("-Ypartial-unification", "-deprecation", "-release", jdk.toString) +def scalacOptions213(jdk: Int) = Seq("-deprecation", "-release", jdk.toString) def scalacOptions3(jdk: Int) = Seq("-deprecation", "-Ykind-projector", "-release", jdk.toString) import microsites.ConfigYml -import sbt.enablePlugins import xerial.sbt.Sonatype._ import scala.xml.{Node => XmlNode, NodeSeq => XmlNodeSeq, _} import scala.xml.transform.{RewriteRule, RuleTransformer} -dynverSonatypeSnapshots in ThisBuild := true +ThisBuild / dynverSonatypeSnapshots := true def commonSettings(jdk: Int) = Seq( organization := "io.github.vigoo", @@ -30,15 +28,15 @@ def commonSettings(jdk: Int) = Seq( ) }), libraryDependencies ++= Seq( - "org.scala-lang.modules" %% "scala-collection-compat" % "2.8.1" + "org.scala-lang.modules" %% "scala-collection-compat" % "2.11.0" ), - coverageEnabled in(Test, compile) := true, - coverageEnabled in(Compile, compile) := false, + Test / compile / coverageEnabled := true, + Compile / compile / coverageEnabled := false, scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 12)) => scalacOptions212 - case Some((2, 13)) => scalacOptions213 + case Some((2, 12)) => scalacOptions212(jdk) + case Some((2, 13)) => scalacOptions213(jdk) case Some((3, _)) => scalacOptions3(jdk) case _ => Nil }), @@ -74,47 +72,21 @@ lazy val prox = project.in(file(".")) .settings( name := "prox", organization := "io.github.vigoo", - skip in publish := true + publish / skip := true ) - .aggregate(proxCore, proxFS2, proxFS23, proxZStream, proxZStream2, proxJava9) + .aggregate(proxCore, proxFS23, proxZStream2, proxJava9) lazy val proxCore = Project("prox-core", file("prox-core")).settings(commonSettings(8)) -lazy val proxFS2 = Project("prox-fs2", file("prox-fs2")).settings(commonSettings(8)).settings( - libraryDependencies ++= Seq( - "org.typelevel" %% "cats-effect" % "2.5.5", - "co.fs2" %% "fs2-core" % "2.5.11", - "co.fs2" %% "fs2-io" % "2.5.11", - - "dev.zio" %% "zio" % zioVersion % "test", - "dev.zio" %% "zio-test" % zioVersion % "test", - "dev.zio" %% "zio-test-sbt" % zioVersion % "test", - "dev.zio" %% "zio-interop-cats" % "2.5.1.0" % "test", - ), - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), -).dependsOn(proxCore) - lazy val proxFS23 = Project("prox-fs2-3", file("prox-fs2-3")).settings(commonSettings(8)).settings( libraryDependencies ++= Seq( - "co.fs2" %% "fs2-core" % "3.3.0", - "co.fs2" %% "fs2-io" % "3.3.0", + "co.fs2" %% "fs2-core" % "3.9.3", + "co.fs2" %% "fs2-io" % "3.9.3", - "dev.zio" %% "zio" % zioVersion % "test", - "dev.zio" %% "zio-test" % zioVersion % "test", - "dev.zio" %% "zio-test-sbt" % zioVersion % "test", - "dev.zio" %% "zio-interop-cats" % "3.2.9.1" % "test", - ), - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), -).dependsOn(proxCore) - -lazy val proxZStream = Project("prox-zstream", file("prox-zstream")).settings(commonSettings(8)).settings( - libraryDependencies ++= Seq( - "dev.zio" %% "zio" % zioVersion, - "dev.zio" %% "zio-streams" % zioVersion, - "dev.zio" %% "zio-prelude" % "1.0.0-RC8", - - "dev.zio" %% "zio-test" % zioVersion % "test", - "dev.zio" %% "zio-test-sbt" % zioVersion % "test", + "dev.zio" %% "zio" % zio2Version % "test", + "dev.zio" %% "zio-test" % zio2Version % "test", + "dev.zio" %% "zio-test-sbt" % zio2Version % "test", + "dev.zio" %% "zio-interop-cats" % "23.1.0.0" % "test", ), testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), ).dependsOn(proxCore) @@ -125,7 +97,7 @@ lazy val proxZStream2 = Project("prox-zstream-2", file("prox-zstream-2")).settin libraryDependencies ++= Seq( "dev.zio" %% "zio" % zio2Version, "dev.zio" %% "zio-streams" % zio2Version, - "dev.zio" %% "zio-prelude" % "1.0.0-RC15", + "dev.zio" %% "zio-prelude" % "1.0.0-RC21", "dev.zio" %% "zio-test" % zio2Version % "test", "dev.zio" %% "zio-test-sbt" % zio2Version % "test", @@ -142,17 +114,17 @@ lazy val docs = project .settings( addCompilerPlugin("org.typelevel" %% s"kind-projector" % "0.13.2" cross CrossVersion.full), publishArtifact := false, - skip in publish := true, + publish / skip := true, scalaVersion := scala213, name := "prox", description := "A Scala library for working with system processes", git.remoteRepo := "git@github.com:vigoo/prox.git", - siteSubdirName in ScalaUnidoc := "api", - addMappingsToSiteDir(mappings in (ScalaUnidoc, packageDoc), siteSubdirName in ScalaUnidoc), + ScalaUnidoc / siteSubdirName := "api", + addMappingsToSiteDir(ScalaUnidoc / packageDoc / mappings, ScalaUnidoc / siteSubdirName), ScalaUnidoc / unidoc / unidocProjectFilter := inProjects( proxCore, - proxFS2, - proxZStream, + proxFS23, + proxZStream2, proxJava9 ), micrositeUrl := "https://vigoo.github.io", @@ -181,7 +153,7 @@ lazy val docs = project micrositeAnalyticsToken := "UA-56320875-3", micrositePushSiteWith := GitHub4s, micrositeGithubToken := sys.env.get("GITHUB_TOKEN"), - includeFilter in makeSite := "*.html" | "*.css" | "*.png" | "*.jpg" | "*.gif" | "*.js" | "*.swf" | "*.txt" | "*.xml" | "*.svg", + makeSite / includeFilter := "*.html" | "*.css" | "*.png" | "*.jpg" | "*.gif" | "*.js" | "*.swf" | "*.txt" | "*.xml" | "*.svg", // Temporary fix to avoid including mdoc in the published POM // skip dependency elements with a scope @@ -197,4 +169,4 @@ lazy val docs = project } }).transform(node).head } - ).dependsOn(proxCore, proxFS2/* todo , proxFS23 */, proxZStream, proxJava9) + ).dependsOn(proxCore, proxFS23, proxZStream2, proxJava9) diff --git a/project/build.properties b/project/build.properties index b46cfa14..63df6ac8 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.6.2 \ No newline at end of file +sbt.version = 1.9.8 \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt index 720d49ea..d83ff70a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,9 @@ -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") -addSbtPlugin("com.47deg" % "sbt-microsites" % "1.3.4") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9") +addSbtPlugin("com.47deg" % "sbt-microsites" % "1.4.4") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.5.2") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") -addSbtPlugin("com.github.sbt" % "sbt-unidoc" % "0.5.0") \ No newline at end of file +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") +addSbtPlugin("com.github.sbt" % "sbt-unidoc" % "0.5.0") + +ThisBuild / libraryDependencySchemes ++= Seq( + "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always +) \ No newline at end of file diff --git a/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala b/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala index 51b6f285..931ba349 100644 --- a/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala +++ b/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala @@ -9,7 +9,7 @@ import scala.concurrent.blocking trait ProxFS2[F[_]] extends Prox { - implicit val instances: Sync[F] with Concurrent[F] + val instances: Sync[F] with Concurrent[F] override type ProxExitCode = cats.effect.ExitCode override type ProxFiber[A] = cats.effect.Fiber[F, Throwable, A] @@ -24,67 +24,89 @@ trait ProxFS2[F[_]] extends Prox { protected override final def exitCodeFromInt(value: Int): ProxExitCode = cats.effect.ExitCode(value) - protected override final def unit: ProxIO[Unit] = Applicative[F].unit + protected override final def unit: ProxIO[Unit] = Applicative[F](instances).unit - protected override final def pure[A](value: A): ProxIO[A] = Applicative[F].pure(value) + protected override final def pure[A](value: A): ProxIO[A] = Applicative[F](instances).pure(value) - protected override final def effect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = + protected override final def effect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = { + implicit val i: Sync[F] with Concurrent[F] = instances Sync[F].adaptError(Sync[F].delay(f)) { case failure: Throwable => wrapError(failure).toThrowable } + } - protected override final def blockingEffect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = + protected override final def blockingEffect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = { + implicit val i: Sync[F] with Concurrent[F] = instances Sync[F].adaptError(Sync[F].interruptibleMany(f)) { case failure: Throwable => wrapError(failure).toThrowable } + } - protected override final def raiseError(error: ProxError): ProxIO[Unit] = ApplicativeError[F, Throwable].raiseError(error.toThrowable) + protected override final def raiseError(error: ProxError): ProxIO[Unit] = ApplicativeError[F, Throwable](instances).raiseError(error.toThrowable) - protected override final def ioMap[A, B](io: ProxIO[A], f: A => B): ProxIO[B] = Applicative[F].map(io)(f) + protected override final def ioMap[A, B](io: ProxIO[A], f: A => B): ProxIO[B] = Applicative[F](instances).map(io)(f) - protected override final def ioFlatMap[A, B](io: ProxIO[A], f: A => ProxIO[B]): ProxIO[B] = FlatMap[F].flatMap(io)(f) + protected override final def ioFlatMap[A, B](io: ProxIO[A], f: A => ProxIO[B]): ProxIO[B] = FlatMap[F](instances).flatMap(io)(f) - protected override final def traverse[A, B](list: List[A])(f: A => ProxIO[B]): ProxIO[List[B]] = Traverse[List].traverse(list)(f) + protected override final def traverse[A, B](list: List[A])(f: A => ProxIO[B]): ProxIO[List[B]] = Traverse[List].traverse(list)(f)(instances) protected override final def identityPipe[A]: ProxPipe[A, A] = identity[ProxStream[A]] protected override final def bracket[A, B](acquire: ProxIO[A])(use: A => ProxIO[B])(fin: (A, IOResult) => ProxIO[Unit]): ProxIO[B] = - Sync[F].bracketCase(acquire)(use) { + Sync[F](instances).bracketCase(acquire)(use) { case (value, Outcome.Succeeded(_)) => fin(value, Completed) case (value, Outcome.Errored(error)) => fin(value, Failed(List(UnknownProxError(error)))) case (value, Outcome.Canceled()) => fin(value, Canceled) } - protected override final def makeResource[A](acquire: ProxIO[A], release: A => ProxIO[Unit]): ProxResource[A] = cats.effect.Resource.make(acquire)(release) + protected override final def makeResource[A](acquire: ProxIO[A], release: A => ProxIO[Unit]): ProxResource[A] = cats.effect.Resource.make(acquire)(release)(instances) - protected override final def useResource[A, B](r: ProxResource[A], f: A => ProxIO[B]): ProxIO[B] = r.use(f) + protected override final def useResource[A, B](r: ProxResource[A], f: A => ProxIO[B]): ProxIO[B] = r.use(f)(instances) - protected override final def joinFiber[A](f: ProxFiber[A]): ProxIO[A] = f.joinWithNever + protected override final def joinFiber[A](f: ProxFiber[A]): ProxIO[A] = f.joinWithNever(instances) protected override final def cancelFiber[A](f: ProxFiber[A]): ProxIO[Unit] = f.cancel - protected override final def startFiber[A](f: ProxIO[A]): ProxIO[ProxFiber[A]] = Concurrent[F].start(f) + protected override final def startFiber[A](f: ProxIO[A]): ProxIO[ProxFiber[A]] = { + implicit val i: Sync[F] with Concurrent[F] = instances + Concurrent[F].start(f) + } - protected override final def drainStream[A](s: ProxStream[A]): ProxIO[Unit] = s.compile.drain + protected override final def drainStream[A](s: ProxStream[A]): ProxIO[Unit] = { + implicit val i: Sync[F] with Concurrent[F] = instances + s.compile.drain + } - protected override final def streamToVector[A](s: ProxStream[A]): ProxIO[Vector[A]] = s.compile.toVector + protected override final def streamToVector[A](s: ProxStream[A]): ProxIO[Vector[A]] = { + implicit val i: Sync[F] with Concurrent[F] = instances + s.compile.toVector + } - protected override final def foldStream[A, B](s: ProxStream[A], init: B, f: (B, A) => B): ProxIO[B] = s.compile.fold(init)(f) + protected override final def foldStream[A, B](s: ProxStream[A], init: B, f: (B, A) => B): ProxIO[B] = { + implicit val i: Sync[F] with Concurrent[F] = instances + s.compile.fold(init)(f) + } - protected override final def foldMonoidStream[A: ProxMonoid](s: ProxStream[A]): ProxIO[A] = s.compile.foldMonoid + protected override final def foldMonoidStream[A: ProxMonoid](s: ProxStream[A]): ProxIO[A] = { + implicit val i: Sync[F] with Concurrent[F] = instances + s.compile.foldMonoid + } protected override final def streamThrough[A, B](s: ProxStream[A], pipe: ProxPipe[A, B]): ProxStream[B] = s.through(pipe) - protected override final def runStreamTo[A](s: ProxStream[A], sink: ProxSink[A]): ProxIO[Unit] = + protected override final def runStreamTo[A](s: ProxStream[A], sink: ProxSink[A]): ProxIO[Unit] = { + implicit val i: Sync[F] with Concurrent[F] = instances s.through(sink).compile.drain + } protected override final def fromJavaInputStream(input: io.InputStream, chunkSize: Int): ProxStream[Byte] = fs2.io.readInputStream( pure(input), chunkSize, - closeAfterUse = true) + closeAfterUse = true)(instances) - protected override final def drainToJavaOutputStream(stream: ProxStream[Byte], output: io.OutputStream, flushChunks: Boolean): ProxIO[Unit] = + protected override final def drainToJavaOutputStream(stream: ProxStream[Byte], output: io.OutputStream, flushChunks: Boolean): ProxIO[Unit] = { + implicit val i: Sync[F] with Concurrent[F] = instances stream .through( if (flushChunks) writeAndFlushOutputStream(output)(_).drain @@ -93,8 +115,10 @@ trait ProxFS2[F[_]] extends Prox { closeAfterUse = true)) .compile .drain + } - private def writeAndFlushOutputStream(stream: java.io.OutputStream): ProxPipe[Byte, Unit] = + private def writeAndFlushOutputStream(stream: java.io.OutputStream): ProxPipe[Byte, Unit] = { + implicit val i: Sync[F] with Concurrent[F] = instances s => { fs2.Stream .bracket(Applicative[F].pure(stream))(os => Sync[F].delay(os.close())) @@ -107,10 +131,11 @@ trait ProxFS2[F[_]] extends Prox { } } } + } } object ProxFS2 { def apply[F[_]](implicit a: Async[F]): ProxFS2[F] = new ProxFS2[F] { - override implicit val instances: Sync[F] with Concurrent[F] = a + override val instances: Sync[F] with Concurrent[F] = a } } diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala index f30952e2..68a8c10b 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala @@ -2,10 +2,10 @@ package io.github.vigoo.prox.tests.fs2 import zio.ZIO import zio.test.Assertion.{equalTo, isEmpty} -import zio.test.{DefaultRunnableSpec, ZSpec, assert, suite} +import zio.test._ -object InterpolatorSpecs extends DefaultRunnableSpec with ProxSpecHelpers { - override val spec: ZSpec[_root_.zio.test.environment.TestEnvironment, Any] = +object InterpolatorSpecs extends ZIOSpecDefault with ProxSpecHelpers { + override val spec = suite("Process interpolators")( suite("cats-effect process interpolator")( proxTest("works with single-word process names") { prox => @@ -14,7 +14,11 @@ object InterpolatorSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = proc"ls" ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(isEmpty)) + assertTrue( + process.command == "ls", + process.arguments.isEmpty + ) + ) }, proxTest("works with interpolated process name") { prox => @@ -24,7 +28,11 @@ object InterpolatorSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = proc"$cmd" ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(isEmpty)) + assertTrue( + process.command == "ls", + process.arguments.isEmpty + ) + ) }, proxTest("works with static parameters") { prox => diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala index cb72c5d9..a5b8e1fb 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala @@ -2,14 +2,14 @@ package io.github.vigoo.prox.tests.fs2 import cats.effect.ExitCode import fs2.io.file.{Files, Flags} -import zio.duration._ +import io.github.vigoo.prox.UnknownProxError import zio.interop.catz._ import zio.test.Assertion._ import zio.test.TestAspect._ import zio.test._ -import zio.{IO, RIO, Task, ZEnv, ZIO} +import zio.{IO, RIO, Task, ZIO, durationInt} -object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { +object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { override val spec = suite("Piping processes together")( @@ -22,7 +22,7 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val processGroup = (Process("echo", List("This is a test string")) | Process("wc", List("-w"))) ># fs2.text.utf8.decode val program = processGroup.run().map(_.output.trim) - assertM(program)(equalTo("5")) + program.map(r => assert(r)(equalTo("5"))) }, proxTest("is possible with multiple") { prox => @@ -41,7 +41,7 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { r => r.output.map(_.stripLineEnd.trim).filter(_.nonEmpty) ) - assertM(program)(hasSameElements(List("1 apple"))) + program.map(r => assert(r)(hasSameElements(List("1 apple")))) }, proxTest("is customizable with pipes") { prox => @@ -62,7 +62,7 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val processGroup = (Process("echo", List("This is a test string")).via(customPipe).to(Process("wc", List("-w")))) ># fs2.text.utf8.decode val program = processGroup.run().map(_.output.trim) - assertM(program)(equalTo("11")) + program.map(r => assert(r)(equalTo("11"))) }, proxTest("can be mapped") { prox => @@ -81,7 +81,7 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val program = processGroup2.run().map(_.output.trim) - assertM(program)(equalTo("5")) + program.map(r => assert(r)(equalTo("5"))) } ), @@ -96,7 +96,7 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { Process("sort") val program = processGroup.start().use { fiber => fiber.cancel } - assertM(program)(equalTo(())) + program.map(r => assert(r)(equalTo(()))) } @@ TestAspect.timeout(5.seconds), proxTest("can be terminated") { prox => @@ -110,12 +110,12 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val program = for { runningProcesses <- processGroup.startProcessGroup() - _ <- ZIO(Thread.sleep(250)) + _ <- ZIO.sleep(250.millis) result <- runningProcesses.terminate() } yield result.exitCodes.toList - assertM(program)(contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1))) - }, + program.map(r => assert(r)(contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1)))) + } @@ TestAspect.withLiveClock, proxTest("can be killed") { prox => import prox._ @@ -128,14 +128,14 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val program = for { runningProcesses <- processGroup.startProcessGroup() - _ <- ZIO(Thread.sleep(250)) + _ <- ZIO.sleep(250.millis) result <- runningProcesses.kill() } yield result.exitCodes // Note: we can't assert on the second process' exit code because there is a race condition // between killing it directly and being stopped because of the upstream process got killed. - assertM(program)(contains(p1 -> ExitCode(137))) - } + program.map(r => assert(r)(contains(p1 -> ExitCode(137)))) + } @@ TestAspect.withLiveClock ), suite("Input redirection")( @@ -148,7 +148,7 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val processGroup = (Process("cat") | Process("wc", List("-w"))) < stream ># fs2.text.utf8.decode val program = processGroup.run().map(_.output.trim) - assertM(program)(equalTo("5")) + program.map(r => assert(r)(equalTo("5"))) }, proxTest("can be fed with an input file") { prox => @@ -158,12 +158,12 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { withTempFile { tempFile => val program = for { - _ <- ZIO(java.nio.file.Files.write(tempFile.toPath, "This is a test string".getBytes("UTF-8"))) + _ <- ZIO.attempt(java.nio.file.Files.write(tempFile.toPath, "This is a test string".getBytes("UTF-8"))) processGroup = (Process("cat") | Process("wc", List("-w"))) < tempFile.toPath ># fs2.text.utf8.decode result <- processGroup.run() } yield result.output.trim - assertM(program)(equalTo("5")) + program .map(r => assert(r)(equalTo("5"))) } } ), @@ -177,10 +177,10 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val processGroup = (Process("echo", List("This is a test string")) | Process("wc", List("-w"))) > tempFile.toPath val program = for { _ <- processGroup.run() - contents <- Files[RIO[ZEnv, *]].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid } yield contents.trim - assertM(program)(equalTo("5")) + program.map(r => assert(r)(equalTo("5"))) } }, ), @@ -212,7 +212,7 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val builder = new StringBuilder - val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => IO { + val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => ZIO.attempt { builder.append(byte.toChar) }.unit) @@ -332,11 +332,11 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) val processGroup = (p1 | p2).customizedPerProcess.errorsToSink { - case p if p == p1 => _.evalMap(byte => IO { + case p if p == p1 => _.evalMap(byte => ZIO.attempt { builder1.append(byte.toChar) }.unit) case p if p == p2 => - _.evalMap(byte => IO { + _.evalMap(byte => ZIO.attempt { builder2.append(byte.toChar) }.unit) } @@ -443,11 +443,11 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { } val program = for { _ <- processGroup.run() - contents1 <- Files[RIO[ZEnv, *]].readAll(fs2.io.file.Path.fromNioPath(tempFile1.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid - contents2 <- Files[RIO[ZEnv, *]].readAll(fs2.io.file.Path.fromNioPath(tempFile2.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents1 <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile1.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents2 <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile2.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid } yield (contents1, contents2) - assertM(program)(equalTo(("Hello", "world"))) + program.map(r => assert(r)(equalTo(("Hello", "world")))) } } }, @@ -575,11 +575,11 @@ object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { }, ), - testM("bound process is not pipeable") { - assertM( - typeCheck("""val bad = (Process("echo", List("Hello world")) ># fs2.text.utf8.decode) | Process("wc", List("-w"))"""))( + test("bound process is not pipeable") { + typeCheck("""val bad = (Process("echo", List("Hello world")) ># fs2.text.utf8.decode) | Process("wc", List("-w"))""").map(r => + assert(r)( isLeft(anything) - ) + )) } ) @@ timeout(60.seconds) @@ sequential } diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala index d9c87816..60c8675d 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala @@ -1,18 +1,14 @@ package io.github.vigoo.prox.tests.fs2 -import fs2.io.file.{Files, Flags} import cats.effect.ExitCode -import zio.clock.Clock -import zio.duration._ +import fs2.io.file.{Files, Flags} +import zio.interop.catz._ import zio.test.Assertion.{anything, equalTo, hasSameElements, isLeft} import zio.test.TestAspect._ import zio.test._ -import zio.{IO, RIO, Task, ZEnv, ZIO} -import zio.interop.catz._ +import zio.{Task, ZIO, durationInt} -import java.nio.file.Paths - -object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { +object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { override val spec = suite("Executing a process")( proxTest("returns the exit code") { prox => @@ -25,7 +21,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { falseResult <- Process("false").run() } yield (trueResult.exitCode, falseResult.exitCode) - assertM(program)(equalTo((ExitCode(0), ExitCode(1)))) + program.map(r => assert(r)(equalTo((ExitCode(0), ExitCode(1))))) }, suite("Output redirection")( @@ -38,10 +34,10 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("echo", List("Hello world!")) > tempFile.toPath val program = for { _ <- process.run() - contents <- Files[RIO[ZEnv, *]].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid } yield contents - assertM(program)(equalTo("Hello world!\n")) + program.map(r => assert(r)(equalTo("Hello world!\n"))) } }, @@ -56,10 +52,10 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val program = for { _ <- process1.run() _ <- process2.run() - contents <- Files[RIO[ZEnv, *]].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid } yield contents - assertM(program)(equalTo("Hello\nworld\n")) + program.map(r => assert(r)(equalTo("Hello\nworld\n"))) } }, @@ -71,7 +67,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("echo", List("Hello world!")) ># fs2.text.utf8.decode val program = process.run().map(_.output) - assertM(program)(equalTo("Hello world!\n")) + program.map(r => assert(r)(equalTo("Hello world!\n"))) }, proxTest("can redirect output to stream folding monoid") { prox => @@ -82,7 +78,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("echo", List("Hello\nworld!")) ># fs2.text.utf8.decode.andThen(fs2.text.lines) val program = process.run().map(_.output) - assertM(program)(equalTo("Helloworld!")) + program.map(r => assert(r)(equalTo("Helloworld!"))) }, proxTest("can redirect output to stream collected to vector") { prox => @@ -98,7 +94,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("echo", List("Hello\nworld!")) >? stream val program = process.run().map(_.output) - assertM(program)(hasSameElements(List(StringLength(5), StringLength(6), StringLength(0)))) + program.map(r => assert(r)(hasSameElements(List(StringLength(5), StringLength(6), StringLength(0))))) }, proxTest("can redirect output to stream and ignore it's result") { prox => @@ -109,7 +105,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("echo", List("Hello\nworld!")).drainOutput(fs2.text.utf8.decode.andThen(fs2.text.lines)) val program = process.run().map(_.output) - assertM(program)(equalTo(())) + program.map(r => assert(r)(equalTo(()))) }, proxTest("can redirect output to stream and fold it") { prox => @@ -124,7 +120,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { ) val program = process.run().map(_.output) - assertM(program)(equalTo(Vector(Some('H'), Some('w'), None))) + program.map(r => assert(r)(equalTo(Vector(Some('H'), Some('w'), None)))) }, proxTest("can redirect output to a sink") { prox => @@ -133,14 +129,14 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner val builder = new StringBuilder - val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => IO { + val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => ZIO.attempt { builder.append(byte.toChar) }.unit) val process = Process("echo", List("Hello world!")) > target val program = process.run().map(_ => builder.toString) - assertM(program)(equalTo("Hello world!\n")) + program.map(r => assert(r)(equalTo("Hello world!\n"))) }, ), suite("Error redirection")( @@ -153,10 +149,10 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("perl", List("-e", "print STDERR 'Hello world!'")) !> tempFile.toPath val program = for { _ <- process.run() - contents <- Files[RIO[ZEnv, *]].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid } yield contents - assertM(program)(equalTo("Hello world!")) + program.map(r => assert(r)(equalTo("Hello world!"))) } }, @@ -171,10 +167,10 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val program = for { _ <- process1.run() _ <- process2.run() - contents <- Files[RIO[ZEnv, *]].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid } yield contents - assertM(program)(equalTo("Helloworld")) + program.map(r => assert(r)(equalTo("Helloworld"))) } }, @@ -186,7 +182,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("perl", List("-e", """print STDERR "Hello"""")) !># fs2.text.utf8.decode val program = process.run().map(_.error) - assertM(program)(equalTo("Hello")) + program.map(r => assert(r)(equalTo("Hello"))) }, proxTest("can redirect error to stream folding monoid") { prox => @@ -197,7 +193,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) !># fs2.text.utf8.decode.andThen(fs2.text.lines) val program = process.run().map(_.error) - assertM(program)(equalTo("Helloworld!")) + program.map(r => assert(r)(equalTo("Helloworld!"))) }, proxTest("can redirect error to stream collected to vector") { prox => @@ -213,7 +209,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) !>? stream val program = process.run().map(_.error) - assertM(program)(hasSameElements(List(StringLength(5), StringLength(6)))) + program.map(r => assert(r)(hasSameElements(List(StringLength(5), StringLength(6))))) }, proxTest("can redirect error to stream and ignore it's result") { prox => @@ -224,7 +220,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")).drainError(fs2.text.utf8.decode.andThen(fs2.text.lines)) val program = process.run().map(_.error) - assertM(program)(equalTo(())) + program.map(r => assert(r)(equalTo(()))) }, proxTest("can redirect error to stream and fold it") { prox => @@ -239,7 +235,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { ) val program = process.run().map(_.error) - assertM(program)(equalTo(Vector(Some('H'), Some('w')))) + program.map(r => assert(r)(equalTo(Vector(Some('H'), Some('w'))))) }, proxTest("can redirect error to a sink") { prox => @@ -248,14 +244,14 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner val builder = new StringBuilder - val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => IO { + val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => ZIO.attempt { builder.append(byte.toChar) }.unit) val process = Process("perl", List("-e", """print STDERR "Hello"""")) !> target val program = process.run().map(_ => builder.toString) - assertM(program)(equalTo("Hello")) + program.map(r => assert(r)(equalTo("Hello"))) }, ), @@ -269,7 +265,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("perl", List("-e", """my $str = <>; print STDERR "$str"""".stripMargin)) < source !># fs2.text.utf8.decode val program = process.run().map(_.error) - assertM(program)(equalTo("This is a test string")) + program.map(r => assert(r)(equalTo("This is a test string"))) }, proxTest("can redirect error first then output to stream") { prox => @@ -280,7 +276,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = (Process("perl", List("-e", """print STDOUT Hello; print STDERR World""".stripMargin)) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) - assertM(program)(equalTo("HelloWorld")) + program.map(r => assert(r)(equalTo("HelloWorld"))) }, proxTest("can redirect output first then error to stream") { prox => @@ -291,7 +287,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = (Process("perl", List("-e", """print STDOUT Hello; print STDERR World""".stripMargin)) ># fs2.text.utf8.decode) !># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) - assertM(program)(equalTo("HelloWorld")) + program.map(r => assert(r)(equalTo("HelloWorld"))) }, proxTest("can redirect output first then error finally input to stream") { prox => @@ -305,7 +301,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { !># fs2.text.utf8.decode) < source val program = process.run().map(r => r.output + r.error) - assertM(program)(equalTo("HelloWorld")) + program.map(r => assert(r)(equalTo("HelloWorld"))) }, proxTest("can redirect output first then input finally error to stream") { prox => @@ -319,7 +315,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { < source) !># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) - assertM(program)(equalTo("HelloWorld")) + program.map(r => assert(r)(equalTo("HelloWorld"))) }, proxTest("can redirect input first then error finally output to stream") { prox => @@ -333,7 +329,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { !># fs2.text.utf8.decode) ># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) - assertM(program)(equalTo("HelloWorld")) + program.map(r => assert(r)(equalTo("HelloWorld"))) }, ), @@ -347,7 +343,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("wc", List("-w")) < source ># fs2.text.utf8.decode val program = process.run().map(_.output.trim) - assertM(program)(equalTo("5")) + program.map(r => assert(r)(equalTo("5"))) }, proxTest("can use stream as input flushing after each chunk") { prox => @@ -359,7 +355,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = (Process("wc", List("-w")) !< source) ># fs2.text.utf8.decode val program = process.run().map(_.output.trim) - assertM(program)(equalTo("5")) + program.map(r => assert(r)(equalTo("5"))) }, ), @@ -370,10 +366,10 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val program = process.start().use { fiber => ZIO(Thread.sleep(250)).flatMap(_ => fiber.cancel) } + val program = process.start().use { fiber => ZIO.sleep(250.millis).flatMap(_ => fiber.cancel) } - assertM(program)(equalTo(())) - } @@ TestAspect.timeout(5.seconds), + program.map(r => assert(r)(equalTo(()))) + } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), proxTest("can be terminated by releasing the resource") { prox => import prox._ @@ -381,10 +377,10 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val program = process.start().use { _ => ZIO(Thread.sleep(250)) } + val program = process.start().use { _ => ZIO.sleep(250.millis) } - assertM(program)(equalTo(())) - } @@ TestAspect.timeout(5.seconds), + program.map(r => assert(r)(equalTo(()))) + } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), proxTest("can be terminated") { prox => import prox._ @@ -394,12 +390,12 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) val program = for { runningProcess <- process.startProcess() - _ <- ZIO(Thread.sleep(250)) + _ <- ZIO.sleep(250.millis) result <- runningProcess.terminate() } yield result.exitCode - assertM(program)(equalTo(ExitCode(1))) - }, + program.map(r => assert(r)(equalTo(ExitCode(1)))) + } @@ TestAspect.withLiveClock, proxTest("can be killed") { prox => import prox._ @@ -409,12 +405,12 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val process = Process("perl", List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""")) val program = for { runningProcess <- process.startProcess() - _ <- ZIO(Thread.sleep(250)) + _ <- ZIO.sleep(250.millis) result <- runningProcess.kill() } yield result.exitCode - assertM(program)(equalTo(ExitCode(137))) - }, + program.map(r => assert(r)(equalTo(ExitCode(137)))) + } @@ TestAspect.withLiveClock, proxTest("can be checked if is alive") { prox => import prox._ @@ -429,7 +425,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { isAliveAfter <- runningProcess.isAlive } yield (isAliveBefore, isAliveAfter) - assertM(program)(equalTo((true, false))) + program.map(r => assert(r)(equalTo((true, false)))) }, ), @@ -443,7 +439,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val p2 = p1.withCommand("echo") val program = p2.run().map(_.output) - assertM(program)(equalTo("Hello world\n")) + program.map(r => assert(r)(equalTo("Hello world\n"))) }, proxTest("can change the arguments") { prox => @@ -455,7 +451,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { val p2 = p1.withArguments(List("Hello", "world")) val program = p2.run().map(_.output) - assertM(program)(equalTo("Hello world\n")) + program.map(r => assert(r)(equalTo("Hello world\n"))) }, proxTest("respects the working directory") { prox => @@ -463,11 +459,11 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - ZIO(java.nio.file.Files.createTempDirectory("prox")).flatMap { tempDirectory => + ZIO.attempt(java.nio.file.Files.createTempDirectory("prox")).flatMap { tempDirectory => val process = (Process("pwd") in tempDirectory) ># fs2.text.utf8.decode val program = process.run().map(_.output.trim) - assertM(program)(equalTo(tempDirectory.toString) || equalTo(s"/private${tempDirectory}")) + program.map(r => assert(r)(equalTo(tempDirectory.toString) || equalTo(s"/private${tempDirectory}"))) } }, @@ -481,7 +477,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) - assertM(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, proxTest("is customizable with excluded environment variables") { prox => @@ -495,7 +491,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { `without` "TEST1") ># fs2.text.utf8.decode val program = process.run().map(_.output) - assertM(program)(equalTo("Hello ! I am prox!\n")) + program.map(r => assert(r)(equalTo("Hello ! I am prox!\n"))) }, proxTest("is customizable with environment variables output is bound") { prox => @@ -508,7 +504,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) - assertM(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, proxTest("is customizable with environment variables if input is bound") { prox => @@ -522,7 +518,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) - assertM(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, proxTest("is customizable with environment variables if error is bound") { prox => @@ -536,7 +532,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) - assertM(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, proxTest("is customizable with environment variables if input and output are bound") { prox => @@ -550,7 +546,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) - assertM(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, @@ -565,7 +561,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) - assertM(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, proxTest("is customizable with environment variables if output and error are bound") { prox => @@ -578,7 +574,7 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) - assertM(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, proxTest("is customizable with environment variables if everything is bound") { prox => @@ -592,27 +588,30 @@ object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) - assertM(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, ), - testM("double output redirect is illegal") { - assertM( - typeCheck("""val bad = Process("echo", List("Hello world")) > new File("x").toPath > new File("y").toPath"""))( + test("double output redirect is illegal") { + typeCheck("""val bad = Process("echo", List("Hello world")) > new File("x").toPath > new File("y").toPath""").map(r => + assert( + r)( isLeft(anything) - ) + )) }, - testM("double error redirect is illegal") { - assertM( - typeCheck("""val bad = Process("echo", List("Hello world")) !> new File("x").toPath !> new File("y").toPath"""))( + test("double error redirect is illegal") { + typeCheck("""val bad = Process("echo", List("Hello world")) !> new File("x").toPath !> new File("y").toPath""").map(r => + assert( + r)( isLeft(anything) - ) + )) }, - testM("double input redirect is illegal") { - assertM( - typeCheck("""val bad = (Process("echo", List("Hello world")) < fs2.Stream("X").through(fs2.text.utf8.encode)) < fs2.Stream("Y").through(fs2.text.utf8.encode)"""))( + test("double input redirect is illegal") { + typeCheck("""val bad = (Process("echo", List("Hello world")) < fs2.Stream("X").through(fs2.text.utf8.encode)) < fs2.Stream("Y").through(fs2.text.utf8.encode)""").map(r => + assert( + r)( isLeft(anything) - ) + )) } ) @@ timeout(60.seconds) @@ sequential } diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala index d8a89aac..4eaa622e 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala @@ -1,26 +1,25 @@ package io.github.vigoo.prox.tests.fs2 -import java.io.File - -import io.github.vigoo.prox.ProxFS2 +import io.github.vigoo.prox.{ProxError, ProxFS2, UnknownProxError} import zio.interop.catz._ -import zio.test.{TestResult, ZSpec, testM} -import zio.{ZIO, Task, URIO, ZEnv} -import zio.RIO +import zio.test._ +import zio.{Task, ZIO} + +import java.io.File trait ProxSpecHelpers { - def proxTest(label: String)(assertion: ProxFS2[Task] => RIO[ZEnv, TestResult]): ZSpec[ZEnv, scala.Throwable] = { - testM(label){ - ZIO.runtime[ZEnv].flatMap { implicit env => + def proxTest(label: String)(assertion: ProxFS2[Task] => ZIO[Any, Throwable, TestResult]): Spec[Any, scala.Throwable] = { + test(label){ + ZIO.runtime[Any].flatMap { implicit env => assertion(ProxFS2[Task]) } } } - def withTempFile[R, A](inner: File => RIO[R, A]): RIO[R, A] = - Task(File.createTempFile("test", "txt")).bracket( - file => URIO(file.delete()), - inner) + def withTempFile[A](inner: File => ZIO[Any, Throwable, A]): ZIO[Any, Throwable, A] = + ZIO.acquireReleaseWith( + ZIO.attempt(File.createTempFile("test", "txt")) + )(file => ZIO.attempt(file.delete()).orDie)(inner) } diff --git a/prox-fs2/src/main/scala/io/github/vigoo/prox/ProxFS2.scala b/prox-fs2/src/main/scala/io/github/vigoo/prox/ProxFS2.scala deleted file mode 100644 index 99a2c65f..00000000 --- a/prox-fs2/src/main/scala/io/github/vigoo/prox/ProxFS2.scala +++ /dev/null @@ -1,122 +0,0 @@ -package io.github.vigoo.prox - -import java.io - -import cats.effect.{Blocker, Concurrent, ContextShift, ExitCase, Sync} -import cats.{Applicative, ApplicativeError, FlatMap, Traverse} - -import scala.concurrent.blocking - -trait ProxFS2[F[_]] extends Prox { - - implicit val concurrent: Concurrent[F] - implicit val blocker: Blocker - implicit val contextShift: ContextShift[F] - - override type ProxExitCode = cats.effect.ExitCode - override type ProxFiber[A] = cats.effect.Fiber[F, A] - override type ProxIO[A] = F[A] - override type ProxResource[A] = cats.effect.Resource[F, A] - - override type ProxPipe[A, B] = fs2.Pipe[F, A, B] - override type ProxSink[A] = fs2.Pipe[F, A, Unit] - override type ProxStream[A] = fs2.Stream[F, A] - - override type ProxMonoid[A] = cats.kernel.Monoid[A] - - protected override final def exitCodeFromInt(value: Int): ProxExitCode = cats.effect.ExitCode(value) - - protected override final def unit: ProxIO[Unit] = Applicative[F].unit - - protected override final def pure[A](value: A): ProxIO[A] = Applicative[F].pure(value) - - protected override final def effect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = - Sync[F].adaptError(Sync[F].delay(f)) { - case failure: Throwable => wrapError(failure).toThrowable - } - - protected override final def blockingEffect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = - Sync[F].adaptError(blocker.delay(f)) { - case failure: Throwable => wrapError(failure).toThrowable - } - - protected override final def raiseError(error: ProxError): ProxIO[Unit] = ApplicativeError[F, Throwable].raiseError(error.toThrowable) - - protected override final def ioMap[A, B](io: ProxIO[A], f: A => B): ProxIO[B] = Applicative[F].map(io)(f) - - protected override final def ioFlatMap[A, B](io: ProxIO[A], f: A => ProxIO[B]): ProxIO[B] = FlatMap[F].flatMap(io)(f) - - protected override final def traverse[A, B](list: List[A])(f: A => ProxIO[B]): ProxIO[List[B]] = Traverse[List].traverse(list)(f) - - protected override final def identityPipe[A]: ProxPipe[A, A] = identity[ProxStream[A]] - - protected override final def bracket[A, B](acquire: ProxIO[A])(use: A => ProxIO[B])(fin: (A, IOResult) => ProxIO[Unit]): ProxIO[B] = - Sync[F].bracketCase(acquire)(use) { - case (value, ExitCase.Completed) => fin(value, Completed) - case (value, ExitCase.Error(error)) => fin(value, Failed(List(UnknownProxError(error)))) - case (value, ExitCase.Canceled) => fin(value, Canceled) - } - - protected override final def makeResource[A](acquire: ProxIO[A], release: A => ProxIO[Unit]): ProxResource[A] = cats.effect.Resource.make(acquire)(release) - - protected override final def useResource[A, B](r: ProxResource[A], f: A => ProxIO[B]): ProxIO[B] = r.use(f) - - protected override final def joinFiber[A](f: ProxFiber[A]): ProxIO[A] = f.join - - protected override final def cancelFiber[A](f: ProxFiber[A]): ProxIO[Unit] = f.cancel - - protected override final def startFiber[A](f: ProxIO[A]): ProxIO[ProxFiber[A]] = Concurrent[F].start(f) - - protected override final def drainStream[A](s: ProxStream[A]): ProxIO[Unit] = s.compile.drain - - protected override final def streamToVector[A](s: ProxStream[A]): ProxIO[Vector[A]] = s.compile.toVector - - protected override final def foldStream[A, B](s: ProxStream[A], init: B, f: (B, A) => B): ProxIO[B] = s.compile.fold(init)(f) - - protected override final def foldMonoidStream[A: ProxMonoid](s: ProxStream[A]): ProxIO[A] = s.compile.foldMonoid - - protected override final def streamThrough[A, B](s: ProxStream[A], pipe: ProxPipe[A, B]): ProxStream[B] = s.through(pipe) - - protected override final def runStreamTo[A](s: ProxStream[A], sink: ProxSink[A]): ProxIO[Unit] = - s.through(sink).compile.drain - - protected override final def fromJavaInputStream(input: io.InputStream, chunkSize: Int): ProxStream[Byte] = - fs2.io.readInputStream( - pure(input), - chunkSize, - closeAfterUse = true, - blocker = blocker) - - protected override final def drainToJavaOutputStream(stream: ProxStream[Byte], output: io.OutputStream, flushChunks: Boolean): ProxIO[Unit] = - stream - .through( - if (flushChunks) writeAndFlushOutputStream(output) - else fs2.io.writeOutputStream( - effect(output, UnknownProxError.apply), - closeAfterUse = true, - blocker = blocker)) - .compile - .drain - - private def writeAndFlushOutputStream(stream: java.io.OutputStream): ProxPipe[Byte, Unit] = - s => { - fs2.Stream - .bracket(Applicative[F].pure(stream))(os => Sync[F].delay(os.close())) - .flatMap { os => - s.chunks.evalMap { chunk => - blocker.delay { - os.write(chunk.toArray) - os.flush() - } - } - } - } -} - -object ProxFS2 { - def apply[F[_]](blk: Blocker)(implicit c: Concurrent[F], cs: ContextShift[F]): ProxFS2[F] = new ProxFS2[F] { - override implicit val concurrent: Concurrent[F] = c - override implicit val blocker: Blocker = blk - override implicit val contextShift: ContextShift[F] = cs - } -} diff --git a/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala b/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala deleted file mode 100644 index f30952e2..00000000 --- a/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.vigoo.prox.tests.fs2 - -import zio.ZIO -import zio.test.Assertion.{equalTo, isEmpty} -import zio.test.{DefaultRunnableSpec, ZSpec, assert, suite} - -object InterpolatorSpecs extends DefaultRunnableSpec with ProxSpecHelpers { - override val spec: ZSpec[_root_.zio.test.environment.TestEnvironment, Any] = - suite("Process interpolators")( - suite("cats-effect process interpolator")( - proxTest("works with single-word process names") { prox => - import prox._ - - val process = proc"ls" - - ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(isEmpty)) - }, - - proxTest("works with interpolated process name") { prox => - import prox._ - - val cmd = "ls" - val process = proc"$cmd" - - ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(isEmpty)) - }, - - proxTest("works with static parameters") { prox => - import prox._ - - val process = proc"ls -hal tmp" - - ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(equalTo(List("-hal", "tmp")))) - }, - - proxTest("works with static parameters and interpolated process name") { prox => - import prox._ - - val cmd = "ls" - val process = proc"$cmd -hal tmp" - - ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(equalTo(List("-hal", "tmp")))) - }, - - proxTest("works with static process name and interpolated parameters") { prox => - import prox._ - - val p1 = "-hal" - val p2 = "tmp" - val process = proc"ls $p1 $p2" - - ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(equalTo(List("-hal", "tmp")))) - }, - - proxTest("works with interpolated name and parameters") { prox => - import prox._ - - val cmd = "ls" - val p1 = "-hal" - val p2 = "tmp" - val process = proc"$cmd $p1 $p2" - - ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(equalTo(List("-hal", "tmp")))) - }, - - proxTest("works with mixed static and interpolated parameters") { prox => - import prox._ - - val p1 = "hello" - val p2 = "dear visitor" - val process = proc"echo $p1, $p2!!!" - - ZIO.succeed( - assert(process.command)(equalTo("echo")) && - assert(process.arguments)(equalTo(List("hello", ",", "dear visitor", "!!!")))) - } - ) - ) -} diff --git a/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala b/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala deleted file mode 100644 index 1422129e..00000000 --- a/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala +++ /dev/null @@ -1,586 +0,0 @@ -package io.github.vigoo.prox.tests.fs2 - -import java.nio.file.Files - -import cats.effect.ExitCode -import zio.clock.Clock -import zio.duration._ -import zio.test.Assertion._ -import zio.test.TestAspect._ -import zio.test._ -import zio.{IO, Task, ZIO} - -object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { - - override val spec = - suite("Piping processes together")( - suite("Piping")( - proxTest("is possible with two") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val processGroup = (Process("echo", List("This is a test string")) | Process("wc", List("-w"))) ># fs2.text.utf8Decode - val program = processGroup.run().map(_.output.trim) - - assertM(program)(equalTo("5")) - }, - - proxTest("is possible with multiple") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val processGroup = ( - Process("echo", List("cat\ncat\ndog\napple")) | - Process("sort") | - Process("uniq", List("-c")) | - Process("head", List("-n 1")) - ) >? fs2.text.utf8Decode.andThen(_.through(fs2.text.lines)) - - val program = processGroup.run().map( - r => r.output.map(_.stripLineEnd.trim).filter(_.nonEmpty) - ) - - assertM(program)(hasSameElements(List("1 apple"))) - }, - - proxTest("is customizable with pipes") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - - val customPipe: fs2.Pipe[Task, Byte, Byte] = - (s: fs2.Stream[Task, Byte]) => s - .through(fs2.text.utf8Decode) - .through(fs2.text.lines) - .map(_.split(' ').toVector) - .map(v => v.map(_ + " !!!").mkString(" ")) - .intersperse("\n") - .through(fs2.text.utf8Encode) - - val processGroup = (Process("echo", List("This is a test string")).via(customPipe).to(Process("wc", List("-w")))) ># fs2.text.utf8Decode - val program = processGroup.run().map(_.output.trim) - - assertM(program)(equalTo("11")) - }, - - proxTest("can be mapped") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val processGroup1 = (Process("!echo", List("This is a test string")) | Process("!wc", List("-w"))) ># fs2.text.utf8Decode - val processGroup2 = processGroup1.map(new ProcessGroup.Mapper[String, Unit] { - override def mapFirst[P <: Process[fs2.Stream[Task, Byte], Unit]](process: P): P = process.withCommand(process.command.tail).asInstanceOf[P] - - override def mapInnerWithIdx[P <: Process.UnboundIProcess[fs2.Stream[Task, Byte], Unit]](process: P, idx: Int): P = process.withCommand(process.command.tail).asInstanceOf[P] - - override def mapLast[P <: Process.UnboundIProcess[String, Unit]](process: P): P = process.withCommand(process.command.tail).asInstanceOf[P] - }) - - val program = processGroup2.run().map(_.output.trim) - - assertM(program)(equalTo("5")) - } - ), - - suite("Termination")( - proxTest("can be terminated with cancellation") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val processGroup = - Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) | - Process("sort") - val program = processGroup.start().use { fiber => fiber.cancel } - - assertM(program)(equalTo(())) - } @@ TestAspect.timeout(5.seconds), - - proxTest[Clock, Throwable]("can be terminated") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val p2 = Process("sort") - val processGroup = p1 | p2 - - val program = for { - runningProcesses <- processGroup.startProcessGroup() - _ <- ZIO(Thread.sleep(250)) - result <- runningProcesses.terminate() - } yield result.exitCodes.toList - - assertM(program)(contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1))) - }, - - proxTest("can be killed") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("perl", List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""")) - val p2 = Process("sort") - val processGroup = p1 | p2 - - val program = for { - runningProcesses <- processGroup.startProcessGroup() - _ <- ZIO(Thread.sleep(250)) - result <- runningProcesses.kill() - } yield result.exitCodes - - // Note: we can't assert on the second process' exit code because there is a race condition - // between killing it directly and being stopped because of the upstream process got killed. - assertM(program)(contains(p1 -> ExitCode(137))) - } - ), - - suite("Input redirection")( - proxTest("can be fed with an input stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val processGroup = (Process("cat") | Process("wc", List("-w"))) < stream ># fs2.text.utf8Decode - val program = processGroup.run().map(_.output.trim) - - assertM(program)(equalTo("5")) - }, - - proxTest("can be fed with an input file") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - withTempFile { tempFile => - val program = for { - _ <- ZIO(Files.write(tempFile.toPath, "This is a test string".getBytes("UTF-8"))) - processGroup = (Process("cat") | Process("wc", List("-w"))) < tempFile.toPath ># fs2.text.utf8Decode - result <- processGroup.run() - } yield result.output.trim - - assertM(program)(equalTo("5")) - } - } - ), - suite("Output redirection")( - proxTest("output can be redirected to file") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - withTempFile { tempFile => - val processGroup = (Process("echo", List("This is a test string")) | Process("wc", List("-w"))) > tempFile.toPath - val program = for { - _ <- processGroup.run() - contents <- fs2.io.file.readAll[Task](tempFile.toPath, blocker, 1024).through(fs2.text.utf8Decode).compile.foldMonoid - } yield contents.trim - - assertM(program)(equalTo("5")) - } - }, - ), - - suite("Error redirection")( - proxTest("can redirect each error output to a stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2) !># fs2.text.utf8Decode - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo("world"))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - proxTest("can redirect each error output to a sink") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - - val builder = new StringBuilder - val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => IO { - builder.append(byte.toChar) - }.unit) - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2) !> target - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(builder.toString.toSeq.sorted)(equalTo("Helloworld".toSeq.sorted)) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - proxTest("can redirect each error output to a vector") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world!"""")) - - val stream = fs2.text.utf8Decode[Task] - .andThen(fs2.text.lines) - .andThen(_.map(s => s.length)) - - val processGroup = (p1 | p2) !>? stream - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(hasSameElements(List(5)))) && - assert(result.errors.get(p2))(isSome(hasSameElements(List(6)))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - proxTest("can drain each error output") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2) drainErrors fs2.text.utf8Decode - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - proxTest("can fold each error output") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("perl", List("-e", "print STDERR 'Hello\nworld'")) - val p2 = Process("perl", List("-e", "print STDERR 'Does\nit\nwork?'")) - val processGroup = (p1 | p2).foldErrors( - fs2.text.utf8Decode.andThen(fs2.text.lines), - Vector.empty, - (l: Vector[Option[Char]], s: String) => l :+ s.headOption - ) - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(Vector(Some('H'), Some('w'))))) && - assert(result.errors.get(p2))(isSome(equalTo(Vector(Some('D'), Some('i'), Some('w'))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - ), - suite("Error redirection customized per process")( - proxTest("can redirect each error output to a stream customized per process") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2).customizedPerProcess.errorsToFoldMonoid { - case p if p == p1 => fs2.text.utf8Decode.andThen(_.map(s => "P1: " + s)) - case p if p == p2 => fs2.text.utf8Decode.andThen(_.map(s => "P2: " + s)) - } - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo("P1: Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo("P2: world"))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - proxTest("can redirect each error output to a sink customized per process") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - - val builder1 = new StringBuilder - val builder2 = new StringBuilder - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2).customizedPerProcess.errorsToSink { - case p if p == p1 => _.evalMap(byte => IO { - builder1.append(byte.toChar) - }.unit) - case p if p == p2 => - _.evalMap(byte => IO { - builder2.append(byte.toChar) - }.unit) - } - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(builder1.toString)(equalTo("Hello")) && - assert(builder2.toString)(equalTo("world")) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - proxTest("can redirect each error output to a vector customized per process") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world!"""")) - - val stream = fs2.text.utf8Decode[Task] - .andThen(fs2.text.lines) - .andThen(_.map(s => s.length)) - - val processGroup = (p1 | p2).customizedPerProcess.errorsToVector { - case p if p == p1 => stream.andThen(_.map(l => (1, l))) - case p if p == p2 => stream.andThen(_.map(l => (2, l))) - } - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(hasSameElements(List((1, 5))))) && - assert(result.errors.get(p2))(isSome(hasSameElements(List((2, 6))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - proxTest("can drain each error output customized per process") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2).customizedPerProcess.drainErrors(_ => fs2.text.utf8Decode) - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - proxTest("can fold each error output customized per process") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("perl", List("-e", "print STDERR 'Hello\nworld'")) - val p2 = Process("perl", List("-e", "print STDERR 'Does\nit\nwork?'")) - val processGroup = (p1 | p2).customizedPerProcess.foldErrors( - { - case p if p == p1 => fs2.text.utf8Decode[Task] - .andThen(fs2.text.lines) - case p if p == p2 => fs2.text.utf8Decode[Task] - .andThen(fs2.text.lines) - .andThen(_.map(_.reverse)) - }, - Vector.empty, - (l: Vector[Option[Char]], s: String) => l :+ s.headOption - ) - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(Vector(Some('H'), Some('w'))))) && - assert(result.errors.get(p2))(isSome(equalTo(Vector(Some('s'), Some('t'), Some('?'))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - proxTest("can redirect each error output to file") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - withTempFile { tempFile1 => - withTempFile { tempFile2 => - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2).customizedPerProcess.errorsToFile { - case p if p == p1 => tempFile1.toPath - case p if p == p2 => tempFile2.toPath - } - val program = for { - _ <- processGroup.run() - contents1 <- fs2.io.file.readAll[Task](tempFile1.toPath, blocker, 1024).through(fs2.text.utf8Decode).compile.foldMonoid - contents2 <- fs2.io.file.readAll[Task](tempFile2.toPath, blocker, 1024).through(fs2.text.utf8Decode).compile.foldMonoid - } yield (contents1, contents2) - - assertM(program)(equalTo(("Hello", "world"))) - } - } - }, - ), - - suite("Redirection ordering")( - proxTest("can redirect each error output to a stream if fed with an input stream and redirected to an output stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = (p1 | p2 | p3) < stream ># fs2.text.utf8Decode !># fs2.text.utf8Decode - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - - proxTest("can redirect output if each error output and input are already redirected") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) < stream !># fs2.text.utf8Decode) ># fs2.text.utf8Decode - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - - proxTest("can attach output and then input stream if each error output and standard output are already redirected") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) !># fs2.text.utf8Decode) ># fs2.text.utf8Decode < stream - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - - proxTest("can attach input and then output stream if each error output and standard output are already redirected") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) !># fs2.text.utf8Decode) < stream ># fs2.text.utf8Decode - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - - proxTest("can attach input stream and errors if standard output is already redirected") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) ># fs2.text.utf8Decode) < stream !># fs2.text.utf8Decode - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - - proxTest("can attach errors and finally input stream if standard output is already redirected") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = (((p1 | p2 | p3) ># fs2.text.utf8Decode) !># fs2.text.utf8Decode) < stream - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - ), - - testM("bound process is not pipeable") { - assertM( - typeCheck("""val bad = (Process("echo", List("Hello world")) ># fs2.text.utf8Decode) | Process("wc", List("-w"))"""))( - isLeft(anything) - ) - } - ) @@ timeout(60.seconds) @@ sequential -} diff --git a/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala b/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala deleted file mode 100644 index 23dbef8a..00000000 --- a/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala +++ /dev/null @@ -1,617 +0,0 @@ -package io.github.vigoo.prox.tests.fs2 - -import java.nio.file.Files - -import cats.effect.ExitCode -import zio.clock.Clock -import zio.duration._ -import zio.test.Assertion.{anything, equalTo, hasSameElements, isLeft} -import zio.test.TestAspect._ -import zio.test._ -import zio.{IO, Task, ZIO} - -object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { - override val spec = - suite("Executing a process")( - proxTest("returns the exit code") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val program = for { - trueResult <- Process("true").run() - falseResult <- Process("false").run() - } yield (trueResult.exitCode, falseResult.exitCode) - - assertM(program)(equalTo((ExitCode(0), ExitCode(1)))) - }, - - suite("Output redirection")( - proxTest("can redirect output to a file") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - withTempFile { tempFile => - val process = Process("echo", List("Hello world!")) > tempFile.toPath - val program = for { - _ <- process.run() - contents <- fs2.io.file.readAll[Task](tempFile.toPath, prox.blocker, 1024).through(fs2.text.utf8Decode).compile.foldMonoid - } yield contents - - assertM(program)(equalTo("Hello world!\n")) - } - }, - - proxTest("can redirect output to append a file") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - withTempFile { tempFile => - val process1 = Process("echo", List("Hello")) > tempFile.toPath - val process2 = Process("echo", List("world")) >> tempFile.toPath - val program = for { - _ <- process1.run() - _ <- process2.run() - contents <- fs2.io.file.readAll[Task](tempFile.toPath, prox.blocker, 1024).through(fs2.text.utf8Decode).compile.foldMonoid - } yield contents - - assertM(program)(equalTo("Hello\nworld\n")) - } - }, - - proxTest("can redirect output to stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("echo", List("Hello world!")) ># fs2.text.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world!\n")) - }, - - proxTest("can redirect output to stream folding monoid") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("echo", List("Hello\nworld!")) ># fs2.text.utf8Decode.andThen(fs2.text.lines) - val program = process.run().map(_.output) - - assertM(program)(equalTo("Helloworld!")) - }, - - proxTest("can redirect output to stream collected to vector") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - case class StringLength(value: Int) - - val stream = fs2.text.utf8Decode[Task] - .andThen(fs2.text.lines) - .andThen(_.map(s => StringLength(s.length))) - val process = Process("echo", List("Hello\nworld!")) >? stream - val program = process.run().map(_.output) - - assertM(program)(hasSameElements(List(StringLength(5), StringLength(6), StringLength(0)))) - }, - - proxTest("can redirect output to stream and ignore it's result") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("echo", List("Hello\nworld!")).drainOutput(fs2.text.utf8Decode.andThen(fs2.text.lines)) - val program = process.run().map(_.output) - - assertM(program)(equalTo(())) - }, - - proxTest("can redirect output to stream and fold it") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("echo", List("Hello\nworld!")).foldOutput( - fs2.text.utf8Decode.andThen(fs2.text.lines), - Vector.empty, - (l: Vector[Option[Char]], s: String) => l :+ s.headOption - ) - val program = process.run().map(_.output) - - assertM(program)(equalTo(Vector(Some('H'), Some('w'), None))) - }, - - proxTest("can redirect output to a sink") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val builder = new StringBuilder - val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => IO { - builder.append(byte.toChar) - }.unit) - - val process = Process("echo", List("Hello world!")) > target - val program = process.run().map(_ => builder.toString) - - assertM(program)(equalTo("Hello world!\n")) - }, - ), - suite("Error redirection")( - proxTest("can redirect error to a file") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - withTempFile { tempFile => - val process = Process("perl", List("-e", "print STDERR 'Hello world!'")) !> tempFile.toPath - val program = for { - _ <- process.run() - contents <- fs2.io.file.readAll[Task](tempFile.toPath, prox.blocker, 1024).through(fs2.text.utf8Decode).compile.foldMonoid - } yield contents - - assertM(program)(equalTo("Hello world!")) - } - }, - - proxTest("can redirect error to append a file") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - withTempFile { tempFile => - val process1 = Process("perl", List("-e", "print STDERR Hello")) !> tempFile.toPath - val process2 = Process("perl", List("-e", "print STDERR world")) !>> tempFile.toPath - val program = for { - _ <- process1.run() - _ <- process2.run() - contents <- fs2.io.file.readAll[Task](tempFile.toPath, prox.blocker, 1024).through(fs2.text.utf8Decode).compile.foldMonoid - } yield contents - - assertM(program)(equalTo("Helloworld")) - } - }, - - proxTest("can redirect error to stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("perl", List("-e", """print STDERR "Hello"""")) !># fs2.text.utf8Decode - val program = process.run().map(_.error) - - assertM(program)(equalTo("Hello")) - }, - - proxTest("can redirect error to stream folding monoid") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) !># fs2.text.utf8Decode.andThen(fs2.text.lines) - val program = process.run().map(_.error) - - assertM(program)(equalTo("Helloworld!")) - }, - - proxTest("can redirect error to stream collected to vector") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - case class StringLength(value: Int) - - val stream = fs2.text.utf8Decode[Task] - .andThen(fs2.text.lines) - .andThen(_.map(s => StringLength(s.length))) - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) !>? stream - val program = process.run().map(_.error) - - assertM(program)(hasSameElements(List(StringLength(5), StringLength(6)))) - }, - - proxTest("can redirect error to stream and ignore it's result") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")).drainError(fs2.text.utf8Decode.andThen(fs2.text.lines)) - val program = process.run().map(_.error) - - assertM(program)(equalTo(())) - }, - - proxTest("can redirect error to stream and fold it") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")).foldError( - fs2.text.utf8Decode.andThen(fs2.text.lines), - Vector.empty, - (l: Vector[Option[Char]], s: String) => l :+ s.headOption - ) - val program = process.run().map(_.error) - - assertM(program)(equalTo(Vector(Some('H'), Some('w')))) - }, - - proxTest("can redirect error to a sink") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val builder = new StringBuilder - val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => IO { - builder.append(byte.toChar) - }.unit) - - val process = Process("perl", List("-e", """print STDERR "Hello"""")) !> target - val program = process.run().map(_ => builder.toString) - - assertM(program)(equalTo("Hello")) - }, - ), - - suite("Redirection ordering")( - proxTest("can redirect first input and then error to stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val process = Process("perl", List("-e", """my $str = <>; print STDERR "$str"""".stripMargin)) < source !># fs2.text.utf8Decode - val program = process.run().map(_.error) - - assertM(program)(equalTo("This is a test string")) - }, - - proxTest("can redirect error first then output to stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = (Process("perl", List("-e", """print STDOUT Hello; print STDERR World""".stripMargin)) !># fs2.text.utf8Decode) ># fs2.text.utf8Decode - val program = process.run().map(r => r.output + r.error) - - assertM(program)(equalTo("HelloWorld")) - }, - - proxTest("can redirect output first then error to stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = (Process("perl", List("-e", """print STDOUT Hello; print STDERR World""".stripMargin)) ># fs2.text.utf8Decode) !># fs2.text.utf8Decode - val program = process.run().map(r => r.output + r.error) - - assertM(program)(equalTo("HelloWorld")) - }, - - proxTest("can redirect output first then error finally input to stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("Hello").through(fs2.text.utf8Encode) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) - ># fs2.text.utf8Decode) - !># fs2.text.utf8Decode) < source - val program = process.run().map(r => r.output + r.error) - - assertM(program)(equalTo("HelloWorld")) - }, - - proxTest("can redirect output first then input finally error to stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("Hello").through(fs2.text.utf8Encode) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) - ># fs2.text.utf8Decode) - < source) !># fs2.text.utf8Decode - val program = process.run().map(r => r.output + r.error) - - assertM(program)(equalTo("HelloWorld")) - }, - - proxTest("can redirect input first then error finally output to stream") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("Hello").through(fs2.text.utf8Encode) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) - < source) - !># fs2.text.utf8Decode) ># fs2.text.utf8Decode - val program = process.run().map(r => r.output + r.error) - - assertM(program)(equalTo("HelloWorld")) - }, - ), - - suite("Input redirection")( - proxTest("can use stream as input") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val process = Process("wc", List("-w")) < source ># fs2.text.utf8Decode - val program = process.run().map(_.output.trim) - - assertM(program)(equalTo("5")) - }, - - proxTest("can use stream as input flushing after each chunk") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("This ", "is a test", " string").through(fs2.text.utf8Encode) - val process = (Process("wc", List("-w")) !< source) ># fs2.text.utf8Decode - val program = process.run().map(_.output.trim) - - assertM(program)(equalTo("5")) - }, - ), - - suite("Termination")( - proxTest("can be terminated with cancellation") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val program = process.start().use { fiber => ZIO(Thread.sleep(250)) *> fiber.cancel } - - assertM(program)(equalTo(())) - } @@ TestAspect.timeout(5.seconds) @@ TestAspect.ignore, - - - proxTest("can be terminated by releasing the resource") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val program = process.start().use { _ => ZIO(Thread.sleep(250)) } - - assertM(program)(equalTo(())) - } @@ TestAspect.timeout(5.seconds) @@ TestAspect.ignore, - - proxTest[Clock, Throwable]("can be terminated") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val program = for { - runningProcess <- process.startProcess() - _ <- ZIO(Thread.sleep(250)) - result <- runningProcess.terminate() - } yield result.exitCode - - assertM(program)(equalTo(ExitCode(1))) - }, - - proxTest("can be killed") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("perl", List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""")) - val program = for { - runningProcess <- process.startProcess() - _ <- ZIO(Thread.sleep(250)) - result <- runningProcess.kill() - } yield result.exitCode - - assertM(program)(equalTo(ExitCode(137))) - }, - - proxTest("can be checked if is alive") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = Process("sleep", List("10")) - val program = for { - runningProcess <- process.startProcess() - isAliveBefore <- runningProcess.isAlive - _ <- runningProcess.terminate() - isAliveAfter <- runningProcess.isAlive - } yield (isAliveBefore, isAliveAfter) - - assertM(program)(equalTo((true, false))) - }, - ), - - suite("Customization")( - proxTest("can change the command") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("something", List("Hello", "world")) ># fs2.text.utf8Decode - val p2 = p1.withCommand("echo") - val program = p2.run().map(_.output) - - assertM(program)(equalTo("Hello world\n")) - }, - - proxTest("can change the arguments") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val p1 = Process("echo") ># fs2.text.utf8Decode - val p2 = p1.withArguments(List("Hello", "world")) - val program = p2.run().map(_.output) - - assertM(program)(equalTo("Hello world\n")) - }, - - proxTest("respects the working directory") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - ZIO(Files.createTempDirectory("prox")).flatMap { tempDirectory => - val process = (Process("pwd") in tempDirectory) ># fs2.text.utf8Decode - val program = process.run().map(_.output.trim) - - assertM(program)(equalTo(tempDirectory.toString) || equalTo(s"/private${tempDirectory}")) - } - }, - - proxTest("is customizable with environment variables") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) ># fs2.text.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - proxTest("is customizable with excluded environment variables") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox") - `without` "TEST1") ># fs2.text.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello ! I am prox!\n")) - }, - - proxTest("is customizable with environment variables output is bound") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) ># fs2.text.utf8Decode - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - proxTest("is customizable with environment variables if input is bound") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val process = ((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) ># fs2.text.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - proxTest("is customizable with environment variables if error is bound") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val process = ((Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) !># fs2.text.utf8Decode) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) ># fs2.text.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - proxTest("is customizable with environment variables if input and output are bound") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val process = (((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) ># fs2.text.utf8Decode) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - - proxTest("is customizable with environment variables if input and error are bound") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val process = (((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) !># fs2.text.utf8Decode) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) ># fs2.text.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - proxTest("is customizable with environment variables if output and error are bound") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = (((Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) !># fs2.text.utf8Decode) ># fs2.text.utf8Decode) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - proxTest("is customizable with environment variables if everything is bound") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val source = fs2.Stream("This is a test string").through(fs2.text.utf8Encode) - val process = ((((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) !># fs2.text.utf8Decode) ># fs2.text.utf8Decode) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - ), - - testM("double output redirect is illegal") { - assertM( - typeCheck("""val bad = Process("echo", List("Hello world")) > new File("x").toPath > new File("y").toPath"""))( - isLeft(anything) - ) - }, - testM("double error redirect is illegal") { - assertM( - typeCheck("""val bad = Process("echo", List("Hello world")) !> new File("x").toPath !> new File("y").toPath"""))( - isLeft(anything) - ) - }, - testM("double input redirect is illegal") { - assertM( - typeCheck("""val bad = (Process("echo", List("Hello world")) < fs2.Stream("X").through(fs2.text.utf8Encode)) < fs2.Stream("Y").through(fs2.text.utf8Encode)"""))( - isLeft(anything) - ) - } - ) @@ timeout(60.seconds) @@ sequential -} diff --git a/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala b/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala deleted file mode 100644 index 220b33e7..00000000 --- a/prox-fs2/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.vigoo.prox.tests.fs2 - -import java.io.File - -import cats.effect.Blocker -import io.github.vigoo.prox.ProxFS2 -import zio.interop.catz._ -import zio.test.{TestResult, ZSpec, testM} -import zio.{Task, URIO} - -trait ProxSpecHelpers { - - def proxTest[Nothing, Throwable](label: String)(assertion: ProxFS2[Task] => Task[TestResult]): ZSpec[Any, scala.Throwable] = { - testM(label)(Blocker[Task].use { blocker => - val prox = ProxFS2[Task](blocker) - assertion(prox) - }) - } - - def withTempFile[A](inner: File => Task[A]): Task[A] = - Task(File.createTempFile("test", "txt")).bracket( - file => URIO(file.delete()), - inner) - -} diff --git a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala index 877ebcc4..3f492699 100644 --- a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala +++ b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala @@ -268,20 +268,20 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { test("can be terminated with cancellation") { val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) val program = ZIO.scoped { - process.start().flatMap { fiber => ZIO.attempt(Thread.sleep(250)) *> fiber.interrupt.unit } + process.start().flatMap { fiber => ZIO.sleep(250.millis) *> fiber.interrupt.unit } } assertZIO(program)(equalTo(())) - } @@ TestAspect.timeout(5.seconds) @@ TestAspect.diagnose(2.seconds), + } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds) @@ TestAspect.diagnose(2.seconds), test("can be terminated by releasing the resource") { val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) val program = ZIO.scoped { - process.start().flatMap { _ => ZIO.attempt(Thread.sleep(250)) } + process.start().flatMap { _ => ZIO.sleep(250.millis) } } assertZIO(program)(equalTo(())) - } @@ TestAspect.timeout(5.seconds), + } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), test("can be terminated") { val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) diff --git a/prox-zstream/src/main/scala/io/github/vigoo/prox/ProxZStream.scala b/prox-zstream/src/main/scala/io/github/vigoo/prox/ProxZStream.scala deleted file mode 100644 index 8f81ff94..00000000 --- a/prox-zstream/src/main/scala/io/github/vigoo/prox/ProxZStream.scala +++ /dev/null @@ -1,147 +0,0 @@ -package io.github.vigoo.prox - -import java.io -import java.io.IOException -import zio.blocking.{Blocking, effectBlocking, effectBlockingInterrupt} -import zio.prelude.Identity -import zio.stream.{ZSink, ZStream, ZTransducer} -import zio._ - -import scala.language.implicitConversions - -trait ProxZStream extends Prox { - - case class TransformAndSink[A, B](transform: ZStream[Blocking, ProxError, A] => ZStream[Blocking, ProxError, B], - sink: ZSink[Blocking, ProxError, B, Any, Unit]) { - private[ProxZStream] def run(s: ZStream[Blocking, ProxError, A]): ZIO[Blocking, ProxError, Unit] = - transform(s).run(sink) - } - object TransformAndSink { - def apply[A, B](transducer: ZTransducer[Blocking, ProxError, A, B], sink: ZSink[Blocking, ProxError, B, Any, Unit]): TransformAndSink[A, B] = - TransformAndSink(_.transduce(transducer), sink) - } - - override type ProxExitCode = zio.ExitCode - override type ProxFiber[A] = zio.Fiber[ProxError, A] - override type ProxIO[A] = ZIO[Blocking, ProxError, A] - override type ProxResource[A] = ZManaged[Blocking, ProxError, A] - override type ProxStream[A] = ZStream[Blocking, ProxError, A] - override type ProxPipe[A, B] = ProxStream[A] => ProxStream[B] - override type ProxSink[A] = TransformAndSink[A, _] - override type ProxMonoid[A] = zio.prelude.Identity[A] - - protected override final def exitCodeFromInt(value: Int): ProxExitCode = - zio.ExitCode(value) - - protected override final def unit: ProxIO[Unit] = - ZIO.unit - - protected override final def pure[A](value: A): ProxIO[A] = - ZIO.succeed(value) - - protected override final def effect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = - ZIO.effect(f).mapError(wrapError) - - protected override final def blockingEffect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = - effectBlockingInterrupt(f).mapError(wrapError).interruptible - - protected override final def raiseError(error: ProxError): ProxIO[Unit] = - ZIO.fail(error) - - protected override final def ioMap[A, B](io: ProxIO[A], f: A => B): ProxIO[B] = - io.map(f) - - protected override final def ioFlatMap[A, B](io: ProxIO[A], f: A => ProxIO[B]): ProxIO[B] = - io.flatMap(f) - - protected override final def traverse[A, B](list: List[A])(f: A => ProxIO[B]): ProxIO[List[B]] = - ZIO.foreach(list)(f) - - protected override final def identityPipe[A]: ProxPipe[A, A] = - identity - - protected override final def bracket[A, B](acquire: ProxIO[A])(use: A => ProxIO[B])(fin: (A, IOResult) => ProxIO[Unit]): ProxIO[B] = { - ZIO.bracketExit(acquire) { (value: A, exit: Exit[ProxError, B]) => - exit match { - case Exit.Success(_) => fin(value, Completed).mapError(_.toThrowable).orDie - case Exit.Failure(cause) => - if (cause.interrupted) { - fin(value, Canceled).mapError(_.toThrowable).orDie - } else { - fin(value, Failed(cause.failures ++ cause.defects.map(UnknownProxError.apply))).mapError(_.toThrowable).orDie - } - } - }(a => ZIO.allowInterrupt *> use(a)) - } - - protected override final def makeResource[A](acquire: ProxIO[A], release: A => ProxIO[Unit]): ProxResource[A] = - ZManaged.make(acquire)(x => release(x).mapError(_.toThrowable).orDie) - - protected override final def useResource[A, B](r: ProxResource[A], f: A => ProxIO[B]): ProxIO[B] = - r.use(f) - - protected override final def joinFiber[A](f: ProxFiber[A]): ProxIO[A] = - f.join - - protected override final def cancelFiber[A](f: ProxFiber[A]): ProxIO[Unit] = - f.interrupt.unit - - protected override final def drainStream[A](s: ProxStream[A]): ProxIO[Unit] = - s.runDrain - - protected override final def streamToVector[A](s: ProxStream[A]): ProxIO[Vector[A]] = - s.runCollect.map(_.toVector) - - protected override final def foldStream[A, B](s: ProxStream[A], init: B, f: (B, A) => B): ProxIO[B] = - s.fold(init)(f) - - protected override final def foldMonoidStream[A: Identity](s: ProxStream[A]): ProxIO[A] = - s.fold(Identity[A].identity)((a, b) => Identity[A].combine(a, b)) - - protected override final def streamThrough[A, B](s: ProxStream[A], pipe: ProxPipe[A, B]): ProxStream[B] = - pipe(s) - - override protected final def runStreamTo[A](s: ProxStream[A], sink: ProxSink[A]): ProxIO[Unit] = - sink.run(s) - - protected override final def fromJavaInputStream(input: io.InputStream, chunkSize: Int): ProxStream[Byte] = - ZStream.fromInputStream(input, chunkSize).mapError(FailedToReadProcessOutput.apply) - - protected override final def drainToJavaOutputStream(stream: ProxStream[Byte], output: io.OutputStream, flushChunks: Boolean): ProxIO[Unit] = { - val managedOutput = ZManaged.make(ZIO.succeed(output))(s => ZIO.effect(s.close()).orDie) - if (flushChunks) { - stream.run(flushingOutputStreamSink(managedOutput).mapError(FailedToWriteProcessInput.apply)).unit - } else { - stream - .run(ZSink - .fromOutputStreamManaged(managedOutput) - .mapError(FailedToWriteProcessInput.apply)).unit - } - } - - private final def flushingOutputStreamSink(managedOutput: ZManaged[Blocking, Nothing, io.OutputStream]): ZSink[Blocking, IOException, Byte, Byte, Long] = { - ZSink.managed(managedOutput) { os => - ZSink.foldLeftChunksM(0L) { (bytesWritten, byteChunk: Chunk[Byte]) => - blocking.effectBlockingInterrupt { - val bytes = byteChunk.toArray - os.write(bytes) - os.flush() - bytesWritten + bytes.length - }.refineOrDie { - case e: IOException => e - } - } - } - } - - protected override final def startFiber[A](f: ProxIO[A]): ProxIO[ProxFiber[A]] = - f.fork - - implicit def transducerAsPipe[A, B](transducer: ZTransducer[Blocking, ProxError, A, B]): ProxPipe[A, B] = - (s: ProxStream[A]) => s.transduce(transducer) - - implicit def sinkAsTransformAndSink[A](sink: ZSink[Blocking, ProxError, A, Any, Unit]): TransformAndSink[A, A] = - TransformAndSink(identity[ZStream[Blocking, ProxError, A]] _, sink) -} - -object zstream extends ProxZStream diff --git a/prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala b/prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala deleted file mode 100644 index c45d495c..00000000 --- a/prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala +++ /dev/null @@ -1,494 +0,0 @@ -package io.github.vigoo.prox.tests.zstream - -import java.nio.charset.StandardCharsets -import java.nio.file.Files - -import io.github.vigoo.prox.{ProxError, UnknownProxError, zstream} -import io.github.vigoo.prox.zstream._ -import zio.blocking.Blocking -import zio.clock.Clock -import zio.duration._ -import zio.stream.{ZSink, ZStream, ZTransducer} -import zio.test.Assertion._ -import zio.test.TestAspect._ -import zio.test._ -import zio.{ExitCode, ZIO} - -object ProcessGroupSpecs extends DefaultRunnableSpec with ProxSpecHelpers { - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - override val spec = - suite("Piping processes together")( - suite("Piping")( - testM("is possible with two") { - - val processGroup = (Process("echo", List("This is a test string")) | Process("wc", List("-w"))) ># ZTransducer.utf8Decode - val program = processGroup.run().map(_.output.trim) - - assertM(program)(equalTo("5")) - }, - - testM("is possible with multiple") { - - val processGroup = ( - Process("echo", List("cat\ncat\ndog\napple")) | - Process("sort") | - Process("uniq", List("-c")) | - Process("head", List("-n 1")) - ) >? (ZTransducer.utf8Decode >>> ZTransducer.splitLines) - - val program = processGroup.run().map( - r => r.output.map(_.stripLineEnd.trim).filter(_.nonEmpty) - ) - - assertM(program)(hasSameElements(List("1 apple"))) - }, - - testM("is customizable with pipes") { - val customPipe = (s: zstream.ProxStream[Byte]) => s - .transduce(ZTransducer.utf8Decode >>> ZTransducer.splitLines) - .map(_.split(' ').toVector) - .map(v => v.map(_ + " !!!").mkString(" ")) - .intersperse("\n") - .flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val processGroup = (Process("echo", List("This is a test string")).via(customPipe).to(Process("wc", List("-w")))) ># ZTransducer.utf8Decode - val program = processGroup.run().map(_.output.trim) - - assertM(program)(equalTo("10")) - }, - - testM("can be mapped") { - import zstream.Process._ - - val processGroup1 = (Process("!echo", List("This is a test string")) | Process("!wc", List("-w"))) ># ZTransducer.utf8Decode - val processGroup2 = processGroup1.map(new ProcessGroup.Mapper[String, Unit] { - override def mapFirst[P <: Process[zstream.ProxStream[Byte], Unit]](process: P): P = process.withCommand(process.command.tail).asInstanceOf[P] - - override def mapInnerWithIdx[P <: UnboundIProcess[zstream.ProxStream[Byte], Unit]](process: P, idx: Int): P = process.withCommand(process.command.tail).asInstanceOf[P] - - override def mapLast[P <: UnboundIProcess[String, Unit]](process: P): P = process.withCommand(process.command.tail).asInstanceOf[P] - }) - - val program = processGroup2.run().map(_.output.trim) - - assertM(program)(equalTo("5")) - } - ), - - suite("Termination")( - testM("can be terminated with cancellation") { - - val processGroup = - Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) | - Process("sort") - val program = processGroup.start().use { fiber => fiber.interrupt.unit } - - assertM(program)(equalTo(())) - } @@ TestAspect.timeout(5.seconds), - - testM[Blocking with Clock, ProxError]("can be terminated") { - - val p1 = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val p2 = Process("sort") - val processGroup = p1 | p2 - - val program = for { - runningProcesses <- processGroup.startProcessGroup() - _ <- ZIO.sleep(250.millis) - result <- runningProcesses.terminate() - } yield result.exitCodes.toList - - assertM(program.provideSomeLayer[Blocking](Clock.live))(contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1))) - }, - - testM("can be killed") { - - val p1 = Process("perl", List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""")) - val p2 = Process("sort") - val processGroup = p1 | p2 - - val program = for { - runningProcesses <- processGroup.startProcessGroup() - _ <- ZIO.sleep(250.millis) - result <- runningProcesses.kill() - } yield result.exitCodes - - // Note: we can't assert on the second process' exit code because there is a race condition - // between killing it directly and being stopped because of the upstream process got killed. - assertM(program.provideSomeLayer[Blocking](Clock.live))( - contains(p1 -> ExitCode(137) - )) - } - ), - - suite("Input redirection")( - testM("can be fed with an input stream") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val processGroup = (Process("cat") | Process("wc", List("-w"))) < stream ># ZTransducer.utf8Decode - val program = processGroup.run().map(_.output.trim) - - assertM(program)(equalTo("5")) - }, - - testM("can be fed with an input file") { - - withTempFile { tempFile => - val program = for { - _ <- ZIO.effect(Files.write(tempFile.toPath, "This is a test string".getBytes("UTF-8"))).mapError(UnknownProxError.apply) - processGroup = (Process("cat") | Process("wc", List("-w"))) < tempFile.toPath ># ZTransducer.utf8Decode - result <- processGroup.run() - } yield result.output.trim - - assertM(program)(equalTo("5")) - } - } - ), - suite("Output redirection")( - testM("output can be redirected to file") { - - withTempFile { tempFile => - val processGroup = (Process("echo", List("This is a test string")) | Process("wc", List("-w"))) > tempFile.toPath - val program = for { - _ <- processGroup.run() - contents <- ZStream.fromFile(tempFile.toPath, 1024).transduce(ZTransducer.utf8Decode).fold("")(_ + _).mapError(UnknownProxError.apply) - } yield contents.trim - - assertM(program)(equalTo("5")) - } - }, - ), - - suite("Error redirection")( - testM("can redirect each error output to a stream") { - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2) !># ZTransducer.utf8Decode - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo("world"))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - testM("can redirect each error output to a sink") { - - - val builder = new StringBuilder - val target: zstream.ProxSink[Byte] = ZSink.foreach[Blocking, ProxError, Byte]((byte: Byte) => ZIO.effect(builder.append(byte.toChar)).mapError(UnknownProxError.apply)) - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2) !> target - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(builder.toString.toSeq.sorted)(equalTo("Helloworld".toSeq.sorted)) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - testM("can redirect each error output to a vector") { - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world!"""")) - - val stream = (ZTransducer.utf8Decode >>> ZTransducer.splitLines).map(_.length) - - val processGroup = (p1 | p2) !>? stream - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(hasSameElements(List(5)))) && - assert(result.errors.get(p2))(isSome(hasSameElements(List(6)))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - testM("can drain each error output") { - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2) drainErrors ZTransducer.utf8Decode - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - testM("can fold each error output") { - - val p1 = Process("perl", List("-e", "print STDERR 'Hello\nworld'")) - val p2 = Process("perl", List("-e", "print STDERR 'Does\nit\nwork?'")) - val processGroup = (p1 | p2).foldErrors( - ZTransducer.utf8Decode >>> ZTransducer.splitLines, - Vector.empty, - (l: Vector[Option[Char]], s: String) => l :+ s.headOption - ) - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(Vector(Some('H'), Some('w'))))) && - assert(result.errors.get(p2))(isSome(equalTo(Vector(Some('D'), Some('i'), Some('w'))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - ), - suite("Error redirection customized per process")( - testM("can redirect each error output to a stream customized per process") { - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2).customizedPerProcess.errorsToFoldMonoid { - case p if p == p1 => ZTransducer.utf8Decode.map(s => "P1: " + s) - case p if p == p2 => ZTransducer.utf8Decode.map(s => "P2: " + s) - } - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo("P1: Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo("P2: world"))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - testM("can redirect each error output to a sink customized per process") { - - - val builder1 = new StringBuilder - val builder2 = new StringBuilder - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2).customizedPerProcess.errorsToSink { - case p if p == p1 => ZSink.foreach((byte: Byte) => ZIO.effect(builder1.append(byte.toChar)).mapError(UnknownProxError.apply)) - case p if p == p2 => ZSink.foreach((byte: Byte) => ZIO.effect(builder2.append(byte.toChar)).mapError(UnknownProxError.apply)) - } - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(builder1.toString)(equalTo("Hello")) && - assert(builder2.toString)(equalTo("world")) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - testM("can redirect each error output to a vector customized per process") { - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world!"""")) - - val stream = (ZTransducer.utf8Decode >>> ZTransducer.splitLines).map(_.length) - - val processGroup = (p1 | p2).customizedPerProcess.errorsToVector { - case p if p == p1 => stream.map(l => (1, l)) - case p if p == p2 => stream.map(l => (2, l)) - } - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(hasSameElements(List((1, 5))))) && - assert(result.errors.get(p2))(isSome(hasSameElements(List((2, 6))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - testM("can drain each error output customized per process") { - - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2).customizedPerProcess.drainErrors(_ => ZTransducer.utf8Decode) - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - testM("can fold each error output customized per process") { - - val p1 = Process("perl", List("-e", "print STDERR 'Hello\nworld'")) - val p2 = Process("perl", List("-e", "print STDERR 'Does\nit\nwork?'")) - val processGroup = (p1 | p2).customizedPerProcess.foldErrors( - { - case p if p == p1 => ZTransducer.utf8Decode >>> ZTransducer.splitLines - case p if p == p2 => (ZTransducer.utf8Decode >>> ZTransducer.splitLines).map(_.reverse) - }, - Vector.empty, - (l: Vector[Option[Char]], s: String) => l :+ s.headOption - ) - val program = processGroup.run() - - program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(Vector(Some('H'), Some('w'))))) && - assert(result.errors.get(p2))(isSome(equalTo(Vector(Some('s'), Some('t'), Some('?'))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) - } - }, - - testM("can redirect each error output to file") { - - withTempFile { tempFile1 => - withTempFile { tempFile2 => - val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) - val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2).customizedPerProcess.errorsToFile { - case p if p == p1 => tempFile1.toPath - case p if p == p2 => tempFile2.toPath - } - val program = for { - _ <- processGroup.run() - contents1 <- ZStream.fromFile(tempFile1.toPath, 1024).transduce(ZTransducer.utf8Decode).fold("")(_ + _).mapError(UnknownProxError.apply) - contents2 <- ZStream.fromFile(tempFile2.toPath, 1024).transduce(ZTransducer.utf8Decode).fold("")(_ + _).mapError(UnknownProxError.apply) - } yield (contents1, contents2) - - assertM(program)(equalTo(("Hello", "world"))) - } - } - }, - ), - - suite("Redirection ordering")( - testM("can redirect each error output to a stream if fed with an input stream and redirected to an output stream") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = (p1 | p2 | p3) < stream ># ZTransducer.utf8Decode !># ZTransducer.utf8Decode - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - - testM("can redirect output if each error output and input are already redirected") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) < stream !># ZTransducer.utf8Decode) ># ZTransducer.utf8Decode - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - - testM("can attach output and then input stream if each error output and standard output are already redirected") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) !># ZTransducer.utf8Decode) ># ZTransducer.utf8Decode < stream - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - - testM("can attach input and then output stream if each error output and standard output are already redirected") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) !># ZTransducer.utf8Decode) < stream ># ZTransducer.utf8Decode - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - - testM("can attach input stream and errors if standard output is already redirected") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) ># ZTransducer.utf8Decode) < stream !># ZTransducer.utf8Decode - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - - testM("can attach errors and finally input stream if standard output is already redirected") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) - val p2 = Process("sort") - val p3 = Process("wc", List("-w")) - val processGroup = (((p1 | p2 | p3) ># ZTransducer.utf8Decode) !># ZTransducer.utf8Decode) < stream - - processGroup.run() - .map { result => - assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) - } - }, - ), - - testM("bound process is not pipeable") { - assertM( - typeCheck("""val bad = (Process("echo", List("Hello world")) ># ZTransducer.utf8Decode) | Process("wc", List("-w"))"""))( - isLeft(anything) - ) - } - ) @@ timeoutWarning(60.seconds) @@ sequential -} diff --git a/prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala b/prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala deleted file mode 100644 index 9312ce91..00000000 --- a/prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala +++ /dev/null @@ -1,451 +0,0 @@ -package io.github.vigoo.prox.tests.zstream - -import java.nio.charset.StandardCharsets -import java.nio.file.Files -import io.github.vigoo.prox.{ProxError, UnknownProxError, zstream} -import io.github.vigoo.prox.zstream._ -import zio.blocking.Blocking -import zio.clock.Clock -import zio.duration._ -import zio.stream.{ZSink, ZStream, ZTransducer} -import zio.test.Assertion.{anything, equalTo, hasSameElements, isLeft} -import zio.test.TestAspect._ -import zio.test.{assertM, _} -import zio.test.environment.Live -import zio.{ExitCode, ZIO} - -object ProcessSpecs extends DefaultRunnableSpec with ProxSpecHelpers { - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - override val spec = - suite("Executing a process")( - testM("returns the exit code") { - val program = for { - trueResult <- Process("true").run() - falseResult <- Process("false").run() - } yield (trueResult.exitCode, falseResult.exitCode) - - assertM(program)(equalTo((ExitCode(0), ExitCode(1)))) - }, - - suite("Output redirection")( - testM("can redirect output to a file") { - withTempFile { tempFile => - val process = Process("echo", List("Hello world!")) > tempFile.toPath - val program = for { - _ <- process.run() - contents <- ZStream.fromFile(tempFile.toPath, 1024).transduce(ZTransducer.utf8Decode).fold("")(_ + _).mapError(UnknownProxError.apply) - } yield contents - - assertM(program)(equalTo("Hello world!\n")) - } - }, - - testM("can redirect output to append a file") { - withTempFile { tempFile => - val process1 = Process("echo", List("Hello")) > tempFile.toPath - val process2 = Process("echo", List("world")) >> tempFile.toPath - val program = for { - _ <- process1.run() - _ <- process2.run() - contents <- ZStream.fromFile(tempFile.toPath, 1024).transduce(ZTransducer.utf8Decode).fold("")(_ + _).mapError(UnknownProxError.apply) - } yield contents - - assertM(program)(equalTo("Hello\nworld\n")) - } - }, - - testM("can redirect output to stream") { - val process = Process("echo", List("Hello world!")) ># ZTransducer.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world!\n")) - }, - - testM("can redirect output to stream folding monoid") { - val process = Process("echo", List("Hello\nworld!")) ># (ZTransducer.utf8Decode >>> ZTransducer.splitLines) - val program = process.run().map(_.output) - - assertM(program)(equalTo("Helloworld!")) - }, - - testM("can redirect output to stream collected to vector") { - case class StringLength(value: Int) - - val stream = - ZTransducer.utf8Decode >>> - ZTransducer.splitLines.map(s => StringLength(s.length)) - - val process = Process("echo", List("Hello\nworld!")) >? stream - val program = process.run().map(_.output) - - assertM(program)(hasSameElements(List(StringLength(5), StringLength(6)))) - }, - - testM("can redirect output to stream and ignore it's result") { - val process = Process("echo", List("Hello\nworld!")).drainOutput(ZTransducer.utf8Decode >>> ZTransducer.splitLines) - val program = process.run().map(_.output) - - assertM(program)(equalTo(())) - }, - - testM("can redirect output to stream and fold it") { - val process = Process("echo", List("Hello\nworld!")).foldOutput( - ZTransducer.utf8Decode >>> ZTransducer.splitLines, - Vector.empty, - (l: Vector[Option[Char]], s: String) => l :+ s.headOption - ) - val program = process.run().map(_.output) - - assertM(program)(equalTo(Vector(Some('H'), Some('w')))) - }, - - testM("can redirect output to a sink") { - val builder = new StringBuilder - val target: zstream.ProxSink[Byte] = ZSink.foreach((byte: Byte) => ZIO.effect(builder.append(byte.toChar)).mapError(UnknownProxError.apply)) - - val process = Process("echo", List("Hello world!")) > target - val program = process.run().as(builder.toString) - - assertM(program)(equalTo("Hello world!\n")) - }, - ), - suite("Error redirection")( - testM("can redirect error to a file") { - withTempFile { tempFile => - val process = Process("perl", List("-e", "print STDERR 'Hello world!'")) !> tempFile.toPath - val program = for { - _ <- process.run() - contents <- ZStream.fromFile(tempFile.toPath, 1024).transduce(ZTransducer.utf8Decode).fold("")(_ + _).mapError(UnknownProxError.apply) - } yield contents - - assertM(program)(equalTo("Hello world!")) - } - }, - - testM("can redirect error to append a file") { - withTempFile { tempFile => - val process1 = Process("perl", List("-e", "print STDERR Hello")) !> tempFile.toPath - val process2 = Process("perl", List("-e", "print STDERR world")) !>> tempFile.toPath - val program = for { - _ <- process1.run() - _ <- process2.run() - contents <- ZStream.fromFile(tempFile.toPath, 1024).transduce(ZTransducer.utf8Decode).fold("")(_ + _).mapError(UnknownProxError.apply) - } yield contents - - assertM(program)(equalTo("Helloworld")) - } - }, - - testM("can redirect error to stream") { - val process = Process("perl", List("-e", """print STDERR "Hello"""")) !># ZTransducer.utf8Decode - val program = process.run().map(_.error) - - assertM(program)(equalTo("Hello")) - }, - - testM("can redirect error to stream folding monoid") { - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) !># (ZTransducer.utf8Decode >>> ZTransducer.splitLines) - val program = process.run().map(_.error) - - assertM(program)(equalTo("Helloworld!")) - }, - - testM("can redirect error to stream collected to vector") { - case class StringLength(value: Int) - - val stream = - ZTransducer.utf8Decode >>> - ZTransducer.splitLines.map(s => StringLength(s.length)) - - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) !>? stream - val program = process.run().map(_.error) - - assertM(program)(hasSameElements(List(StringLength(5), StringLength(6)))) - }, - - testM("can redirect error to stream and ignore it's result") { - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")).drainError(ZTransducer.utf8Decode >>> ZTransducer.splitLines) - val program = process.run().map(_.error) - - assertM(program)(equalTo(())) - }, - - testM("can redirect error to stream and fold it") { - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")).foldError( - ZTransducer.utf8Decode >>> ZTransducer.splitLines, - Vector.empty, - (l: Vector[Option[Char]], s: String) => l :+ s.headOption - ) - val program = process.run().map(_.error) - - assertM(program)(equalTo(Vector(Some('H'), Some('w')))) - }, - - testM("can redirect error to a sink") { - val builder = new StringBuilder - val target: zstream.ProxSink[Byte] = ZSink.foreach((byte: Byte) => ZIO.effect(builder.append(byte.toChar)).mapError(UnknownProxError.apply)) - - val process = Process("perl", List("-e", """print STDERR "Hello"""")) !> target - val program = process.run().as(builder.toString) - - assertM(program)(equalTo("Hello")) - }, - ), - - suite("Redirection ordering")( - testM("can redirect first input and then error to stream") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = Process("perl", List("-e", """my $str = <>; print STDERR "$str"""".stripMargin)) < source !># ZTransducer.utf8Decode - val program = process.run().map(_.error) - - assertM(program)(equalTo("This is a test string")) - }, - - testM("can redirect error first then output to stream") { - val process = (Process("perl", List("-e", """print STDOUT Hello; print STDERR World""".stripMargin)) !># ZTransducer.utf8Decode) ># ZTransducer.utf8Decode - val program = process.run().map(r => r.output + r.error) - - assertM(program)(equalTo("HelloWorld")) - }, - - testM("can redirect output first then error to stream") { - val process = (Process("perl", List("-e", """print STDOUT Hello; print STDERR World""".stripMargin)) ># ZTransducer.utf8Decode) !># ZTransducer.utf8Decode - val program = process.run().map(r => r.output + r.error) - - assertM(program)(equalTo("HelloWorld")) - }, - - testM("can redirect output first then error finally input to stream") { - val source = ZStream("Hello").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) - ># ZTransducer.utf8Decode) - !># ZTransducer.utf8Decode) < source - val program = process.run().map(r => r.output + r.error) - - assertM(program)(equalTo("HelloWorld")) - }, - - testM("can redirect output first then input finally error to stream") { - val source = ZStream("Hello").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) - ># ZTransducer.utf8Decode) - < source) !># ZTransducer.utf8Decode - val program = process.run().map(r => r.output + r.error) - - assertM(program)(equalTo("HelloWorld")) - }, - - testM("can redirect input first then error finally output to stream") { - val source = ZStream("Hello").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) - < source) - !># ZTransducer.utf8Decode) ># ZTransducer.utf8Decode - val program = process.run().map(r => r.output + r.error) - - assertM(program)(equalTo("HelloWorld")) - }, - ), - - suite("Input redirection")( - testM("can use stream as input") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = Process("wc", List("-w")) < source ># ZTransducer.utf8Decode - val program = process.run().map(_.output.trim) - - assertM(program)(equalTo("5")) - }, - - testM("can use stream as input flushing after each chunk") { - val source = ZStream("This ", "is a test", " string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = (Process("wc", List("-w")) !< source) ># ZTransducer.utf8Decode - val program = process.run().map(_.output.trim) - - assertM(program)(equalTo("5")) - }, - ), - - suite("Termination")( - testM("can be terminated with cancellation") { - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val program = process.start().use { fiber => ZIO(Thread.sleep(250)) *> fiber.interrupt.unit } - - assertM(program)(equalTo(())) - } @@ TestAspect.timeout(5.seconds), - - testM("can be terminated by releasing the resource") { - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val program = process.start().use { _ => ZIO(Thread.sleep(250)) } - - assertM(program)(equalTo(())) - } @@ TestAspect.timeout(5.seconds), - - testM[Blocking with Clock, ProxError]("can be terminated") { - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val program = for { - runningProcess <- process.startProcess() - _ <- ZIO.sleep(250.millis) - result <- runningProcess.terminate() - } yield result.exitCode - - assertM(program.provideSomeLayer[Blocking](Clock.live))(equalTo(ExitCode(1))) - }, - - testM("can be killed") { - val process = Process("perl", List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""")) - val program = for { - runningProcess <- process.startProcess() - _ <- ZIO(Thread.sleep(250)) - result <- runningProcess.kill() - } yield result.exitCode - - assertM(program.provideSomeLayer[Blocking](Clock.live))(equalTo(ExitCode(137))) - }, - - testM("can be checked if is alive") { - val process = Process("sleep", List("10")) - val program = for { - runningProcess <- process.startProcess() - isAliveBefore <- runningProcess.isAlive - _ <- runningProcess.terminate() - isAliveAfter <- runningProcess.isAlive - } yield (isAliveBefore, isAliveAfter) - - assertM(program)(equalTo((true, false))) - }, - ), - - suite("Customization")( - testM("can change the command") { - val p1 = Process("something", List("Hello", "world")) ># ZTransducer.utf8Decode - val p2 = p1.withCommand("echo") - val program = p2.run().map(_.output) - - assertM(program)(equalTo("Hello world\n")) - }, - - testM("can change the arguments") { - val p1 = Process("echo") ># ZTransducer.utf8Decode - val p2 = p1.withArguments(List("Hello", "world")) - val program = p2.run().map(_.output) - - assertM(program)(equalTo("Hello world\n")) - }, - - testM("respects the working directory") { - ZIO(Files.createTempDirectory("prox")).flatMap { tempDirectory => - val process = (Process("pwd") in tempDirectory) ># ZTransducer.utf8Decode - val program = process.run().map(_.output.trim) - - assertM(program)(equalTo(tempDirectory.toString) || equalTo(s"/private${tempDirectory}")) - } - }, - - testM("is customizable with environment variables") { - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) ># ZTransducer.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - testM("is customizable with excluded environment variables") { - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox") - `without` "TEST1") ># ZTransducer.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello ! I am prox!\n")) - }, - - testM("is customizable with environment variables output is bound") { - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) ># ZTransducer.utf8Decode - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - testM("is customizable with environment variables if input is bound") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) ># ZTransducer.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - testM("is customizable with environment variables if error is bound") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) !># ZTransducer.utf8Decode) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) ># ZTransducer.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - testM("is customizable with environment variables if input and output are bound") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = (((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) ># ZTransducer.utf8Decode) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - testM("is customizable with environment variables if input and error are bound") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = (((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) !># ZTransducer.utf8Decode) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) ># ZTransducer.utf8Decode - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - testM("is customizable with environment variables if output and error are bound") { - val process = (((Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) !># ZTransducer.utf8Decode) ># ZTransducer.utf8Decode) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - - testM("is customizable with environment variables if everything is bound") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) !># ZTransducer.utf8Decode) ># ZTransducer.utf8Decode) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) - val program = process.run().map(_.output) - - assertM(program)(equalTo("Hello world! I am prox!\n")) - }, - ), - - testM("double output redirect is illegal") { - assertM( - typeCheck("""val bad = Process("echo", List("Hello world")) > new File("x").toPath > new File("y").toPath"""))( - isLeft(anything) - ) - }, - testM("double error redirect is illegal") { - assertM( - typeCheck("""val bad = Process("echo", List("Hello world")) !> new File("x").toPath !> new File("y").toPath"""))( - isLeft(anything) - ) - }, - testM("double input redirect is illegal") { - assertM( - typeCheck("""val bad = (Process("echo", List("Hello world")) < ZStream("X").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)))) < ZStream("Y").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)))"""))( - isLeft(anything) - ) - } - ) @@ timeout(60.seconds) @@ sequential -} diff --git a/prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProxSpecHelpers.scala b/prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProxSpecHelpers.scala deleted file mode 100644 index 8162148b..00000000 --- a/prox-zstream/src/test/scala/io/github/vigoo/prox/tests/zstream/ProxSpecHelpers.scala +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.vigoo.prox.tests.zstream - -import java.io.File - -import io.github.vigoo.prox.{ProxError, UnknownProxError} -import zio.ZIO -import zio.blocking.Blocking - -trait ProxSpecHelpers { - - def withTempFile[A](inner: File => ZIO[Blocking, ProxError, A]): ZIO[Blocking, ProxError, A] = - ZIO.effect(File.createTempFile("test", "txt")) - .mapError(UnknownProxError.apply) - .bracket( - file => ZIO.effect(file.delete()).orDie, - inner) - -} From a64ff72283d13de6340988e7172e4b553107d651 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 20 Jan 2024 12:05:18 +0100 Subject: [PATCH 2/6] ScalaFmt --- .scalafmt.conf | 2 + build.sbt | 4 +- project/plugins.sbt | 5 +- .../scala/io/github/vigoo/prox/common.scala | 68 +- .../scala/io/github/vigoo/prox/errors.scala | 46 +- .../scala/io/github/vigoo/prox/package.scala | 38 +- .../io/github/vigoo/prox/path/package.scala | 8 +- .../scala/io/github/vigoo/prox/process.scala | 576 ++++++--- .../io/github/vigoo/prox/processgroup.scala | 545 ++++++--- .../io/github/vigoo/prox/redirection.scala | 1074 +++++++++++------ .../scala/io/github/vigoo/prox/runner.scala | 310 +++-- .../scala/io/github/vigoo/prox/runtime.scala | 61 +- .../scala/io/github/vigoo/prox/syntax.scala | 100 +- .../scala/io/github/vigoo/prox/ProxFS2.scala | 131 +- .../prox/tests/fs2/InterpolatorSpecs.scala | 78 +- .../prox/tests/fs2/ProcessGroupSpecs.scala | 619 ++++++---- .../vigoo/prox/tests/fs2/ProcessSpecs.scala | 595 ++++++--- .../prox/tests/fs2/ProxSpecHelpers.scala | 10 +- .../io/github/vigoo/prox/java9/runner.scala | 13 +- .../io/github/vigoo/prox/ProxZStream.scala | 187 ++- .../tests/zstream/ProcessGroupSpecs.scala | 511 +++++--- .../prox/tests/zstream/ProcessSpecs.scala | 370 ++++-- .../prox/tests/zstream/ProxSpecHelpers.scala | 9 +- 23 files changed, 3542 insertions(+), 1818 deletions(-) create mode 100644 .scalafmt.conf diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 00000000..b276dbf7 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,2 @@ +version = 3.7.17 +runner.dialect = "scala213source3" \ No newline at end of file diff --git a/build.sbt b/build.sbt index 2d2a9a29..ba4fa610 100644 --- a/build.sbt +++ b/build.sbt @@ -4,8 +4,8 @@ val scala3 = "3.3.1" val zio2Version = "2.0.21" -def scalacOptions212(jdk: Int) = Seq("-Ypartial-unification", "-deprecation", "-release", jdk.toString) -def scalacOptions213(jdk: Int) = Seq("-deprecation", "-release", jdk.toString) +def scalacOptions212(jdk: Int) = Seq("-Ypartial-unification", "-deprecation", "-Xsource:3", "-release", jdk.toString) +def scalacOptions213(jdk: Int) = Seq("-deprecation", "-Xsource:3", "-release", jdk.toString) def scalacOptions3(jdk: Int) = Seq("-deprecation", "-Ykind-projector", "-release", jdk.toString) import microsites.ConfigYml diff --git a/project/plugins.sbt b/project/plugins.sbt index d83ff70a..4cd46ec7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,8 +1,9 @@ addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9") -addSbtPlugin("com.47deg" % "sbt-microsites" % "1.4.4") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.5.2") +addSbtPlugin("com.47deg" % "sbt-microsites" % "1.4.4") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.5.2") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") addSbtPlugin("com.github.sbt" % "sbt-unidoc" % "0.5.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") ThisBuild / libraryDependencySchemes ++= Seq( "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/common.scala b/prox-core/src/main/scala/io/github/vigoo/prox/common.scala index 3eee5f18..6bcf4d3b 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/common.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/common.scala @@ -12,46 +12,68 @@ trait CommonModule { type Self <: ProcessLikeConfiguration - protected def applyConfiguration(workingDirectory: Option[Path], - environmentVariables: Map[String, String], - removedEnvironmentVariables: Set[String]): Self + protected def applyConfiguration( + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): Self - /** - * Changes the working directory of the process + /** Changes the working directory of the process * - * @param workingDirectory the working directory - * @return a new process with the working directory set + * @param workingDirectory + * the working directory + * @return + * a new process with the working directory set */ def in(workingDirectory: Path): Self = - applyConfiguration(workingDirectory = Some(workingDirectory), environmentVariables, removedEnvironmentVariables) + applyConfiguration( + workingDirectory = Some(workingDirectory), + environmentVariables, + removedEnvironmentVariables + ) - /** - * Use the inherited working directory of the process instead of an explicit one + /** Use the inherited working directory of the process instead of an + * explicit one * - * @return a new process with the working directory cleared + * @return + * a new process with the working directory cleared */ def inInheritedWorkingDirectory(): Self = - applyConfiguration(workingDirectory = None, environmentVariables, removedEnvironmentVariables) + applyConfiguration( + workingDirectory = None, + environmentVariables, + removedEnvironmentVariables + ) - /** - * Adds an environment variable to the process + /** Adds an environment variable to the process * - * @param nameValuePair A pair of name and value - * @return a new process with the working directory set + * @param nameValuePair + * A pair of name and value + * @return + * a new process with the working directory set */ def `with`(nameValuePair: (String, String)): Self = - applyConfiguration(workingDirectory, environmentVariables = environmentVariables + nameValuePair, removedEnvironmentVariables) + applyConfiguration( + workingDirectory, + environmentVariables = environmentVariables + nameValuePair, + removedEnvironmentVariables + ) - /** - * Removes an environment variable from the process + /** Removes an environment variable from the process * * Usable to remove variables inherited from the parent process. * - * @param name Name of the environment variable - * @return a new process with the working directory set + * @param name + * Name of the environment variable + * @return + * a new process with the working directory set */ def without(name: String): Self = - applyConfiguration(workingDirectory, environmentVariables, removedEnvironmentVariables = removedEnvironmentVariables + name) + applyConfiguration( + workingDirectory, + environmentVariables, + removedEnvironmentVariables = removedEnvironmentVariables + name + ) } -} \ No newline at end of file +} diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/errors.scala b/prox-core/src/main/scala/io/github/vigoo/prox/errors.scala index 9b3a0d05..1420c84d 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/errors.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/errors.scala @@ -4,42 +4,56 @@ sealed trait ProxError { def toThrowable: Throwable } -final case class FailedToReadProcessOutputException(reason: Throwable) extends Exception(s"Failed to read process output", reason) -final case class FailedToReadProcessOutput(reason: Throwable) extends ProxError { - override def toThrowable: Throwable = FailedToReadProcessOutputException(reason) -} - -final case class FailedToWriteProcessInputException(reason: Throwable) extends Exception(s"Failed to write process input", reason) -final case class FailedToWriteProcessInput(reason: Throwable) extends ProxError { - override def toThrowable: Throwable = FailedToWriteProcessInputException(reason) -} - -final case class UnknownProxErrorException(reason: Throwable) extends Exception(s"Unknown prox failure", reason) +final case class FailedToReadProcessOutputException(reason: Throwable) + extends Exception(s"Failed to read process output", reason) +final case class FailedToReadProcessOutput(reason: Throwable) + extends ProxError { + override def toThrowable: Throwable = FailedToReadProcessOutputException( + reason + ) +} + +final case class FailedToWriteProcessInputException(reason: Throwable) + extends Exception(s"Failed to write process input", reason) +final case class FailedToWriteProcessInput(reason: Throwable) + extends ProxError { + override def toThrowable: Throwable = FailedToWriteProcessInputException( + reason + ) +} + +final case class UnknownProxErrorException(reason: Throwable) + extends Exception(s"Unknown prox failure", reason) final case class UnknownProxError(reason: Throwable) extends ProxError { override def toThrowable: Throwable = UnknownProxErrorException(reason) } -final case class MultipleProxErrorsException(value: List[ProxError]) extends Exception(s"Multiple prox failures: ${value.mkString(", ")}") +final case class MultipleProxErrorsException(value: List[ProxError]) + extends Exception(s"Multiple prox failures: ${value.mkString(", ")}") final case class MultipleProxErrors(errors: List[ProxError]) extends ProxError { override def toThrowable: Throwable = MultipleProxErrorsException(errors) } -final case class FailedToQueryStateException(reason: Throwable) extends Exception(s"Failed to query state of process", reason) +final case class FailedToQueryStateException(reason: Throwable) + extends Exception(s"Failed to query state of process", reason) final case class FailedToQueryState(reason: Throwable) extends ProxError { override def toThrowable: Throwable = FailedToQueryStateException(reason) } -final case class FailedToDestroyException(reason: Throwable) extends Exception(s"Failed to destroy process", reason) +final case class FailedToDestroyException(reason: Throwable) + extends Exception(s"Failed to destroy process", reason) final case class FailedToDestroy(reason: Throwable) extends ProxError { override def toThrowable: Throwable = FailedToDestroyException(reason) } -final case class FailedToWaitForExitException(reason: Throwable) extends Exception(s"Failed to wait for process to exit", reason) +final case class FailedToWaitForExitException(reason: Throwable) + extends Exception(s"Failed to wait for process to exit", reason) final case class FailedToWaitForExit(reason: Throwable) extends ProxError { override def toThrowable: Throwable = FailedToWaitForExitException(reason) } -final case class FailedToStartProcessException(reason: Throwable) extends Exception(s"Failed to start process", reason) +final case class FailedToStartProcessException(reason: Throwable) + extends Exception(s"Failed to start process", reason) final case class FailedToStartProcess(reason: Throwable) extends ProxError { override def toThrowable: Throwable = FailedToStartProcessException(reason) } diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/package.scala b/prox-core/src/main/scala/io/github/vigoo/prox/package.scala index 02140684..6300ee41 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/package.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/package.scala @@ -4,28 +4,34 @@ package io.github.vigoo * * Refer to the user guide for more information. * - * A process to be executed is represented by the [[Process]] trait. Once it has finished running the results - * are in [[ProcessResult]]. We call a group of processes attached together a process group, represented by + * A process to be executed is represented by the [[Process]] trait. Once it + * has finished running the results are in [[ProcessResult]]. We call a group + * of processes attached together a process group, represented by * [[ProcessGroup]], its result is described by [[ProcessGroupResult]]. * - * Redirection of input, output and error is enabled by the [[RedirectableInput]], [[RedirectableOutput]] and - * [[RedirectableError]] trait for single processes, and the [[RedirectableErrors]] trait for process groups. + * Redirection of input, output and error is enabled by the + * [[RedirectableInput]], [[RedirectableOutput]] and [[RedirectableError]] + * trait for single processes, and the [[RedirectableErrors]] trait for process + * groups. * - * Processes and process groups are executed by a [[ProcessRunner]], the default implementation is called - * [[JVMProcessRunner]]. + * Processes and process groups are executed by a [[ProcessRunner]], the + * default implementation is called [[JVMProcessRunner]]. * - * @author Daniel Vigovszky + * @author + * Daniel Vigovszky */ package object prox { - trait Prox extends ProxRuntime - with CommonModule - with ProcessModule - with ProcessGroupModule - with RedirectionModule - with ProcessRunnerModule - with SyntaxModule + trait Prox + extends ProxRuntime + with CommonModule + with ProcessModule + with ProcessGroupModule + with RedirectionModule + with ProcessRunnerModule + with SyntaxModule - - /** Common base trait for processes and process groups, used in constraints in the redirection traits */ + /** Common base trait for processes and process groups, used in constraints in + * the redirection traits + */ trait ProcessLike } diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/path/package.scala b/prox-core/src/main/scala/io/github/vigoo/prox/path/package.scala index 4e7fd51a..c0ba03cf 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/path/package.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/path/package.scala @@ -4,6 +4,7 @@ import java.nio.file.{Path, Paths} /** Small helper package to work with Java NIO paths */ package object path { + /** The home directory */ val home: Path = Paths.get(java.lang.System.getProperty("user.home")) @@ -12,10 +13,13 @@ package object path { /** Extension methods for [[java.nio.file.Path]] */ implicit class PathOps(value: Path) { + /** Resolves a child of the given path * - * @param child The child's name - * @return Returns the path to the given child + * @param child + * The child's name + * @return + * Returns the path to the given child */ def /(child: String): Path = value.resolve(child) } diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/process.scala b/prox-core/src/main/scala/io/github/vigoo/prox/process.scala index 14a36ffa..5bac6ad8 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/process.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/process.scala @@ -5,35 +5,44 @@ import java.nio.file.Path trait ProcessModule { this: Prox => - /** - * Result of a finished process + /** Result of a finished process * - * @tparam O Output type - * @tparam E Error output type + * @tparam O + * Output type + * @tparam E + * Error output type */ trait ProcessResult[+O, +E] { + /** The exit code of the process */ val exitCode: ProxExitCode - /** Output value of the process, depends on what output redirection was applied */ + /** Output value of the process, depends on what output redirection was + * applied + */ val output: O - /** Error output value of the process, depends on what error redirection was applied */ + /** Error output value of the process, depends on what error redirection was + * applied + */ val error: E } /** Default implementation of [[ProcessResult]] */ - case class SimpleProcessResult[+O, +E](override val exitCode: ProxExitCode, - override val output: O, - override val error: E) - extends ProcessResult[O, E] + case class SimpleProcessResult[+O, +E]( + override val exitCode: ProxExitCode, + override val output: O, + override val error: E + ) extends ProcessResult[O, E] - /** - * Representation of a running process + /** Representation of a running process * - * @tparam O Output type - * @tparam E Error output type - * @tparam Info Runner-specific process information + * @tparam O + * Output type + * @tparam E + * Error output type + * @tparam Info + * Runner-specific process information */ trait RunningProcess[O, E, +Info] { val runningInput: ProxFiber[Unit] @@ -57,35 +66,45 @@ trait ProcessModule { def mapInfo[I2](f: Info => I2): RunningProcess[O, E, I2] = new RunningProcess[O, E, I2] { - override val runningInput: ProxFiber[Unit] = RunningProcess.this.runningInput - override val runningOutput: ProxFiber[O] = RunningProcess.this.runningOutput - override val runningError: ProxFiber[E] = RunningProcess.this.runningError + override val runningInput: ProxFiber[Unit] = + RunningProcess.this.runningInput + override val runningOutput: ProxFiber[O] = + RunningProcess.this.runningOutput + override val runningError: ProxFiber[E] = + RunningProcess.this.runningError override val info: I2 = f(RunningProcess.this.info) override def isAlive: ProxIO[Boolean] = RunningProcess.this.isAlive - override def kill(): ProxIO[ProcessResult[O, E]] = RunningProcess.this.kill() + override def kill(): ProxIO[ProcessResult[O, E]] = + RunningProcess.this.kill() - override def terminate(): ProxIO[ProcessResult[O, E]] = RunningProcess.this.terminate() + override def terminate(): ProxIO[ProcessResult[O, E]] = + RunningProcess.this.terminate() - override def waitForExit(): ProxIO[ProcessResult[O, E]] = RunningProcess.this.waitForExit() + override def waitForExit(): ProxIO[ProcessResult[O, E]] = + RunningProcess.this.waitForExit() } } - /** - * Describes a system process to be executed + /** Describes a system process to be executed * - * This base trait is always extended with redirection and configuration capabilities represented by the - * traits [[ProcessConfiguration]], [[RedirectableInput]], [[RedirectableOutput]] and [[RedirectableError]]. + * This base trait is always extended with redirection and configuration + * capabilities represented by the traits [[ProcessConfiguration]], + * [[RedirectableInput]], [[RedirectableOutput]] and [[RedirectableError]]. * - * To create a process use the constructor in the companion object [[Process.apply]]. + * To create a process use the constructor in the companion object + * [[Process.apply]]. * - * The process specification not only encodes the process to be started but also how its input, output and error - * streams are redirected and executed. For this reason the effect type is also bound by the process, not just at + * The process specification not only encodes the process to be started but + * also how its input, output and error streams are redirected and executed. + * For this reason the effect type is also bound by the process, not just at * execution time. * - * @tparam O Output type - * @tparam E Error output type + * @tparam O + * Output type + * @tparam E + * Error output type */ trait Process[O, E] extends ProcessLike with ProcessConfiguration { override type Self <: Process[O, E] @@ -102,86 +121,128 @@ trait ProcessModule { val runErrorStream: java.io.InputStream => ProxIO[E] val inputRedirection: InputRedirection - /** - * Starts the process asynchronously and returns the [[RunningProcess]] interface for it + /** Starts the process asynchronously and returns the [[RunningProcess]] + * interface for it * - * This is the most advanced way to start processes. See [[start]] and [[run]] as alternatives. + * This is the most advanced way to start processes. See [[start]] and + * [[run]] as alternatives. * - * @param runner The process runner to be used - * @tparam Info The runner-specific process info type - * @return interface for handling the running process + * @param runner + * The process runner to be used + * @tparam Info + * The runner-specific process info type + * @return + * interface for handling the running process */ - def startProcess[Info]()(implicit runner: ProcessRunner[Info]): ProxIO[RunningProcess[O, E, Info]] = + def startProcess[Info]()(implicit + runner: ProcessRunner[Info] + ): ProxIO[RunningProcess[O, E, Info]] = runner.startProcess(this) - /** - * Starts the process asynchronously and returns a closeable fiber representing it + /** Starts the process asynchronously and returns a closeable fiber + * representing it * - * Joining the fiber waits for the process to be terminated. Canceling the fiber terminates - * the process normally (with SIGTERM). + * Joining the fiber waits for the process to be terminated. Canceling the + * fiber terminates the process normally (with SIGTERM). * - * @param runner The process runner to be used - * @return a managed fiber representing the running process + * @param runner + * The process runner to be used + * @return + * a managed fiber representing the running process */ - def start[Info]()(implicit runner: ProcessRunner[Info]): ProxResource[ProxFiber[ProcessResult[O, E]]] = + def start[Info]()(implicit + runner: ProcessRunner[Info] + ): ProxResource[ProxFiber[ProcessResult[O, E]]] = runner.start(this) - /** - * Starts the process asynchronously and blocks the execution until it is finished + /** Starts the process asynchronously and blocks the execution until it is + * finished * - * @param runner The process runner to be used - * @return the result of the finished process + * @param runner + * The process runner to be used + * @return + * the result of the finished process */ - def run[Info]()(implicit runner: ProcessRunner[Info]): ProxIO[ProcessResult[O, E]] = + def run[Info]()(implicit + runner: ProcessRunner[Info] + ): ProxIO[ProcessResult[O, E]] = start().use(_.join) } - /** - * The capability to configure process execution details - * + /** The capability to configure process execution details */ trait ProcessConfiguration extends ProcessLikeConfiguration { this: Process[_, _] => // override type Self <: ProcessConfiguration - override protected def applyConfiguration(workingDirectory: Option[Path], environmentVariables: Map[String, String], removedEnvironmentVariables: Set[String]): Self = - selfCopy(command, arguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + override protected def applyConfiguration( + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): Self = + selfCopy( + command, + arguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) - protected def selfCopy(command: String, - arguments: List[String], - workingDirectory: Option[Path], - environmentVariables: Map[String, String], - removedEnvironmentVariables: Set[String]): Self + protected def selfCopy( + command: String, + arguments: List[String], + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): Self - /** - * Replaces the command + /** Replaces the command * - * @param newCommand new value for the command to be executed - * @return returns a new process specification + * @param newCommand + * new value for the command to be executed + * @return + * returns a new process specification */ def withCommand(newCommand: String): Self = - selfCopy(newCommand, arguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + selfCopy( + newCommand, + arguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) - /** - * Replaces the arguments + /** Replaces the arguments * - * @param newArguments new list of arguments - * @return returns a new process specification + * @param newArguments + * new list of arguments + * @return + * returns a new process specification */ def withArguments(newArguments: List[String]): Self = - selfCopy(command, newArguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + selfCopy( + command, + newArguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) } object Process { + /** Process with unbound input stream */ - type UnboundIProcess[O, E] = Process[O, E] with RedirectableInput[Process[O, E]] + type UnboundIProcess[O, E] = Process[O, E] + with RedirectableInput[Process[O, E]] /** Process with unbound output stream */ - type UnboundOProcess[E] = Process[Unit, E] with RedirectableOutput[Process[*, E]] + type UnboundOProcess[E] = Process[Unit, E] + with RedirectableOutput[Process[*, E]] /** Process with unbound error stream */ - type UnboundEProcess[O] = Process[O, Unit] with RedirectableError[Process[O, *]] + type UnboundEProcess[O] = Process[O, Unit] + with RedirectableError[Process[O, *]] /** Process with unbound input and output streams */ type UnboundIOProcess[E] = Process[Unit, E] @@ -205,41 +266,57 @@ trait ProcessModule { with RedirectableError[UnboundIOProcess[*]] /** Process with bound input, output and error streams */ - case class ProcessImplIOE[O, E](override val command: String, - override val arguments: List[String], - override val workingDirectory: Option[Path], - override val environmentVariables: Map[String, String], - override val removedEnvironmentVariables: Set[String], - override val outputRedirection: OutputRedirection, - override val runOutputStream: java.io.InputStream => ProxIO[O], - override val errorRedirection: OutputRedirection, - override val runErrorStream: java.io.InputStream => ProxIO[E], - override val inputRedirection: InputRedirection) - extends Process[O, E] { + case class ProcessImplIOE[O, E]( + override val command: String, + override val arguments: List[String], + override val workingDirectory: Option[Path], + override val environmentVariables: Map[String, String], + override val removedEnvironmentVariables: Set[String], + override val outputRedirection: OutputRedirection, + override val runOutputStream: java.io.InputStream => ProxIO[O], + override val errorRedirection: OutputRedirection, + override val runErrorStream: java.io.InputStream => ProxIO[E], + override val inputRedirection: InputRedirection + ) extends Process[O, E] { override type Self = ProcessImplIOE[O, E] - override protected def selfCopy(command: String, arguments: List[String], workingDirectory: Option[Path], environmentVariables: Map[String, String], removedEnvironmentVariables: Set[String]): ProcessImplIOE[O, E] = - copy(command, arguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + override protected def selfCopy( + command: String, + arguments: List[String], + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): ProcessImplIOE[O, E] = + copy( + command, + arguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) } /** Process with bound input and output streams */ - case class ProcessImplIO[O](override val command: String, - override val arguments: List[String], - override val workingDirectory: Option[Path], - override val environmentVariables: Map[String, String], - override val removedEnvironmentVariables: Set[String], - override val outputRedirection: OutputRedirection, - override val runOutputStream: java.io.InputStream => ProxIO[O], - override val errorRedirection: OutputRedirection, - override val runErrorStream: java.io.InputStream => ProxIO[Unit], - override val inputRedirection: InputRedirection) - extends Process[O, Unit] + case class ProcessImplIO[O]( + override val command: String, + override val arguments: List[String], + override val workingDirectory: Option[Path], + override val environmentVariables: Map[String, String], + override val removedEnvironmentVariables: Set[String], + override val outputRedirection: OutputRedirection, + override val runOutputStream: java.io.InputStream => ProxIO[O], + override val errorRedirection: OutputRedirection, + override val runErrorStream: java.io.InputStream => ProxIO[Unit], + override val inputRedirection: InputRedirection + ) extends Process[O, Unit] with RedirectableError[ProcessImplIOE[O, *]] { override type Self = ProcessImplIO[O] - override def connectError[R <: OutputRedirection, RE](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RE]): ProcessImplIOE[O, RE] = + override def connectError[R <: OutputRedirection, RE](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RE] + ): ProcessImplIOE[O, RE] = ProcessImplIOE( command, arguments, @@ -253,27 +330,42 @@ trait ProcessModule { inputRedirection ) - override protected def selfCopy(command: String, arguments: List[String], workingDirectory: Option[Path], environmentVariables: Map[String, String], removedEnvironmentVariables: Set[String]): ProcessImplIO[O] = - copy(command, arguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + override protected def selfCopy( + command: String, + arguments: List[String], + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): ProcessImplIO[O] = + copy( + command, + arguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) } /** Process with bound input and error streams */ - case class ProcessImplIE[E](override val command: String, - override val arguments: List[String], - override val workingDirectory: Option[Path], - override val environmentVariables: Map[String, String], - override val removedEnvironmentVariables: Set[String], - override val outputRedirection: OutputRedirection, - override val runOutputStream: java.io.InputStream => ProxIO[Unit], - override val errorRedirection: OutputRedirection, - override val runErrorStream: java.io.InputStream => ProxIO[E], - override val inputRedirection: InputRedirection) - extends Process[Unit, E] + case class ProcessImplIE[E]( + override val command: String, + override val arguments: List[String], + override val workingDirectory: Option[Path], + override val environmentVariables: Map[String, String], + override val removedEnvironmentVariables: Set[String], + override val outputRedirection: OutputRedirection, + override val runOutputStream: java.io.InputStream => ProxIO[Unit], + override val errorRedirection: OutputRedirection, + override val runErrorStream: java.io.InputStream => ProxIO[E], + override val inputRedirection: InputRedirection + ) extends Process[Unit, E] with RedirectableOutput[ProcessImplIOE[*, E]] { override type Self = ProcessImplIE[E] - override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RO]): ProcessImplIOE[RO, E] = + override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RO] + ): ProcessImplIOE[RO, E] = ProcessImplIOE( command, arguments, @@ -287,27 +379,42 @@ trait ProcessModule { inputRedirection ) - override protected def selfCopy(command: String, arguments: List[String], workingDirectory: Option[Path], environmentVariables: Map[String, String], removedEnvironmentVariables: Set[String]): ProcessImplIE[E] = - copy(command, arguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + override protected def selfCopy( + command: String, + arguments: List[String], + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): ProcessImplIE[E] = + copy( + command, + arguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) } /** Process with bound output and error streams */ - case class ProcessImplOE[O, E](override val command: String, - override val arguments: List[String], - override val workingDirectory: Option[Path], - override val environmentVariables: Map[String, String], - override val removedEnvironmentVariables: Set[String], - override val outputRedirection: OutputRedirection, - override val runOutputStream: java.io.InputStream => ProxIO[O], - override val errorRedirection: OutputRedirection, - override val runErrorStream: java.io.InputStream => ProxIO[E], - override val inputRedirection: InputRedirection) - extends Process[O, E] + case class ProcessImplOE[O, E]( + override val command: String, + override val arguments: List[String], + override val workingDirectory: Option[Path], + override val environmentVariables: Map[String, String], + override val removedEnvironmentVariables: Set[String], + override val outputRedirection: OutputRedirection, + override val runOutputStream: java.io.InputStream => ProxIO[O], + override val errorRedirection: OutputRedirection, + override val runErrorStream: java.io.InputStream => ProxIO[E], + override val inputRedirection: InputRedirection + ) extends Process[O, E] with RedirectableInput[ProcessImplIOE[O, E]] { override type Self = ProcessImplOE[O, E] - override def connectInput(source: InputRedirection): ProcessImplIOE[O, E] = + override def connectInput( + source: InputRedirection + ): ProcessImplIOE[O, E] = ProcessImplIOE( command, arguments, @@ -321,22 +428,35 @@ trait ProcessModule { source ) - override protected def selfCopy(command: String, arguments: List[String], workingDirectory: Option[Path], environmentVariables: Map[String, String], removedEnvironmentVariables: Set[String]): ProcessImplOE[O, E] = - copy(command, arguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + override protected def selfCopy( + command: String, + arguments: List[String], + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): ProcessImplOE[O, E] = + copy( + command, + arguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) } /** Process with bound output streams */ - case class ProcessImplO[O](override val command: String, - override val arguments: List[String], - override val workingDirectory: Option[Path], - override val environmentVariables: Map[String, String], - override val removedEnvironmentVariables: Set[String], - override val outputRedirection: OutputRedirection, - override val runOutputStream: java.io.InputStream => ProxIO[O], - override val errorRedirection: OutputRedirection, - override val runErrorStream: java.io.InputStream => ProxIO[Unit], - override val inputRedirection: InputRedirection) - extends Process[O, Unit] + case class ProcessImplO[O]( + override val command: String, + override val arguments: List[String], + override val workingDirectory: Option[Path], + override val environmentVariables: Map[String, String], + override val removedEnvironmentVariables: Set[String], + override val outputRedirection: OutputRedirection, + override val runOutputStream: java.io.InputStream => ProxIO[O], + override val errorRedirection: OutputRedirection, + override val runErrorStream: java.io.InputStream => ProxIO[Unit], + override val inputRedirection: InputRedirection + ) extends Process[O, Unit] with RedirectableError[ProcessImplOE[O, *]] with RedirectableInput[ProcessImplIO[O]] { @@ -356,7 +476,9 @@ trait ProcessModule { source ) - override def connectError[R <: OutputRedirection, RE](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RE]): ProcessImplOE[O, RE] = + override def connectError[R <: OutputRedirection, RE](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RE] + ): ProcessImplOE[O, RE] = ProcessImplOE( command, arguments, @@ -370,22 +492,35 @@ trait ProcessModule { inputRedirection ) - override protected def selfCopy(command: String, arguments: List[String], workingDirectory: Option[Path], environmentVariables: Map[String, String], removedEnvironmentVariables: Set[String]): ProcessImplO[O] = - copy(command, arguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + override protected def selfCopy( + command: String, + arguments: List[String], + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): ProcessImplO[O] = + copy( + command, + arguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) } /** Process with bound error streams */ - case class ProcessImplE[E](override val command: String, - override val arguments: List[String], - override val workingDirectory: Option[Path], - override val environmentVariables: Map[String, String], - override val removedEnvironmentVariables: Set[String], - override val outputRedirection: OutputRedirection, - override val runOutputStream: java.io.InputStream => ProxIO[Unit], - override val errorRedirection: OutputRedirection, - override val runErrorStream: java.io.InputStream => ProxIO[E], - override val inputRedirection: InputRedirection) - extends Process[Unit, E] + case class ProcessImplE[E]( + override val command: String, + override val arguments: List[String], + override val workingDirectory: Option[Path], + override val environmentVariables: Map[String, String], + override val removedEnvironmentVariables: Set[String], + override val outputRedirection: OutputRedirection, + override val runOutputStream: java.io.InputStream => ProxIO[Unit], + override val errorRedirection: OutputRedirection, + override val runErrorStream: java.io.InputStream => ProxIO[E], + override val inputRedirection: InputRedirection + ) extends Process[Unit, E] with RedirectableInput[ProcessImplIE[E]] with RedirectableOutput[ProcessImplOE[*, E]] { @@ -405,7 +540,9 @@ trait ProcessModule { source ) - override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RO]): ProcessImplOE[RO, E] = + override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RO] + ): ProcessImplOE[RO, E] = ProcessImplOE( command, arguments, @@ -419,28 +556,43 @@ trait ProcessModule { inputRedirection ) - override protected def selfCopy(command: String, arguments: List[String], workingDirectory: Option[Path], environmentVariables: Map[String, String], removedEnvironmentVariables: Set[String]): ProcessImplE[E] = - copy(command, arguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + override protected def selfCopy( + command: String, + arguments: List[String], + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): ProcessImplE[E] = + copy( + command, + arguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) } /** Process with bound input streams */ - case class ProcessImplI(override val command: String, - override val arguments: List[String], - override val workingDirectory: Option[Path], - override val environmentVariables: Map[String, String], - override val removedEnvironmentVariables: Set[String], - override val outputRedirection: OutputRedirection, - override val runOutputStream: java.io.InputStream => ProxIO[Unit], - override val errorRedirection: OutputRedirection, - override val runErrorStream: java.io.InputStream => ProxIO[Unit], - override val inputRedirection: InputRedirection) - extends Process[Unit, Unit] + case class ProcessImplI( + override val command: String, + override val arguments: List[String], + override val workingDirectory: Option[Path], + override val environmentVariables: Map[String, String], + override val removedEnvironmentVariables: Set[String], + override val outputRedirection: OutputRedirection, + override val runOutputStream: java.io.InputStream => ProxIO[Unit], + override val errorRedirection: OutputRedirection, + override val runErrorStream: java.io.InputStream => ProxIO[Unit], + override val inputRedirection: InputRedirection + ) extends Process[Unit, Unit] with RedirectableOutput[ProcessImplIO[*]] with RedirectableError[ProcessImplIE[*]] { override type Self = ProcessImplI - def connectOutput[R <: OutputRedirection, RO](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RO]): ProcessImplIO[RO] = + def connectOutput[R <: OutputRedirection, RO](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RO] + ): ProcessImplIO[RO] = ProcessImplIO( command, arguments, @@ -454,7 +606,9 @@ trait ProcessModule { inputRedirection ) - override def connectError[R <: OutputRedirection, RE](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RE]): ProcessImplIE[RE] = + override def connectError[R <: OutputRedirection, RE](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RE] + ): ProcessImplIE[RE] = ProcessImplIE( command, arguments, @@ -468,29 +622,44 @@ trait ProcessModule { inputRedirection ) - override protected def selfCopy(command: String, arguments: List[String], workingDirectory: Option[Path], environmentVariables: Map[String, String], removedEnvironmentVariables: Set[String]): ProcessImplI = - copy(command, arguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + override protected def selfCopy( + command: String, + arguments: List[String], + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): ProcessImplI = + copy( + command, + arguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) } /** Process with no streams bound */ - case class ProcessImpl(override val command: String, - override val arguments: List[String], - override val workingDirectory: Option[Path], - override val environmentVariables: Map[String, String], - override val removedEnvironmentVariables: Set[String], - override val outputRedirection: OutputRedirection, - override val runOutputStream: java.io.InputStream => ProxIO[Unit], - override val errorRedirection: OutputRedirection, - override val runErrorStream: java.io.InputStream => ProxIO[Unit], - override val inputRedirection: InputRedirection) - extends Process[Unit, Unit] + case class ProcessImpl( + override val command: String, + override val arguments: List[String], + override val workingDirectory: Option[Path], + override val environmentVariables: Map[String, String], + override val removedEnvironmentVariables: Set[String], + override val outputRedirection: OutputRedirection, + override val runOutputStream: java.io.InputStream => ProxIO[Unit], + override val errorRedirection: OutputRedirection, + override val runErrorStream: java.io.InputStream => ProxIO[Unit], + override val inputRedirection: InputRedirection + ) extends Process[Unit, Unit] with RedirectableOutput[ProcessImplO[*]] with RedirectableError[ProcessImplE[*]] with RedirectableInput[ProcessImplI] { override type Self = ProcessImpl - def connectOutput[R <: OutputRedirection, RO](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RO]): ProcessImplO[RO] = + def connectOutput[R <: OutputRedirection, RO](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RO] + ): ProcessImplO[RO] = ProcessImplO( command, arguments, @@ -504,7 +673,9 @@ trait ProcessModule { inputRedirection ) - override def connectError[R <: OutputRedirection, RE](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RE]): ProcessImplE[RE] = + override def connectError[R <: OutputRedirection, RE](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RE] + ): ProcessImplE[RE] = ProcessImplE( command, arguments, @@ -532,27 +703,44 @@ trait ProcessModule { source ) - override protected def selfCopy(command: String, arguments: List[String], workingDirectory: Option[Path], environmentVariables: Map[String, String], removedEnvironmentVariables: Set[String]): ProcessImpl = - copy(command, arguments, workingDirectory, environmentVariables, removedEnvironmentVariables) + override protected def selfCopy( + command: String, + arguments: List[String], + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): ProcessImpl = + copy( + command, + arguments, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) } - /** - * Defines a process to be executed + /** Defines a process to be executed * - * The process by default uses the standard input, output and error streams. + * The process by default uses the standard input, output and error + * streams. * - * @param command Command to be executed - * @param arguments Arguments for the command - * @return the process specification + * @param command + * Command to be executed + * @param arguments + * Arguments for the command + * @return + * the process specification */ - def apply(command: String, arguments: List[String] = List.empty): ProcessImpl = + def apply( + command: String, + arguments: List[String] = List.empty + ): ProcessImpl = ProcessImpl( command, arguments, workingDirectory = None, environmentVariables = Map.empty, removedEnvironmentVariables = Set.empty, - outputRedirection = StdOut(), runOutputStream = _ => unit, errorRedirection = StdOut(), @@ -561,4 +749,4 @@ trait ProcessModule { ) } -} \ No newline at end of file +} diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/processgroup.scala b/prox-core/src/main/scala/io/github/vigoo/prox/processgroup.scala index 88178a6e..61a7eef8 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/processgroup.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/processgroup.scala @@ -5,35 +5,44 @@ import java.nio.file.Path trait ProcessGroupModule { this: Prox => - /** - * Result of an executed process group + /** Result of an executed process group * - * @tparam O Output type - * @tparam E Error output type + * @tparam O + * Output type + * @tparam E + * Error output type */ trait ProcessGroupResult[+O, +E] { - /** Per-process exit codes. The key is the original process passed to the piping operator */ + + /** Per-process exit codes. The key is the original process passed to the + * piping operator + */ val exitCodes: Map[Process[Unit, Unit], ProxExitCode] /** Output of the last process in the group */ val output: O - /** Per-process error outputs. The key is the original process passed to the piping operator */ + /** Per-process error outputs. The key is the original process passed to the + * piping operator + */ val errors: Map[Process[Unit, Unit], E] } /** Default implementation of [[ProcessGroupResult]] */ - case class SimpleProcessGroupResult[+O, +E](override val exitCodes: Map[Process[Unit, Unit], ProxExitCode], - override val output: O, - override val errors: Map[Process[Unit, Unit], E]) - extends ProcessGroupResult[O, E] + case class SimpleProcessGroupResult[+O, +E]( + override val exitCodes: Map[Process[Unit, Unit], ProxExitCode], + override val output: O, + override val errors: Map[Process[Unit, Unit], E] + ) extends ProcessGroupResult[O, E] - /** - * Representation of a running process group + /** Representation of a running process group * - * @tparam O Output type - * @tparam E Error output type - * @tparam Info Runner-specific per-process information type + * @tparam O + * Output type + * @tparam E + * Error output type + * @tparam Info + * Runner-specific per-process information type */ trait RunningProcessGroup[O, E, +Info] { val runningOutput: ProxFiber[O] @@ -41,7 +50,8 @@ trait ProcessGroupModule { /** Runner-specific information about each running process */ val info: Map[Process[Unit, Unit], Info] - /** Forcibly terminates all processes in the group. Blocks until it is done. */ + /** Forcibly terminates all processes in the group. Blocks until it is done. + */ def kill(): ProxIO[ProcessGroupResult[O, E]] /** Terminates all processes in the group. Blocks until it is done. */ @@ -50,78 +60,106 @@ trait ProcessGroupModule { /** Blocks until the processes finish running */ def waitForExit(): ProxIO[ProcessGroupResult[O, E]] - def mapInfo[I2](f: (Process[Unit, Unit], Info) => I2): RunningProcessGroup[O, E, I2] = + def mapInfo[I2]( + f: (Process[Unit, Unit], Info) => I2 + ): RunningProcessGroup[O, E, I2] = new RunningProcessGroup[O, E, I2] { - override val runningOutput: ProxFiber[O] = RunningProcessGroup.this.runningOutput - override val info: Map[Process[Unit, Unit], I2] = RunningProcessGroup.this.info.map { case (key, value) => - key -> f(key, value) - } + override val runningOutput: ProxFiber[O] = + RunningProcessGroup.this.runningOutput + override val info: Map[Process[Unit, Unit], I2] = + RunningProcessGroup.this.info.map { case (key, value) => + key -> f(key, value) + } - override def kill(): ProxIO[ProcessGroupResult[O, E]] = RunningProcessGroup.this.kill() + override def kill(): ProxIO[ProcessGroupResult[O, E]] = + RunningProcessGroup.this.kill() - override def terminate(): ProxIO[ProcessGroupResult[O, E]] = RunningProcessGroup.this.terminate() + override def terminate(): ProxIO[ProcessGroupResult[O, E]] = + RunningProcessGroup.this.terminate() - override def waitForExit(): ProxIO[ProcessGroupResult[O, E]] = RunningProcessGroup.this.waitForExit() + override def waitForExit(): ProxIO[ProcessGroupResult[O, E]] = + RunningProcessGroup.this.waitForExit() } } - /** - * Process group is two or more processes attached to each other + /** Process group is two or more processes attached to each other * - * This implements a pipeline of processes. The input of the first process and the output of the last - * process is redirectable with the [[RedirectableInput]] and [[RedirectableOutput]] traits. The processes - * are attached to each other's input/output streams, the pipe between them is customizable. + * This implements a pipeline of processes. The input of the first process + * and the output of the last process is redirectable with the + * [[RedirectableInput]] and [[RedirectableOutput]] traits. The processes are + * attached to each other's input/output streams, the pipe between them is + * customizable. * - * The error streams are also redirectable with the [[RedirectableErrors]] trait. + * The error streams are also redirectable with the [[RedirectableErrors]] + * trait. * - * @tparam O Output type - * @tparam E Error output type + * @tparam O + * Output type + * @tparam E + * Error output type */ - trait ProcessGroup[O, E] extends ProcessLike with ProcessGroupConfiguration[O, E] { + trait ProcessGroup[O, E] + extends ProcessLike + with ProcessGroupConfiguration[O, E] { val firstProcess: Process[ProxStream[Byte], E] val innerProcesses: List[Process.UnboundIProcess[ProxStream[Byte], E]] val lastProcess: Process.UnboundIProcess[O, E] val originalProcesses: List[Process[Unit, Unit]] - /** - * Starts the process group asynchronously and returns the [[RunningProcessGroup]] interface for it + /** Starts the process group asynchronously and returns the + * [[RunningProcessGroup]] interface for it * - * This is the most advanced way to start process groups. See [[start]] and [[run]] as alternatives. + * This is the most advanced way to start process groups. See [[start]] and + * [[run]] as alternatives. * - * @param runner The process runner to be used - * @tparam Info The runner-specific information about the started processes - * @return interface for handling the running process group + * @param runner + * The process runner to be used + * @tparam Info + * The runner-specific information about the started processes + * @return + * interface for handling the running process group */ - def startProcessGroup[Info]()(implicit runner: ProcessRunner[Info]): ProxIO[RunningProcessGroup[O, E, Info]] = + def startProcessGroup[Info]()(implicit + runner: ProcessRunner[Info] + ): ProxIO[RunningProcessGroup[O, E, Info]] = runner.startProcessGroup(this) - /** - * Starts the process group asynchronously and returns a closeable fiber representing it + /** Starts the process group asynchronously and returns a closeable fiber + * representing it * - * Joining the fiber waits for the processes to be terminated. Canceling the fiber terminates - * the processesnormally (with SIGTERM). + * Joining the fiber waits for the processes to be terminated. Canceling + * the fiber terminates the processesnormally (with SIGTERM). * - * @param runner The process runner to be used - * @return a managed fiber representing the running processes + * @param runner + * The process runner to be used + * @return + * a managed fiber representing the running processes */ - def start[Info]()(implicit runner: ProcessRunner[Info]): ProxResource[ProxFiber[ProcessGroupResult[O, E]]] = + def start[Info]()(implicit + runner: ProcessRunner[Info] + ): ProxResource[ProxFiber[ProcessGroupResult[O, E]]] = runner.start(this) - /** - * Starts the process group asynchronously and blocks the execution until it is finished + /** Starts the process group asynchronously and blocks the execution until + * it is finished * - * @param runner The process runner to be used - * @return the result of the finished processes + * @param runner + * The process runner to be used + * @return + * the result of the finished processes */ - def run[Info]()(implicit runner: ProcessRunner[Info]): ProxIO[ProcessGroupResult[O, E]] = + def run[Info]()(implicit + runner: ProcessRunner[Info] + ): ProxIO[ProcessGroupResult[O, E]] = start().use(_.join) - /** - * Applies the given mapper to each process in the group + /** Applies the given mapper to each process in the group * - * @param f process mapper - * @return a new process group with all the processes altered by the mapper + * @param f + * process mapper + * @return + * a new process group with all the processes altered by the mapper */ def map(f: ProcessGroup.Mapper[O, E]): Self } @@ -150,44 +188,84 @@ trait ProcessGroupModule { allProcesses.map(_.removedEnvironmentVariables).reduce(_ intersect _) } - override protected def applyConfiguration(workingDirectory: Option[Path], environmentVariables: Map[String, String], removedEnvironmentVariables: Set[String]): Self = + override protected def applyConfiguration( + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): Self = map(new ProcessGroup.Mapper[O, E] { - override def mapFirst[P <: Process[ProxStream[Byte], E]](process: P): P = - ConfigApplication[P](process, workingDirectory, environmentVariables, removedEnvironmentVariables) - - override def mapInnerWithIdx[P <: Process.UnboundIProcess[ProxStream[Byte], E]](process: P, idx: Int): P = - ConfigApplication[P](process, workingDirectory, environmentVariables, removedEnvironmentVariables) - - override def mapLast[P <: Process.UnboundIProcess[O, E]](process: P): P = - ConfigApplication[P](process, workingDirectory, environmentVariables, removedEnvironmentVariables) + override def mapFirst[P <: Process[ProxStream[Byte], E]]( + process: P + ): P = + ConfigApplication[P]( + process, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) + + override def mapInnerWithIdx[ + P <: Process.UnboundIProcess[ProxStream[Byte], E] + ](process: P, idx: Int): P = + ConfigApplication[P]( + process, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) + + override def mapLast[P <: Process.UnboundIProcess[O, E]]( + process: P + ): P = + ConfigApplication[P]( + process, + workingDirectory, + environmentVariables, + removedEnvironmentVariables + ) }) class ConfigApplication[P <: ProcessLikeConfiguration] { // NOTE: Unfortunately we have no proof that P#Self == P so we cast - private def applyWorkingDirectory(workingDirectory: Option[Path])(process: P): P = + private def applyWorkingDirectory( + workingDirectory: Option[Path] + )(process: P): P = workingDirectory match { case Some(path) => (process in path).asInstanceOf[P] case None => process.inInheritedWorkingDirectory().asInstanceOf[P] } - private def addEnvironmentVariables(environmentVariables: Seq[(String, String)])(process: P): P = - environmentVariables.foldLeft(process) { case (proc, pair) => (proc `with` pair).asInstanceOf[P] } + private def addEnvironmentVariables( + environmentVariables: Seq[(String, String)] + )(process: P): P = + environmentVariables.foldLeft(process) { case (proc, pair) => + (proc `with` pair).asInstanceOf[P] + } - private def removeEnvironmentVariables(environmentVariables: Seq[String])(process: P): P = - environmentVariables.foldLeft(process) { case (proc, name) => (proc without name).asInstanceOf[P] } + private def removeEnvironmentVariables( + environmentVariables: Seq[String] + )(process: P): P = + environmentVariables.foldLeft(process) { case (proc, name) => + (proc without name).asInstanceOf[P] + } - def apply(process: P, - workingDirectory: Option[Path], - environmentVariables: Map[String, String], - removedEnvironmentVariables: Set[String]): P = + def apply( + process: P, + workingDirectory: Option[Path], + environmentVariables: Map[String, String], + removedEnvironmentVariables: Set[String] + ): P = (applyWorkingDirectory(workingDirectory) _ compose addEnvironmentVariables(environmentVariables.toSeq) compose - removeEnvironmentVariables(removedEnvironmentVariables.toSeq)) (process) + removeEnvironmentVariables(removedEnvironmentVariables.toSeq))( + process + ) } object ConfigApplication { - def apply[P <: ProcessLikeConfiguration]: ConfigApplication[P] = new ConfigApplication[P] + def apply[P <: ProcessLikeConfiguration]: ConfigApplication[P] = + new ConfigApplication[P] } } @@ -198,24 +276,32 @@ trait ProcessGroupModule { trait Mapper[O, E] { def mapFirst[P <: Process[ProxStream[Byte], E]](process: P): P - def mapInnerWithIdx[P <: Process.UnboundIProcess[ProxStream[Byte], E]](process: P, idx: Int): P + def mapInnerWithIdx[P <: Process.UnboundIProcess[ProxStream[Byte], E]]( + process: P, + idx: Int + ): P def mapLast[P <: Process.UnboundIProcess[O, E]](process: P): P } /** Process group with bound input, output and error streams */ - case class ProcessGroupImplIOE[O, E](override val firstProcess: Process[ProxStream[Byte], E], - override val innerProcesses: List[Process.UnboundIProcess[ProxStream[Byte], E]], - override val lastProcess: Process.UnboundIProcess[O, E], - override val originalProcesses: List[Process[Unit, Unit]]) - extends ProcessGroup[O, E] { + case class ProcessGroupImplIOE[O, E]( + override val firstProcess: Process[ProxStream[Byte], E], + override val innerProcesses: List[ + Process.UnboundIProcess[ProxStream[Byte], E] + ], + override val lastProcess: Process.UnboundIProcess[O, E], + override val originalProcesses: List[Process[Unit, Unit]] + ) extends ProcessGroup[O, E] { override type Self = ProcessGroupImplIOE[O, E] def map(f: ProcessGroup.Mapper[O, E]): ProcessGroupImplIOE[O, E] = { copy( firstProcess = f.mapFirst(this.firstProcess), - innerProcesses = this.innerProcesses.zipWithIndex.map { case (p, idx) => f.mapInnerWithIdx(p, idx + 1) }, + innerProcesses = this.innerProcesses.zipWithIndex.map { + case (p, idx) => f.mapInnerWithIdx(p, idx + 1) + }, lastProcess = f.mapLast(this.lastProcess), originalProcesses ) @@ -223,11 +309,14 @@ trait ProcessGroupModule { } /** Process group with bound input and output streams */ - case class ProcessGroupImplIO[O](override val firstProcess: Process.UnboundEProcess[ProxStream[Byte]], - override val innerProcesses: List[Process.UnboundIEProcess[ProxStream[Byte]]], - override val lastProcess: Process.UnboundIEProcess[O], - override val originalProcesses: List[Process[Unit, Unit]]) - extends ProcessGroup[O, Unit] + case class ProcessGroupImplIO[O]( + override val firstProcess: Process.UnboundEProcess[ProxStream[Byte]], + override val innerProcesses: List[ + Process.UnboundIEProcess[ProxStream[Byte]] + ], + override val lastProcess: Process.UnboundIEProcess[O], + override val originalProcesses: List[Process[Unit, Unit]] + ) extends ProcessGroup[O, Unit] with RedirectableErrors[ProcessGroupImplIOE[O, *]] { override type Self = ProcessGroupImplIO[O] @@ -235,31 +324,52 @@ trait ProcessGroupModule { def map(f: ProcessGroup.Mapper[O, Unit]): ProcessGroupImplIO[O] = { copy( firstProcess = f.mapFirst(this.firstProcess), - innerProcesses = this.innerProcesses.zipWithIndex.map { case (p, idx) => f.mapInnerWithIdx(p, idx + 1) }, + innerProcesses = this.innerProcesses.zipWithIndex.map { + case (p, idx) => f.mapInnerWithIdx(p, idx + 1) + }, lastProcess = f.mapLast(this.lastProcess), originalProcesses ) } - override def connectErrors[R <: GroupErrorRedirection, OR <: OutputRedirection, E](target: R) - (implicit groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], - outputRedirectionType: OutputRedirectionType.Aux[OR, E]): ProcessGroupImplIOE[O, E] = { + override def connectErrors[ + R <: GroupErrorRedirection, + OR <: OutputRedirection, + E + ](target: R)(implicit + groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], + outputRedirectionType: OutputRedirectionType.Aux[OR, E] + ): ProcessGroupImplIOE[O, E] = { val origs = originalProcesses.reverse.toVector ProcessGroupImplIOE( - firstProcess.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs.head)), - innerProcesses.zipWithIndex.map { case (p, idx) => p.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs(idx + 1))) }, - lastProcess.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs.last)), + firstProcess.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs.head) + ), + innerProcesses.zipWithIndex.map { case (p, idx) => + p.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs(idx + 1)) + ) + }, + lastProcess.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs.last) + ), originalProcesses ) } } /** Process group with bound input and error streams */ - case class ProcessGroupImplIE[E](override val firstProcess: Process[ProxStream[Byte], E], - override val innerProcesses: List[Process.UnboundIProcess[ProxStream[Byte], E]], - override val lastProcess: Process.UnboundIOProcess[E], - override val originalProcesses: List[Process[Unit, Unit]]) - extends ProcessGroup[Unit, E] + case class ProcessGroupImplIE[E]( + override val firstProcess: Process[ProxStream[Byte], E], + override val innerProcesses: List[ + Process.UnboundIProcess[ProxStream[Byte], E] + ], + override val lastProcess: Process.UnboundIOProcess[E], + override val originalProcesses: List[Process[Unit, Unit]] + ) extends ProcessGroup[Unit, E] with RedirectableOutput[ProcessGroupImplIOE[*, E]] { override type Self = ProcessGroupImplIE[E] @@ -267,13 +377,17 @@ trait ProcessGroupModule { def map(f: ProcessGroup.Mapper[Unit, E]): ProcessGroupImplIE[E] = { copy( firstProcess = f.mapFirst(this.firstProcess), - innerProcesses = this.innerProcesses.zipWithIndex.map { case (p, idx) => f.mapInnerWithIdx(p, idx + 1) }, + innerProcesses = this.innerProcesses.zipWithIndex.map { + case (p, idx) => f.mapInnerWithIdx(p, idx + 1) + }, lastProcess = f.mapLast(this.lastProcess), originalProcesses ) } - override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RO]): ProcessGroupImplIOE[RO, E] = { + override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RO] + ): ProcessGroupImplIOE[RO, E] = { ProcessGroupImplIOE( firstProcess, innerProcesses, @@ -284,11 +398,14 @@ trait ProcessGroupModule { } /** Process group with bound output and error streams */ - case class ProcessGroupImplOE[O, E](override val firstProcess: Process.UnboundIProcess[ProxStream[Byte], E], - override val innerProcesses: List[Process.UnboundIProcess[ProxStream[Byte], E]], - override val lastProcess: Process.UnboundIProcess[O, E], - override val originalProcesses: List[Process[Unit, Unit]]) - extends ProcessGroup[O, E] + case class ProcessGroupImplOE[O, E]( + override val firstProcess: Process.UnboundIProcess[ProxStream[Byte], E], + override val innerProcesses: List[ + Process.UnboundIProcess[ProxStream[Byte], E] + ], + override val lastProcess: Process.UnboundIProcess[O, E], + override val originalProcesses: List[Process[Unit, Unit]] + ) extends ProcessGroup[O, E] with RedirectableInput[ProcessGroupImplIOE[O, E]] { override type Self = ProcessGroupImplOE[O, E] @@ -296,13 +413,17 @@ trait ProcessGroupModule { def map(f: ProcessGroup.Mapper[O, E]): ProcessGroupImplOE[O, E] = { copy( firstProcess = f.mapFirst(this.firstProcess), - innerProcesses = this.innerProcesses.zipWithIndex.map { case (p, idx) => f.mapInnerWithIdx(p, idx + 1) }, + innerProcesses = this.innerProcesses.zipWithIndex.map { + case (p, idx) => f.mapInnerWithIdx(p, idx + 1) + }, lastProcess = f.mapLast(this.lastProcess), originalProcesses ) } - override def connectInput(source: InputRedirection): ProcessGroupImplIOE[O, E] = { + override def connectInput( + source: InputRedirection + ): ProcessGroupImplIOE[O, E] = { ProcessGroupImplIOE( firstProcess.connectInput(source), innerProcesses, @@ -313,11 +434,14 @@ trait ProcessGroupModule { } /** Process group with bound input stream */ - case class ProcessGroupImplI(override val firstProcess: Process.UnboundEProcess[ProxStream[Byte]], - override val innerProcesses: List[Process.UnboundIEProcess[ProxStream[Byte]]], - override val lastProcess: Process.UnboundProcess, - override val originalProcesses: List[Process[Unit, Unit]]) - extends ProcessGroup[Unit, Unit] + case class ProcessGroupImplI( + override val firstProcess: Process.UnboundEProcess[ProxStream[Byte]], + override val innerProcesses: List[ + Process.UnboundIEProcess[ProxStream[Byte]] + ], + override val lastProcess: Process.UnboundProcess, + override val originalProcesses: List[Process[Unit, Unit]] + ) extends ProcessGroup[Unit, Unit] with RedirectableOutput[ProcessGroupImplIO[*]] with RedirectableErrors[ProcessGroupImplIE[*]] { @@ -326,13 +450,17 @@ trait ProcessGroupModule { def map(f: ProcessGroup.Mapper[Unit, Unit]): ProcessGroupImplI = { copy( firstProcess = f.mapFirst(this.firstProcess), - innerProcesses = this.innerProcesses.zipWithIndex.map { case (p, idx) => f.mapInnerWithIdx(p, idx + 1) }, + innerProcesses = this.innerProcesses.zipWithIndex.map { + case (p, idx) => f.mapInnerWithIdx(p, idx + 1) + }, lastProcess = f.mapLast(this.lastProcess), originalProcesses ) } - override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RO]): ProcessGroupImplIO[RO] = { + override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RO] + ): ProcessGroupImplIO[RO] = { ProcessGroupImplIO( firstProcess, innerProcesses, @@ -341,25 +469,44 @@ trait ProcessGroupModule { ) } - override def connectErrors[R <: GroupErrorRedirection, OR <: OutputRedirection, E](target: R) - (implicit groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], - outputRedirectionType: OutputRedirectionType.Aux[OR, E]): ProcessGroupImplIE[E] = { + override def connectErrors[ + R <: GroupErrorRedirection, + OR <: OutputRedirection, + E + ](target: R)(implicit + groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], + outputRedirectionType: OutputRedirectionType.Aux[OR, E] + ): ProcessGroupImplIE[E] = { val origs = originalProcesses.reverse.toVector ProcessGroupImplIE( - firstProcess.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs.head)), - innerProcesses.zipWithIndex.map { case (p, idx) => p.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs(idx + 1))) }, - lastProcess.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs.last)), + firstProcess.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs.head) + ), + innerProcesses.zipWithIndex.map { case (p, idx) => + p.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs(idx + 1)) + ) + }, + lastProcess.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs.last) + ), originalProcesses ) } } /** Process group with bound output stream */ - case class ProcessGroupImplO[O](override val firstProcess: Process.UnboundIEProcess[ProxStream[Byte]], - override val innerProcesses: List[Process.UnboundIEProcess[ProxStream[Byte]]], - override val lastProcess: Process.UnboundIEProcess[O], - override val originalProcesses: List[Process[Unit, Unit]]) - extends ProcessGroup[O, Unit] + case class ProcessGroupImplO[O]( + override val firstProcess: Process.UnboundIEProcess[ProxStream[Byte]], + override val innerProcesses: List[ + Process.UnboundIEProcess[ProxStream[Byte]] + ], + override val lastProcess: Process.UnboundIEProcess[O], + override val originalProcesses: List[Process[Unit, Unit]] + ) extends ProcessGroup[O, Unit] with RedirectableInput[ProcessGroupImplIO[O]] with RedirectableErrors[ProcessGroupImplOE[O, *]] { @@ -368,13 +515,17 @@ trait ProcessGroupModule { def map(f: ProcessGroup.Mapper[O, Unit]): ProcessGroupImplO[O] = { copy( firstProcess = f.mapFirst(this.firstProcess), - innerProcesses = this.innerProcesses.zipWithIndex.map { case (p, idx) => f.mapInnerWithIdx(p, idx + 1) }, + innerProcesses = this.innerProcesses.zipWithIndex.map { + case (p, idx) => f.mapInnerWithIdx(p, idx + 1) + }, lastProcess = f.mapLast(this.lastProcess), originalProcesses ) } - override def connectInput(source: InputRedirection): ProcessGroupImplIO[O] = { + override def connectInput( + source: InputRedirection + ): ProcessGroupImplIO[O] = { ProcessGroupImplIO( firstProcess.connectInput(source), innerProcesses, @@ -383,25 +534,44 @@ trait ProcessGroupModule { ) } - override def connectErrors[R <: GroupErrorRedirection, OR <: OutputRedirection, E](target: R) - (implicit groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], - outputRedirectionType: OutputRedirectionType.Aux[OR, E]): ProcessGroupImplOE[O, E] = { + override def connectErrors[ + R <: GroupErrorRedirection, + OR <: OutputRedirection, + E + ](target: R)(implicit + groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], + outputRedirectionType: OutputRedirectionType.Aux[OR, E] + ): ProcessGroupImplOE[O, E] = { val origs = originalProcesses.reverse.toVector ProcessGroupImplOE( - firstProcess.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs.head)), - innerProcesses.zipWithIndex.map { case (p, idx) => p.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs(idx + 1))) }, - lastProcess.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs.last)), + firstProcess.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs.head) + ), + innerProcesses.zipWithIndex.map { case (p, idx) => + p.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs(idx + 1)) + ) + }, + lastProcess.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs.last) + ), originalProcesses ) } } /** Process group with bound error stream */ - case class ProcessGroupImplE[E](override val firstProcess: Process.UnboundIProcess[ProxStream[Byte], E], - override val innerProcesses: List[Process.UnboundIProcess[ProxStream[Byte], E]], - override val lastProcess: Process.UnboundIOProcess[E], - override val originalProcesses: List[Process[Unit, Unit]]) - extends ProcessGroup[Unit, E] + case class ProcessGroupImplE[E]( + override val firstProcess: Process.UnboundIProcess[ProxStream[Byte], E], + override val innerProcesses: List[ + Process.UnboundIProcess[ProxStream[Byte], E] + ], + override val lastProcess: Process.UnboundIOProcess[E], + override val originalProcesses: List[Process[Unit, Unit]] + ) extends ProcessGroup[Unit, E] with RedirectableOutput[ProcessGroupImplOE[*, E]] with RedirectableInput[ProcessGroupImplIE[E]] { @@ -410,13 +580,17 @@ trait ProcessGroupModule { def map(f: ProcessGroup.Mapper[Unit, E]): ProcessGroupImplE[E] = { copy( firstProcess = f.mapFirst(this.firstProcess), - innerProcesses = this.innerProcesses.zipWithIndex.map { case (p, idx) => f.mapInnerWithIdx(p, idx + 1) }, + innerProcesses = this.innerProcesses.zipWithIndex.map { + case (p, idx) => f.mapInnerWithIdx(p, idx + 1) + }, lastProcess = f.mapLast(this.lastProcess), originalProcesses ) } - override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RO]): ProcessGroupImplOE[RO, E] = { + override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RO] + ): ProcessGroupImplOE[RO, E] = { ProcessGroupImplOE( firstProcess, innerProcesses, @@ -425,7 +599,9 @@ trait ProcessGroupModule { ) } - override def connectInput(source: InputRedirection): ProcessGroupImplIE[E] = { + override def connectInput( + source: InputRedirection + ): ProcessGroupImplIE[E] = { ProcessGroupImplIE( firstProcess.connectInput(source), innerProcesses, @@ -436,20 +612,30 @@ trait ProcessGroupModule { } /** Process group with unbound input, output and error streams */ - case class ProcessGroupImpl(override val firstProcess: Process.UnboundIEProcess[ProxStream[Byte]], - override val innerProcesses: List[Process.UnboundIEProcess[ProxStream[Byte]]], - override val lastProcess: Process.UnboundProcess, - override val originalProcesses: List[Process[Unit, Unit]]) - extends ProcessGroup[Unit, Unit] + case class ProcessGroupImpl( + override val firstProcess: Process.UnboundIEProcess[ProxStream[Byte]], + override val innerProcesses: List[ + Process.UnboundIEProcess[ProxStream[Byte]] + ], + override val lastProcess: Process.UnboundProcess, + override val originalProcesses: List[Process[Unit, Unit]] + ) extends ProcessGroup[Unit, Unit] with RedirectableOutput[ProcessGroupImplO[*]] with RedirectableInput[ProcessGroupImplI] with RedirectableErrors[ProcessGroupImplE[*]] { override type Self = ProcessGroupImpl - def pipeInto(other: Process.UnboundProcess, - channel: ProxPipe[Byte, Byte]): ProcessGroupImpl = { - val pl1 = lastProcess.connectOutput(OutputStreamThroughPipe(channel, (stream: ProxStream[Byte]) => pure(stream))) + def pipeInto( + other: Process.UnboundProcess, + channel: ProxPipe[Byte, Byte] + ): ProcessGroupImpl = { + val pl1 = lastProcess.connectOutput( + OutputStreamThroughPipe( + channel, + (stream: ProxStream[Byte]) => pure(stream) + ) + ) copy( innerProcesses = pl1 :: innerProcesses, @@ -461,17 +647,26 @@ trait ProcessGroupModule { def |(other: Process.UnboundProcess): ProcessGroupImpl = pipeInto(other, identityPipe) - - def via(channel: ProxPipe[Byte, Byte]): PipeBuilderSyntax[ProcessGroupImpl] = - new PipeBuilderSyntax(new PipeBuilder[ProcessGroupImpl] { - override def build(other: Process.UnboundProcess, channel: ProxPipe[Byte, Byte]): ProcessGroupImpl = - ProcessGroupImpl.this.pipeInto(other, channel) - }, channel) + def via( + channel: ProxPipe[Byte, Byte] + ): PipeBuilderSyntax[ProcessGroupImpl] = + new PipeBuilderSyntax( + new PipeBuilder[ProcessGroupImpl] { + override def build( + other: Process.UnboundProcess, + channel: ProxPipe[Byte, Byte] + ): ProcessGroupImpl = + ProcessGroupImpl.this.pipeInto(other, channel) + }, + channel + ) def map(f: ProcessGroup.Mapper[Unit, Unit]): ProcessGroupImpl = { copy( firstProcess = f.mapFirst(this.firstProcess), - innerProcesses = this.innerProcesses.zipWithIndex.map { case (p, idx) => f.mapInnerWithIdx(p, idx + 1) }, + innerProcesses = this.innerProcesses.zipWithIndex.map { + case (p, idx) => f.mapInnerWithIdx(p, idx + 1) + }, lastProcess = f.mapLast(this.lastProcess), originalProcesses ) @@ -485,19 +680,37 @@ trait ProcessGroupModule { originalProcesses ) - override def connectErrors[R <: GroupErrorRedirection, OR <: OutputRedirection, E](target: R) - (implicit groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], - outputRedirectionType: OutputRedirectionType.Aux[OR, E]): ProcessGroupImplE[E] = { + override def connectErrors[ + R <: GroupErrorRedirection, + OR <: OutputRedirection, + E + ](target: R)(implicit + groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], + outputRedirectionType: OutputRedirectionType.Aux[OR, E] + ): ProcessGroupImplE[E] = { val origs = originalProcesses.reverse.toVector ProcessGroupImplE( - firstProcess.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs.head)), - innerProcesses.zipWithIndex.map { case (p, idx) => p.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs(idx + 1))) }, - lastProcess.connectError(groupErrorRedirectionType.toOutputRedirectionType(target, origs.last)), + firstProcess.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs.head) + ), + innerProcesses.zipWithIndex.map { case (p, idx) => + p.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs(idx + 1)) + ) + }, + lastProcess.connectError( + groupErrorRedirectionType + .toOutputRedirectionType(target, origs.last) + ), originalProcesses ) } - override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, RO]): ProcessGroupImplO[RO] = { + override def connectOutput[R <: OutputRedirection, RO](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, RO] + ): ProcessGroupImplO[RO] = { ProcessGroupImplO( firstProcess, innerProcesses, @@ -509,4 +722,4 @@ trait ProcessGroupModule { } -} \ No newline at end of file +} diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/redirection.scala b/prox-core/src/main/scala/io/github/vigoo/prox/redirection.scala index d0f492a8..801092c3 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/redirection.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/redirection.scala @@ -6,578 +6,788 @@ import java.nio.file.Path trait RedirectionModule { this: Prox => - /** - * The capability to redirect the output of a process or a process group + /** The capability to redirect the output of a process or a process group * - * @tparam P Self type without the [[RedirectableOutput]] capability + * @tparam P + * Self type without the [[RedirectableOutput]] capability */ trait RedirectableOutput[+P[_] <: ProcessLike] { - /** - * The low level operation to attach an output to a process + /** The low level operation to attach an output to a process * - * Use one of the other methods of this trait for convenience. This is the place where the output type gets - * calculated with a helper type class called [[OutputRedirectionType]] which implements the type level + * Use one of the other methods of this trait for convenience. This is the + * place where the output type gets calculated with a helper type class + * called [[OutputRedirectionType]] which implements the type level * computation for figuring out O. * - * @param target Redirection target - * @param outputRedirectionType Helper for dependent output type - * @tparam R Output redirection type - * @tparam O Output type - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param target + * Redirection target + * @param outputRedirectionType + * Helper for dependent output type + * @tparam R + * Output redirection type + * @tparam O + * Output type + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ - def connectOutput[R <: OutputRedirection, O](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, O]): P[O] + def connectOutput[R <: OutputRedirection, O](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, O] + ): P[O] - /** - * Redirects the output to a sink. + /** Redirects the output to a sink. * - * The process output type will be [[Unit]]. - * An alias for [[toSink]] + * The process output type will be [[Unit]]. An alias for [[toSink]] * - * @param sink Target sink - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param sink + * Target sink + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def >(sink: ProxSink[Byte]): P[Unit] = toSink(sink) - /** - * Redirects the output to a sink. + /** Redirects the output to a sink. * - * The process output type will be [[Unit]]. - * An alias for [[>]] + * The process output type will be [[Unit]]. An alias for [[>]] * - * @param sink Target sink - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param sink + * Target sink + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def toSink(sink: ProxSink[Byte]): P[Unit] = connectOutput(OutputStreamToSink(sink)) - /** - * Redirects the output to a pipe and folds its output with a monoid instance. + /** Redirects the output to a pipe and folds its output with a monoid + * instance. * - * The process output type will be the same as the pipe's output type. - * An alias for [[toFoldMonoid]] + * The process output type will be the same as the pipe's output type. An + * alias for [[toFoldMonoid]] * - * @param pipe Target pipe - * @tparam O Output type of the pipe. Must have a monoid instance. - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe. Must have a monoid instance. + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def >#[O: ProxMonoid](pipe: ProxPipe[Byte, O]): P[O] = toFoldMonoid(pipe) - /** - * Redirects the output to a pipe and folds its output with a monoid instance. + /** Redirects the output to a pipe and folds its output with a monoid + * instance. * - * The process output type will be the same as the pipe's output type. - * An alias for [[>#]] + * The process output type will be the same as the pipe's output type. An + * alias for [[>#]] * - * @param pipe Target pipe - * @tparam O Output type of the pipe. Must have a monoid instance. - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe. Must have a monoid instance. + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def toFoldMonoid[O: ProxMonoid](pipe: ProxPipe[Byte, O]): P[O] = - connectOutput(OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.foldMonoid)) - - /** - * Redirects the output to a pipe and collects its output to a vector - * - * The process output type will be a vector of the pipe's output type. - * An alias for [[toVector]] - * - * @param pipe Target pipe - * @tparam O Output type of the pipe - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + connectOutput( + OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.foldMonoid) + ) + + /** Redirects the output to a pipe and collects its output to a vector + * + * The process output type will be a vector of the pipe's output type. An + * alias for [[toVector]] + * + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def >?[O](pipe: ProxPipe[Byte, O]): P[Vector[O]] = toVector(pipe) - /** - * Redirects the output to a pipe and collects its output to a vector + /** Redirects the output to a pipe and collects its output to a vector * - * The process output type will be a vector of the pipe's output type. - * An alias for [[>?]] + * The process output type will be a vector of the pipe's output type. An + * alias for [[>?]] * - * @param pipe Target pipe - * @tparam O Output type of the pipe - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def toVector[O](pipe: ProxPipe[Byte, O]): P[Vector[O]] = - connectOutput(OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.toVector)) + connectOutput( + OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.toVector) + ) - /** - * Redirects the output to a pipe and drains it regardless of its output type. + /** Redirects the output to a pipe and drains it regardless of its output + * type. * * The process output type will be [[Unit]]. * - * @param pipe Target pipe - * @tparam O Output type of the pipe - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def drainOutput[O](pipe: ProxPipe[Byte, O]): P[Unit] = - connectOutput(OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.drain)) + connectOutput( + OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.drain) + ) - /** - * Redirects the output to a pipe and folds it with a custom function. + /** Redirects the output to a pipe and folds it with a custom function. * * The process output type will be R. * - * @param pipe Target pipe - * @param init The initial value for the fold - * @param fn The fold function - * @tparam O Output type of the pipe - * @tparam R Result type of the fold - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param pipe + * Target pipe + * @param init + * The initial value for the fold + * @param fn + * The fold function + * @tparam O + * Output type of the pipe + * @tparam R + * Result type of the fold + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ - def foldOutput[O, R](pipe: ProxPipe[Byte, O], init: R, fn: (R, O) => R): P[R] = - connectOutput(OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.fold(init, (fn)))) + def foldOutput[O, R]( + pipe: ProxPipe[Byte, O], + init: R, + fn: (R, O) => R + ): P[R] = + connectOutput( + OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.fold(init, (fn))) + ) - /** - * Redirects the output to a file natively + /** Redirects the output to a file natively * * An alias for [[toFile]] * - * @param path Target file path - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param path + * Target file path + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def >(path: Path): P[Unit] = toFile(path) - /** - * Redirects the output to a file natively + /** Redirects the output to a file natively * * An alias for [[>]] * - * @param path Target file path - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param path + * Target file path + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def toFile(path: Path): P[Unit] = connectOutput(OutputFile(path, append = false)) - /** - * Redirects the output to a file natively in append mode + /** Redirects the output to a file natively in append mode * * An alias for [[appendToFile]] * - * @param path Target file path - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param path + * Target file path + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def >>(path: Path): P[Unit] = appendToFile(path) - /** - * Redirects the output to a file natively in append mode + /** Redirects the output to a file natively in append mode * * An alias for [[>>]] * - * @param path Target file path - * @return Returns a new process or process group with its output redirected and its output redirection capability removed. + * @param path + * Target file path + * @return + * Returns a new process or process group with its output redirected and + * its output redirection capability removed. */ def appendToFile(path: Path): P[Unit] = connectOutput(OutputFile(path, append = true)) } - /** - * The capability to redirect the error output of a process + /** The capability to redirect the error output of a process * - * @tparam P Self type without the [[RedirectableError]] capability + * @tparam P + * Self type without the [[RedirectableError]] capability */ trait RedirectableError[+P[_] <: Process[_, _]] { - /** - * The low level operation to attach an error output to a process + + /** The low level operation to attach an error output to a process * - * Use one of the other methods of this trait for convenience. This is the place where the output type gets - * calculated with a helper type class called [[OutputRedirectionType]] which implements the type level + * Use one of the other methods of this trait for convenience. This is the + * place where the output type gets calculated with a helper type class + * called [[OutputRedirectionType]] which implements the type level * computation for figuring out E. * - * @param target Redirection target - * @param outputRedirectionType Helper for dependent error output type - * @tparam R Error output redirection type - * @tparam E Error output type - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param target + * Redirection target + * @param outputRedirectionType + * Helper for dependent error output type + * @tparam R + * Error output redirection type + * @tparam E + * Error output type + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ - def connectError[R <: OutputRedirection, E](target: R)(implicit outputRedirectionType: OutputRedirectionType.Aux[R, E]): P[E] + def connectError[R <: OutputRedirection, E](target: R)(implicit + outputRedirectionType: OutputRedirectionType.Aux[R, E] + ): P[E] - /** - * Redirects the error output to a sink. + /** Redirects the error output to a sink. * - * The process error output type will be [[Unit]]. - * An alias for [[errorToSink]] + * The process error output type will be [[Unit]]. An alias for + * [[errorToSink]] * - * @param sink Target sink - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param sink + * Target sink + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def !>(sink: ProxSink[Byte]): P[Unit] = errorToSink(sink) - /** - * Redirects the error output to a sink. + /** Redirects the error output to a sink. * - * The process error output type will be [[Unit]]. - * An alias for [[!>]] + * The process error output type will be [[Unit]]. An alias for [[!>]] * - * @param sink Target sink - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param sink + * Target sink + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def errorToSink(sink: ProxSink[Byte]): P[Unit] = connectError(OutputStreamToSink(sink)) - /** - * Redirects the error output to a pipe and folds its output with a monoid instance. + /** Redirects the error output to a pipe and folds its output with a monoid + * instance. * - * The process error output type will be the same as the pipe's output type. - * An alias for [[errorToFoldMonoid]] + * The process error output type will be the same as the pipe's output + * type. An alias for [[errorToFoldMonoid]] * - * @param pipe Target pipe - * @tparam O Output type of the pipe. Must have a monoid instance. - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe. Must have a monoid instance. + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def !>#[O: ProxMonoid](pipe: ProxPipe[Byte, O]): P[O] = errorToFoldMonoid(pipe) - /** - * Redirects the error output to a pipe and folds its output with a monoid instance. + /** Redirects the error output to a pipe and folds its output with a monoid + * instance. * - * The process error output type will be the same as the pipe's output type. - * An alias for [[!>#]] + * The process error output type will be the same as the pipe's output + * type. An alias for [[!>#]] * - * @param pipe Target pipe - * @tparam O Output type of the pipe. Must have a monoid instance. - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe. Must have a monoid instance. + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def errorToFoldMonoid[O: ProxMonoid](pipe: ProxPipe[Byte, O]): P[O] = - connectError(OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.foldMonoid)) - - /** - * Redirects the error output to a pipe and collects its output to a vector - * - * The process error output type will be a vector of the pipe's output type. - * An alias for [[errorToVector]] - * - * @param pipe Target pipe - * @tparam O Output type of the pipe - * @return Returns a new process with its error output redirected and its error redirection capability removed. + connectError( + OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.foldMonoid) + ) + + /** Redirects the error output to a pipe and collects its output to a vector + * + * The process error output type will be a vector of the pipe's output + * type. An alias for [[errorToVector]] + * + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def !>?[O](pipe: ProxPipe[Byte, O]): P[Vector[O]] = errorToVector(pipe) - /** - * Redirects the error output to a pipe and collects its output to a vector + /** Redirects the error output to a pipe and collects its output to a vector * - * The process error output type will be a vector of the pipe's output type. - * An alias for [[!>?]] + * The process error output type will be a vector of the pipe's output + * type. An alias for [[!>?]] * - * @param pipe Target pipe - * @tparam O Output type of the pipe - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def errorToVector[O](pipe: ProxPipe[Byte, O]): P[Vector[O]] = - connectError(OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.toVector)) + connectError( + OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.toVector) + ) - /** - * Redirects the error output to a pipe and drains it regardless of its output type. + /** Redirects the error output to a pipe and drains it regardless of its + * output type. * * The process error output type will be [[Unit]]. * - * @param pipe Target pipe - * @tparam O Output type of the pipe - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def drainError[O](pipe: ProxPipe[Byte, O]): P[Unit] = connectError(OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.drain)) - /** - * Redirects the error output to a pipe and folds it with a custom function. + /** Redirects the error output to a pipe and folds it with a custom + * function. * * The process error output type will be R. * - * @param pipe Target pipe - * @param init The initial value for the fold - * @param fn The fold function - * @tparam O Output type of the pipe - * @tparam R Result type of the fold - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param pipe + * Target pipe + * @param init + * The initial value for the fold + * @param fn + * The fold function + * @tparam O + * Output type of the pipe + * @tparam R + * Result type of the fold + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ - def foldError[O, R](pipe: ProxPipe[Byte, O], init: R, fn: (R, O) => R): P[R] = - connectError(OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.fold(init, fn))) + def foldError[O, R]( + pipe: ProxPipe[Byte, O], + init: R, + fn: (R, O) => R + ): P[R] = + connectError( + OutputStreamThroughPipe(pipe, (s: ProxStream[O]) => s.fold(init, fn)) + ) - /** - * Redirects the error output to a file natively + /** Redirects the error output to a file natively * * An alias for [[errorToFile]] * - * @param path Target file path - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param path + * Target file path + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def !>(path: Path): P[Unit] = errorToFile(path) - /** - * Redirects the error output to a file natively + /** Redirects the error output to a file natively * * An alias for [[!>]] * - * @param path Target file path - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param path + * Target file path + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def errorToFile(path: Path): P[Unit] = connectError(OutputFile(path, append = false)) - /** - * Redirects the error output to a file natively in append mode + /** Redirects the error output to a file natively in append mode * * An alias for [[appendErrorToFile]] * - * @param path Target file path - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param path + * Target file path + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def !>>(path: Path): P[Unit] = appendErrorToFile(path) - /** - * Redirects the error output to a file natively in append mode + /** Redirects the error output to a file natively in append mode * * An alias for [[!>>]] * - * @param path Target file path - * @return Returns a new process with its error output redirected and its error redirection capability removed. + * @param path + * Target file path + * @return + * Returns a new process with its error output redirected and its error + * redirection capability removed. */ def appendErrorToFile(path: Path): P[Unit] = connectError(OutputFile(path, append = true)) } - /** - * The capability to redirect all the error outputs simultaneously of a process group + /** The capability to redirect all the error outputs simultaneously of a + * process group * - * @tparam P Self type without the [[RedirectableErrors]] capability + * @tparam P + * Self type without the [[RedirectableErrors]] capability */ trait RedirectableErrors[+P[_] <: ProcessGroup[_, _]] { + /** A more advanced interface for customizing the redirection per process */ lazy val customizedPerProcess: RedirectableErrors.CustomizedPerProcess[P] = new RedirectableErrors.CustomizedPerProcess[P] { - override def connectErrors[R <: GroupErrorRedirection, OR <: OutputRedirection, E](target: R) - (implicit groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], - outputRedirectionType: OutputRedirectionType.Aux[OR, E]): P[E] = + override def connectErrors[ + R <: GroupErrorRedirection, + OR <: OutputRedirection, + E + ](target: R)(implicit + groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], + outputRedirectionType: OutputRedirectionType.Aux[OR, E] + ): P[E] = RedirectableErrors.this.connectErrors(target) } - /** - * The low level operation to attach an error output to all the processes in the group. - * - * Use one of the other methods of this trait or the advanced interface represented by [[customizedPerProcess]] for - * convenience. - * - * This is the place where the process group's error output type gets calculated using the [[GroupErrorRedirectionType]] - * and [[OutputRedirectionType]] type classes. - * - * @param target Redirection target - * @param groupErrorRedirectionType Helper for dependent error output type - * @param outputRedirectionType Helper for dependent error output type - * @tparam R Error output grouped redirection type - * @tparam OR Error output redirection type - * @tparam E Error output type - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + /** The low level operation to attach an error output to all the processes + * in the group. + * + * Use one of the other methods of this trait or the advanced interface + * represented by [[customizedPerProcess]] for convenience. + * + * This is the place where the process group's error output type gets + * calculated using the [[GroupErrorRedirectionType]] and + * [[OutputRedirectionType]] type classes. + * + * @param target + * Redirection target + * @param groupErrorRedirectionType + * Helper for dependent error output type + * @param outputRedirectionType + * Helper for dependent error output type + * @tparam R + * Error output grouped redirection type + * @tparam OR + * Error output redirection type + * @tparam E + * Error output type + * @return + * Returns a new process group with all the error streams redirected and + * the error redirection capability removed. */ - def connectErrors[R <: GroupErrorRedirection, OR <: OutputRedirection, E](target: R) - (implicit groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], - outputRedirectionType: OutputRedirectionType.Aux[OR, E]): P[E] - - /** - * Redirects the error outputs to a sink. - * - * The process error output type will be [[Unit]]. - * An alias for [[errorsToSink]] - * - * @param sink Target sink - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + def connectErrors[R <: GroupErrorRedirection, OR <: OutputRedirection, E]( + target: R + )(implicit + groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], + outputRedirectionType: OutputRedirectionType.Aux[OR, E] + ): P[E] + + /** Redirects the error outputs to a sink. + * + * The process error output type will be [[Unit]]. An alias for + * [[errorsToSink]] + * + * @param sink + * Target sink + * @return + * Returns a new process group with all the error streams redirected and + * the error redirection capability removed. */ def !>(sink: ProxSink[Byte]): P[Unit] = errorsToSink(sink) - /** - * Redirects the error outputs to a sink. + /** Redirects the error outputs to a sink. * - * The process error output type will be [[Unit]]. - * An alias for [[!>]] + * The process error output type will be [[Unit]]. An alias for [[!>]] * - * @param sink Target sink - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param sink + * Target sink + * @return + * Returns a new process group with all the error streams redirected and + * the error redirection capability removed. */ def errorsToSink(sink: ProxSink[Byte]): P[Unit] = customizedPerProcess.errorsToSink(_ => sink) - /** - * Redirects the error outputs to a pipe and folds its output with a monoid instance. + /** Redirects the error outputs to a pipe and folds its output with a monoid + * instance. * - * The process error output type will be the same as the pipe's output type. - * An alias for [[errorsToFoldMonoid]] + * The process error output type will be the same as the pipe's output + * type. An alias for [[errorsToFoldMonoid]] * - * @param pipe Target pipe - * @tparam O Output type of the pipe. Must have a monoid instance. - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe. Must have a monoid instance. + * @return + * Returns a new process group with all the error streams redirected and + * the error redirection capability removed. */ def !>#[O: ProxMonoid](pipe: ProxPipe[Byte, O]): P[O] = errorsToFoldMonoid(pipe) - /** - * Redirects the error outputs to a pipe and folds its output with a monoid instance. + /** Redirects the error outputs to a pipe and folds its output with a monoid + * instance. * - * The process error output type will be the same as the pipe's output type. - * An alias for [[!>#]] + * The process error output type will be the same as the pipe's output + * type. An alias for [[!>#]] * - * @param pipe Target pipe - * @tparam O Output type of the pipe. Must have a monoid instance. - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe. Must have a monoid instance. + * @return + * Returns a new process group with all the error streams redirected and + * the error redirection capability removed. */ def errorsToFoldMonoid[O: ProxMonoid](pipe: ProxPipe[Byte, O]): P[O] = customizedPerProcess.errorsToFoldMonoid(_ => pipe) - /** - * Redirects the error outputs to a pipe and collects its output to a vector + /** Redirects the error outputs to a pipe and collects its output to a + * vector * - * The process error output type will be a vector of the pipe's output type. - * An alias for [[errorsToVector]] + * The process error output type will be a vector of the pipe's output + * type. An alias for [[errorsToVector]] * - * @param pipe Target pipe - * @tparam O Output type of the pipe - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe + * @return + * Returns a new process group with all the error streams redirected and + * the error redirection capability removed. */ def !>?[O](pipe: ProxPipe[Byte, O]): P[Vector[O]] = errorsToVector(pipe) - /** - * Redirects the error outputs to a pipe and collects its output to a vector + /** Redirects the error outputs to a pipe and collects its output to a + * vector * - * The process error output type will be a vector of the pipe's output type. - * An alias for [[!>?]] + * The process error output type will be a vector of the pipe's output + * type. An alias for [[!>?]] * - * @param pipe Target pipe - * @tparam O Output type of the pipe - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe + * @return + * Returns a new process group with all the error streams redirected and + * the error redirection capability removed. */ def errorsToVector[O](pipe: ProxPipe[Byte, O]): P[Vector[O]] = customizedPerProcess.errorsToVector(_ => pipe) - /** - * Redirects the error outputs to a pipe and drains it regardless of its output type. + /** Redirects the error outputs to a pipe and drains it regardless of its + * output type. * * The process error output type will be [[Unit]]. * - * @param pipe Target pipe - * @tparam O Output type of the pipe - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pipe + * Target pipe + * @tparam O + * Output type of the pipe + * @return + * Returns a new process group with all the error streams redirected and + * the error redirection capability removed. */ def drainErrors[O](pipe: ProxPipe[Byte, O]): P[Unit] = customizedPerProcess.drainErrors(_ => pipe) - /** - * Redirects the error outputs to a pipe and folds it with a custom function. + /** Redirects the error outputs to a pipe and folds it with a custom + * function. * * The process error output type will be R. * - * @param pipe Target pipe - * @param init The initial value for the fold - * @param fn The fold function - * @tparam O Output type of the pipe - * @tparam R Result type of the fold - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pipe + * Target pipe + * @param init + * The initial value for the fold + * @param fn + * The fold function + * @tparam O + * Output type of the pipe + * @tparam R + * Result type of the fold + * @return + * Returns a new process group with all the error streams redirected and + * the error redirection capability removed. */ - def foldErrors[O, R](pipe: ProxPipe[Byte, O], init: R, fn: (R, O) => R): P[R] = + def foldErrors[O, R]( + pipe: ProxPipe[Byte, O], + init: R, + fn: (R, O) => R + ): P[R] = customizedPerProcess.foldErrors(_ => pipe, init, fn) } object RedirectableErrors { - /** - * Advanced version of the [[RedirectableErrors]] interface enabling per-process customizations. + /** Advanced version of the [[RedirectableErrors]] interface enabling + * per-process customizations. * - * @tparam P Self type without [[RedirectableErrors]] + * @tparam P + * Self type without [[RedirectableErrors]] */ trait CustomizedPerProcess[+P[_] <: ProcessGroup[_, _]] { - /** See [[RedirectableErrors.connectErrors]] */ - def connectErrors[R <: GroupErrorRedirection, OR <: OutputRedirection, E](target: R) - (implicit groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], - outputRedirectionType: OutputRedirectionType.Aux[OR, E]): P[E] - /** - * Redirects the error outputs to a sink. + /** See [[RedirectableErrors.connectErrors]] */ + def connectErrors[R <: GroupErrorRedirection, OR <: OutputRedirection, E]( + target: R + )(implicit + groupErrorRedirectionType: GroupErrorRedirectionType.Aux[R, OR, E], + outputRedirectionType: OutputRedirectionType.Aux[OR, E] + ): P[E] + + /** Redirects the error outputs to a sink. * * The process error output type will be [[Unit]]. * - * @param sinkFn Function to get a sink for each process in the group - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param sinkFn + * Function to get a sink for each process in the group + * @return + * Returns a new process group with all the error streams redirected + * and the error redirection capability removed. */ def errorsToSink(sinkFn: Process[_, _] => ProxSink[Byte]): P[Unit] = connectErrors(AllCapturedToSink(sinkFn)) - /** - * Redirects the error outputs to a pipe and folds its output with a monoid instance. + /** Redirects the error outputs to a pipe and folds its output with a + * monoid instance. * - * The process error output type will be the same as the pipe's output type. + * The process error output type will be the same as the pipe's output + * type. * - * @param pipeFn A function to get a pipe for each process in the group - * @tparam O Output type of the pipe. Must have a monoid instance. - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pipeFn + * A function to get a pipe for each process in the group + * @tparam O + * Output type of the pipe. Must have a monoid instance. + * @return + * Returns a new process group with all the error streams redirected + * and the error redirection capability removed. */ - def errorsToFoldMonoid[O: ProxMonoid](pipeFn: Process[_, _] => ProxPipe[Byte, O]): P[O] = - connectErrors(AllCapturedThroughPipe(pipeFn, (s: ProxStream[O]) => s.foldMonoid)) - - /** - * Redirects the error outputs to a pipe and collects its output to a vector + def errorsToFoldMonoid[O: ProxMonoid]( + pipeFn: Process[_, _] => ProxPipe[Byte, O] + ): P[O] = + connectErrors( + AllCapturedThroughPipe(pipeFn, (s: ProxStream[O]) => s.foldMonoid) + ) + + /** Redirects the error outputs to a pipe and collects its output to a + * vector * - * The process error output type will be a vector of the pipe's output type. + * The process error output type will be a vector of the pipe's output + * type. * - * @param pipeFn A function to get a pipe for each process in the group - * @tparam O Output type of the pipe - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pipeFn + * A function to get a pipe for each process in the group + * @tparam O + * Output type of the pipe + * @return + * Returns a new process group with all the error streams redirected + * and the error redirection capability removed. */ - def errorsToVector[O](pipeFn: Process[_, _] => ProxPipe[Byte, O]): P[Vector[O]] = - connectErrors(AllCapturedThroughPipe(pipeFn, (s: ProxStream[O]) => s.toVector)) - - /** - * Redirects the error outputs to a pipe and drains it regardless of its output type. + def errorsToVector[O]( + pipeFn: Process[_, _] => ProxPipe[Byte, O] + ): P[Vector[O]] = + connectErrors( + AllCapturedThroughPipe(pipeFn, (s: ProxStream[O]) => s.toVector) + ) + + /** Redirects the error outputs to a pipe and drains it regardless of its + * output type. * * The process error output type will be [[Unit]]. * - * @param pipeFn A function to get a pipe for each process in the group - * @tparam O Output type of the pipe - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pipeFn + * A function to get a pipe for each process in the group + * @tparam O + * Output type of the pipe + * @return + * Returns a new process group with all the error streams redirected + * and the error redirection capability removed. */ def drainErrors[O](pipeFn: Process[_, _] => ProxPipe[Byte, O]): P[Unit] = - connectErrors(AllCapturedThroughPipe(pipeFn, (s: ProxStream[O]) => s.drain)) + connectErrors( + AllCapturedThroughPipe(pipeFn, (s: ProxStream[O]) => s.drain) + ) - /** - * Redirects the error outputs to a pipe and folds it with a custom function. + /** Redirects the error outputs to a pipe and folds it with a custom + * function. * * The process error output type will be R. * - * @param pipeFn A function to get a pipe for each process in the group - * @param init The initial value for the fold - * @param fn The fold function - * @tparam O Output type of the pipe - * @tparam R Result type of the fold - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pipeFn + * A function to get a pipe for each process in the group + * @param init + * The initial value for the fold + * @param fn + * The fold function + * @tparam O + * Output type of the pipe + * @tparam R + * Result type of the fold + * @return + * Returns a new process group with all the error streams redirected + * and the error redirection capability removed. */ - def foldErrors[O, R](pipeFn: Process[_, _] => ProxPipe[Byte, O], init: R, fn: (R, O) => R): P[R] = - connectErrors(AllCapturedThroughPipe(pipeFn, (s: ProxStream[O]) => s.fold(init, fn))) - - /** - * Redirects the error outputs to one file per process + def foldErrors[O, R]( + pipeFn: Process[_, _] => ProxPipe[Byte, O], + init: R, + fn: (R, O) => R + ): P[R] = + connectErrors( + AllCapturedThroughPipe(pipeFn, (s: ProxStream[O]) => s.fold(init, fn)) + ) + + /** Redirects the error outputs to one file per process * * The process error output type will be [[Unit]]. * - * @param pathFn A function to get a file path for each process in the group - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pathFn + * A function to get a file path for each process in the group + * @return + * Returns a new process group with all the error streams redirected + * and the error redirection capability removed. */ def errorsToFile(pathFn: Process[_, _] => Path): P[Unit] = connectErrors(AllToFile(pathFn, append = false)) - /** - * Redirects the error outputs to one file per process in append mode + /** Redirects the error outputs to one file per process in append mode * * The process error output type will be [[Unit]]. * - * @param pathFn A function to get a file path for each process in the group - * @return Returns a new process group with all the error streams redirected and the error redirection capability removed. + * @param pathFn + * A function to get a file path for each process in the group + * @return + * Returns a new process group with all the error streams redirected + * and the error redirection capability removed. */ def appendErrorsToFile(pathFn: Process[_, _] => Path): P[Unit] = connectErrors(AllToFile(pathFn, append = true)) @@ -585,115 +795,143 @@ trait RedirectionModule { } - /** - * The capability to redirect the input of a process or process group + /** The capability to redirect the input of a process or process group * * @tparam P */ trait RedirectableInput[+P <: ProcessLike] { - /** - * The low level method to attach an input to a process or process group. + + /** The low level method to attach an input to a process or process group. * * Use the other methods in this trait for convenience. * - * @param source Redirection source - * @return A new process or process group with the input redirected and the input redirection capability removed. + * @param source + * Redirection source + * @return + * A new process or process group with the input redirected and the input + * redirection capability removed. */ def connectInput(source: InputRedirection): P - /** - * Feed the process input from a file natively. + /** Feed the process input from a file natively. * * An alias for [[fromFile]]. * - * @param path Path to the file - * @return A new process or process group with the input redirected and the input redirection capability removed. + * @param path + * Path to the file + * @return + * A new process or process group with the input redirected and the input + * redirection capability removed. */ def <(path: Path): P = fromFile(path) - /** - * Feed the process input from a file natively. + /** Feed the process input from a file natively. * * An alias for [[<]]. * - * @param path Path to the file - * @return A new process or process group with the input redirected and the input redirection capability removed. + * @param path + * Path to the file + * @return + * A new process or process group with the input redirected and the input + * redirection capability removed. */ def fromFile(path: Path): P = connectInput(InputFile(path)) - /** - * Feed the process input from a byte stream. + /** Feed the process input from a byte stream. * * An alias for [[fromStream]]. * - * @param stream Input stream - * @return A new process or process group with the input redirected and the input redirection capability removed. + * @param stream + * Input stream + * @return + * A new process or process group with the input redirected and the input + * redirection capability removed. */ def <(stream: ProxStream[Byte]): P = fromStream(stream, flushChunks = false) - /** - * Feed the process input from a byte stream with flushing per chunks enabled. + /** Feed the process input from a byte stream with flushing per chunks + * enabled. * * An alias for [[fromStream]]. * - * @param stream Input stream - * @return A new process or process group with the input redirected and the input redirection capability removed. + * @param stream + * Input stream + * @return + * A new process or process group with the input redirected and the input + * redirection capability removed. */ def !<(stream: ProxStream[Byte]): P = fromStream(stream, flushChunks = true) - /** - * Feed the process input from a byte stream. + /** Feed the process input from a byte stream. * * An alias for [[<]] and [[!<]]. * - * @param stream Input stream - * @param flushChunks Flush the process input stream after each chunk - * @return A new process or process group with the input redirected and the input redirection capability removed. + * @param stream + * Input stream + * @param flushChunks + * Flush the process input stream after each chunk + * @return + * A new process or process group with the input redirected and the input + * redirection capability removed. */ def fromStream(stream: ProxStream[Byte], flushChunks: Boolean): P = connectInput(InputStream(stream, flushChunks)) } - /** Supported output redirection types. Should not be used directly, see the redirection traits instead. */ + /** Supported output redirection types. Should not be used directly, see the + * redirection traits instead. + */ sealed trait OutputRedirection case class StdOut() extends OutputRedirection case class OutputFile(path: Path, append: Boolean) extends OutputRedirection - case class OutputStreamThroughPipe[O, OR](pipe: ProxPipe[Byte, O], - runner: ProxStream[O] => ProxIO[OR], - chunkSize: Int = 8192) extends OutputRedirection + case class OutputStreamThroughPipe[O, OR]( + pipe: ProxPipe[Byte, O], + runner: ProxStream[O] => ProxIO[OR], + chunkSize: Int = 8192 + ) extends OutputRedirection - case class OutputStreamToSink(sink: ProxSink[Byte], - chunkSize: Int = 8192) extends OutputRedirection + case class OutputStreamToSink(sink: ProxSink[Byte], chunkSize: Int = 8192) + extends OutputRedirection - /** Supported process group error redirection types. Should not be used directly, see the redirection traits instead. */ + /** Supported process group error redirection types. Should not be used + * directly, see the redirection traits instead. + */ sealed trait GroupErrorRedirection case class AllToStdErr() extends GroupErrorRedirection - case class AllToFile(pathFn: Process[_, _] => Path, append: Boolean) extends GroupErrorRedirection + case class AllToFile(pathFn: Process[_, _] => Path, append: Boolean) + extends GroupErrorRedirection - case class AllCapturedThroughPipe[O, OR](pipeFn: Process[_, _] => ProxPipe[Byte, O], - runner: ProxStream[O] => ProxIO[OR], - chunkSize: Int = 8192) extends GroupErrorRedirection + case class AllCapturedThroughPipe[O, OR]( + pipeFn: Process[_, _] => ProxPipe[Byte, O], + runner: ProxStream[O] => ProxIO[OR], + chunkSize: Int = 8192 + ) extends GroupErrorRedirection - case class AllCapturedToSink(sinkFn: Process[_, _] => ProxSink[Byte], - chunkSize: Int = 8192) extends GroupErrorRedirection + case class AllCapturedToSink( + sinkFn: Process[_, _] => ProxSink[Byte], + chunkSize: Int = 8192 + ) extends GroupErrorRedirection - /** Supported input redirection types. Should not be used directly, see the redirection traits instead. */ + /** Supported input redirection types. Should not be used directly, see the + * redirection traits instead. + */ sealed trait InputRedirection case class StdIn() extends InputRedirection case class InputFile(path: Path) extends InputRedirection - case class InputStream(stream: ProxStream[Byte], flushChunks: Boolean) extends InputRedirection + case class InputStream(stream: ProxStream[Byte], flushChunks: Boolean) + extends InputRedirection /** Helper type class for output and error redirection dependent typing */ trait OutputRedirectionType[R] { @@ -707,32 +945,48 @@ trait RedirectionModule { type Out = O } - implicit def outputRedirectionTypeOfStdOut: Aux[StdOut, Unit] = new OutputRedirectionType[StdOut] { - override type Out = Unit - - override def runner(of: StdOut)(nativeStream: java.io.InputStream): ProxIO[Unit] = unit - } + implicit def outputRedirectionTypeOfStdOut: Aux[StdOut, Unit] = + new OutputRedirectionType[StdOut] { + override type Out = Unit - implicit def outputRedirectionTypeOfFile: Aux[OutputFile, Unit] = new OutputRedirectionType[OutputFile] { - override type Out = Unit + override def runner(of: StdOut)( + nativeStream: java.io.InputStream + ): ProxIO[Unit] = unit + } - override def runner(of: OutputFile)(nativeStream: java.io.InputStream): ProxIO[Unit] = unit - } + implicit def outputRedirectionTypeOfFile: Aux[OutputFile, Unit] = + new OutputRedirectionType[OutputFile] { + override type Out = Unit - implicit def outputRedirectionTypeOfStreamThroughPipe[O, OR]: Aux[OutputStreamThroughPipe[O, OR], OR] = new OutputRedirectionType[OutputStreamThroughPipe[O, OR]] { - override type Out = OR + override def runner(of: OutputFile)( + nativeStream: java.io.InputStream + ): ProxIO[Unit] = unit + } - override def runner(of: OutputStreamThroughPipe[O, OR])(nativeStream: java.io.InputStream): ProxIO[OR] = { - of.runner(fromJavaInputStream(nativeStream, of.chunkSize).through(of.pipe)) + implicit def outputRedirectionTypeOfStreamThroughPipe[O, OR] + : Aux[OutputStreamThroughPipe[O, OR], OR] = + new OutputRedirectionType[OutputStreamThroughPipe[O, OR]] { + override type Out = OR + + override def runner( + of: OutputStreamThroughPipe[O, OR] + )(nativeStream: java.io.InputStream): ProxIO[OR] = { + of.runner( + fromJavaInputStream(nativeStream, of.chunkSize).through(of.pipe) + ) + } } - } - implicit def outputRedirectionTypeOfStreamToSink[O]: Aux[OutputStreamToSink, Unit] = new OutputRedirectionType[OutputStreamToSink] { - override type Out = Unit + implicit def outputRedirectionTypeOfStreamToSink[O] + : Aux[OutputStreamToSink, Unit] = + new OutputRedirectionType[OutputStreamToSink] { + override type Out = Unit - override def runner(of: OutputStreamToSink)(nativeStream: io.InputStream): ProxIO[Unit] = - fromJavaInputStream(nativeStream, of.chunkSize).run(of.sink) - } + override def runner(of: OutputStreamToSink)( + nativeStream: io.InputStream + ): ProxIO[Unit] = + fromJavaInputStream(nativeStream, of.chunkSize).run(of.sink) + } } /** Helper type class for process group error redirection dependent typing */ @@ -749,35 +1003,61 @@ trait RedirectionModule { type OutputR = OR } - implicit def groupErrorRedirectionTypeOfStdErr: Aux[AllToStdErr, StdOut, Unit] = new GroupErrorRedirectionType[AllToStdErr] { - override type Out = Unit - override type OutputR = StdOut + implicit def groupErrorRedirectionTypeOfStdErr + : Aux[AllToStdErr, StdOut, Unit] = + new GroupErrorRedirectionType[AllToStdErr] { + override type Out = Unit + override type OutputR = StdOut - override def toOutputRedirectionType(redir: AllToStdErr, process: Process[_, _]): StdOut = StdOut() - } + override def toOutputRedirectionType( + redir: AllToStdErr, + process: Process[_, _] + ): StdOut = StdOut() + } - implicit def groupErrorRedirectionTypeOfFile: Aux[AllToFile, OutputFile, Unit] = new GroupErrorRedirectionType[AllToFile] { - override type Out = Unit - override type OutputR = OutputFile + implicit def groupErrorRedirectionTypeOfFile + : Aux[AllToFile, OutputFile, Unit] = + new GroupErrorRedirectionType[AllToFile] { + override type Out = Unit + override type OutputR = OutputFile - override def toOutputRedirectionType(redir: AllToFile, process: Process[_, _]): OutputFile = OutputFile(redir.pathFn(process), redir.append) - } + override def toOutputRedirectionType( + redir: AllToFile, + process: Process[_, _] + ): OutputFile = OutputFile(redir.pathFn(process), redir.append) + } - implicit def groupErrorRedirectionTypeOfStreamThroughPipe[O, OR]: Aux[AllCapturedThroughPipe[O, OR], OutputStreamThroughPipe[O, OR], OR] = new GroupErrorRedirectionType[AllCapturedThroughPipe[O, OR]] { + implicit def groupErrorRedirectionTypeOfStreamThroughPipe[O, OR]: Aux[ + AllCapturedThroughPipe[O, OR], + OutputStreamThroughPipe[O, OR], + OR + ] = new GroupErrorRedirectionType[AllCapturedThroughPipe[O, OR]] { override type Out = OR override type OutputR = OutputStreamThroughPipe[O, OR] - override def toOutputRedirectionType(redir: AllCapturedThroughPipe[O, OR], process: Process[_, _]): OutputStreamThroughPipe[O, OR] = - OutputStreamThroughPipe(redir.pipeFn(process), redir.runner, redir.chunkSize) + override def toOutputRedirectionType( + redir: AllCapturedThroughPipe[O, OR], + process: Process[_, _] + ): OutputStreamThroughPipe[O, OR] = + OutputStreamThroughPipe( + redir.pipeFn(process), + redir.runner, + redir.chunkSize + ) } - implicit val groupErrorRedirectionTypeOfStreamToSink: Aux[AllCapturedToSink, OutputStreamToSink, Unit] = new GroupErrorRedirectionType[AllCapturedToSink] { - override type Out = Unit - override type OutputR = OutputStreamToSink - - override def toOutputRedirectionType(redir: AllCapturedToSink, process: Process[_, _]): OutputStreamToSink = - OutputStreamToSink(redir.sinkFn(process), redir.chunkSize) - } + implicit val groupErrorRedirectionTypeOfStreamToSink + : Aux[AllCapturedToSink, OutputStreamToSink, Unit] = + new GroupErrorRedirectionType[AllCapturedToSink] { + override type Out = Unit + override type OutputR = OutputStreamToSink + + override def toOutputRedirectionType( + redir: AllCapturedToSink, + process: Process[_, _] + ): OutputStreamToSink = + OutputStreamToSink(redir.sinkFn(process), redir.chunkSize) + } } -} \ No newline at end of file +} diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/runner.scala b/prox-core/src/main/scala/io/github/vigoo/prox/runner.scala index 47ae4ff6..7a9ce4ab 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/runner.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/runner.scala @@ -8,73 +8,96 @@ import scala.jdk.CollectionConverters._ trait ProcessRunnerModule { this: Prox => - /** - * Interface for running processes and process groups + /** Interface for running processes and process groups * * The default implementation is [[JVMProcessRunner]] * - * @tparam Info The type of information provided for a started process + * @tparam Info + * The type of information provided for a started process */ trait ProcessRunner[Info] { - /** - * Starts the process asynchronously and returns the [[RunningProcess]] interface for it + + /** Starts the process asynchronously and returns the [[RunningProcess]] + * interface for it * - * @param process The process to be started - * @tparam O Output type - * @tparam E Error output type - * @return interface for handling the running process + * @param process + * The process to be started + * @tparam O + * Output type + * @tparam E + * Error output type + * @return + * interface for handling the running process */ - def startProcess[O, E](process: Process[O, E]): ProxIO[RunningProcess[O, E, Info]] + def startProcess[O, E]( + process: Process[O, E] + ): ProxIO[RunningProcess[O, E, Info]] - /** - * Starts the process asynchronously and returns a managed fiber representing it + /** Starts the process asynchronously and returns a managed fiber + * representing it * * Joining the fiber means waiting until the process gets terminated. * Cancelling the fiber terminates the process. * - * @param process The process to be started - * @tparam O Output type - * @tparam E Error output type - * @return interface for handling the running process + * @param process + * The process to be started + * @tparam O + * Output type + * @tparam E + * Error output type + * @return + * interface for handling the running process */ - def start[O, E](process: Process[O, E]): ProxResource[ProxFiber[ProcessResult[O, E]]] = { - val run = startFiber( - bracket(startProcess(process)) { runningProcess => - runningProcess.waitForExit() - } { - case (_, Completed) => - unit - case (_, Failed(reason)) => - raiseError(reason.toSingleError) - case (runningProcess, Canceled) => - runningProcess.terminate().map(_ => ()) - }) + def start[O, E]( + process: Process[O, E] + ): ProxResource[ProxFiber[ProcessResult[O, E]]] = { + val run = startFiber(bracket(startProcess(process)) { runningProcess => + runningProcess.waitForExit() + } { + case (_, Completed) => + unit + case (_, Failed(reason)) => + raiseError(reason.toSingleError) + case (runningProcess, Canceled) => + runningProcess.terminate().map(_ => ()) + }) makeResource(run, _.cancel) } - /** - * Starts a process group asynchronously and returns an interface for them + /** Starts a process group asynchronously and returns an interface for them * - * @param processGroup The process group to start - * @tparam O Output type - * @tparam E Error output type - * @return interface for handling the running process group + * @param processGroup + * The process group to start + * @tparam O + * Output type + * @tparam E + * Error output type + * @return + * interface for handling the running process group */ - def startProcessGroup[O, E](processGroup: ProcessGroup[O, E]): ProxIO[RunningProcessGroup[O, E, Info]] + def startProcessGroup[O, E]( + processGroup: ProcessGroup[O, E] + ): ProxIO[RunningProcessGroup[O, E, Info]] - /** - * Starts the process group asynchronously and returns a managed fiber representing it + /** Starts the process group asynchronously and returns a managed fiber + * representing it * * Joining the fiber means waiting until the process gets terminated. * Cancelling the fiber terminates the process. * - * @param processGroup The process group to be started - * @tparam O Output type - * @tparam E Error output type - * @return interface for handling the running process + * @param processGroup + * The process group to be started + * @tparam O + * Output type + * @tparam E + * Error output type + * @return + * interface for handling the running process */ - def start[O, E](processGroup: ProcessGroup[O, E]): ProxResource[ProxFiber[ProcessGroupResult[O, E]]] = { + def start[O, E]( + processGroup: ProcessGroup[O, E] + ): ProxResource[ProxFiber[ProcessGroupResult[O, E]]] = { val run = startFiber( bracket(startProcessGroup(processGroup)) { runningProcess => @@ -96,25 +119,33 @@ trait ProcessRunnerModule { class JVMProcessInfo() /** Default implementation of [[RunningProcess]] using the Java process API */ - class JVMRunningProcess[O, E, +Info <: JVMProcessInfo](val nativeProcess: JvmProcess, - override val runningInput: ProxFiber[Unit], - override val runningOutput: ProxFiber[O], - override val runningError: ProxFiber[E], - override val info: Info) - extends RunningProcess[O, E, Info] { + class JVMRunningProcess[O, E, +Info <: JVMProcessInfo]( + val nativeProcess: JvmProcess, + override val runningInput: ProxFiber[Unit], + override val runningOutput: ProxFiber[O], + override val runningError: ProxFiber[E], + override val info: Info + ) extends RunningProcess[O, E, Info] { def isAlive: ProxIO[Boolean] = effect(nativeProcess.isAlive, FailedToQueryState.apply) def kill(): ProxIO[ProcessResult[O, E]] = - effect(nativeProcess.destroyForcibly(), FailedToDestroy.apply).flatMap(_ => waitForExit()) + effect(nativeProcess.destroyForcibly(), FailedToDestroy.apply).flatMap( + _ => waitForExit() + ) def terminate(): ProxIO[ProcessResult[O, E]] = - effect(nativeProcess.destroy(), FailedToDestroy.apply).flatMap(_ => waitForExit()) + effect(nativeProcess.destroy(), FailedToDestroy.apply).flatMap(_ => + waitForExit() + ) def waitForExit(): ProxIO[ProcessResult[O, E]] = { for { - exitCode <- blockingEffect(nativeProcess.waitFor(), FailedToWaitForExit.apply) + exitCode <- blockingEffect( + nativeProcess.waitFor(), + FailedToWaitForExit.apply + ) _ <- runningInput.join output <- runningOutput.join error <- runningError.join @@ -122,19 +153,25 @@ trait ProcessRunnerModule { } } - /** Default implementation of [[RunningProcessGroup]] using the Java process API */ - class JVMRunningProcessGroup[O, E, +Info <: JVMProcessInfo](runningProcesses: Map[Process[Unit, Unit], RunningProcess[_, E, Info]], - override val runningOutput: ProxFiber[O]) - extends RunningProcessGroup[O, E, Info] { + /** Default implementation of [[RunningProcessGroup]] using the Java process + * API + */ + class JVMRunningProcessGroup[O, E, +Info <: JVMProcessInfo]( + runningProcesses: Map[Process[Unit, Unit], RunningProcess[_, E, Info]], + override val runningOutput: ProxFiber[O] + ) extends RunningProcessGroup[O, E, Info] { override val info: Map[Process[Unit, Unit], Info] = runningProcesses.map { case (key, value) => (key, value.info) } def kill(): ProxIO[ProcessGroupResult[O, E]] = - traverse(runningProcesses.values.toList)(_.kill().map(_ => ())).flatMap(_ => waitForExit()) + traverse(runningProcesses.values.toList)(_.kill().map(_ => ())).flatMap( + _ => waitForExit() + ) def terminate(): ProxIO[ProcessGroupResult[O, E]] = - traverse(runningProcesses.values.toList)(_.terminate().map(_ => ())).flatMap(_ => waitForExit()) + traverse(runningProcesses.values.toList)(_.terminate().map(_ => ())) + .flatMap(_ => waitForExit()) def waitForExit(): ProxIO[ProcessGroupResult[O, E]] = for { @@ -142,51 +179,88 @@ trait ProcessRunnerModule { rp.waitForExit().map((result: ProcessResult[_, E]) => spec -> result) } lastOutput <- runningOutput.join - exitCodes = results.map { case (proc, result) => proc -> result.exitCode }.toMap - errors = results.map { case (proc, result) => proc -> result.error }.toMap + exitCodes = results.map { case (proc, result) => + proc -> result.exitCode + }.toMap + errors = results.map { case (proc, result) => + proc -> result.error + }.toMap } yield SimpleProcessGroupResult(exitCodes, lastOutput, errors) } /** Default implementation of [[ProcessRunner]] using the Java process API */ abstract class JVMProcessRunnerBase[Info <: JVMProcessInfo] - extends ProcessRunner[Info] { + extends ProcessRunner[Info] { import JVMProcessRunnerBase._ - override def startProcess[O, E](process: Process[O, E]): ProxIO[RunningProcess[O, E, Info]] = { - val builder = withEnvironmentVariables(process, - withWorkingDirectory(process, - new ProcessBuilder((process.command :: process.arguments).asJava))) + override def startProcess[O, E]( + process: Process[O, E] + ): ProxIO[RunningProcess[O, E, Info]] = { + val builder = withEnvironmentVariables( + process, + withWorkingDirectory( + process, + new ProcessBuilder((process.command :: process.arguments).asJava) + ) + ) - builder.redirectOutput(ouptutRedirectionToNative(process.outputRedirection)) + builder.redirectOutput( + ouptutRedirectionToNative(process.outputRedirection) + ) builder.redirectError(ouptutRedirectionToNative(process.errorRedirection)) builder.redirectInput(inputRedirectionToNative(process.inputRedirection)) for { nativeProcess <- effect(builder.start(), FailedToStartProcess.apply) processInfo <- getProcessInfo(nativeProcess) - nativeOutputStream <- effect(nativeProcess.getInputStream, UnknownProxError.apply) - nativeErrorStream <- effect(nativeProcess.getErrorStream, UnknownProxError.apply) + nativeOutputStream <- effect( + nativeProcess.getInputStream, + UnknownProxError.apply + ) + nativeErrorStream <- effect( + nativeProcess.getErrorStream, + UnknownProxError.apply + ) inputStream = runInputStream(process, nativeProcess) runningInput <- startFiber(inputStream) runningOutput <- startFiber(process.runOutputStream(nativeOutputStream)) runningError <- startFiber(process.runErrorStream(nativeErrorStream)) - } yield new JVMRunningProcess(nativeProcess, runningInput, runningOutput, runningError, processInfo) + } yield new JVMRunningProcess( + nativeProcess, + runningInput, + runningOutput, + runningError, + processInfo + ) } protected def getProcessInfo(process: JvmProcess): ProxIO[Info] - private def connectAndStartProcesses[E](firstProcess: Process[ProxStream[Byte], E] with RedirectableInput[Process[ProxStream[Byte], E]], - previousOutput: ProxStream[Byte], - remainingProcesses: List[Process[ProxStream[Byte], E] with RedirectableInput[Process[ProxStream[Byte], E]]], - startedProcesses: List[RunningProcess[_, E, Info]]): ProxIO[(List[RunningProcess[_, E, Info]], ProxStream[Byte])] = { - startProcess(firstProcess.connectInput(InputStream(previousOutput, flushChunks = false))).flatMap { first => + private def connectAndStartProcesses[E]( + firstProcess: Process[ProxStream[Byte], E] + with RedirectableInput[Process[ProxStream[Byte], E]], + previousOutput: ProxStream[Byte], + remainingProcesses: List[Process[ProxStream[Byte], E] + with RedirectableInput[Process[ProxStream[Byte], E]]], + startedProcesses: List[RunningProcess[_, E, Info]] + ): ProxIO[(List[RunningProcess[_, E, Info]], ProxStream[Byte])] = { + startProcess( + firstProcess.connectInput( + InputStream(previousOutput, flushChunks = false) + ) + ).flatMap { first => first.runningOutput.join.flatMap { firstOutput => val updatedStartedProcesses = first :: startedProcesses remainingProcesses match { case nextProcess :: rest => - connectAndStartProcesses(nextProcess, firstOutput, rest, updatedStartedProcesses) + connectAndStartProcesses( + nextProcess, + firstOutput, + rest, + updatedStartedProcesses + ) case Nil => pure((updatedStartedProcesses.reverse, firstOutput)) } @@ -194,41 +268,69 @@ trait ProcessRunnerModule { } } - override def startProcessGroup[O, E](processGroup: ProcessGroup[O, E]): ProxIO[RunningProcessGroup[O, E, Info]] = + override def startProcessGroup[O, E]( + processGroup: ProcessGroup[O, E] + ): ProxIO[RunningProcessGroup[O, E, Info]] = for { first <- startProcess(processGroup.firstProcess) firstOutput <- first.runningOutput.join - innerResult <- if (processGroup.innerProcesses.isEmpty) { - pure((List.empty, firstOutput)) - } else { - val inner = processGroup.innerProcesses.reverse - connectAndStartProcesses(inner.head, firstOutput, inner.tail, List.empty) - } + innerResult <- + if (processGroup.innerProcesses.isEmpty) { + pure((List.empty, firstOutput)) + } else { + val inner = processGroup.innerProcesses.reverse + connectAndStartProcesses( + inner.head, + firstOutput, + inner.tail, + List.empty + ) + } (inner, lastInput) = innerResult - last <- startProcess(processGroup.lastProcess.connectInput(InputStream(lastInput, flushChunks = false))) - runningProcesses = processGroup.originalProcesses.reverse.zip((first :: inner) :+ last).toMap + last <- startProcess( + processGroup.lastProcess.connectInput( + InputStream(lastInput, flushChunks = false) + ) + ) + runningProcesses = processGroup.originalProcesses.reverse + .zip((first :: inner) :+ last) + .toMap } yield new JVMRunningProcessGroup[O, E, Info]( runningProcesses, - last.runningOutput) + last.runningOutput + ) - private def runInputStream[O, E](process: Process[O, E], nativeProcess: JvmProcess): ProxIO[Unit] = { + private def runInputStream[O, E]( + process: Process[O, E], + nativeProcess: JvmProcess + ): ProxIO[Unit] = { process.inputRedirection match { - case StdIn() => unit + case StdIn() => unit case InputFile(_) => unit case InputStream(stream, flushChunks) => - drainToJavaOutputStream(stream, nativeProcess.getOutputStream, flushChunks) + drainToJavaOutputStream( + stream, + nativeProcess.getOutputStream, + flushChunks + ) } } } object JVMProcessRunnerBase { - def withWorkingDirectory[O, E](process: Process[O, E], builder: ProcessBuilder): ProcessBuilder = + def withWorkingDirectory[O, E]( + process: Process[O, E], + builder: ProcessBuilder + ): ProcessBuilder = process.workingDirectory match { case Some(directory) => builder.directory(directory.toFile) - case None => builder + case None => builder } - def withEnvironmentVariables[O, E](process: Process[O, E], builder: ProcessBuilder): ProcessBuilder = { + def withEnvironmentVariables[O, E]( + process: Process[O, E], + builder: ProcessBuilder + ): ProcessBuilder = { process.environmentVariables.foreach { case (name, value) => builder.environment().put(name, value) } @@ -238,30 +340,36 @@ trait ProcessRunnerModule { builder } - def ouptutRedirectionToNative(outputRedirection: OutputRedirection): ProcessBuilder.Redirect = { + def ouptutRedirectionToNative( + outputRedirection: OutputRedirection + ): ProcessBuilder.Redirect = { outputRedirection match { - case StdOut() => ProcessBuilder.Redirect.INHERIT + case StdOut() => ProcessBuilder.Redirect.INHERIT case OutputFile(path, false) => ProcessBuilder.Redirect.to(path.toFile) - case OutputFile(path, true) => ProcessBuilder.Redirect.appendTo(path.toFile) + case OutputFile(path, true) => + ProcessBuilder.Redirect.appendTo(path.toFile) case OutputStreamThroughPipe(_, _, _) => ProcessBuilder.Redirect.PIPE - case OutputStreamToSink(_, _) => ProcessBuilder.Redirect.PIPE + case OutputStreamToSink(_, _) => ProcessBuilder.Redirect.PIPE } } - def inputRedirectionToNative(inputRedirection: InputRedirection): ProcessBuilder.Redirect = { + def inputRedirectionToNative( + inputRedirection: InputRedirection + ): ProcessBuilder.Redirect = { inputRedirection match { - case StdIn() => ProcessBuilder.Redirect.INHERIT - case InputFile(path) => ProcessBuilder.Redirect.from(path.toFile) + case StdIn() => ProcessBuilder.Redirect.INHERIT + case InputFile(path) => ProcessBuilder.Redirect.from(path.toFile) case InputStream(_, _) => ProcessBuilder.Redirect.PIPE } } } - class JVMProcessRunner() - extends JVMProcessRunnerBase[JVMProcessInfo] { + class JVMProcessRunner() extends JVMProcessRunnerBase[JVMProcessInfo] { - override protected def getProcessInfo(process: JvmProcess): ProxIO[JVMProcessInfo] = + override protected def getProcessInfo( + process: JvmProcess + ): ProxIO[JVMProcessInfo] = effect(new JVMProcessInfo(), UnknownProxError.apply) } -} \ No newline at end of file +} diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/runtime.scala b/prox-core/src/main/scala/io/github/vigoo/prox/runtime.scala index eb95bdd4..ff63313f 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/runtime.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/runtime.scala @@ -23,31 +23,61 @@ trait ProxRuntime { protected def unit: ProxIO[Unit] protected def pure[A](value: A): ProxIO[A] protected def effect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] - protected def blockingEffect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] + protected def blockingEffect[A]( + f: => A, + wrapError: Throwable => ProxError + ): ProxIO[A] protected def raiseError(error: ProxError): ProxIO[Unit] protected def ioMap[A, B](io: ProxIO[A], f: A => B): ProxIO[B] protected def ioFlatMap[A, B](io: ProxIO[A], f: A => ProxIO[B]): ProxIO[B] - protected def traverse[A, B](list: List[A])(f: A => ProxIO[B]): ProxIO[List[B]] + protected def traverse[A, B](list: List[A])( + f: A => ProxIO[B] + ): ProxIO[List[B]] protected def identityPipe[A]: ProxPipe[A, A] - protected def bracket[A, B](acquire: ProxIO[A])(use: A => ProxIO[B])(fin: (A, IOResult) => ProxIO[Unit]): ProxIO[B] + protected def bracket[A, B](acquire: ProxIO[A])(use: A => ProxIO[B])( + fin: (A, IOResult) => ProxIO[Unit] + ): ProxIO[B] - protected def makeResource[A](acquire: ProxIO[A], release: A => ProxIO[Unit]): ProxResource[A] - protected def useResource[A, B](r: ProxResource[A], f: A => ProxIO[B]): ProxIO[B] + protected def makeResource[A]( + acquire: ProxIO[A], + release: A => ProxIO[Unit] + ): ProxResource[A] + protected def useResource[A, B]( + r: ProxResource[A], + f: A => ProxIO[B] + ): ProxIO[B] protected def joinFiber[A](f: ProxFiber[A]): ProxIO[A] protected def cancelFiber[A](f: ProxFiber[A]): ProxIO[Unit] protected def drainStream[A](s: ProxStream[A]): ProxIO[Unit] protected def streamToVector[A](s: ProxStream[A]): ProxIO[Vector[A]] - protected def foldStream[A, B](s: ProxStream[A], init: B, f: (B, A) => B): ProxIO[B] - protected def foldMonoidStream[A : ProxMonoid](s: ProxStream[A]): ProxIO[A] - protected def streamThrough[A, B](s: ProxStream[A], pipe: ProxPipe[A, B]): ProxStream[B] - protected def runStreamTo[A](s: ProxStream[A], sink: ProxSink[A]): ProxIO[Unit] - - protected def fromJavaInputStream(input: java.io.InputStream, chunkSize: Int): ProxStream[Byte] - protected def drainToJavaOutputStream(stream: ProxStream[Byte], output: java.io.OutputStream, flushChunks: Boolean): ProxIO[Unit] + protected def foldStream[A, B]( + s: ProxStream[A], + init: B, + f: (B, A) => B + ): ProxIO[B] + protected def foldMonoidStream[A: ProxMonoid](s: ProxStream[A]): ProxIO[A] + protected def streamThrough[A, B]( + s: ProxStream[A], + pipe: ProxPipe[A, B] + ): ProxStream[B] + protected def runStreamTo[A]( + s: ProxStream[A], + sink: ProxSink[A] + ): ProxIO[Unit] + + protected def fromJavaInputStream( + input: java.io.InputStream, + chunkSize: Int + ): ProxStream[Byte] + protected def drainToJavaOutputStream( + stream: ProxStream[Byte], + output: java.io.OutputStream, + flushChunks: Boolean + ): ProxIO[Unit] protected def startFiber[A](f: ProxIO[A]): ProxIO[ProxFiber[A]] @@ -73,16 +103,17 @@ trait ProxRuntime { def run(sink: ProxSink[A]): ProxIO[Unit] = runStreamTo(s, sink) } - protected implicit class MonoidStreamOps[A : ProxMonoid](s: ProxStream[A]) { + protected implicit class MonoidStreamOps[A: ProxMonoid](s: ProxStream[A]) { def foldMonoid: ProxIO[A] = foldMonoidStream(s) } protected implicit class ListProxErrorOps(list: List[ProxError]) { def toSingleError: ProxError = list match { - case Nil => UnknownProxError(new IllegalArgumentException("Error list is empty")) + case Nil => + UnknownProxError(new IllegalArgumentException("Error list is empty")) case List(single) => single - case _ => MultipleProxErrors(list) + case _ => MultipleProxErrors(list) } } } diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/syntax.scala b/prox-core/src/main/scala/io/github/vigoo/prox/syntax.scala index c90bd188..c43f6dd7 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/syntax.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/syntax.scala @@ -3,22 +3,33 @@ package io.github.vigoo.prox trait SyntaxModule { this: Prox => - /** Extension methods for unbound processes enabling the creation of process groups */ + /** Extension methods for unbound processes enabling the creation of process + * groups + */ implicit class ProcessPiping(process: Process.UnboundProcess) { - /** - * Attaches the output of this process to an other process' input + /** Attaches the output of this process to an other process' input * * Use the [[|]] or the [[via]] methods instead for more readability. * - * @param other The other process - * @param channel Pipe between the two processes - * @return Returns a [[ProcessGroup]] + * @param other + * The other process + * @param channel + * Pipe between the two processes + * @return + * Returns a [[ProcessGroup]] */ - def pipeInto(other: Process.UnboundProcess, - channel: ProxPipe[Byte, Byte]): ProcessGroup.ProcessGroupImpl = { + def pipeInto( + other: Process.UnboundProcess, + channel: ProxPipe[Byte, Byte] + ): ProcessGroup.ProcessGroupImpl = { - val p1 = process.connectOutput(OutputStreamThroughPipe(channel, (stream: ProxStream[Byte]) => pure(stream))) + val p1 = process.connectOutput( + OutputStreamThroughPipe( + channel, + (stream: ProxStream[Byte]) => pure(stream) + ) + ) ProcessGroup.ProcessGroupImpl( p1, @@ -28,46 +39,59 @@ trait SyntaxModule { ) } - /** - * Attaches the output of this process to an other process' input + /** Attaches the output of this process to an other process' input * - * @param other The other process - * @return Returns a [[ProcessGroup]] + * @param other + * The other process + * @return + * Returns a [[ProcessGroup]] */ def |(other: Process.UnboundProcess): ProcessGroup.ProcessGroupImpl = pipeInto(other, identityPipe) - /** - * Attaches the output of this process to an other process' input with a custom channel + /** Attaches the output of this process to an other process' input with a + * custom channel * * There is a syntax helper step to allow the following syntax: * {{{ * val processGroup = process1.via(channel).to(process2) * }}} * - * @param channel Pipe between the two processes - * @return Returns a syntax helper trait that has a [[PipeBuilderSyntax.to]] method to finish the construction + * @param channel + * Pipe between the two processes + * @return + * Returns a syntax helper trait that has a [[PipeBuilderSyntax.to]] + * method to finish the construction */ - def via(channel: ProxPipe[Byte, Byte]): PipeBuilderSyntax[ProcessGroup.ProcessGroupImpl] = - new PipeBuilderSyntax(new PipeBuilder[ProcessGroup.ProcessGroupImpl] { - override def build(other: Process.UnboundProcess, channel: ProxPipe[Byte, Byte]): ProcessGroup.ProcessGroupImpl = - process.pipeInto(other, channel) - }, channel) + def via( + channel: ProxPipe[Byte, Byte] + ): PipeBuilderSyntax[ProcessGroup.ProcessGroupImpl] = + new PipeBuilderSyntax( + new PipeBuilder[ProcessGroup.ProcessGroupImpl] { + override def build( + other: Process.UnboundProcess, + channel: ProxPipe[Byte, Byte] + ): ProcessGroup.ProcessGroupImpl = + process.pipeInto(other, channel) + }, + channel + ) } trait PipeBuilder[P] { - def build(other: Process.UnboundProcess, - channel: ProxPipe[Byte, Byte]): P + def build(other: Process.UnboundProcess, channel: ProxPipe[Byte, Byte]): P } - class PipeBuilderSyntax[P](builder: PipeBuilder[P], channel: ProxPipe[Byte, Byte]) { + class PipeBuilderSyntax[P]( + builder: PipeBuilder[P], + channel: ProxPipe[Byte, Byte] + ) { def to(other: Process.UnboundProcess): P = builder.build(other, channel) } - /** - * String interpolator for an alternative of [[Process.apply]] + /** String interpolator for an alternative of [[Process.apply]] * * {{{ * val process = proc"ls -hal $dir" @@ -78,17 +102,25 @@ trait SyntaxModule { def proc(args: Any*): Process.ProcessImpl = { val staticParts = ctx.parts.map(Left.apply) val injectedParts = args.map(Right.apply) - val parts = staticParts.zipAll(injectedParts, Left(""), Right("")).flatMap { case (a, b) => List(a, b) } - val words = parts.flatMap { - case Left(value) => value.trim.split(' ') - case Right(value) => List(value.toString) - }.filter(_.nonEmpty).toList + val parts = + staticParts.zipAll(injectedParts, Left(""), Right("")).flatMap { + case (a, b) => List(a, b) + } + val words = parts + .flatMap { + case Left(value) => value.trim.split(' ') + case Right(value) => List(value.toString) + } + .filter(_.nonEmpty) + .toList words match { case head :: remaining => Process(head, remaining) case Nil => - throw new IllegalArgumentException(s"The proc interpolator needs at least a process name") + throw new IllegalArgumentException( + s"The proc interpolator needs at least a process name" + ) } } } -} \ No newline at end of file +} diff --git a/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala b/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala index 931ba349..b58c7b6f 100644 --- a/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala +++ b/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala @@ -22,102 +22,161 @@ trait ProxFS2[F[_]] extends Prox { override type ProxMonoid[A] = cats.kernel.Monoid[A] - protected override final def exitCodeFromInt(value: Int): ProxExitCode = cats.effect.ExitCode(value) + protected override final def exitCodeFromInt(value: Int): ProxExitCode = + cats.effect.ExitCode(value) - protected override final def unit: ProxIO[Unit] = Applicative[F](instances).unit + protected override final def unit: ProxIO[Unit] = + Applicative[F](instances).unit - protected override final def pure[A](value: A): ProxIO[A] = Applicative[F](instances).pure(value) + protected override final def pure[A](value: A): ProxIO[A] = + Applicative[F](instances).pure(value) - protected override final def effect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = { + protected override final def effect[A]( + f: => A, + wrapError: Throwable => ProxError + ): ProxIO[A] = { implicit val i: Sync[F] with Concurrent[F] = instances - Sync[F].adaptError(Sync[F].delay(f)) { - case failure: Throwable => wrapError(failure).toThrowable + Sync[F].adaptError(Sync[F].delay(f)) { case failure: Throwable => + wrapError(failure).toThrowable } } - protected override final def blockingEffect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = { + protected override final def blockingEffect[A]( + f: => A, + wrapError: Throwable => ProxError + ): ProxIO[A] = { implicit val i: Sync[F] with Concurrent[F] = instances Sync[F].adaptError(Sync[F].interruptibleMany(f)) { case failure: Throwable => wrapError(failure).toThrowable } } - protected override final def raiseError(error: ProxError): ProxIO[Unit] = ApplicativeError[F, Throwable](instances).raiseError(error.toThrowable) + protected override final def raiseError(error: ProxError): ProxIO[Unit] = + ApplicativeError[F, Throwable](instances).raiseError(error.toThrowable) - protected override final def ioMap[A, B](io: ProxIO[A], f: A => B): ProxIO[B] = Applicative[F](instances).map(io)(f) + protected override final def ioMap[A, B]( + io: ProxIO[A], + f: A => B + ): ProxIO[B] = Applicative[F](instances).map(io)(f) - protected override final def ioFlatMap[A, B](io: ProxIO[A], f: A => ProxIO[B]): ProxIO[B] = FlatMap[F](instances).flatMap(io)(f) + protected override final def ioFlatMap[A, B]( + io: ProxIO[A], + f: A => ProxIO[B] + ): ProxIO[B] = FlatMap[F](instances).flatMap(io)(f) - protected override final def traverse[A, B](list: List[A])(f: A => ProxIO[B]): ProxIO[List[B]] = Traverse[List].traverse(list)(f)(instances) + protected override final def traverse[A, B](list: List[A])( + f: A => ProxIO[B] + ): ProxIO[List[B]] = Traverse[List].traverse(list)(f)(instances) - protected override final def identityPipe[A]: ProxPipe[A, A] = identity[ProxStream[A]] + protected override final def identityPipe[A]: ProxPipe[A, A] = + identity[ProxStream[A]] - protected override final def bracket[A, B](acquire: ProxIO[A])(use: A => ProxIO[B])(fin: (A, IOResult) => ProxIO[Unit]): ProxIO[B] = + protected override final def bracket[A, B]( + acquire: ProxIO[A] + )(use: A => ProxIO[B])(fin: (A, IOResult) => ProxIO[Unit]): ProxIO[B] = Sync[F](instances).bracketCase(acquire)(use) { case (value, Outcome.Succeeded(_)) => fin(value, Completed) - case (value, Outcome.Errored(error)) => fin(value, Failed(List(UnknownProxError(error)))) + case (value, Outcome.Errored(error)) => + fin(value, Failed(List(UnknownProxError(error)))) case (value, Outcome.Canceled()) => fin(value, Canceled) } - protected override final def makeResource[A](acquire: ProxIO[A], release: A => ProxIO[Unit]): ProxResource[A] = cats.effect.Resource.make(acquire)(release)(instances) + protected override final def makeResource[A]( + acquire: ProxIO[A], + release: A => ProxIO[Unit] + ): ProxResource[A] = cats.effect.Resource.make(acquire)(release)(instances) - protected override final def useResource[A, B](r: ProxResource[A], f: A => ProxIO[B]): ProxIO[B] = r.use(f)(instances) + protected override final def useResource[A, B]( + r: ProxResource[A], + f: A => ProxIO[B] + ): ProxIO[B] = r.use(f)(instances) - protected override final def joinFiber[A](f: ProxFiber[A]): ProxIO[A] = f.joinWithNever(instances) + protected override final def joinFiber[A](f: ProxFiber[A]): ProxIO[A] = + f.joinWithNever(instances) - protected override final def cancelFiber[A](f: ProxFiber[A]): ProxIO[Unit] = f.cancel + protected override final def cancelFiber[A](f: ProxFiber[A]): ProxIO[Unit] = + f.cancel - protected override final def startFiber[A](f: ProxIO[A]): ProxIO[ProxFiber[A]] = { + protected override final def startFiber[A]( + f: ProxIO[A] + ): ProxIO[ProxFiber[A]] = { implicit val i: Sync[F] with Concurrent[F] = instances Concurrent[F].start(f) } - protected override final def drainStream[A](s: ProxStream[A]): ProxIO[Unit] = { + protected override final def drainStream[A]( + s: ProxStream[A] + ): ProxIO[Unit] = { implicit val i: Sync[F] with Concurrent[F] = instances s.compile.drain } - protected override final def streamToVector[A](s: ProxStream[A]): ProxIO[Vector[A]] = { + protected override final def streamToVector[A]( + s: ProxStream[A] + ): ProxIO[Vector[A]] = { implicit val i: Sync[F] with Concurrent[F] = instances s.compile.toVector } - protected override final def foldStream[A, B](s: ProxStream[A], init: B, f: (B, A) => B): ProxIO[B] = { + protected override final def foldStream[A, B]( + s: ProxStream[A], + init: B, + f: (B, A) => B + ): ProxIO[B] = { implicit val i: Sync[F] with Concurrent[F] = instances s.compile.fold(init)(f) } - protected override final def foldMonoidStream[A: ProxMonoid](s: ProxStream[A]): ProxIO[A] = { + protected override final def foldMonoidStream[A: ProxMonoid]( + s: ProxStream[A] + ): ProxIO[A] = { implicit val i: Sync[F] with Concurrent[F] = instances s.compile.foldMonoid } - protected override final def streamThrough[A, B](s: ProxStream[A], pipe: ProxPipe[A, B]): ProxStream[B] = s.through(pipe) + protected override final def streamThrough[A, B]( + s: ProxStream[A], + pipe: ProxPipe[A, B] + ): ProxStream[B] = s.through(pipe) - protected override final def runStreamTo[A](s: ProxStream[A], sink: ProxSink[A]): ProxIO[Unit] = { + protected override final def runStreamTo[A]( + s: ProxStream[A], + sink: ProxSink[A] + ): ProxIO[Unit] = { implicit val i: Sync[F] with Concurrent[F] = instances s.through(sink).compile.drain } - protected override final def fromJavaInputStream(input: io.InputStream, chunkSize: Int): ProxStream[Byte] = - fs2.io.readInputStream( - pure(input), - chunkSize, - closeAfterUse = true)(instances) - - protected override final def drainToJavaOutputStream(stream: ProxStream[Byte], output: io.OutputStream, flushChunks: Boolean): ProxIO[Unit] = { + protected override final def fromJavaInputStream( + input: io.InputStream, + chunkSize: Int + ): ProxStream[Byte] = + fs2.io.readInputStream(pure(input), chunkSize, closeAfterUse = true)( + instances + ) + + protected override final def drainToJavaOutputStream( + stream: ProxStream[Byte], + output: io.OutputStream, + flushChunks: Boolean + ): ProxIO[Unit] = { implicit val i: Sync[F] with Concurrent[F] = instances stream .through( if (flushChunks) writeAndFlushOutputStream(output)(_).drain - else fs2.io.writeOutputStream( - effect(output, UnknownProxError.apply), - closeAfterUse = true)) + else + fs2.io.writeOutputStream( + effect(output, UnknownProxError.apply), + closeAfterUse = true + ) + ) .compile .drain } - private def writeAndFlushOutputStream(stream: java.io.OutputStream): ProxPipe[Byte, Unit] = { + private def writeAndFlushOutputStream( + stream: java.io.OutputStream + ): ProxPipe[Byte, Unit] = { implicit val i: Sync[F] with Concurrent[F] = instances s => { fs2.Stream diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala index 68a8c10b..0e2d9359 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala @@ -20,7 +20,6 @@ object InterpolatorSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) ) }, - proxTest("works with interpolated process name") { prox => import prox._ @@ -34,37 +33,44 @@ object InterpolatorSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) ) }, - proxTest("works with static parameters") { prox => import prox._ val process = proc"ls -hal tmp" ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(equalTo(List("-hal", "tmp")))) + assert(process.command)(equalTo("ls")) && assert(process.arguments)( + equalTo(List("-hal", "tmp")) + ) + ) }, + proxTest("works with static parameters and interpolated process name") { + prox => + import prox._ - proxTest("works with static parameters and interpolated process name") { prox => - import prox._ - - val cmd = "ls" - val process = proc"$cmd -hal tmp" + val cmd = "ls" + val process = proc"$cmd -hal tmp" - ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(equalTo(List("-hal", "tmp")))) + ZIO.succeed( + assert(process.command)(equalTo("ls")) && assert( + process.arguments + )(equalTo(List("-hal", "tmp"))) + ) }, - - proxTest("works with static process name and interpolated parameters") { prox => - import prox._ - - val p1 = "-hal" - val p2 = "tmp" - val process = proc"ls $p1 $p2" - - ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(equalTo(List("-hal", "tmp")))) + proxTest("works with static process name and interpolated parameters") { + prox => + import prox._ + + val p1 = "-hal" + val p2 = "tmp" + val process = proc"ls $p1 $p2" + + ZIO.succeed( + assert(process.command)(equalTo("ls")) && assert( + process.arguments + )(equalTo(List("-hal", "tmp"))) + ) }, - proxTest("works with interpolated name and parameters") { prox => import prox._ @@ -74,19 +80,25 @@ object InterpolatorSpecs extends ZIOSpecDefault with ProxSpecHelpers { val process = proc"$cmd $p1 $p2" ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)(equalTo(List("-hal", "tmp")))) + assert(process.command)(equalTo("ls")) && assert(process.arguments)( + equalTo(List("-hal", "tmp")) + ) + ) }, - - proxTest("works with mixed static and interpolated parameters") { prox => - import prox._ - - val p1 = "hello" - val p2 = "dear visitor" - val process = proc"echo $p1, $p2!!!" - - ZIO.succeed( - assert(process.command)(equalTo("echo")) && - assert(process.arguments)(equalTo(List("hello", ",", "dear visitor", "!!!")))) + proxTest("works with mixed static and interpolated parameters") { + prox => + import prox._ + + val p1 = "hello" + val p2 = "dear visitor" + val process = proc"echo $p1, $p2!!!" + + ZIO.succeed( + assert(process.command)(equalTo("echo")) && + assert(process.arguments)( + equalTo(List("hello", ",", "dear visitor", "!!!")) + ) + ) } ) ) diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala index a5b8e1fb..bc091dfb 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala @@ -17,94 +17,117 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest("is possible with two") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val processGroup = (Process("echo", List("This is a test string")) | Process("wc", List("-w"))) ># fs2.text.utf8.decode + val processGroup = (Process( + "echo", + List("This is a test string") + ) | Process("wc", List("-w"))) ># fs2.text.utf8.decode val program = processGroup.run().map(_.output.trim) program.map(r => assert(r)(equalTo("5"))) }, - proxTest("is possible with multiple") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val processGroup = ( Process("echo", List("cat\ncat\ndog\napple")) | Process("sort") | Process("uniq", List("-c")) | Process("head", List("-n 1")) - ) >? fs2.text.utf8.decode.andThen(_.through(fs2.text.lines)) + ) >? fs2.text.utf8.decode.andThen(_.through(fs2.text.lines)) - val program = processGroup.run().map( - r => r.output.map(_.stripLineEnd.trim).filter(_.nonEmpty) - ) + val program = processGroup + .run() + .map(r => r.output.map(_.stripLineEnd.trim).filter(_.nonEmpty)) program.map(r => assert(r)(hasSameElements(List("1 apple")))) }, - proxTest("is customizable with pipes") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val customPipe: fs2.Pipe[Task, Byte, Byte] = - (s: fs2.Stream[Task, Byte]) => s - .through(fs2.text.utf8.decode) - .through(fs2.text.lines) - .map(_.split(' ').toVector) - .map(v => v.map(_ + " !!!").mkString(" ")) - .intersperse("\n") - .through(fs2.text.utf8.encode) - - val processGroup = (Process("echo", List("This is a test string")).via(customPipe).to(Process("wc", List("-w")))) ># fs2.text.utf8.decode + (s: fs2.Stream[Task, Byte]) => + s + .through(fs2.text.utf8.decode) + .through(fs2.text.lines) + .map(_.split(' ').toVector) + .map(v => v.map(_ + " !!!").mkString(" ")) + .intersperse("\n") + .through(fs2.text.utf8.encode) + + val processGroup = (Process("echo", List("This is a test string")) + .via(customPipe) + .to(Process("wc", List("-w")))) ># fs2.text.utf8.decode val program = processGroup.run().map(_.output.trim) program.map(r => assert(r)(equalTo("11"))) }, - proxTest("can be mapped") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val processGroup1 = (Process("!echo", List("This is a test string")) | Process("!wc", List("-w"))) ># fs2.text.utf8.decode - val processGroup2 = processGroup1.map(new ProcessGroup.Mapper[String, Unit] { - override def mapFirst[P <: Process[fs2.Stream[Task, Byte], Unit]](process: P): P = process.withCommand(process.command.tail).asInstanceOf[P] - - override def mapInnerWithIdx[P <: Process.UnboundIProcess[fs2.Stream[Task, Byte], Unit]](process: P, idx: Int): P = process.withCommand(process.command.tail).asInstanceOf[P] - - override def mapLast[P <: Process.UnboundIProcess[String, Unit]](process: P): P = process.withCommand(process.command.tail).asInstanceOf[P] - }) + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner + + val processGroup1 = (Process( + "!echo", + List("This is a test string") + ) | Process("!wc", List("-w"))) ># fs2.text.utf8.decode + val processGroup2 = + processGroup1.map(new ProcessGroup.Mapper[String, Unit] { + override def mapFirst[P <: Process[fs2.Stream[Task, Byte], Unit]]( + process: P + ): P = process.withCommand(process.command.tail).asInstanceOf[P] + + override def mapInnerWithIdx[ + P <: Process.UnboundIProcess[fs2.Stream[Task, Byte], Unit] + ](process: P, idx: Int): P = + process.withCommand(process.command.tail).asInstanceOf[P] + + override def mapLast[P <: Process.UnboundIProcess[String, Unit]]( + process: P + ): P = process.withCommand(process.command.tail).asInstanceOf[P] + }) val program = processGroup2.run().map(_.output.trim) program.map(r => assert(r)(equalTo("5"))) } ), - suite("Termination")( proxTest("can be terminated with cancellation") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val processGroup = - Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) | + Process( + "perl", + List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") + ) | Process("sort") val program = processGroup.start().use { fiber => fiber.cancel } program.map(r => assert(r)(equalTo(()))) } @@ TestAspect.timeout(5.seconds), - proxTest("can be terminated") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val p1 = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) + val p1 = Process( + "perl", + List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") + ) val p2 = Process("sort") val processGroup = p1 | p2 @@ -114,15 +137,22 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { result <- runningProcesses.terminate() } yield result.exitCodes.toList - program.map(r => assert(r)(contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1)))) + program.map(r => + assert(r)( + contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1)) + ) + ) } @@ TestAspect.withLiveClock, - proxTest("can be killed") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val p1 = Process("perl", List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""")) + val p1 = Process( + "perl", + List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""") + ) val p2 = Process("sort") val processGroup = p1 | p2 @@ -137,33 +167,45 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map(r => assert(r)(contains(p1 -> ExitCode(137)))) } @@ TestAspect.withLiveClock ), - suite("Input redirection")( proxTest("can be fed with an input stream") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val processGroup = (Process("cat") | Process("wc", List("-w"))) < stream ># fs2.text.utf8.decode + val stream = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val processGroup = (Process("cat") | Process( + "wc", + List("-w") + )) < stream ># fs2.text.utf8.decode val program = processGroup.run().map(_.output.trim) program.map(r => assert(r)(equalTo("5"))) }, - proxTest("can be fed with an input file") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner withTempFile { tempFile => val program = for { - _ <- ZIO.attempt(java.nio.file.Files.write(tempFile.toPath, "This is a test string".getBytes("UTF-8"))) - processGroup = (Process("cat") | Process("wc", List("-w"))) < tempFile.toPath ># fs2.text.utf8.decode + _ <- ZIO.attempt( + java.nio.file.Files.write( + tempFile.toPath, + "This is a test string".getBytes("UTF-8") + ) + ) + processGroup = (Process("cat") | Process( + "wc", + List("-w") + )) < tempFile.toPath ># fs2.text.utf8.decode result <- processGroup.run() } yield result.output.trim - program .map(r => assert(r)(equalTo("5"))) + program.map(r => assert(r)(equalTo("5"))) } } ), @@ -171,25 +213,38 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest("output can be redirected to file") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner withTempFile { tempFile => - val processGroup = (Process("echo", List("This is a test string")) | Process("wc", List("-w"))) > tempFile.toPath + val processGroup = (Process( + "echo", + List("This is a test string") + ) | Process("wc", List("-w"))) > tempFile.toPath val program = for { _ <- processGroup.run() - contents <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents <- Files + .forAsync[Task] + .readAll( + fs2.io.file.Path.fromNioPath(tempFile.toPath), + 1024, + Flags.Read + ) + .through(fs2.text.utf8.decode) + .compile + .foldMonoid } yield contents.trim program.map(r => assert(r)(equalTo("5"))) } - }, + } ), - suite("Error redirection")( proxTest("can redirect each error output to a stream") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) @@ -198,23 +253,24 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo("world"))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo("world"))) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - proxTest("can redirect each error output to a sink") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val builder = new StringBuilder - val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => ZIO.attempt { - builder.append(byte.toChar) - }.unit) + val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => + ZIO.attempt { + builder.append(byte.toChar) + }.unit + ) val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) @@ -223,23 +279,26 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map { result => assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(builder.toString.toSeq.sorted)(equalTo("Helloworld".toSeq.sorted)) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo(()))) && + assert(result.output)(equalTo(())) && + assert(builder.toString.toSeq.sorted)( + equalTo("Helloworld".toSeq.sorted) + ) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - proxTest("can redirect each error output to a vector") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world!"""")) - val stream = fs2.text.utf8.decode[Task] + val stream = fs2.text.utf8 + .decode[Task] .andThen(fs2.text.lines) .andThen(_.map(s => s.length)) @@ -248,17 +307,17 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map { result => assert(result.errors.get(p1))(isSome(hasSameElements(List(5)))) && - assert(result.errors.get(p2))(isSome(hasSameElements(List(6)))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(hasSameElements(List(6)))) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - proxTest("can drain each error output") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) @@ -267,17 +326,17 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map { result => assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo(()))) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - proxTest("can fold each error output") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val p1 = Process("perl", List("-e", "print STDERR 'Hello\nworld'")) val p2 = Process("perl", List("-e", "print STDERR 'Does\nit\nwork?'")) @@ -289,42 +348,52 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val program = processGroup.run() program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(Vector(Some('H'), Some('w'))))) && - assert(result.errors.get(p2))(isSome(equalTo(Vector(Some('D'), Some('i'), Some('w'))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p1))( + isSome(equalTo(Vector(Some('H'), Some('w')))) + ) && + assert(result.errors.get(p2))( + isSome(equalTo(Vector(Some('D'), Some('i'), Some('w')))) + ) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } - }, + } ), suite("Error redirection customized per process")( - proxTest("can redirect each error output to a stream customized per process") { prox => + proxTest( + "can redirect each error output to a stream customized per process" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) val processGroup = (p1 | p2).customizedPerProcess.errorsToFoldMonoid { - case p if p == p1 => fs2.text.utf8.decode.andThen(_.map(s => "P1: " + s)) - case p if p == p2 => fs2.text.utf8.decode.andThen(_.map(s => "P2: " + s)) + case p if p == p1 => + fs2.text.utf8.decode.andThen(_.map(s => "P1: " + s)) + case p if p == p2 => + fs2.text.utf8.decode.andThen(_.map(s => "P2: " + s)) } val program = processGroup.run() program.map { result => assert(result.errors.get(p1))(isSome(equalTo("P1: Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo("P2: world"))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo("P2: world"))) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - - proxTest("can redirect each error output to a sink customized per process") { prox => + proxTest( + "can redirect each error output to a sink customized per process" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val builder1 = new StringBuilder val builder2 = new StringBuilder @@ -332,36 +401,44 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) val processGroup = (p1 | p2).customizedPerProcess.errorsToSink { - case p if p == p1 => _.evalMap(byte => ZIO.attempt { - builder1.append(byte.toChar) - }.unit) + case p if p == p1 => + _.evalMap(byte => + ZIO.attempt { + builder1.append(byte.toChar) + }.unit + ) case p if p == p2 => - _.evalMap(byte => ZIO.attempt { - builder2.append(byte.toChar) - }.unit) + _.evalMap(byte => + ZIO.attempt { + builder2.append(byte.toChar) + }.unit + ) } val program = processGroup.run() program.map { result => assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(builder1.toString)(equalTo("Hello")) && - assert(builder2.toString)(equalTo("world")) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo(()))) && + assert(result.output)(equalTo(())) && + assert(builder1.toString)(equalTo("Hello")) && + assert(builder2.toString)(equalTo("world")) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - - proxTest("can redirect each error output to a vector customized per process") { prox => + proxTest( + "can redirect each error output to a vector customized per process" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world!"""")) - val stream = fs2.text.utf8.decode[Task] + val stream = fs2.text.utf8 + .decode[Task] .andThen(fs2.text.lines) .andThen(_.map(s => s.length)) @@ -372,47 +449,57 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val program = processGroup.run() program.map { result => - assert(result.errors.get(p1))(isSome(hasSameElements(List((1, 5))))) && - assert(result.errors.get(p2))(isSome(hasSameElements(List((2, 6))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p1))( + isSome(hasSameElements(List((1, 5)))) + ) && + assert(result.errors.get(p2))( + isSome(hasSameElements(List((2, 6)))) + ) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - proxTest("can drain each error output customized per process") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2).customizedPerProcess.drainErrors(_ => fs2.text.utf8.decode) + val processGroup = (p1 | p2).customizedPerProcess.drainErrors(_ => + fs2.text.utf8.decode + ) val program = processGroup.run() program.map { result => assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo(()))) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - proxTest("can fold each error output customized per process") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val p1 = Process("perl", List("-e", "print STDERR 'Hello\nworld'")) val p2 = Process("perl", List("-e", "print STDERR 'Does\nit\nwork?'")) val processGroup = (p1 | p2).customizedPerProcess.foldErrors( { - case p if p == p1 => fs2.text.utf8.decode[Task] - .andThen(fs2.text.lines) - case p if p == p2 => fs2.text.utf8.decode[Task] - .andThen(fs2.text.lines) - .andThen(_.map(_.reverse)) + case p if p == p1 => + fs2.text.utf8 + .decode[Task] + .andThen(fs2.text.lines) + case p if p == p2 => + fs2.text.utf8 + .decode[Task] + .andThen(fs2.text.lines) + .andThen(_.map(_.reverse)) }, Vector.empty, (l: Vector[Option[Char]], s: String) => l :+ s.headOption @@ -420,18 +507,22 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val program = processGroup.run() program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(Vector(Some('H'), Some('w'))))) && - assert(result.errors.get(p2))(isSome(equalTo(Vector(Some('s'), Some('t'), Some('?'))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p1))( + isSome(equalTo(Vector(Some('H'), Some('w')))) + ) && + assert(result.errors.get(p2))( + isSome(equalTo(Vector(Some('s'), Some('t'), Some('?')))) + ) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - proxTest("can redirect each error output to file") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner withTempFile { tempFile1 => withTempFile { tempFile2 => @@ -443,143 +534,229 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { } val program = for { _ <- processGroup.run() - contents1 <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile1.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid - contents2 <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile2.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents1 <- Files + .forAsync[Task] + .readAll( + fs2.io.file.Path.fromNioPath(tempFile1.toPath), + 1024, + Flags.Read + ) + .through(fs2.text.utf8.decode) + .compile + .foldMonoid + contents2 <- Files + .forAsync[Task] + .readAll( + fs2.io.file.Path.fromNioPath(tempFile2.toPath), + 1024, + Flags.Read + ) + .through(fs2.text.utf8.decode) + .compile + .foldMonoid } yield (contents1, contents2) program.map(r => assert(r)(equalTo(("Hello", "world")))) } } - }, + } ), - suite("Redirection ordering")( - proxTest("can redirect each error output to a stream if fed with an input stream and redirected to an output stream") { prox => + proxTest( + "can redirect each error output to a stream if fed with an input stream and redirected to an output stream" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner + + val stream = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = (p1 | p2 | p3) < stream ># fs2.text.utf8.decode !># fs2.text.utf8.decode + val processGroup = + (p1 | p2 | p3) < stream ># fs2.text.utf8.decode !># fs2.text.utf8.decode - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } }, - - proxTest("can redirect output if each error output and input are already redirected") { prox => + proxTest( + "can redirect output if each error output and input are already redirected" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner + + val stream = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) < stream !># fs2.text.utf8.decode) ># fs2.text.utf8.decode + val processGroup = + ((p1 | p2 | p3) < stream !># fs2.text.utf8.decode) ># fs2.text.utf8.decode - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } }, - - proxTest("can attach output and then input stream if each error output and standard output are already redirected") { prox => + proxTest( + "can attach output and then input stream if each error output and standard output are already redirected" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner + + val stream = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode < stream + val processGroup = + ((p1 | p2 | p3) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode < stream - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } }, - - proxTest("can attach input and then output stream if each error output and standard output are already redirected") { prox => + proxTest( + "can attach input and then output stream if each error output and standard output are already redirected" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner + + val stream = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) !># fs2.text.utf8.decode) < stream ># fs2.text.utf8.decode + val processGroup = + ((p1 | p2 | p3) !># fs2.text.utf8.decode) < stream ># fs2.text.utf8.decode - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } }, - - proxTest("can attach input stream and errors if standard output is already redirected") { prox => + proxTest( + "can attach input stream and errors if standard output is already redirected" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner + + val stream = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) ># fs2.text.utf8.decode) < stream !># fs2.text.utf8.decode + val processGroup = + ((p1 | p2 | p3) ># fs2.text.utf8.decode) < stream !># fs2.text.utf8.decode - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } }, - - proxTest("can attach errors and finally input stream if standard output is already redirected") { prox => + proxTest( + "can attach errors and finally input stream if standard output is already redirected" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val stream = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner + + val stream = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = (((p1 | p2 | p3) ># fs2.text.utf8.decode) !># fs2.text.utf8.decode) < stream + val processGroup = + (((p1 | p2 | p3) ># fs2.text.utf8.decode) !># fs2.text.utf8.decode) < stream - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } - }, + } ), - test("bound process is not pipeable") { - typeCheck("""val bad = (Process("echo", List("Hello world")) ># fs2.text.utf8.decode) | Process("wc", List("-w"))""").map(r => - assert(r)( - isLeft(anything) - )) + typeCheck( + """val bad = (Process("echo", List("Hello world")) ># fs2.text.utf8.decode) | Process("wc", List("-w"))""" + ).map(r => + assert(r)( + isLeft(anything) + ) + ) } ) @@ timeout(60.seconds) @@ sequential } diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala index 60c8675d..703e2444 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala @@ -14,7 +14,8 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest("returns the exit code") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val program = for { trueResult <- Process("true").run() @@ -23,28 +24,38 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map(r => assert(r)(equalTo((ExitCode(0), ExitCode(1))))) }, - suite("Output redirection")( proxTest("can redirect output to a file") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner withTempFile { tempFile => - val process = Process("echo", List("Hello world!")) > tempFile.toPath + val process = + Process("echo", List("Hello world!")) > tempFile.toPath val program = for { _ <- process.run() - contents <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents <- Files + .forAsync[Task] + .readAll( + fs2.io.file.Path.fromNioPath(tempFile.toPath), + 1024, + Flags.Read + ) + .through(fs2.text.utf8.decode) + .compile + .foldMonoid } yield contents program.map(r => assert(r)(equalTo("Hello world!\n"))) } }, - proxTest("can redirect output to append a file") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner withTempFile { tempFile => val process1 = Process("echo", List("Hello")) > tempFile.toPath @@ -52,66 +63,89 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val program = for { _ <- process1.run() _ <- process2.run() - contents <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents <- Files + .forAsync[Task] + .readAll( + fs2.io.file.Path.fromNioPath(tempFile.toPath), + 1024, + Flags.Read + ) + .through(fs2.text.utf8.decode) + .compile + .foldMonoid } yield contents program.map(r => assert(r)(equalTo("Hello\nworld\n"))) } }, - proxTest("can redirect output to stream") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = Process("echo", List("Hello world!")) ># fs2.text.utf8.decode + val process = + Process("echo", List("Hello world!")) ># fs2.text.utf8.decode val program = process.run().map(_.output) program.map(r => assert(r)(equalTo("Hello world!\n"))) }, - proxTest("can redirect output to stream folding monoid") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = Process("echo", List("Hello\nworld!")) ># fs2.text.utf8.decode.andThen(fs2.text.lines) + val process = Process( + "echo", + List("Hello\nworld!") + ) ># fs2.text.utf8.decode.andThen(fs2.text.lines) val program = process.run().map(_.output) program.map(r => assert(r)(equalTo("Helloworld!"))) }, - proxTest("can redirect output to stream collected to vector") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner case class StringLength(value: Int) - val stream = fs2.text.utf8.decode[Task] + val stream = fs2.text.utf8 + .decode[Task] .andThen(fs2.text.lines) .andThen(_.map(s => StringLength(s.length))) val process = Process("echo", List("Hello\nworld!")) >? stream val program = process.run().map(_.output) - program.map(r => assert(r)(hasSameElements(List(StringLength(5), StringLength(6), StringLength(0))))) + program.map(r => + assert(r)( + hasSameElements( + List(StringLength(5), StringLength(6), StringLength(0)) + ) + ) + ) }, + proxTest("can redirect output to stream and ignore it's result") { + prox => + import prox._ - proxTest("can redirect output to stream and ignore it's result") { prox => - import prox._ + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + val process = Process("echo", List("Hello\nworld!")).drainOutput( + fs2.text.utf8.decode.andThen(fs2.text.lines) + ) + val program = process.run().map(_.output) - val process = Process("echo", List("Hello\nworld!")).drainOutput(fs2.text.utf8.decode.andThen(fs2.text.lines)) - val program = process.run().map(_.output) - - program.map(r => assert(r)(equalTo(()))) + program.map(r => assert(r)(equalTo(()))) }, - proxTest("can redirect output to stream and fold it") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val process = Process("echo", List("Hello\nworld!")).foldOutput( fs2.text.utf8.decode.andThen(fs2.text.lines), @@ -120,115 +154,165 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo(Vector(Some('H'), Some('w'), None)))) + program.map(r => + assert(r)(equalTo(Vector(Some('H'), Some('w'), None))) + ) }, - proxTest("can redirect output to a sink") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val builder = new StringBuilder - val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => ZIO.attempt { - builder.append(byte.toChar) - }.unit) + val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => + ZIO.attempt { + builder.append(byte.toChar) + }.unit + ) val process = Process("echo", List("Hello world!")) > target val program = process.run().map(_ => builder.toString) program.map(r => assert(r)(equalTo("Hello world!\n"))) - }, + } ), suite("Error redirection")( proxTest("can redirect error to a file") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner withTempFile { tempFile => - val process = Process("perl", List("-e", "print STDERR 'Hello world!'")) !> tempFile.toPath + val process = Process( + "perl", + List("-e", "print STDERR 'Hello world!'") + ) !> tempFile.toPath val program = for { _ <- process.run() - contents <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents <- Files + .forAsync[Task] + .readAll( + fs2.io.file.Path.fromNioPath(tempFile.toPath), + 1024, + Flags.Read + ) + .through(fs2.text.utf8.decode) + .compile + .foldMonoid } yield contents program.map(r => assert(r)(equalTo("Hello world!"))) } }, - proxTest("can redirect error to append a file") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner withTempFile { tempFile => - val process1 = Process("perl", List("-e", "print STDERR Hello")) !> tempFile.toPath - val process2 = Process("perl", List("-e", "print STDERR world")) !>> tempFile.toPath + val process1 = Process( + "perl", + List("-e", "print STDERR Hello") + ) !> tempFile.toPath + val process2 = Process( + "perl", + List("-e", "print STDERR world") + ) !>> tempFile.toPath val program = for { _ <- process1.run() _ <- process2.run() - contents <- Files.forAsync[Task].readAll(fs2.io.file.Path.fromNioPath(tempFile.toPath), 1024, Flags.Read).through(fs2.text.utf8.decode).compile.foldMonoid + contents <- Files + .forAsync[Task] + .readAll( + fs2.io.file.Path.fromNioPath(tempFile.toPath), + 1024, + Flags.Read + ) + .through(fs2.text.utf8.decode) + .compile + .foldMonoid } yield contents program.map(r => assert(r)(equalTo("Helloworld"))) } }, - proxTest("can redirect error to stream") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = Process("perl", List("-e", """print STDERR "Hello"""")) !># fs2.text.utf8.decode + val process = Process( + "perl", + List("-e", """print STDERR "Hello"""") + ) !># fs2.text.utf8.decode val program = process.run().map(_.error) program.map(r => assert(r)(equalTo("Hello"))) }, - proxTest("can redirect error to stream folding monoid") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) !># fs2.text.utf8.decode.andThen(fs2.text.lines) + val process = Process( + "perl", + List("-e", "print STDERR 'Hello\nworld!'") + ) !># fs2.text.utf8.decode.andThen(fs2.text.lines) val program = process.run().map(_.error) program.map(r => assert(r)(equalTo("Helloworld!"))) }, - proxTest("can redirect error to stream collected to vector") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner case class StringLength(value: Int) - val stream = fs2.text.utf8.decode[Task] + val stream = fs2.text.utf8 + .decode[Task] .andThen(fs2.text.lines) .andThen(_.map(s => StringLength(s.length))) - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) !>? stream + val process = Process( + "perl", + List("-e", "print STDERR 'Hello\nworld!'") + ) !>? stream val program = process.run().map(_.error) - program.map(r => assert(r)(hasSameElements(List(StringLength(5), StringLength(6))))) + program.map(r => + assert(r)(hasSameElements(List(StringLength(5), StringLength(6)))) + ) }, + proxTest("can redirect error to stream and ignore it's result") { + prox => + import prox._ - proxTest("can redirect error to stream and ignore it's result") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")).drainError(fs2.text.utf8.decode.andThen(fs2.text.lines)) - val program = process.run().map(_.error) + val process = + Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) + .drainError(fs2.text.utf8.decode.andThen(fs2.text.lines)) + val program = process.run().map(_.error) - program.map(r => assert(r)(equalTo(()))) + program.map(r => assert(r)(equalTo(()))) }, - proxTest("can redirect error to stream and fold it") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")).foldError( + val process = Process( + "perl", + List("-e", "print STDERR 'Hello\nworld!'") + ).foldError( fs2.text.utf8.decode.andThen(fs2.text.lines), Vector.empty, (l: Vector[Option[Char]], s: String) => l :+ s.headOption @@ -237,157 +321,210 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map(r => assert(r)(equalTo(Vector(Some('H'), Some('w'))))) }, - proxTest("can redirect error to a sink") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val builder = new StringBuilder - val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => ZIO.attempt { - builder.append(byte.toChar) - }.unit) + val target: fs2.Pipe[Task, Byte, Unit] = _.evalMap(byte => + ZIO.attempt { + builder.append(byte.toChar) + }.unit + ) - val process = Process("perl", List("-e", """print STDERR "Hello"""")) !> target + val process = + Process("perl", List("-e", """print STDERR "Hello"""")) !> target val program = process.run().map(_ => builder.toString) program.map(r => assert(r)(equalTo("Hello"))) - }, + } ), - suite("Redirection ordering")( proxTest("can redirect first input and then error to stream") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val source = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val process = Process("perl", List("-e", """my $str = <>; print STDERR "$str"""".stripMargin)) < source !># fs2.text.utf8.decode + val source = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val process = Process( + "perl", + List("-e", """my $str = <>; print STDERR "$str"""".stripMargin) + ) < source !># fs2.text.utf8.decode val program = process.run().map(_.error) program.map(r => assert(r)(equalTo("This is a test string"))) }, - proxTest("can redirect error first then output to stream") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = (Process("perl", List("-e", """print STDOUT Hello; print STDERR World""".stripMargin)) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode + val process = (Process( + "perl", + List("-e", """print STDOUT Hello; print STDERR World""".stripMargin) + ) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) program.map(r => assert(r)(equalTo("HelloWorld"))) }, - proxTest("can redirect output first then error to stream") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = (Process("perl", List("-e", """print STDOUT Hello; print STDERR World""".stripMargin)) ># fs2.text.utf8.decode) !># fs2.text.utf8.decode + val process = (Process( + "perl", + List("-e", """print STDOUT Hello; print STDERR World""".stripMargin) + ) ># fs2.text.utf8.decode) !># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) program.map(r => assert(r)(equalTo("HelloWorld"))) }, - - proxTest("can redirect output first then error finally input to stream") { prox => + proxTest( + "can redirect output first then error finally input to stream" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val source = fs2.Stream("Hello").through(fs2.text.utf8.encode) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) + val process = ((Process( + "perl", + List( + "-e", + """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin + ) + ) ># fs2.text.utf8.decode) !># fs2.text.utf8.decode) < source val program = process.run().map(r => r.output + r.error) program.map(r => assert(r)(equalTo("HelloWorld"))) }, - - proxTest("can redirect output first then input finally error to stream") { prox => + proxTest( + "can redirect output first then input finally error to stream" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val source = fs2.Stream("Hello").through(fs2.text.utf8.encode) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) + val process = ((Process( + "perl", + List( + "-e", + """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin + ) + ) ># fs2.text.utf8.decode) < source) !># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) program.map(r => assert(r)(equalTo("HelloWorld"))) }, - - proxTest("can redirect input first then error finally output to stream") { prox => + proxTest( + "can redirect input first then error finally output to stream" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val source = fs2.Stream("Hello").through(fs2.text.utf8.encode) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) + val process = ((Process( + "perl", + List( + "-e", + """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin + ) + ) < source) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) program.map(r => assert(r)(equalTo("HelloWorld"))) - }, + } ), - suite("Input redirection")( proxTest("can use stream as input") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val source = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val process = Process("wc", List("-w")) < source ># fs2.text.utf8.decode + val source = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val process = + Process("wc", List("-w")) < source ># fs2.text.utf8.decode val program = process.run().map(_.output.trim) program.map(r => assert(r)(equalTo("5"))) }, - proxTest("can use stream as input flushing after each chunk") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val source = fs2.Stream("This ", "is a test", " string").through(fs2.text.utf8.encode) - val process = (Process("wc", List("-w")) !< source) ># fs2.text.utf8.decode + val source = fs2 + .Stream("This ", "is a test", " string") + .through(fs2.text.utf8.encode) + val process = + (Process("wc", List("-w")) !< source) ># fs2.text.utf8.decode val program = process.run().map(_.output.trim) program.map(r => assert(r)(equalTo("5"))) - }, + } ), - suite("Termination")( proxTest("can be terminated with cancellation") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) - val program = process.start().use { fiber => ZIO.sleep(250.millis).flatMap(_ => fiber.cancel) } + val process = Process( + "perl", + List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") + ) + val program = process.start().use { fiber => + ZIO.sleep(250.millis).flatMap(_ => fiber.cancel) + } program.map(r => assert(r)(equalTo(()))) } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), - proxTest("can be terminated by releasing the resource") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) + val process = Process( + "perl", + List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") + ) val program = process.start().use { _ => ZIO.sleep(250.millis) } program.map(r => assert(r)(equalTo(()))) } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), - proxTest("can be terminated") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) + val process = Process( + "perl", + List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") + ) val program = for { runningProcess <- process.startProcess() _ <- ZIO.sleep(250.millis) @@ -396,13 +533,16 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map(r => assert(r)(equalTo(ExitCode(1)))) } @@ TestAspect.withLiveClock, - proxTest("can be killed") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = Process("perl", List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""")) + val process = Process( + "perl", + List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""") + ) val program = for { runningProcess <- process.startProcess() _ <- ZIO.sleep(250.millis) @@ -411,11 +551,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map(r => assert(r)(equalTo(ExitCode(137)))) } @@ TestAspect.withLiveClock, - proxTest("can be checked if is alive") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val process = Process("sleep", List("10")) val program = for { @@ -426,26 +566,27 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { } yield (isAliveBefore, isAliveAfter) program.map(r => assert(r)(equalTo((true, false)))) - }, + } ), - suite("Customization")( proxTest("can change the command") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val p1 = Process("something", List("Hello", "world")) ># fs2.text.utf8.decode + val p1 = + Process("something", List("Hello", "world")) ># fs2.text.utf8.decode val p2 = p1.withCommand("echo") val program = p2.run().map(_.output) program.map(r => assert(r)(equalTo("Hello world\n"))) }, - proxTest("can change the arguments") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner val p1 = Process("echo") ># fs2.text.utf8.decode val p2 = p1.withArguments(List("Hello", "world")) @@ -453,165 +594,219 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map(r => assert(r)(equalTo("Hello world\n"))) }, - proxTest("respects the working directory") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - ZIO.attempt(java.nio.file.Files.createTempDirectory("prox")).flatMap { tempDirectory => - val process = (Process("pwd") in tempDirectory) ># fs2.text.utf8.decode - val program = process.run().map(_.output.trim) + ZIO.attempt(java.nio.file.Files.createTempDirectory("prox")).flatMap { + tempDirectory => + val process = + (Process("pwd") in tempDirectory) ># fs2.text.utf8.decode + val program = process.run().map(_.output.trim) - program.map(r => assert(r)(equalTo(tempDirectory.toString) || equalTo(s"/private${tempDirectory}"))) + program.map(r => + assert(r)( + equalTo(tempDirectory.toString) || equalTo( + s"/private${tempDirectory}" + ) + ) + ) } }, - proxTest("is customizable with environment variables") { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode + val process = + (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) + `with` ("TEST1" -> "world") + `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, + proxTest("is customizable with excluded environment variables") { + prox => + import prox._ - proxTest("is customizable with excluded environment variables") { prox => - import prox._ - - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox") - `without` "TEST1") ># fs2.text.utf8.decode - val program = process.run().map(_.output) + val process = + (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) + `with` ("TEST1" -> "world") + `with` ("TEST2" -> "prox") + `without` "TEST1") ># fs2.text.utf8.decode + val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello ! I am prox!\n"))) + program.map(r => assert(r)(equalTo("Hello ! I am prox!\n"))) }, + proxTest("is customizable with environment variables output is bound") { + prox => + import prox._ - proxTest("is customizable with environment variables output is bound") { prox => - import prox._ + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) ># fs2.text.utf8.decode - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) - val program = process.run().map(_.output) + val process = (Process( + "sh", + List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") + ) ># fs2.text.utf8.decode + `with` ("TEST1" -> "world") + `with` ("TEST2" -> "prox")) + val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) + program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, - - proxTest("is customizable with environment variables if input is bound") { prox => + proxTest( + "is customizable with environment variables if input is bound" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val source = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val process = ((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) + val source = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val process = ((Process( + "sh", + List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") + ) < source) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, - - proxTest("is customizable with environment variables if error is bound") { prox => + proxTest( + "is customizable with environment variables if error is bound" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val source = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val process = ((Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) !># fs2.text.utf8.decode) + val source = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val process = ((Process( + "sh", + List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") + ) !># fs2.text.utf8.decode) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, - - proxTest("is customizable with environment variables if input and output are bound") { prox => + proxTest( + "is customizable with environment variables if input and output are bound" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val source = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val process = (((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) ># fs2.text.utf8.decode) + val source = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val process = (((Process( + "sh", + List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") + ) < source) ># fs2.text.utf8.decode) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, - - - proxTest("is customizable with environment variables if input and error are bound") { prox => + proxTest( + "is customizable with environment variables if input and error are bound" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val source = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val process = (((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) !># fs2.text.utf8.decode) + val source = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val process = (((Process( + "sh", + List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") + ) < source) !># fs2.text.utf8.decode) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, - - proxTest("is customizable with environment variables if output and error are bound") { prox => + proxTest( + "is customizable with environment variables if output and error are bound" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val process = (((Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode) + val process = (((Process( + "sh", + List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") + ) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) }, - - proxTest("is customizable with environment variables if everything is bound") { prox => + proxTest( + "is customizable with environment variables if everything is bound" + ) { prox => import prox._ - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner - val source = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val process = ((((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode) + val source = + fs2.Stream("This is a test string").through(fs2.text.utf8.encode) + val process = ((((Process( + "sh", + List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") + ) < source) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) - }, + } ), - test("double output redirect is illegal") { - typeCheck("""val bad = Process("echo", List("Hello world")) > new File("x").toPath > new File("y").toPath""").map(r => - assert( - r)( - isLeft(anything) - )) + typeCheck( + """val bad = Process("echo", List("Hello world")) > new File("x").toPath > new File("y").toPath""" + ).map(r => + assert(r)( + isLeft(anything) + ) + ) }, test("double error redirect is illegal") { - typeCheck("""val bad = Process("echo", List("Hello world")) !> new File("x").toPath !> new File("y").toPath""").map(r => - assert( - r)( - isLeft(anything) - )) + typeCheck( + """val bad = Process("echo", List("Hello world")) !> new File("x").toPath !> new File("y").toPath""" + ).map(r => + assert(r)( + isLeft(anything) + ) + ) }, test("double input redirect is illegal") { - typeCheck("""val bad = (Process("echo", List("Hello world")) < fs2.Stream("X").through(fs2.text.utf8.encode)) < fs2.Stream("Y").through(fs2.text.utf8.encode)""").map(r => - assert( - r)( - isLeft(anything) - )) + typeCheck( + """val bad = (Process("echo", List("Hello world")) < fs2.Stream("X").through(fs2.text.utf8.encode)) < fs2.Stream("Y").through(fs2.text.utf8.encode)""" + ).map(r => + assert(r)( + isLeft(anything) + ) + ) } ) @@ timeout(60.seconds) @@ sequential } diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala index 4eaa622e..b4ff21cf 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala @@ -9,15 +9,19 @@ import java.io.File trait ProxSpecHelpers { - def proxTest(label: String)(assertion: ProxFS2[Task] => ZIO[Any, Throwable, TestResult]): Spec[Any, scala.Throwable] = { - test(label){ + def proxTest(label: String)( + assertion: ProxFS2[Task] => ZIO[Any, Throwable, TestResult] + ): Spec[Any, scala.Throwable] = { + test(label) { ZIO.runtime[Any].flatMap { implicit env => assertion(ProxFS2[Task]) } } } - def withTempFile[A](inner: File => ZIO[Any, Throwable, A]): ZIO[Any, Throwable, A] = + def withTempFile[A]( + inner: File => ZIO[Any, Throwable, A] + ): ZIO[Any, Throwable, A] = ZIO.acquireReleaseWith( ZIO.attempt(File.createTempFile("test", "txt")) )(file => ZIO.attempt(file.delete()).orDie)(inner) diff --git a/prox-java9/src/main/scala/io/github/vigoo/prox/java9/runner.scala b/prox-java9/src/main/scala/io/github/vigoo/prox/java9/runner.scala index d281ea5a..9508f684 100644 --- a/prox-java9/src/main/scala/io/github/vigoo/prox/java9/runner.scala +++ b/prox-java9/src/main/scala/io/github/vigoo/prox/java9/runner.scala @@ -9,10 +9,13 @@ trait Java9Module { case class JVM9ProcessInfo(pid: Long) extends JVMProcessInfo - class JVM9ProcessRunner() - extends JVMProcessRunnerBase[JVM9ProcessInfo] { + class JVM9ProcessRunner() extends JVMProcessRunnerBase[JVM9ProcessInfo] { - override protected def getProcessInfo(process: JvmProcess): ProxIO[JVM9ProcessInfo] = - effect(process.pid(), FailedToQueryState.apply).map(pid => JVM9ProcessInfo(pid)) + override protected def getProcessInfo( + process: JvmProcess + ): ProxIO[JVM9ProcessInfo] = + effect(process.pid(), FailedToQueryState.apply).map(pid => + JVM9ProcessInfo(pid) + ) } -} \ No newline at end of file +} diff --git a/prox-zstream-2/src/main/scala/io/github/vigoo/prox/ProxZStream.scala b/prox-zstream-2/src/main/scala/io/github/vigoo/prox/ProxZStream.scala index 02550244..58fe3989 100644 --- a/prox-zstream-2/src/main/scala/io/github/vigoo/prox/ProxZStream.scala +++ b/prox-zstream-2/src/main/scala/io/github/vigoo/prox/ProxZStream.scala @@ -11,13 +11,20 @@ import scala.language.implicitConversions trait ProxZStream extends Prox { - case class TransformAndSink[A, B](transform: ZStream[Any, ProxError, A] => ZStream[Any, ProxError, B], - sink: ZSink[Any, ProxError, B, Any, Unit]) { - private[ProxZStream] def run(s: ZStream[Any, ProxError, A]): ZIO[Any, ProxError, Unit] = + case class TransformAndSink[A, B]( + transform: ZStream[Any, ProxError, A] => ZStream[Any, ProxError, B], + sink: ZSink[Any, ProxError, B, Any, Unit] + ) { + private[ProxZStream] def run( + s: ZStream[Any, ProxError, A] + ): ZIO[Any, ProxError, Unit] = transform(s).run(sink) } object TransformAndSink { - def apply[A, B](transducer: ZPipeline[Any, ProxError, A, B], sink: ZSink[Any, ProxError, B, Any, Unit]): TransformAndSink[A, B] = + def apply[A, B]( + transducer: ZPipeline[Any, ProxError, A, B], + sink: ZSink[Any, ProxError, B, Any, Unit] + ): TransformAndSink[A, B] = TransformAndSink(_.via(transducer), sink) } @@ -39,45 +46,74 @@ trait ProxZStream extends Prox { protected override final def pure[A](value: A): ProxIO[A] = ZIO.succeed(value) - protected override final def effect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = + protected override final def effect[A]( + f: => A, + wrapError: Throwable => ProxError + ): ProxIO[A] = ZIO.attempt(f).mapError(wrapError) - protected override final def blockingEffect[A](f: => A, wrapError: Throwable => ProxError): ProxIO[A] = + protected override final def blockingEffect[A]( + f: => A, + wrapError: Throwable => ProxError + ): ProxIO[A] = ZIO.attemptBlockingInterrupt(f).mapError(wrapError).interruptible protected override final def raiseError(error: ProxError): ProxIO[Unit] = ZIO.fail(error) - protected override final def ioMap[A, B](io: ProxIO[A], f: A => B): ProxIO[B] = + protected override final def ioMap[A, B]( + io: ProxIO[A], + f: A => B + ): ProxIO[B] = io.map(f) - protected override final def ioFlatMap[A, B](io: ProxIO[A], f: A => ProxIO[B]): ProxIO[B] = + protected override final def ioFlatMap[A, B]( + io: ProxIO[A], + f: A => ProxIO[B] + ): ProxIO[B] = io.flatMap(f) - protected override final def traverse[A, B](list: List[A])(f: A => ProxIO[B]): ProxIO[List[B]] = + protected override final def traverse[A, B](list: List[A])( + f: A => ProxIO[B] + ): ProxIO[List[B]] = ZIO.foreach(list)(f) protected override final def identityPipe[A]: ProxPipe[A, A] = identity - protected override final def bracket[A, B](acquire: ProxIO[A])(use: A => ProxIO[B])(fin: (A, IOResult) => ProxIO[Unit]): ProxIO[B] = { - ZIO.acquireReleaseExitWith(acquire) { (value: A, exit: Exit[ProxError, B]) => - exit match { - case Exit.Success(_) => fin(value, Completed).mapError(_.toThrowable).orDie - case Exit.Failure(cause) => - if (cause.isInterrupted) { - fin(value, Canceled).mapError(_.toThrowable).orDie - } else { - fin(value, Failed(cause.failures ++ cause.defects.map(UnknownProxError.apply))).mapError(_.toThrowable).orDie - } - } + protected override final def bracket[A, B]( + acquire: ProxIO[A] + )(use: A => ProxIO[B])(fin: (A, IOResult) => ProxIO[Unit]): ProxIO[B] = { + ZIO.acquireReleaseExitWith(acquire) { + (value: A, exit: Exit[ProxError, B]) => + exit match { + case Exit.Success(_) => + fin(value, Completed).mapError(_.toThrowable).orDie + case Exit.Failure(cause) => + if (cause.isInterrupted) { + fin(value, Canceled).mapError(_.toThrowable).orDie + } else { + fin( + value, + Failed( + cause.failures ++ cause.defects.map(UnknownProxError.apply) + ) + ).mapError(_.toThrowable).orDie + } + } }(a => ZIO.allowInterrupt *> use(a)) } - protected override final def makeResource[A](acquire: ProxIO[A], release: A => ProxIO[Unit]): ProxResource[A] = + protected override final def makeResource[A]( + acquire: ProxIO[A], + release: A => ProxIO[Unit] + ): ProxResource[A] = ZIO.acquireRelease(acquire)(x => release(x).mapError(_.toThrowable).orDie) - protected override final def useResource[A, B](r: ProxResource[A], f: A => ProxIO[B]): ProxIO[B] = + protected override final def useResource[A, B]( + r: ProxResource[A], + f: A => ProxIO[B] + ): ProxIO[B] = ZIO.scoped(r.flatMap(f)) protected override final def joinFiber[A](f: ProxFiber[A]): ProxIO[A] = @@ -89,62 +125,111 @@ trait ProxZStream extends Prox { protected override final def drainStream[A](s: ProxStream[A]): ProxIO[Unit] = s.runDrain - protected override final def streamToVector[A](s: ProxStream[A]): ProxIO[Vector[A]] = + protected override final def streamToVector[A]( + s: ProxStream[A] + ): ProxIO[Vector[A]] = s.runCollect.map(_.toVector) - protected override final def foldStream[A, B](s: ProxStream[A], init: B, f: (B, A) => B): ProxIO[B] = + protected override final def foldStream[A, B]( + s: ProxStream[A], + init: B, + f: (B, A) => B + ): ProxIO[B] = s.runFold(init)(f) - protected override final def foldMonoidStream[A: Identity](s: ProxStream[A]): ProxIO[A] = + protected override final def foldMonoidStream[A: Identity]( + s: ProxStream[A] + ): ProxIO[A] = s.runFold(Identity[A].identity)((a, b) => Identity[A].combine(a, b)) - protected override final def streamThrough[A, B](s: ProxStream[A], pipe: ProxPipe[A, B]): ProxStream[B] = + protected override final def streamThrough[A, B]( + s: ProxStream[A], + pipe: ProxPipe[A, B] + ): ProxStream[B] = pipe(s) - override protected final def runStreamTo[A](s: ProxStream[A], sink: ProxSink[A]): ProxIO[Unit] = + override protected final def runStreamTo[A]( + s: ProxStream[A], + sink: ProxSink[A] + ): ProxIO[Unit] = sink.run(s) - protected override final def fromJavaInputStream(input: io.InputStream, chunkSize: Int): ProxStream[Byte] = - ZStream.fromInputStream(input, chunkSize).mapError(FailedToReadProcessOutput.apply) - - protected override final def drainToJavaOutputStream(stream: ProxStream[Byte], output: io.OutputStream, flushChunks: Boolean): ProxIO[Unit] = { - val managedOutput = ZIO.acquireRelease(ZIO.succeed(output))(s => ZIO.attempt(s.close()).orDie) + protected override final def fromJavaInputStream( + input: io.InputStream, + chunkSize: Int + ): ProxStream[Byte] = + ZStream + .fromInputStream(input, chunkSize) + .mapError(FailedToReadProcessOutput.apply) + + protected override final def drainToJavaOutputStream( + stream: ProxStream[Byte], + output: io.OutputStream, + flushChunks: Boolean + ): ProxIO[Unit] = { + val managedOutput = + ZIO.acquireRelease(ZIO.succeed(output))(s => ZIO.attempt(s.close()).orDie) if (flushChunks) { - stream.run(flushingOutputStreamSink(managedOutput).mapError(FailedToWriteProcessInput.apply)).unit + stream + .run( + flushingOutputStreamSink(managedOutput) + .mapError(FailedToWriteProcessInput.apply) + ) + .unit } else { stream - .run(ZSink - .fromOutputStreamScoped(managedOutput) - .mapError(FailedToWriteProcessInput.apply)).unit + .run( + ZSink + .fromOutputStreamScoped(managedOutput) + .mapError(FailedToWriteProcessInput.apply) + ) + .unit } } - private final def flushingOutputStreamSink(managedOutput: ZIO[Scope, Nothing, io.OutputStream]): ZSink[Any, IOException, Byte, Byte, Long] = + private final def flushingOutputStreamSink( + managedOutput: ZIO[Scope, Nothing, io.OutputStream] + ): ZSink[Any, IOException, Byte, Byte, Long] = ZSink.unwrapScoped { managedOutput.map { os => ZSink.foldLeftChunksZIO(0L) { (bytesWritten, byteChunk: Chunk[Byte]) => - ZIO.attemptBlockingInterrupt { - val bytes = byteChunk.toArray - os.write(bytes) - os.flush() - bytesWritten + bytes.length - }.refineOrDie { - case e: IOException => e - } + ZIO + .attemptBlockingInterrupt { + val bytes = byteChunk.toArray + os.write(bytes) + os.flush() + bytesWritten + bytes.length + } + .refineOrDie { case e: IOException => + e + } } } } - protected override final def startFiber[A](f: ProxIO[A]): ProxIO[ProxFiber[A]] = + protected override final def startFiber[A]( + f: ProxIO[A] + ): ProxIO[ProxFiber[A]] = f.fork - implicit def transducerAsPipe[A, B](transducer: ZPipeline[Any, ProxError, A, B]): ProxPipe[A, B] = + implicit def transducerAsPipe[A, B]( + transducer: ZPipeline[Any, ProxError, A, B] + ): ProxPipe[A, B] = (s: ProxStream[A]) => s.via(transducer) - implicit def transducerAsPipeThrowable[A, B](transducer: ZPipeline[Any, Throwable, A, B]): ProxPipe[A, B] = - (s: ProxStream[A]) => s.via(ZPipeline.fromChannel(transducer.channel.mapError(UnknownProxError.apply))) - - implicit def sinkAsTransformAndSink[A](sink: ZSink[Any, ProxError, A, Any, Unit]): TransformAndSink[A, A] = + implicit def transducerAsPipeThrowable[A, B]( + transducer: ZPipeline[Any, Throwable, A, B] + ): ProxPipe[A, B] = + (s: ProxStream[A]) => + s.via( + ZPipeline.fromChannel( + transducer.channel.mapError(UnknownProxError.apply) + ) + ) + + implicit def sinkAsTransformAndSink[A]( + sink: ZSink[Any, ProxError, A, Any, Unit] + ): TransformAndSink[A, A] = TransformAndSink(identity[ZStream[Any, ProxError, A]] _, sink) } diff --git a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala index a4b5e5fa..74fbd3f2 100644 --- a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala +++ b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala @@ -12,19 +12,22 @@ import java.nio.charset.StandardCharsets import java.nio.file.Files object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner override val spec = suite("Piping processes together")( suite("Piping")( test("is possible with two") { - val processGroup = (Process("echo", List("This is a test string")) | Process("wc", List("-w"))) ># ZPipeline.utf8Decode + val processGroup = (Process( + "echo", + List("This is a test string") + ) | Process("wc", List("-w"))) ># ZPipeline.utf8Decode val program = processGroup.run().map(_.output.trim) assertZIO(program)(equalTo("5")) }, - test("is possible with multiple") { val processGroup = ( @@ -32,64 +35,86 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { Process("sort") | Process("uniq", List("-c")) | Process("head", List("-n 1")) - ) >? (ZPipeline.utf8Decode >>> ZPipeline.splitLines) + ) >? (ZPipeline.utf8Decode >>> ZPipeline.splitLines) - val program = processGroup.run().map( - r => r.output.map(_.stripLineEnd.trim).filter(_.nonEmpty) - ) + val program = processGroup + .run() + .map(r => r.output.map(_.stripLineEnd.trim).filter(_.nonEmpty)) assertZIO(program)(hasSameElements(List("1 apple"))) }, - test("is customizable with pipes") { - val customPipe = (s: zstream.ProxStream[Byte]) => s - .via( - ZPipeline.fromChannel( - (ZPipeline.utf8Decode >>> ZPipeline.splitLines).channel.mapError(UnknownProxError.apply) + val customPipe = (s: zstream.ProxStream[Byte]) => + s + .via( + ZPipeline.fromChannel( + (ZPipeline.utf8Decode >>> ZPipeline.splitLines).channel + .mapError(UnknownProxError.apply) + ) ) - ) - .map(_.split(' ').toVector) - .map(v => v.map(_ + " !!!").mkString(" ")) - .intersperse("\n") - .flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val processGroup = Process("echo", List("This is a test string")).via(customPipe).to(Process("wc", List("-w"))) ># ZPipeline.utf8Decode + .map(_.split(' ').toVector) + .map(v => v.map(_ + " !!!").mkString(" ")) + .intersperse("\n") + .flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val processGroup = Process("echo", List("This is a test string")) + .via(customPipe) + .to(Process("wc", List("-w"))) ># ZPipeline.utf8Decode val program = processGroup.run().map(_.output.trim) assertZIO(program)(equalTo("10")) }, - test("can be mapped") { import zstream.Process._ - val processGroup1 = (Process("!echo", List("This is a test string")) | Process("!wc", List("-w"))) ># ZPipeline.utf8Decode - val processGroup2 = processGroup1.map(new ProcessGroup.Mapper[String, Unit] { - override def mapFirst[P <: Process[zstream.ProxStream[Byte], Unit]](process: P): P = process.withCommand(process.command.tail).asInstanceOf[P] - - override def mapInnerWithIdx[P <: UnboundIProcess[zstream.ProxStream[Byte], Unit]](process: P, idx: Int): P = process.withCommand(process.command.tail).asInstanceOf[P] - - override def mapLast[P <: UnboundIProcess[String, Unit]](process: P): P = process.withCommand(process.command.tail).asInstanceOf[P] - }) + val processGroup1 = (Process( + "!echo", + List("This is a test string") + ) | Process("!wc", List("-w"))) ># ZPipeline.utf8Decode + val processGroup2 = + processGroup1.map(new ProcessGroup.Mapper[String, Unit] { + override def mapFirst[ + P <: Process[zstream.ProxStream[Byte], Unit] + ](process: P): P = + process.withCommand(process.command.tail).asInstanceOf[P] + + override def mapInnerWithIdx[ + P <: UnboundIProcess[zstream.ProxStream[Byte], Unit] + ](process: P, idx: Int): P = + process.withCommand(process.command.tail).asInstanceOf[P] + + override def mapLast[P <: UnboundIProcess[String, Unit]]( + process: P + ): P = process.withCommand(process.command.tail).asInstanceOf[P] + }) val program = processGroup2.run().map(_.output.trim) assertZIO(program)(equalTo("5")) } ), - suite("Termination")( test("can be terminated with cancellation") { val processGroup = - Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) | + Process( + "perl", + List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") + ) | Process("sort") - val program = ZIO.scoped { processGroup.start().flatMap { fiber => fiber.interrupt.unit } } + val program = ZIO.scoped { + processGroup.start().flatMap { fiber => fiber.interrupt.unit } + } assertZIO(program)(equalTo(())) } @@ TestAspect.timeout(5.seconds), - test("can be terminated") { - val p1 = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) + val p1 = Process( + "perl", + List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") + ) val p2 = Process("sort") val processGroup = p1 | p2 @@ -99,12 +124,16 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { result <- runningProcesses.terminate() } yield result.exitCodes.toList - assertZIO(program)(contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1))) + assertZIO(program)( + contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1)) + ) } @@ withLiveClock, - test("can be killed") { - val p1 = Process("perl", List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""")) + val p1 = Process( + "perl", + List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""") + ) val p2 = Process("sort") val processGroup = p1 | p2 @@ -116,28 +145,39 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { // Note: we can't assert on the second process' exit code because there is a race condition // between killing it directly and being stopped because of the upstream process got killed. - assertZIO(program)( - contains(p1 -> ExitCode(137) - )) + assertZIO(program)(contains(p1 -> ExitCode(137))) } @@ withLiveClock ), - suite("Input redirection")( test("can be fed with an input stream") { - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val processGroup = (Process("cat") | Process("wc", List("-w"))) < stream ># ZPipeline.utf8Decode + val stream = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val processGroup = (Process("cat") | Process( + "wc", + List("-w") + )) < stream ># ZPipeline.utf8Decode val program = processGroup.run().map(_.output.trim) assertZIO(program)(equalTo("5")) }, - test("can be fed with an input file") { withTempFile { tempFile => val program = for { - _ <- ZIO.attempt(Files.write(tempFile.toPath, "This is a test string".getBytes("UTF-8"))).mapError(UnknownProxError.apply) - processGroup = (Process("cat") | Process("wc", List("-w"))) < tempFile.toPath ># ZPipeline.utf8Decode + _ <- ZIO + .attempt( + Files.write( + tempFile.toPath, + "This is a test string".getBytes("UTF-8") + ) + ) + .mapError(UnknownProxError.apply) + processGroup = (Process("cat") | Process( + "wc", + List("-w") + )) < tempFile.toPath ># ZPipeline.utf8Decode result <- processGroup.run() } yield result.output.trim @@ -149,17 +189,23 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { test("output can be redirected to file") { withTempFile { tempFile => - val processGroup = (Process("echo", List("This is a test string")) | Process("wc", List("-w"))) > tempFile.toPath + val processGroup = (Process( + "echo", + List("This is a test string") + ) | Process("wc", List("-w"))) > tempFile.toPath val program = for { _ <- processGroup.run() - contents <- ZStream.fromFile(tempFile, 1024).via(ZPipeline.utf8Decode).runFold("")(_ + _).mapError(UnknownProxError.apply) + contents <- ZStream + .fromFile(tempFile, 1024) + .via(ZPipeline.utf8Decode) + .runFold("")(_ + _) + .mapError(UnknownProxError.apply) } yield contents.trim assertZIO(program)(equalTo("5")) } - }, + } ), - suite("Error redirection")( test("can redirect each error output to a stream") { @@ -170,18 +216,20 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo("world"))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo("world"))) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - test("can redirect each error output to a sink") { - val builder = new StringBuilder - val target: zstream.ProxSink[Byte] = ZSink.foreach((byte: Byte) => ZIO.attempt(builder.append(byte.toChar)).mapError(UnknownProxError.apply)) + val target: zstream.ProxSink[Byte] = ZSink.foreach((byte: Byte) => + ZIO + .attempt(builder.append(byte.toChar)) + .mapError(UnknownProxError.apply) + ) val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) @@ -190,33 +238,35 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map { result => assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(builder.toString.toSeq.sorted)(equalTo("Helloworld".toSeq.sorted)) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo(()))) && + assert(result.output)(equalTo(())) && + assert(builder.toString.toSeq.sorted)( + equalTo("Helloworld".toSeq.sorted) + ) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - test("can redirect each error output to a vector") { val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world!"""")) - val stream = ZPipeline.utf8Decode >>> ZPipeline.splitLines >>> ZPipeline.map[String, Int](_.length) + val stream = + ZPipeline.utf8Decode >>> ZPipeline.splitLines >>> ZPipeline + .map[String, Int](_.length) val processGroup = (p1 | p2) !>? stream val program = processGroup.run() program.map { result => assert(result.errors.get(p1))(isSome(hasSameElements(List(5)))) && - assert(result.errors.get(p2))(isSome(hasSameElements(List(6)))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(hasSameElements(List(6)))) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - test("can drain each error output") { val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) @@ -226,13 +276,12 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map { result => assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo(()))) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - test("can fold each error output") { val p1 = Process("perl", List("-e", "print STDERR 'Hello\nworld'")) @@ -245,36 +294,44 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val program = processGroup.run() program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(Vector(Some('H'), Some('w'))))) && - assert(result.errors.get(p2))(isSome(equalTo(Vector(Some('D'), Some('i'), Some('w'))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p1))( + isSome(equalTo(Vector(Some('H'), Some('w')))) + ) && + assert(result.errors.get(p2))( + isSome(equalTo(Vector(Some('D'), Some('i'), Some('w')))) + ) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } - }, + } ), suite("Error redirection customized per process")( - test("can redirect each error output to a stream customized per process") { + test( + "can redirect each error output to a stream customized per process" + ) { val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) val processGroup = (p1 | p2).customizedPerProcess.errorsToFoldMonoid { - case p if p == p1 => ZPipeline.utf8Decode >>> ZPipeline.map(s => "P1: " + s) - case p if p == p2 => ZPipeline.utf8Decode >>> ZPipeline.map(s => "P2: " + s) + case p if p == p1 => + ZPipeline.utf8Decode >>> ZPipeline.map(s => "P1: " + s) + case p if p == p2 => + ZPipeline.utf8Decode >>> ZPipeline.map(s => "P2: " + s) } val program = processGroup.run() program.map { result => assert(result.errors.get(p1))(isSome(equalTo("P1: HelloP1: "))) && - assert(result.errors.get(p2))(isSome(equalTo("P2: worldP2: "))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo("P2: worldP2: "))) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - - test("can redirect each error output to a sink customized per process") { - + test( + "can redirect each error output to a sink customized per process" + ) { val builder1 = new StringBuilder val builder2 = new StringBuilder @@ -282,28 +339,41 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) val processGroup = (p1 | p2).customizedPerProcess.errorsToSink { - case p if p == p1 => ZSink.foreach((byte: Byte) => ZIO.attempt(builder1.append(byte.toChar)).mapError(UnknownProxError.apply)) - case p if p == p2 => ZSink.foreach((byte: Byte) => ZIO.attempt(builder2.append(byte.toChar)).mapError(UnknownProxError.apply)) + case p if p == p1 => + ZSink.foreach((byte: Byte) => + ZIO + .attempt(builder1.append(byte.toChar)) + .mapError(UnknownProxError.apply) + ) + case p if p == p2 => + ZSink.foreach((byte: Byte) => + ZIO + .attempt(builder2.append(byte.toChar)) + .mapError(UnknownProxError.apply) + ) } val program = processGroup.run() program.map { result => assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(builder1.toString)(equalTo("Hello")) && - assert(builder2.toString)(equalTo("world")) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo(()))) && + assert(result.output)(equalTo(())) && + assert(builder1.toString)(equalTo("Hello")) && + assert(builder2.toString)(equalTo("world")) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - - test("can redirect each error output to a vector customized per process") { + test( + "can redirect each error output to a vector customized per process" + ) { val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world!"""")) - val stream = ZPipeline.utf8Decode >>> ZPipeline.splitLines >>> ZPipeline.map[String, Int](_.length) + val stream = + ZPipeline.utf8Decode >>> ZPipeline.splitLines >>> ZPipeline + .map[String, Int](_.length) val processGroup = (p1 | p2).customizedPerProcess.errorsToVector { case p if p == p1 => stream >>> ZPipeline.map(l => (1, l)) @@ -312,30 +382,34 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val program = processGroup.run() program.map { result => - assert(result.errors.get(p1))(isSome(hasSameElements(List((1, 5))))) && - assert(result.errors.get(p2))(isSome(hasSameElements(List((2, 6))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p1))( + isSome(hasSameElements(List((1, 5)))) + ) && + assert(result.errors.get(p2))( + isSome(hasSameElements(List((2, 6)))) + ) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - test("can drain each error output customized per process") { val p1 = Process("perl", List("-e", """print STDERR "Hello"""")) val p2 = Process("perl", List("-e", """print STDERR "world"""")) - val processGroup = (p1 | p2).customizedPerProcess.drainErrors(_ => ZPipeline.utf8Decode) + val processGroup = (p1 | p2).customizedPerProcess.drainErrors(_ => + ZPipeline.utf8Decode + ) val program = processGroup.run() program.map { result => assert(result.errors.get(p1))(isSome(equalTo(()))) && - assert(result.errors.get(p2))(isSome(equalTo(()))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p2))(isSome(equalTo(()))) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - test("can fold each error output customized per process") { val p1 = Process("perl", List("-e", "print STDERR 'Hello\nworld'")) @@ -343,7 +417,9 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val processGroup = (p1 | p2).customizedPerProcess.foldErrors( { case p if p == p1 => ZPipeline.utf8Decode >>> ZPipeline.splitLines - case p if p == p2 => ZPipeline.utf8Decode >>> ZPipeline.splitLines >>> ZPipeline.map[String, String](_.reverse) + case p if p == p2 => + ZPipeline.utf8Decode >>> ZPipeline.splitLines >>> ZPipeline + .map[String, String](_.reverse) }, Vector.empty, (l: Vector[Option[Char]], s: String) => l :+ s.headOption @@ -351,14 +427,17 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val program = processGroup.run() program.map { result => - assert(result.errors.get(p1))(isSome(equalTo(Vector(Some('H'), Some('w'))))) && - assert(result.errors.get(p2))(isSome(equalTo(Vector(Some('s'), Some('t'), Some('?'))))) && - assert(result.output)(equalTo(())) && - assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && - assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) + assert(result.errors.get(p1))( + isSome(equalTo(Vector(Some('H'), Some('w')))) + ) && + assert(result.errors.get(p2))( + isSome(equalTo(Vector(Some('s'), Some('t'), Some('?')))) + ) && + assert(result.output)(equalTo(())) && + assert(result.exitCodes.get(p1))(isSome(equalTo(ExitCode(0)))) && + assert(result.exitCodes.get(p2))(isSome(equalTo(ExitCode(0)))) } }, - test("can redirect each error output to file") { withTempFile { tempFile1 => @@ -371,123 +450,199 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { } val program = for { _ <- processGroup.run() - contents1 <- ZStream.fromFile(tempFile1, 1024).via(ZPipeline.utf8Decode).runFold("")(_ + _).mapError(UnknownProxError.apply) - contents2 <- ZStream.fromFile(tempFile2, 1024).via(ZPipeline.utf8Decode).runFold("")(_ + _).mapError(UnknownProxError.apply) + contents1 <- ZStream + .fromFile(tempFile1, 1024) + .via(ZPipeline.utf8Decode) + .runFold("")(_ + _) + .mapError(UnknownProxError.apply) + contents2 <- ZStream + .fromFile(tempFile2, 1024) + .via(ZPipeline.utf8Decode) + .runFold("")(_ + _) + .mapError(UnknownProxError.apply) } yield (contents1, contents2) assertZIO(program)(equalTo(("Hello", "world"))) } } - }, + } ), - suite("Redirection ordering")( - test("can redirect each error output to a stream if fed with an input stream and redirected to an output stream") { + test( + "can redirect each error output to a stream if fed with an input stream and redirected to an output stream" + ) { - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + val stream = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = (p1 | p2 | p3) < stream ># ZPipeline.utf8Decode !># ZPipeline.utf8Decode + val processGroup = + (p1 | p2 | p3) < stream ># ZPipeline.utf8Decode !># ZPipeline.utf8Decode - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } }, + test( + "can redirect output if each error output and input are already redirected" + ) { - test("can redirect output if each error output and input are already redirected") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + val stream = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) < stream !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode + val processGroup = + ((p1 | p2 | p3) < stream !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } }, + test( + "can attach output and then input stream if each error output and standard output are already redirected" + ) { - test("can attach output and then input stream if each error output and standard output are already redirected") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + val stream = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode < stream + val processGroup = + ((p1 | p2 | p3) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode < stream - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } }, + test( + "can attach input and then output stream if each error output and standard output are already redirected" + ) { - test("can attach input and then output stream if each error output and standard output are already redirected") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + val stream = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) !># ZPipeline.utf8Decode) < stream ># ZPipeline.utf8Decode + val processGroup = + ((p1 | p2 | p3) !># ZPipeline.utf8Decode) < stream ># ZPipeline.utf8Decode - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } }, + test( + "can attach input stream and errors if standard output is already redirected" + ) { - test("can attach input stream and errors if standard output is already redirected") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + val stream = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = ((p1 | p2 | p3) ># ZPipeline.utf8Decode) < stream !># ZPipeline.utf8Decode + val processGroup = + ((p1 | p2 | p3) ># ZPipeline.utf8Decode) < stream !># ZPipeline.utf8Decode - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } }, + test( + "can attach errors and finally input stream if standard output is already redirected" + ) { - test("can attach errors and finally input stream if standard output is already redirected") { - - val stream = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val p1 = Process("perl", List("-e", """my $str=<>; print STDERR Hello; print STDOUT "$str"""")) + val stream = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val p1 = Process( + "perl", + List( + "-e", + """my $str=<>; print STDERR Hello; print STDOUT "$str"""" + ) + ) val p2 = Process("sort") val p3 = Process("wc", List("-w")) - val processGroup = (((p1 | p2 | p3) ># ZPipeline.utf8Decode) !># ZPipeline.utf8Decode) < stream + val processGroup = + (((p1 | p2 | p3) ># ZPipeline.utf8Decode) !># ZPipeline.utf8Decode) < stream - processGroup.run() + processGroup + .run() .map { result => assert(result.errors.get(p1))(isSome(equalTo("Hello"))) && - assert(result.errors.get(p2))(isSome(equalTo(""))) && - assert(result.errors.get(p3))(isSome(equalTo(""))) && - assert(result.output.trim)(equalTo("5")) + assert(result.errors.get(p2))(isSome(equalTo(""))) && + assert(result.errors.get(p3))(isSome(equalTo(""))) && + assert(result.output.trim)(equalTo("5")) } - }, + } ), - test("bound process is not pipeable") { assertZIO( - typeCheck("""val bad = (Process("echo", List("Hello world")) ># ZPipeline.utf8Decode) | Process("wc", List("-w"))"""))( + typeCheck( + """val bad = (Process("echo", List("Hello world")) ># ZPipeline.utf8Decode) | Process("wc", List("-w"))""" + ) + )( isLeft(anything) ) } diff --git a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala index 3f492699..176269d7 100644 --- a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala +++ b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala @@ -12,7 +12,8 @@ import zio.test._ import zio.{ExitCode, ZIO} object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { - implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner + implicit val processRunner: ProcessRunner[JVMProcessInfo] = + new JVMProcessRunner override val spec = suite("Executing a process")( @@ -24,20 +25,23 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { assertZIO(program)(equalTo((ExitCode(0), ExitCode(1)))) }, - suite("Output redirection")( test("can redirect output to a file") { withTempFile { tempFile => - val process = Process("echo", List("Hello world!")) > tempFile.toPath + val process = + Process("echo", List("Hello world!")) > tempFile.toPath val program = for { _ <- process.run() - contents <- ZStream.fromFile(tempFile, 1024).via(ZPipeline.utf8Decode).runFold("")(_ + _).mapError(UnknownProxError.apply) + contents <- ZStream + .fromFile(tempFile, 1024) + .via(ZPipeline.utf8Decode) + .runFold("")(_ + _) + .mapError(UnknownProxError.apply) } yield contents assertZIO(program)(equalTo("Hello world!\n")) } }, - test("can redirect output to append a file") { withTempFile { tempFile => val process1 = Process("echo", List("Hello")) > tempFile.toPath @@ -45,27 +49,32 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val program = for { _ <- process1.run() _ <- process2.run() - contents <- ZStream.fromFile(tempFile, 1024).via(ZPipeline.utf8Decode).runFold("")(_ + _).mapError(UnknownProxError.apply) + contents <- ZStream + .fromFile(tempFile, 1024) + .via(ZPipeline.utf8Decode) + .runFold("")(_ + _) + .mapError(UnknownProxError.apply) } yield contents assertZIO(program)(equalTo("Hello\nworld\n")) } }, - test("can redirect output to stream") { - val process = Process("echo", List("Hello world!")) ># ZPipeline.utf8Decode + val process = + Process("echo", List("Hello world!")) ># ZPipeline.utf8Decode val program = process.run().map(_.output) assertZIO(program)(equalTo("Hello world!\n")) }, - test("can redirect output to stream folding monoid") { - val process = Process("echo", List("Hello\nworld!")) ># (ZPipeline.utf8Decode >>> ZPipeline.splitLines) + val process = Process( + "echo", + List("Hello\nworld!") + ) ># (ZPipeline.utf8Decode >>> ZPipeline.splitLines) val program = process.run().map(_.output) assertZIO(program)(equalTo("Helloworld!")) }, - test("can redirect output to stream collected to vector") { case class StringLength(value: Int) @@ -77,16 +86,17 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val process = Process("echo", List("Hello\nworld!")) >? stream val program = process.run().map(_.output) - assertZIO(program)(hasSameElements(List(StringLength(5), StringLength(6)))) + assertZIO(program)( + hasSameElements(List(StringLength(5), StringLength(6))) + ) }, - test("can redirect output to stream and ignore it's result") { - val process = Process("echo", List("Hello\nworld!")).drainOutput(ZPipeline.utf8Decode >>> ZPipeline.splitLines) + val process = Process("echo", List("Hello\nworld!")) + .drainOutput(ZPipeline.utf8Decode >>> ZPipeline.splitLines) val program = process.run().map(_.output) assertZIO(program)(equalTo(())) }, - test("can redirect output to stream and fold it") { val process = Process("echo", List("Hello\nworld!")).foldOutput( ZPipeline.utf8Decode >>> ZPipeline.splitLines, @@ -97,58 +107,80 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { assertZIO(program)(equalTo(Vector(Some('H'), Some('w')))) }, - test("can redirect output to a sink") { val builder = new StringBuilder - val target: zstream.ProxSink[Byte] = ZSink.foreach((byte: Byte) => ZIO.attempt(builder.append(byte.toChar)).mapError(UnknownProxError.apply)) + val target: zstream.ProxSink[Byte] = ZSink.foreach((byte: Byte) => + ZIO + .attempt(builder.append(byte.toChar)) + .mapError(UnknownProxError.apply) + ) val process = Process("echo", List("Hello world!")) > target val program = process.run().as(builder.toString) assertZIO(program)(equalTo("Hello world!\n")) - }, + } ), suite("Error redirection")( test("can redirect error to a file") { withTempFile { tempFile => - val process = Process("perl", List("-e", "print STDERR 'Hello world!'")) !> tempFile.toPath + val process = Process( + "perl", + List("-e", "print STDERR 'Hello world!'") + ) !> tempFile.toPath val program = for { _ <- process.run() - contents <- ZStream.fromFile(tempFile, 1024).via(ZPipeline.utf8Decode).runFold("")(_ + _).mapError(UnknownProxError.apply) + contents <- ZStream + .fromFile(tempFile, 1024) + .via(ZPipeline.utf8Decode) + .runFold("")(_ + _) + .mapError(UnknownProxError.apply) } yield contents assertZIO(program)(equalTo("Hello world!")) } }, - test("can redirect error to append a file") { withTempFile { tempFile => - val process1 = Process("perl", List("-e", "print STDERR Hello")) !> tempFile.toPath - val process2 = Process("perl", List("-e", "print STDERR world")) !>> tempFile.toPath + val process1 = Process( + "perl", + List("-e", "print STDERR Hello") + ) !> tempFile.toPath + val process2 = Process( + "perl", + List("-e", "print STDERR world") + ) !>> tempFile.toPath val program = for { _ <- process1.run() _ <- process2.run() - contents <- ZStream.fromFile(tempFile, 1024).via(ZPipeline.utf8Decode).runFold("")(_ + _).mapError(UnknownProxError.apply) + contents <- ZStream + .fromFile(tempFile, 1024) + .via(ZPipeline.utf8Decode) + .runFold("")(_ + _) + .mapError(UnknownProxError.apply) } yield contents assertZIO(program)(equalTo("Helloworld")) } }, - test("can redirect error to stream") { - val process = Process("perl", List("-e", """print STDERR "Hello"""")) !># ZPipeline.utf8Decode + val process = Process( + "perl", + List("-e", """print STDERR "Hello"""") + ) !># ZPipeline.utf8Decode val program = process.run().map(_.error) assertZIO(program)(equalTo("Hello")) }, - test("can redirect error to stream folding monoid") { - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) !># (ZPipeline.utf8Decode >>> ZPipeline.splitLines) + val process = Process( + "perl", + List("-e", "print STDERR 'Hello\nworld!'") + ) !># (ZPipeline.utf8Decode >>> ZPipeline.splitLines) val program = process.run().map(_.error) assertZIO(program)(equalTo("Helloworld!")) }, - test("can redirect error to stream collected to vector") { case class StringLength(value: Int) @@ -157,21 +189,29 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ZPipeline.splitLines >>> ZPipeline.map[String, StringLength](s => StringLength(s.length)) - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) !>? stream + val process = Process( + "perl", + List("-e", "print STDERR 'Hello\nworld!'") + ) !>? stream val program = process.run().map(_.error) - assertZIO(program)(hasSameElements(List(StringLength(5), StringLength(6)))) + assertZIO(program)( + hasSameElements(List(StringLength(5), StringLength(6))) + ) }, - test("can redirect error to stream and ignore it's result") { - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")).drainError(ZPipeline.utf8Decode >>> ZPipeline.splitLines) + val process = + Process("perl", List("-e", "print STDERR 'Hello\nworld!'")) + .drainError(ZPipeline.utf8Decode >>> ZPipeline.splitLines) val program = process.run().map(_.error) assertZIO(program)(equalTo(())) }, - test("can redirect error to stream and fold it") { - val process = Process("perl", List("-e", "print STDERR 'Hello\nworld!'")).foldError( + val process = Process( + "perl", + List("-e", "print STDERR 'Hello\nworld!'") + ).foldError( ZPipeline.utf8Decode >>> ZPipeline.splitLines, Vector.empty, (l: Vector[Option[Char]], s: String) => l :+ s.headOption @@ -180,111 +220,158 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { assertZIO(program)(equalTo(Vector(Some('H'), Some('w')))) }, - test("can redirect error to a sink") { val builder = new StringBuilder - val target: zstream.ProxSink[Byte] = ZSink.foreach((byte: Byte) => ZIO.attempt(builder.append(byte.toChar)).mapError(UnknownProxError.apply)) + val target: zstream.ProxSink[Byte] = ZSink.foreach((byte: Byte) => + ZIO + .attempt(builder.append(byte.toChar)) + .mapError(UnknownProxError.apply) + ) - val process = Process("perl", List("-e", """print STDERR "Hello"""")) !> target + val process = + Process("perl", List("-e", """print STDERR "Hello"""")) !> target val program = process.run().as(builder.toString) assertZIO(program)(equalTo("Hello")) - }, + } ), - suite("Redirection ordering")( test("can redirect first input and then error to stream") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = Process("perl", List("-e", """my $str = <>; print STDERR "$str"""".stripMargin)) < source !># ZPipeline.utf8Decode + val source = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val process = Process( + "perl", + List("-e", """my $str = <>; print STDERR "$str"""".stripMargin) + ) < source !># ZPipeline.utf8Decode val program = process.run().map(_.error) assertZIO(program)(equalTo("This is a test string")) }, - test("can redirect error first then output to stream") { - val process = (Process("perl", List("-e", """print STDOUT Hello; print STDERR World""".stripMargin)) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode + val process = (Process( + "perl", + List("-e", """print STDOUT Hello; print STDERR World""".stripMargin) + ) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode val program = process.run().map(r => r.output + r.error) assertZIO(program)(equalTo("HelloWorld")) }, - test("can redirect output first then error to stream") { - val process = (Process("perl", List("-e", """print STDOUT Hello; print STDERR World""".stripMargin)) ># ZPipeline.utf8Decode) !># ZPipeline.utf8Decode + val process = (Process( + "perl", + List("-e", """print STDOUT Hello; print STDERR World""".stripMargin) + ) ># ZPipeline.utf8Decode) !># ZPipeline.utf8Decode val program = process.run().map(r => r.output + r.error) assertZIO(program)(equalTo("HelloWorld")) }, - test("can redirect output first then error finally input to stream") { - val source = ZStream("Hello").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) + val source = ZStream("Hello").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val process = ((Process( + "perl", + List( + "-e", + """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin + ) + ) ># ZPipeline.utf8Decode) !># ZPipeline.utf8Decode) < source val program = process.run().map(r => r.output + r.error) assertZIO(program)(equalTo("HelloWorld")) }, - test("can redirect output first then input finally error to stream") { - val source = ZStream("Hello").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) + val source = ZStream("Hello").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val process = ((Process( + "perl", + List( + "-e", + """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin + ) + ) ># ZPipeline.utf8Decode) < source) !># ZPipeline.utf8Decode val program = process.run().map(r => r.output + r.error) assertZIO(program)(equalTo("HelloWorld")) }, - test("can redirect input first then error finally output to stream") { - val source = ZStream("Hello").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((Process("perl", List("-e", """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin)) + val source = ZStream("Hello").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val process = ((Process( + "perl", + List( + "-e", + """my $str = <>; print STDOUT "$str"; print STDERR World""".stripMargin + ) + ) < source) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode val program = process.run().map(r => r.output + r.error) assertZIO(program)(equalTo("HelloWorld")) - }, + } ), - suite("Input redirection")( test("can use stream as input") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = Process("wc", List("-w")) < source ># ZPipeline.utf8Decode + val source = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val process = + Process("wc", List("-w")) < source ># ZPipeline.utf8Decode val program = process.run().map(_.output.trim) assertZIO(program)(equalTo("5")) }, - test("can use stream as input flushing after each chunk") { - val source = ZStream("This ", "is a test", " string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = (Process("wc", List("-w")) !< source) ># ZPipeline.utf8Decode + val source = ZStream("This ", "is a test", " string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val process = + (Process("wc", List("-w")) !< source) ># ZPipeline.utf8Decode val program = process.run().map(_.output.trim) assertZIO(program)(equalTo("5")) - }, + } ), - suite("Termination")( test("can be terminated with cancellation") { - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) + val process = Process( + "perl", + List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") + ) val program = ZIO.scoped { - process.start().flatMap { fiber => ZIO.sleep(250.millis) *> fiber.interrupt.unit } + process.start().flatMap { fiber => + ZIO.sleep(250.millis) *> fiber.interrupt.unit + } } assertZIO(program)(equalTo(())) - } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds) @@ TestAspect.diagnose(2.seconds), - + } @@ TestAspect.withLiveClock @@ TestAspect.timeout( + 5.seconds + ) @@ TestAspect.diagnose(2.seconds), test("can be terminated by releasing the resource") { - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) + val process = Process( + "perl", + List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") + ) val program = ZIO.scoped { process.start().flatMap { _ => ZIO.sleep(250.millis) } } assertZIO(program)(equalTo(())) } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), - test("can be terminated") { - val process = Process("perl", List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""")) + val process = Process( + "perl", + List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") + ) val program = for { runningProcess <- process.startProcess() _ <- ZIO.sleep(250.millis) @@ -293,9 +380,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { assertZIO(program)(equalTo(ExitCode(1))) } @@ withLiveClock, - test("can be killed") { - val process = Process("perl", List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""")) + val process = Process( + "perl", + List("-e", """$SIG{TERM} = 'IGNORE'; sleep 30; exit 2""") + ) val program = for { runningProcess <- process.startProcess() _ <- ZIO.sleep(250.millis) @@ -304,7 +393,6 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { assertZIO(program)(equalTo(ExitCode(137))) } @@ withLiveClock, - test("can be checked if is alive") { val process = Process("sleep", List("10")) val program = for { @@ -315,18 +403,17 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { } yield (isAliveBefore, isAliveAfter) assertZIO(program)(equalTo((true, false))) - }, + } ), - suite("Customization")( test("can change the command") { - val p1 = Process("something", List("Hello", "world")) ># ZPipeline.utf8Decode + val p1 = + Process("something", List("Hello", "world")) ># ZPipeline.utf8Decode val p2 = p1.withCommand("echo") val program = p2.run().map(_.output) assertZIO(program)(equalTo("Hello world\n")) }, - test("can change the arguments") { val p1 = Process("echo") ># ZPipeline.utf8Decode val p2 = p1.withArguments(List("Hello", "world")) @@ -334,118 +421,161 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { assertZIO(program)(equalTo("Hello world\n")) }, - test("respects the working directory") { - ZIO.attempt(Files.createTempDirectory("prox")).flatMap { tempDirectory => - val process = (Process("pwd") in tempDirectory) ># ZPipeline.utf8Decode - val program = process.run().map(_.output.trim) - - assertZIO(program)(equalTo(tempDirectory.toString) || equalTo(s"/private${tempDirectory}")) + ZIO.attempt(Files.createTempDirectory("prox")).flatMap { + tempDirectory => + val process = + (Process("pwd") in tempDirectory) ># ZPipeline.utf8Decode + val program = process.run().map(_.output.trim) + + assertZIO(program)( + equalTo(tempDirectory.toString) || equalTo( + s"/private${tempDirectory}" + ) + ) } }, - test("is customizable with environment variables") { - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) ># ZPipeline.utf8Decode + val process = + (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) + `with` ("TEST1" -> "world") + `with` ("TEST2" -> "prox")) ># ZPipeline.utf8Decode val program = process.run().map(_.output) assertZIO(program)(equalTo("Hello world! I am prox!\n")) }, - test("is customizable with excluded environment variables") { - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) - `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox") - `without` "TEST1") ># ZPipeline.utf8Decode + val process = + (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) + `with` ("TEST1" -> "world") + `with` ("TEST2" -> "prox") + `without` "TEST1") ># ZPipeline.utf8Decode val program = process.run().map(_.output) assertZIO(program)(equalTo("Hello ! I am prox!\n")) }, - test("is customizable with environment variables output is bound") { - val process = (Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) ># ZPipeline.utf8Decode + val process = (Process( + "sh", + List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") + ) ># ZPipeline.utf8Decode `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) assertZIO(program)(equalTo("Hello world! I am prox!\n")) }, - test("is customizable with environment variables if input is bound") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) + val source = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val process = ((Process( + "sh", + List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") + ) < source) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) ># ZPipeline.utf8Decode val program = process.run().map(_.output) assertZIO(program)(equalTo("Hello world! I am prox!\n")) }, - test("is customizable with environment variables if error is bound") { - val process = ((Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) !># ZPipeline.utf8Decode) + val process = ((Process( + "sh", + List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") + ) !># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) ># ZPipeline.utf8Decode val program = process.run().map(_.output) assertZIO(program)(equalTo("Hello world! I am prox!\n")) }, - - test("is customizable with environment variables if input and output are bound") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = (((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) ># ZPipeline.utf8Decode) + test( + "is customizable with environment variables if input and output are bound" + ) { + val source = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val process = (((Process( + "sh", + List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") + ) < source) ># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) assertZIO(program)(equalTo("Hello world! I am prox!\n")) }, - - test("is customizable with environment variables if input and error are bound") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = (((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) !># ZPipeline.utf8Decode) + test( + "is customizable with environment variables if input and error are bound" + ) { + val source = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val process = (((Process( + "sh", + List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") + ) < source) !># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) ># ZPipeline.utf8Decode val program = process.run().map(_.output) assertZIO(program)(equalTo("Hello world! I am prox!\n")) }, - - test("is customizable with environment variables if output and error are bound") { - val process = (((Process("sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"")) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode) + test( + "is customizable with environment variables if output and error are bound" + ) { + val process = (((Process( + "sh", + List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") + ) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) assertZIO(program)(equalTo("Hello world! I am prox!\n")) }, - - test("is customizable with environment variables if everything is bound") { - val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8))) - val process = ((((Process("sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"")) < source) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode) + test( + "is customizable with environment variables if everything is bound" + ) { + val source = ZStream("This is a test string").flatMap(s => + ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) + ) + val process = ((((Process( + "sh", + List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") + ) < source) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) assertZIO(program)(equalTo("Hello world! I am prox!\n")) - }, + } ), - test("double output redirect is illegal") { assertZIO( - typeCheck("""val bad = Process("echo", List("Hello world")) > new File("x").toPath > new File("y").toPath"""))( + typeCheck( + """val bad = Process("echo", List("Hello world")) > new File("x").toPath > new File("y").toPath""" + ) + )( isLeft(anything) ) }, test("double error redirect is illegal") { assertZIO( - typeCheck("""val bad = Process("echo", List("Hello world")) !> new File("x").toPath !> new File("y").toPath"""))( + typeCheck( + """val bad = Process("echo", List("Hello world")) !> new File("x").toPath !> new File("y").toPath""" + ) + )( isLeft(anything) ) }, test("double input redirect is illegal") { assertZIO( - typeCheck("""val bad = (Process("echo", List("Hello world")) < ZStream("X").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)))) < ZStream("Y").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)))"""))( + typeCheck( + """val bad = (Process("echo", List("Hello world")) < ZStream("X").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)))) < ZStream("Y").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)))""" + ) + )( isLeft(anything) ) } diff --git a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProxSpecHelpers.scala b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProxSpecHelpers.scala index c0d7be2c..47208b88 100644 --- a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProxSpecHelpers.scala +++ b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProxSpecHelpers.scala @@ -7,9 +7,12 @@ import zio.ZIO trait ProxSpecHelpers { - def withTempFile[A](inner: File => ZIO[Any, ProxError, A]): ZIO[Any, ProxError, A] = + def withTempFile[A]( + inner: File => ZIO[Any, ProxError, A] + ): ZIO[Any, ProxError, A] = ZIO.acquireReleaseWith( - ZIO.attempt(File.createTempFile("test", "txt")) - .mapError(UnknownProxError.apply) + ZIO + .attempt(File.createTempFile("test", "txt")) + .mapError(UnknownProxError.apply) )(file => ZIO.attempt(file.delete()).orDie)(inner) } From ea2ca3031e8d7c1c05df6d2862d3daaacd995b0c Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 20 Jan 2024 12:33:36 +0100 Subject: [PATCH 3/6] Modernize --- .../scala/io/github/vigoo/prox/runner.scala | 6 +- .../scala/io/github/vigoo/prox/ProxFS2.scala | 26 ++- .../prox/tests/fs2/InterpolatorSpecs.scala | 53 ++--- .../prox/tests/fs2/ProcessGroupSpecs.scala | 93 ++++----- .../vigoo/prox/tests/fs2/ProcessSpecs.scala | 190 +++++++++--------- .../prox/tests/fs2/ProxSpecHelpers.scala | 6 +- .../io/github/vigoo/prox/java9/runner.scala | 2 +- .../io/github/vigoo/prox/ProxZStream.scala | 4 +- .../tests/zstream/ProcessGroupSpecs.scala | 42 ++-- .../prox/tests/zstream/ProcessSpecs.scala | 136 +++++++------ 10 files changed, 278 insertions(+), 280 deletions(-) diff --git a/prox-core/src/main/scala/io/github/vigoo/prox/runner.scala b/prox-core/src/main/scala/io/github/vigoo/prox/runner.scala index 7a9ce4ab..2e7a1147 100644 --- a/prox-core/src/main/scala/io/github/vigoo/prox/runner.scala +++ b/prox-core/src/main/scala/io/github/vigoo/prox/runner.scala @@ -1,9 +1,7 @@ package io.github.vigoo.prox -import java.lang.{Process => JvmProcess} - -import scala.concurrent.blocking -import scala.jdk.CollectionConverters._ +import java.lang.{Process as JvmProcess} +import scala.jdk.CollectionConverters.* trait ProcessRunnerModule { this: Prox => diff --git a/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala b/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala index b58c7b6f..9bf85570 100644 --- a/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala +++ b/prox-fs2-3/src/main/scala/io/github/vigoo/prox/ProxFS2.scala @@ -5,11 +5,9 @@ import java.io import cats.effect.{Concurrent, Outcome, Async, Sync} import cats.{Applicative, ApplicativeError, FlatMap, Traverse} -import scala.concurrent.blocking - trait ProxFS2[F[_]] extends Prox { - val instances: Sync[F] with Concurrent[F] + val instances: Sync[F] & Concurrent[F] override type ProxExitCode = cats.effect.ExitCode override type ProxFiber[A] = cats.effect.Fiber[F, Throwable, A] @@ -35,7 +33,7 @@ trait ProxFS2[F[_]] extends Prox { f: => A, wrapError: Throwable => ProxError ): ProxIO[A] = { - implicit val i: Sync[F] with Concurrent[F] = instances + implicit val i: Sync[F] & Concurrent[F] = instances Sync[F].adaptError(Sync[F].delay(f)) { case failure: Throwable => wrapError(failure).toThrowable } @@ -45,7 +43,7 @@ trait ProxFS2[F[_]] extends Prox { f: => A, wrapError: Throwable => ProxError ): ProxIO[A] = { - implicit val i: Sync[F] with Concurrent[F] = instances + implicit val i: Sync[F] & Concurrent[F] = instances Sync[F].adaptError(Sync[F].interruptibleMany(f)) { case failure: Throwable => wrapError(failure).toThrowable } @@ -100,21 +98,21 @@ trait ProxFS2[F[_]] extends Prox { protected override final def startFiber[A]( f: ProxIO[A] ): ProxIO[ProxFiber[A]] = { - implicit val i: Sync[F] with Concurrent[F] = instances + implicit val i: Sync[F] & Concurrent[F] = instances Concurrent[F].start(f) } protected override final def drainStream[A]( s: ProxStream[A] ): ProxIO[Unit] = { - implicit val i: Sync[F] with Concurrent[F] = instances + implicit val i: Sync[F] & Concurrent[F] = instances s.compile.drain } protected override final def streamToVector[A]( s: ProxStream[A] ): ProxIO[Vector[A]] = { - implicit val i: Sync[F] with Concurrent[F] = instances + implicit val i: Sync[F] & Concurrent[F] = instances s.compile.toVector } @@ -123,14 +121,14 @@ trait ProxFS2[F[_]] extends Prox { init: B, f: (B, A) => B ): ProxIO[B] = { - implicit val i: Sync[F] with Concurrent[F] = instances + implicit val i: Sync[F] & Concurrent[F] = instances s.compile.fold(init)(f) } protected override final def foldMonoidStream[A: ProxMonoid]( s: ProxStream[A] ): ProxIO[A] = { - implicit val i: Sync[F] with Concurrent[F] = instances + implicit val i: Sync[F] & Concurrent[F] = instances s.compile.foldMonoid } @@ -143,7 +141,7 @@ trait ProxFS2[F[_]] extends Prox { s: ProxStream[A], sink: ProxSink[A] ): ProxIO[Unit] = { - implicit val i: Sync[F] with Concurrent[F] = instances + implicit val i: Sync[F] & Concurrent[F] = instances s.through(sink).compile.drain } @@ -160,7 +158,7 @@ trait ProxFS2[F[_]] extends Prox { output: io.OutputStream, flushChunks: Boolean ): ProxIO[Unit] = { - implicit val i: Sync[F] with Concurrent[F] = instances + implicit val i: Sync[F] & Concurrent[F] = instances stream .through( if (flushChunks) writeAndFlushOutputStream(output)(_).drain @@ -177,7 +175,7 @@ trait ProxFS2[F[_]] extends Prox { private def writeAndFlushOutputStream( stream: java.io.OutputStream ): ProxPipe[Byte, Unit] = { - implicit val i: Sync[F] with Concurrent[F] = instances + implicit val i: Sync[F] & Concurrent[F] = instances s => { fs2.Stream .bracket(Applicative[F].pure(stream))(os => Sync[F].delay(os.close())) @@ -195,6 +193,6 @@ trait ProxFS2[F[_]] extends Prox { object ProxFS2 { def apply[F[_]](implicit a: Async[F]): ProxFS2[F] = new ProxFS2[F] { - override val instances: Sync[F] with Concurrent[F] = a + override val instances: Sync[F] & Concurrent[F] = a } } diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala index 0e2d9359..7cf039d9 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/InterpolatorSpecs.scala @@ -1,15 +1,14 @@ package io.github.vigoo.prox.tests.fs2 -import zio.ZIO -import zio.test.Assertion.{equalTo, isEmpty} -import zio.test._ +import zio.test.* +import zio.{Scope, ZIO} object InterpolatorSpecs extends ZIOSpecDefault with ProxSpecHelpers { - override val spec = + override val spec: Spec[TestEnvironment & Scope, Any] = suite("Process interpolators")( suite("cats-effect process interpolator")( proxTest("works with single-word process names") { prox => - import prox._ + import prox.* val process = proc"ls" @@ -21,7 +20,7 @@ object InterpolatorSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) }, proxTest("works with interpolated process name") { prox => - import prox._ + import prox.* val cmd = "ls" val process = proc"$cmd" @@ -34,45 +33,48 @@ object InterpolatorSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) }, proxTest("works with static parameters") { prox => - import prox._ + import prox.* val process = proc"ls -hal tmp" ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)( - equalTo(List("-hal", "tmp")) + assertTrue( + process.command == "ls", + process.arguments == List("-hal", "tmp") ) ) }, proxTest("works with static parameters and interpolated process name") { prox => - import prox._ + import prox.* val cmd = "ls" val process = proc"$cmd -hal tmp" ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert( - process.arguments - )(equalTo(List("-hal", "tmp"))) + assertTrue( + process.command == "ls", + process.arguments == List("-hal", "tmp") + ) ) }, proxTest("works with static process name and interpolated parameters") { prox => - import prox._ + import prox.* val p1 = "-hal" val p2 = "tmp" val process = proc"ls $p1 $p2" ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert( - process.arguments - )(equalTo(List("-hal", "tmp"))) + assertTrue( + process.command == "ls", + process.arguments == List("-hal", "tmp") + ) ) }, proxTest("works with interpolated name and parameters") { prox => - import prox._ + import prox.* val cmd = "ls" val p1 = "-hal" @@ -80,24 +82,25 @@ object InterpolatorSpecs extends ZIOSpecDefault with ProxSpecHelpers { val process = proc"$cmd $p1 $p2" ZIO.succeed( - assert(process.command)(equalTo("ls")) && assert(process.arguments)( - equalTo(List("-hal", "tmp")) + assertTrue( + process.command == "ls", + process.arguments == List("-hal", "tmp") ) ) }, proxTest("works with mixed static and interpolated parameters") { prox => - import prox._ + import prox.* val p1 = "hello" val p2 = "dear visitor" val process = proc"echo $p1, $p2!!!" ZIO.succeed( - assert(process.command)(equalTo("echo")) && - assert(process.arguments)( - equalTo(List("hello", ",", "dear visitor", "!!!")) - ) + assertTrue( + process.command == "echo", + process.arguments == List("hello", ",", "dear visitor", "!!!") + ) ) } ) diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala index bc091dfb..659a20f4 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala @@ -2,20 +2,19 @@ package io.github.vigoo.prox.tests.fs2 import cats.effect.ExitCode import fs2.io.file.{Files, Flags} -import io.github.vigoo.prox.UnknownProxError -import zio.interop.catz._ -import zio.test.Assertion._ -import zio.test.TestAspect._ -import zio.test._ -import zio.{IO, RIO, Task, ZIO, durationInt} +import zio.interop.catz.* +import zio.test.* +import zio.test.Assertion.* +import zio.test.TestAspect.* +import zio.{Scope, Task, ZIO, durationInt} object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { - override val spec = + override val spec: Spec[TestEnvironment & Scope, Any] = suite("Piping processes together")( suite("Piping")( proxTest("is possible with two") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -26,10 +25,10 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) | Process("wc", List("-w"))) ># fs2.text.utf8.decode val program = processGroup.run().map(_.output.trim) - program.map(r => assert(r)(equalTo("5"))) + program.map(r => assertTrue(r == "5")) }, proxTest("is possible with multiple") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -48,7 +47,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map(r => assert(r)(hasSameElements(List("1 apple")))) }, proxTest("is customizable with pipes") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -63,15 +62,15 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { .intersperse("\n") .through(fs2.text.utf8.encode) - val processGroup = (Process("echo", List("This is a test string")) + val processGroup = Process("echo", List("This is a test string")) .via(customPipe) - .to(Process("wc", List("-w")))) ># fs2.text.utf8.decode + .to(Process("wc", List("-w"))) ># fs2.text.utf8.decode val program = processGroup.run().map(_.output.trim) - program.map(r => assert(r)(equalTo("11"))) + program.map(r => assertTrue(r == "11")) }, proxTest("can be mapped") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -98,12 +97,12 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val program = processGroup2.run().map(_.output.trim) - program.map(r => assert(r)(equalTo("5"))) + program.map(r => assertTrue(r == "5")) } ), suite("Termination")( proxTest("can be terminated with cancellation") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -116,10 +115,10 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { Process("sort") val program = processGroup.start().use { fiber => fiber.cancel } - program.map(r => assert(r)(equalTo(()))) + program.map(r => assertTrue(r == ())) } @@ TestAspect.timeout(5.seconds), proxTest("can be terminated") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -137,14 +136,10 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { result <- runningProcesses.terminate() } yield result.exitCodes.toList - program.map(r => - assert(r)( - contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1)) - ) - ) + program.map(r => assertTrue(r.contains(p1 -> ExitCode(1)))) } @@ TestAspect.withLiveClock, proxTest("can be killed") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -169,7 +164,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { ), suite("Input redirection")( proxTest("can be fed with an input stream") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -182,10 +177,10 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { )) < stream ># fs2.text.utf8.decode val program = processGroup.run().map(_.output.trim) - program.map(r => assert(r)(equalTo("5"))) + program.map(r => assertTrue(r == "5")) }, proxTest("can be fed with an input file") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -205,13 +200,13 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { result <- processGroup.run() } yield result.output.trim - program.map(r => assert(r)(equalTo("5"))) + program.map(r => assertTrue(r == "5")) } } ), suite("Output redirection")( proxTest("output can be redirected to file") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -235,13 +230,13 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { .foldMonoid } yield contents.trim - program.map(r => assert(r)(equalTo("5"))) + program.map(r => assertTrue(r == "5")) } } ), suite("Error redirection")( proxTest("can redirect each error output to a stream") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -260,7 +255,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { } }, proxTest("can redirect each error output to a sink") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -289,7 +284,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { } }, proxTest("can redirect each error output to a vector") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -314,7 +309,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { } }, proxTest("can drain each error output") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -333,7 +328,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { } }, proxTest("can fold each error output") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -364,7 +359,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest( "can redirect each error output to a stream customized per process" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -390,7 +385,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest( "can redirect each error output to a sink customized per process" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -429,7 +424,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest( "can redirect each error output to a vector customized per process" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -461,7 +456,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { } }, proxTest("can drain each error output customized per process") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -482,7 +477,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { } }, proxTest("can fold each error output customized per process") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -519,7 +514,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { } }, proxTest("can redirect each error output to file") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -556,7 +551,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { .foldMonoid } yield (contents1, contents2) - program.map(r => assert(r)(equalTo(("Hello", "world")))) + program.map(r => assertTrue(r == ("Hello", "world"))) } } } @@ -565,7 +560,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest( "can redirect each error output to a stream if fed with an input stream and redirected to an output stream" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -596,7 +591,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest( "can redirect output if each error output and input are already redirected" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -627,7 +622,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest( "can attach output and then input stream if each error output and standard output are already redirected" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -658,7 +653,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest( "can attach input and then output stream if each error output and standard output are already redirected" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -689,7 +684,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest( "can attach input stream and errors if standard output is already redirected" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -720,7 +715,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { proxTest( "can attach errors and finally input stream if standard output is already redirected" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala index 703e2444..8e37c44b 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala @@ -2,17 +2,17 @@ package io.github.vigoo.prox.tests.fs2 import cats.effect.ExitCode import fs2.io.file.{Files, Flags} -import zio.interop.catz._ +import zio.interop.catz.* import zio.test.Assertion.{anything, equalTo, hasSameElements, isLeft} -import zio.test.TestAspect._ -import zio.test._ -import zio.{Task, ZIO, durationInt} +import zio.test.TestAspect.* +import zio.test.* +import zio.{Scope, Task, ZIO, durationInt} object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { - override val spec = + override val spec: Spec[TestEnvironment & Scope, Any] = suite("Executing a process")( proxTest("returns the exit code") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -22,11 +22,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { falseResult <- Process("false").run() } yield (trueResult.exitCode, falseResult.exitCode) - program.map(r => assert(r)(equalTo((ExitCode(0), ExitCode(1))))) + program.map(r => assertTrue(r == (ExitCode(0), ExitCode(1)))) }, suite("Output redirection")( proxTest("can redirect output to a file") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -48,11 +48,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .foldMonoid } yield contents - program.map(r => assert(r)(equalTo("Hello world!\n"))) + program.map(r => assertTrue(r == "Hello world!\n")) } }, proxTest("can redirect output to append a file") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -75,11 +75,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .foldMonoid } yield contents - program.map(r => assert(r)(equalTo("Hello\nworld\n"))) + program.map(r => assertTrue(r == "Hello\nworld\n")) } }, proxTest("can redirect output to stream") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -88,10 +88,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { Process("echo", List("Hello world!")) ># fs2.text.utf8.decode val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world!\n"))) + program.map(r => assertTrue(r == "Hello world!\n")) }, proxTest("can redirect output to stream folding monoid") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -102,10 +102,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) ># fs2.text.utf8.decode.andThen(fs2.text.lines) val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Helloworld!"))) + program.map(r => assertTrue(r == "Helloworld!")) }, proxTest("can redirect output to stream collected to vector") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -129,7 +129,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { }, proxTest("can redirect output to stream and ignore it's result") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -139,10 +139,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo(()))) + program.map(r => assertTrue(r == ())) }, proxTest("can redirect output to stream and fold it") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -154,12 +154,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val program = process.run().map(_.output) - program.map(r => - assert(r)(equalTo(Vector(Some('H'), Some('w'), None))) - ) + program.map(r => assertTrue(r == Vector(Some('H'), Some('w'), None))) }, proxTest("can redirect output to a sink") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -172,14 +170,14 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val process = Process("echo", List("Hello world!")) > target - val program = process.run().map(_ => builder.toString) + val program = process.run().as(builder.toString) - program.map(r => assert(r)(equalTo("Hello world!\n"))) + program.map(r => assertTrue(r == "Hello world!\n")) } ), suite("Error redirection")( proxTest("can redirect error to a file") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -203,11 +201,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .foldMonoid } yield contents - program.map(r => assert(r)(equalTo("Hello world!"))) + program.map(r => assertTrue(r == "Hello world!")) } }, proxTest("can redirect error to append a file") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -236,11 +234,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .foldMonoid } yield contents - program.map(r => assert(r)(equalTo("Helloworld"))) + program.map(r => assertTrue(r == "Helloworld")) } }, proxTest("can redirect error to stream") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -251,10 +249,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) !># fs2.text.utf8.decode val program = process.run().map(_.error) - program.map(r => assert(r)(equalTo("Hello"))) + program.map(r => assertTrue(r == "Hello")) }, proxTest("can redirect error to stream folding monoid") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -265,10 +263,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) !># fs2.text.utf8.decode.andThen(fs2.text.lines) val program = process.run().map(_.error) - program.map(r => assert(r)(equalTo("Helloworld!"))) + program.map(r => assertTrue(r == "Helloworld!")) }, proxTest("can redirect error to stream collected to vector") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -291,7 +289,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { }, proxTest("can redirect error to stream and ignore it's result") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -301,10 +299,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .drainError(fs2.text.utf8.decode.andThen(fs2.text.lines)) val program = process.run().map(_.error) - program.map(r => assert(r)(equalTo(()))) + program.map(r => assertTrue(r == ())) }, proxTest("can redirect error to stream and fold it") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -319,10 +317,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val program = process.run().map(_.error) - program.map(r => assert(r)(equalTo(Vector(Some('H'), Some('w'))))) + program.map(r => assertTrue(r == Vector(Some('H'), Some('w')))) }, proxTest("can redirect error to a sink") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -336,14 +334,14 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val process = Process("perl", List("-e", """print STDERR "Hello"""")) !> target - val program = process.run().map(_ => builder.toString) + val program = process.run().as(builder.toString) - program.map(r => assert(r)(equalTo("Hello"))) + program.map(r => assertTrue(r == "Hello")) } ), suite("Redirection ordering")( proxTest("can redirect first input and then error to stream") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -356,10 +354,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) < source !># fs2.text.utf8.decode val program = process.run().map(_.error) - program.map(r => assert(r)(equalTo("This is a test string"))) + program.map(r => assertTrue(r == "This is a test string")) }, proxTest("can redirect error first then output to stream") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -370,10 +368,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) - program.map(r => assert(r)(equalTo("HelloWorld"))) + program.map(r => assertTrue(r == "HelloWorld")) }, proxTest("can redirect output first then error to stream") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -384,12 +382,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) ># fs2.text.utf8.decode) !># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) - program.map(r => assert(r)(equalTo("HelloWorld"))) + program.map(r => assertTrue(r == "HelloWorld")) }, proxTest( "can redirect output first then error finally input to stream" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -406,12 +404,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { !># fs2.text.utf8.decode) < source val program = process.run().map(r => r.output + r.error) - program.map(r => assert(r)(equalTo("HelloWorld"))) + program.map(r => assertTrue(r == "HelloWorld")) }, proxTest( "can redirect output first then input finally error to stream" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -428,12 +426,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { < source) !># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) - program.map(r => assert(r)(equalTo("HelloWorld"))) + program.map(r => assertTrue(r == "HelloWorld")) }, proxTest( "can redirect input first then error finally output to stream" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -450,12 +448,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { !># fs2.text.utf8.decode) ># fs2.text.utf8.decode val program = process.run().map(r => r.output + r.error) - program.map(r => assert(r)(equalTo("HelloWorld"))) + program.map(r => assertTrue(r == "HelloWorld")) } ), suite("Input redirection")( proxTest("can use stream as input") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -466,10 +464,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { Process("wc", List("-w")) < source ># fs2.text.utf8.decode val program = process.run().map(_.output.trim) - program.map(r => assert(r)(equalTo("5"))) + program.map(r => assertTrue(r == "5")) }, proxTest("can use stream as input flushing after each chunk") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -481,12 +479,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { (Process("wc", List("-w")) !< source) ># fs2.text.utf8.decode val program = process.run().map(_.output.trim) - program.map(r => assert(r)(equalTo("5"))) + program.map(r => assertTrue(r == "5")) } ), suite("Termination")( proxTest("can be terminated with cancellation") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -496,13 +494,13 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") ) val program = process.start().use { fiber => - ZIO.sleep(250.millis).flatMap(_ => fiber.cancel) + fiber.cancel.delay(250.millis) } - program.map(r => assert(r)(equalTo(()))) + program.map(r => assertTrue(r == ())) } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), proxTest("can be terminated by releasing the resource") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -513,10 +511,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val program = process.start().use { _ => ZIO.sleep(250.millis) } - program.map(r => assert(r)(equalTo(()))) + program.map(r => assertTrue(r == ())) } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), proxTest("can be terminated") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -531,10 +529,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { result <- runningProcess.terminate() } yield result.exitCode - program.map(r => assert(r)(equalTo(ExitCode(1)))) + program.map(r => assertTrue(r == ExitCode(1))) } @@ TestAspect.withLiveClock, proxTest("can be killed") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -549,10 +547,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { result <- runningProcess.kill() } yield result.exitCode - program.map(r => assert(r)(equalTo(ExitCode(137)))) + program.map(r => assertTrue(r == ExitCode(137))) } @@ TestAspect.withLiveClock, proxTest("can be checked if is alive") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -565,12 +563,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { isAliveAfter <- runningProcess.isAlive } yield (isAliveBefore, isAliveAfter) - program.map(r => assert(r)(equalTo((true, false)))) + program.map(r => assertTrue(r == (true, false))) } ), suite("Customization")( proxTest("can change the command") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -580,10 +578,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val p2 = p1.withCommand("echo") val program = p2.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world\n"))) + program.map(r => assertTrue(r == "Hello world\n")) }, proxTest("can change the arguments") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -592,10 +590,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val p2 = p1.withArguments(List("Hello", "world")) val program = p2.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world\n"))) + program.map(r => assertTrue(r == "Hello world\n")) }, proxTest("respects the working directory") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -616,7 +614,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { } }, proxTest("is customizable with environment variables") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -627,11 +625,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, proxTest("is customizable with excluded environment variables") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -643,11 +641,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `without` "TEST1") ># fs2.text.utf8.decode val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello ! I am prox!\n"))) + program.map(r => assertTrue(r == "Hello ! I am prox!\n")) }, proxTest("is customizable with environment variables output is bound") { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -660,12 +658,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, proxTest( "is customizable with environment variables if input is bound" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -680,18 +678,16 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, proxTest( "is customizable with environment variables if error is bound" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - val source = - fs2.Stream("This is a test string").through(fs2.text.utf8.encode) val process = ((Process( "sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") @@ -700,32 +696,32 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, proxTest( "is customizable with environment variables if input and output are bound" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner val source = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val process = (((Process( + val process = ((Process( "sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") ) < source) ># fs2.text.utf8.decode) `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) + `with` ("TEST2" -> "prox") val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, proxTest( "is customizable with environment variables if input and error are bound" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -740,30 +736,30 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># fs2.text.utf8.decode val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, proxTest( "is customizable with environment variables if output and error are bound" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - val process = (((Process( + val process = ((Process( "sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") ) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode) `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) + `with` ("TEST2" -> "prox") val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, proxTest( "is customizable with environment variables if everything is bound" ) { prox => - import prox._ + import prox.* implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner @@ -778,7 +774,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) - program.map(r => assert(r)(equalTo("Hello world! I am prox!\n"))) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) } ), test("double output redirect is illegal") { diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala index b4ff21cf..7ad21974 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProxSpecHelpers.scala @@ -1,8 +1,8 @@ package io.github.vigoo.prox.tests.fs2 -import io.github.vigoo.prox.{ProxError, ProxFS2, UnknownProxError} -import zio.interop.catz._ -import zio.test._ +import io.github.vigoo.prox.ProxFS2 +import zio.interop.catz.* +import zio.test.* import zio.{Task, ZIO} import java.io.File diff --git a/prox-java9/src/main/scala/io/github/vigoo/prox/java9/runner.scala b/prox-java9/src/main/scala/io/github/vigoo/prox/java9/runner.scala index 9508f684..7ebfcacf 100644 --- a/prox-java9/src/main/scala/io/github/vigoo/prox/java9/runner.scala +++ b/prox-java9/src/main/scala/io/github/vigoo/prox/java9/runner.scala @@ -1,6 +1,6 @@ package io.github.vigoo.prox.java9 -import java.lang.{Process => JvmProcess} +import java.lang.Process as JvmProcess import io.github.vigoo.prox.{FailedToQueryState, Prox} diff --git a/prox-zstream-2/src/main/scala/io/github/vigoo/prox/ProxZStream.scala b/prox-zstream-2/src/main/scala/io/github/vigoo/prox/ProxZStream.scala index 58fe3989..9f9eb162 100644 --- a/prox-zstream-2/src/main/scala/io/github/vigoo/prox/ProxZStream.scala +++ b/prox-zstream-2/src/main/scala/io/github/vigoo/prox/ProxZStream.scala @@ -5,7 +5,7 @@ import java.io.IOException import zio.prelude.Identity import zio.stream.{ZSink, ZStream, ZPipeline} -import zio._ +import zio.* import scala.language.implicitConversions @@ -34,7 +34,7 @@ trait ProxZStream extends Prox { override type ProxResource[A] = ZIO[Scope, ProxError, A] override type ProxStream[A] = ZStream[Any, ProxError, A] override type ProxPipe[A, B] = ProxStream[A] => ProxStream[B] - override type ProxSink[A] = TransformAndSink[A, _] + override type ProxSink[A] = TransformAndSink[A, ?] override type ProxMonoid[A] = zio.prelude.Identity[A] protected override final def exitCodeFromInt(value: Int): ProxExitCode = diff --git a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala index 74fbd3f2..27adebbb 100644 --- a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala +++ b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala @@ -1,12 +1,12 @@ package io.github.vigoo.prox.tests.zstream -import io.github.vigoo.prox.zstream._ +import io.github.vigoo.prox.zstream.* import io.github.vigoo.prox.{UnknownProxError, zstream} -import zio.{Clock, _} -import zio.stream.{ZSink, ZStream, ZPipeline} -import zio.test.Assertion._ -import zio.test.TestAspect._ -import zio.test._ +import zio.* +import zio.stream.{ZPipeline, ZSink, ZStream} +import zio.test.* +import zio.test.Assertion.* +import zio.test.TestAspect.* import java.nio.charset.StandardCharsets import java.nio.file.Files @@ -15,7 +15,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - override val spec = + override val spec: Spec[TestEnvironment & Scope, Any] = suite("Piping processes together")( suite("Piping")( test("is possible with two") { @@ -26,7 +26,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) | Process("wc", List("-w"))) ># ZPipeline.utf8Decode val program = processGroup.run().map(_.output.trim) - assertZIO(program)(equalTo("5")) + program.map(r => assertTrue(r == "5")) }, test("is possible with multiple") { @@ -41,7 +41,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { .run() .map(r => r.output.map(_.stripLineEnd.trim).filter(_.nonEmpty)) - assertZIO(program)(hasSameElements(List("1 apple"))) + program.map(r => assert(r)(hasSameElements(List("1 apple")))) }, test("is customizable with pipes") { val customPipe = (s: zstream.ProxStream[Byte]) => @@ -63,10 +63,10 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { .to(Process("wc", List("-w"))) ># ZPipeline.utf8Decode val program = processGroup.run().map(_.output.trim) - assertZIO(program)(equalTo("10")) + program.map(r => assertTrue(r == "10")) }, test("can be mapped") { - import zstream.Process._ + import zstream.Process.* val processGroup1 = (Process( "!echo", @@ -91,7 +91,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { val program = processGroup2.run().map(_.output.trim) - assertZIO(program)(equalTo("5")) + program.map(r => assertTrue(r == "5")) } ), suite("Termination")( @@ -107,7 +107,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { processGroup.start().flatMap { fiber => fiber.interrupt.unit } } - assertZIO(program)(equalTo(())) + program.map(r => assertTrue(r == ())) } @@ TestAspect.timeout(5.seconds), test("can be terminated") { @@ -124,8 +124,10 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { result <- runningProcesses.terminate() } yield result.exitCodes.toList - assertZIO(program)( - contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1)) + program.map(r => + assert(r)( + contains[(Process[Unit, Unit], ProxExitCode)](p1 -> ExitCode(1)) + ) ) } @@ withLiveClock, test("can be killed") { @@ -145,7 +147,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { // Note: we can't assert on the second process' exit code because there is a race condition // between killing it directly and being stopped because of the upstream process got killed. - assertZIO(program)(contains(p1 -> ExitCode(137))) + program.map(r => assert(r)(contains(p1 -> ExitCode(137)))) } @@ withLiveClock ), suite("Input redirection")( @@ -160,7 +162,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { )) < stream ># ZPipeline.utf8Decode val program = processGroup.run().map(_.output.trim) - assertZIO(program)(equalTo("5")) + program.map(r => assertTrue(r == "5")) }, test("can be fed with an input file") { @@ -181,7 +183,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { result <- processGroup.run() } yield result.output.trim - assertZIO(program)(equalTo("5")) + program.map(r => assertTrue(r == "5")) } } ), @@ -202,7 +204,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { .mapError(UnknownProxError.apply) } yield contents.trim - assertZIO(program)(equalTo("5")) + program.map(r => assertTrue(r == "5")) } } ), @@ -462,7 +464,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { .mapError(UnknownProxError.apply) } yield (contents1, contents2) - assertZIO(program)(equalTo(("Hello", "world"))) + program.map(r => assertTrue(r == ("Hello", "world"))) } } } diff --git a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala index 176269d7..d8a9c7dd 100644 --- a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala +++ b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala @@ -1,21 +1,21 @@ package io.github.vigoo.prox.tests.zstream -import java.nio.charset.StandardCharsets -import java.nio.file.Files -import io.github.vigoo.prox.{ProxError, UnknownProxError, zstream} -import io.github.vigoo.prox.zstream._ -import zio._ +import io.github.vigoo.prox.zstream.* +import io.github.vigoo.prox.{UnknownProxError, zstream} import zio.stream.{ZPipeline, ZSink, ZStream} +import zio.test.* import zio.test.Assertion.{anything, equalTo, hasSameElements, isLeft} -import zio.test.TestAspect._ -import zio.test._ -import zio.{ExitCode, ZIO} +import zio.test.TestAspect.* +import zio.* + +import java.nio.charset.StandardCharsets +import java.nio.file.Files object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - override val spec = + override val spec: Spec[TestEnvironment & Scope, Any] = suite("Executing a process")( test("returns the exit code") { val program = for { @@ -23,7 +23,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { falseResult <- Process("false").run() } yield (trueResult.exitCode, falseResult.exitCode) - assertZIO(program)(equalTo((ExitCode(0), ExitCode(1)))) + program.map(r => assertTrue(r == (ExitCode(0), ExitCode(1)))) }, suite("Output redirection")( test("can redirect output to a file") { @@ -39,7 +39,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .mapError(UnknownProxError.apply) } yield contents - assertZIO(program)(equalTo("Hello world!\n")) + program.map(r => assertTrue(r == "Hello world!\n")) } }, test("can redirect output to append a file") { @@ -56,7 +56,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .mapError(UnknownProxError.apply) } yield contents - assertZIO(program)(equalTo("Hello\nworld\n")) + program.map(r => assertTrue(r == "Hello\nworld\n")) } }, test("can redirect output to stream") { @@ -64,7 +64,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { Process("echo", List("Hello world!")) ># ZPipeline.utf8Decode val program = process.run().map(_.output) - assertZIO(program)(equalTo("Hello world!\n")) + program.map(r => assertTrue(r == "Hello world!\n")) }, test("can redirect output to stream folding monoid") { val process = Process( @@ -73,7 +73,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) ># (ZPipeline.utf8Decode >>> ZPipeline.splitLines) val program = process.run().map(_.output) - assertZIO(program)(equalTo("Helloworld!")) + program.map(r => assertTrue(r == "Helloworld!")) }, test("can redirect output to stream collected to vector") { case class StringLength(value: Int) @@ -86,8 +86,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val process = Process("echo", List("Hello\nworld!")) >? stream val program = process.run().map(_.output) - assertZIO(program)( - hasSameElements(List(StringLength(5), StringLength(6))) + program.map(r => + assert(r)( + hasSameElements(List(StringLength(5), StringLength(6))) + ) ) }, test("can redirect output to stream and ignore it's result") { @@ -95,7 +97,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .drainOutput(ZPipeline.utf8Decode >>> ZPipeline.splitLines) val program = process.run().map(_.output) - assertZIO(program)(equalTo(())) + program.map(r => assertTrue(r == ())) }, test("can redirect output to stream and fold it") { val process = Process("echo", List("Hello\nworld!")).foldOutput( @@ -105,7 +107,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val program = process.run().map(_.output) - assertZIO(program)(equalTo(Vector(Some('H'), Some('w')))) + program.map(r => assertTrue(r == Vector(Some('H'), Some('w')))) }, test("can redirect output to a sink") { val builder = new StringBuilder @@ -118,7 +120,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val process = Process("echo", List("Hello world!")) > target val program = process.run().as(builder.toString) - assertZIO(program)(equalTo("Hello world!\n")) + program.map(r => assertTrue(r == "Hello world!\n")) } ), suite("Error redirection")( @@ -137,7 +139,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .mapError(UnknownProxError.apply) } yield contents - assertZIO(program)(equalTo("Hello world!")) + program.map(r => assertTrue(r == "Hello world!")) } }, test("can redirect error to append a file") { @@ -160,7 +162,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .mapError(UnknownProxError.apply) } yield contents - assertZIO(program)(equalTo("Helloworld")) + program.map(r => assertTrue(r == "Helloworld")) } }, test("can redirect error to stream") { @@ -170,7 +172,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) !># ZPipeline.utf8Decode val program = process.run().map(_.error) - assertZIO(program)(equalTo("Hello")) + program.map(r => assertTrue(r == "Hello")) }, test("can redirect error to stream folding monoid") { val process = Process( @@ -179,7 +181,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) !># (ZPipeline.utf8Decode >>> ZPipeline.splitLines) val program = process.run().map(_.error) - assertZIO(program)(equalTo("Helloworld!")) + program.map(r => assertTrue(r == "Helloworld!")) }, test("can redirect error to stream collected to vector") { case class StringLength(value: Int) @@ -195,8 +197,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) !>? stream val program = process.run().map(_.error) - assertZIO(program)( - hasSameElements(List(StringLength(5), StringLength(6))) + program.map(r => + assert(r)( + hasSameElements(List(StringLength(5), StringLength(6))) + ) ) }, test("can redirect error to stream and ignore it's result") { @@ -205,7 +209,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .drainError(ZPipeline.utf8Decode >>> ZPipeline.splitLines) val program = process.run().map(_.error) - assertZIO(program)(equalTo(())) + program.map(r => assertTrue(r == ())) }, test("can redirect error to stream and fold it") { val process = Process( @@ -218,7 +222,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val program = process.run().map(_.error) - assertZIO(program)(equalTo(Vector(Some('H'), Some('w')))) + program.map(r => assertTrue(r == Vector(Some('H'), Some('w')))) }, test("can redirect error to a sink") { val builder = new StringBuilder @@ -232,7 +236,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { Process("perl", List("-e", """print STDERR "Hello"""")) !> target val program = process.run().as(builder.toString) - assertZIO(program)(equalTo("Hello")) + program.map(r => assertTrue(r == "Hello")) } ), suite("Redirection ordering")( @@ -246,7 +250,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) < source !># ZPipeline.utf8Decode val program = process.run().map(_.error) - assertZIO(program)(equalTo("This is a test string")) + program.map(r => assertTrue(r == "This is a test string")) }, test("can redirect error first then output to stream") { val process = (Process( @@ -255,7 +259,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode val program = process.run().map(r => r.output + r.error) - assertZIO(program)(equalTo("HelloWorld")) + program.map(r => assertTrue(r == "HelloWorld")) }, test("can redirect output first then error to stream") { val process = (Process( @@ -264,7 +268,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) ># ZPipeline.utf8Decode) !># ZPipeline.utf8Decode val program = process.run().map(r => r.output + r.error) - assertZIO(program)(equalTo("HelloWorld")) + program.map(r => assertTrue(r == "HelloWorld")) }, test("can redirect output first then error finally input to stream") { val source = ZStream("Hello").flatMap(s => @@ -281,7 +285,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { !># ZPipeline.utf8Decode) < source val program = process.run().map(r => r.output + r.error) - assertZIO(program)(equalTo("HelloWorld")) + program.map(r => assertTrue(r == "HelloWorld")) }, test("can redirect output first then input finally error to stream") { val source = ZStream("Hello").flatMap(s => @@ -298,7 +302,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { < source) !># ZPipeline.utf8Decode val program = process.run().map(r => r.output + r.error) - assertZIO(program)(equalTo("HelloWorld")) + program.map(r => assertTrue(r == "HelloWorld")) }, test("can redirect input first then error finally output to stream") { val source = ZStream("Hello").flatMap(s => @@ -315,7 +319,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode val program = process.run().map(r => r.output + r.error) - assertZIO(program)(equalTo("HelloWorld")) + program.map(r => assertTrue(r == "HelloWorld")) } ), suite("Input redirection")( @@ -327,7 +331,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { Process("wc", List("-w")) < source ># ZPipeline.utf8Decode val program = process.run().map(_.output.trim) - assertZIO(program)(equalTo("5")) + program.map(r => assertTrue(r == "5")) }, test("can use stream as input flushing after each chunk") { val source = ZStream("This ", "is a test", " string").flatMap(s => @@ -337,7 +341,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { (Process("wc", List("-w")) !< source) ># ZPipeline.utf8Decode val program = process.run().map(_.output.trim) - assertZIO(program)(equalTo("5")) + program.map(r => assertTrue(r == "5")) } ), suite("Termination")( @@ -348,11 +352,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val program = ZIO.scoped { process.start().flatMap { fiber => - ZIO.sleep(250.millis) *> fiber.interrupt.unit + fiber.interrupt.unit.delay(250.millis) } } - assertZIO(program)(equalTo(())) + program.map(r => assertTrue(r == ())) } @@ TestAspect.withLiveClock @@ TestAspect.timeout( 5.seconds ) @@ TestAspect.diagnose(2.seconds), @@ -362,10 +366,10 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { List("-e", """$SIG{TERM} = sub { exit 1 }; sleep 30; exit 0""") ) val program = ZIO.scoped { - process.start().flatMap { _ => ZIO.sleep(250.millis) } + process.start() *> ZIO.sleep(250.millis) } - assertZIO(program)(equalTo(())) + program.map(r => assertTrue(r == ())) } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), test("can be terminated") { val process = Process( @@ -378,7 +382,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { result <- runningProcess.terminate() } yield result.exitCode - assertZIO(program)(equalTo(ExitCode(1))) + program.map(r => assertTrue(r == ExitCode(1))) } @@ withLiveClock, test("can be killed") { val process = Process( @@ -391,7 +395,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { result <- runningProcess.kill() } yield result.exitCode - assertZIO(program)(equalTo(ExitCode(137))) + program.map(r => assertTrue(r == ExitCode(137))) } @@ withLiveClock, test("can be checked if is alive") { val process = Process("sleep", List("10")) @@ -402,7 +406,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { isAliveAfter <- runningProcess.isAlive } yield (isAliveBefore, isAliveAfter) - assertZIO(program)(equalTo((true, false))) + program.map(r => assertTrue(r == (true, false))) } ), suite("Customization")( @@ -412,14 +416,14 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val p2 = p1.withCommand("echo") val program = p2.run().map(_.output) - assertZIO(program)(equalTo("Hello world\n")) + program.map(r => assertTrue(r == "Hello world\n")) }, test("can change the arguments") { val p1 = Process("echo") ># ZPipeline.utf8Decode val p2 = p1.withArguments(List("Hello", "world")) val program = p2.run().map(_.output) - assertZIO(program)(equalTo("Hello world\n")) + program.map(r => assertTrue(r == "Hello world\n")) }, test("respects the working directory") { ZIO.attempt(Files.createTempDirectory("prox")).flatMap { @@ -428,9 +432,11 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { (Process("pwd") in tempDirectory) ># ZPipeline.utf8Decode val program = process.run().map(_.output.trim) - assertZIO(program)( - equalTo(tempDirectory.toString) || equalTo( - s"/private${tempDirectory}" + program.map(r => + assert(r)( + equalTo(tempDirectory.toString) || equalTo( + s"/private${tempDirectory}" + ) ) ) } @@ -442,7 +448,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># ZPipeline.utf8Decode val program = process.run().map(_.output) - assertZIO(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, test("is customizable with excluded environment variables") { val process = @@ -452,18 +458,18 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `without` "TEST1") ># ZPipeline.utf8Decode val program = process.run().map(_.output) - assertZIO(program)(equalTo("Hello ! I am prox!\n")) + program.map(r => assertTrue(r == "Hello ! I am prox!\n")) }, test("is customizable with environment variables output is bound") { - val process = (Process( + val process = Process( "sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") ) ># ZPipeline.utf8Decode `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) + `with` ("TEST2" -> "prox") val program = process.run().map(_.output) - assertZIO(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, test("is customizable with environment variables if input is bound") { val source = ZStream("This is a test string").flatMap(s => @@ -477,7 +483,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># ZPipeline.utf8Decode val program = process.run().map(_.output) - assertZIO(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, test("is customizable with environment variables if error is bound") { val process = ((Process( @@ -488,7 +494,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># ZPipeline.utf8Decode val program = process.run().map(_.output) - assertZIO(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, test( "is customizable with environment variables if input and output are bound" @@ -496,15 +502,15 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) ) - val process = (((Process( + val process = ((Process( "sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") ) < source) ># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) + `with` ("TEST2" -> "prox") val program = process.run().map(_.output) - assertZIO(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, test( "is customizable with environment variables if input and error are bound" @@ -520,20 +526,20 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { `with` ("TEST2" -> "prox")) ># ZPipeline.utf8Decode val program = process.run().map(_.output) - assertZIO(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, test( "is customizable with environment variables if output and error are bound" ) { - val process = (((Process( + val process = ((Process( "sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") ) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) + `with` ("TEST2" -> "prox") val program = process.run().map(_.output) - assertZIO(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) }, test( "is customizable with environment variables if everything is bound" @@ -541,15 +547,15 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) ) - val process = ((((Process( + val process = (((Process( "sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") ) < source) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox")) + `with` ("TEST2" -> "prox") val program = process.run().map(_.output) - assertZIO(program)(equalTo("Hello world! I am prox!\n")) + program.map(r => assertTrue(r == "Hello world! I am prox!\n")) } ), test("double output redirect is illegal") { From 42f7e86e28d3fde9ce127619d0af8bfb1317741f Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 20 Jan 2024 12:38:58 +0100 Subject: [PATCH 4/6] Fixes --- .../prox/tests/fs2/ProcessGroupSpecs.scala | 2 +- .../vigoo/prox/tests/fs2/ProcessSpecs.scala | 16 ++++++------- .../tests/zstream/ProcessGroupSpecs.scala | 2 +- .../prox/tests/zstream/ProcessSpecs.scala | 24 +++++++++---------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala index 659a20f4..391952f2 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessGroupSpecs.scala @@ -115,7 +115,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { Process("sort") val program = processGroup.start().use { fiber => fiber.cancel } - program.map(r => assertTrue(r == ())) + program.as(assertCompletes) } @@ TestAspect.timeout(5.seconds), proxTest("can be terminated") { prox => import prox.* diff --git a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala index 8e37c44b..65d91e27 100644 --- a/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala +++ b/prox-fs2-3/src/test/scala/io/github/vigoo/prox/tests/fs2/ProcessSpecs.scala @@ -139,7 +139,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val program = process.run().map(_.output) - program.map(r => assertTrue(r == ())) + program.as(assertCompletes) }, proxTest("can redirect output to stream and fold it") { prox => import prox.* @@ -299,7 +299,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .drainError(fs2.text.utf8.decode.andThen(fs2.text.lines)) val program = process.run().map(_.error) - program.map(r => assertTrue(r == ())) + program.as(assertCompletes) }, proxTest("can redirect error to stream and fold it") { prox => import prox.* @@ -497,7 +497,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { fiber.cancel.delay(250.millis) } - program.map(r => assertTrue(r == ())) + program.as(assertCompletes) } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), proxTest("can be terminated by releasing the resource") { prox => import prox.* @@ -511,7 +511,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { ) val program = process.start().use { _ => ZIO.sleep(250.millis) } - program.map(r => assertTrue(r == ())) + program.as(assertCompletes) } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), proxTest("can be terminated") { prox => import prox.* @@ -708,12 +708,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val source = fs2.Stream("This is a test string").through(fs2.text.utf8.encode) - val process = ((Process( + val process = (((Process( "sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") ) < source) ># fs2.text.utf8.decode) `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox") + `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) program.map(r => assertTrue(r == "Hello world! I am prox!\n")) @@ -746,12 +746,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { implicit val processRunner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner - val process = ((Process( + val process = (((Process( "sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") ) !># fs2.text.utf8.decode) ># fs2.text.utf8.decode) `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox") + `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) program.map(r => assertTrue(r == "Hello world! I am prox!\n")) diff --git a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala index 27adebbb..2dc9813b 100644 --- a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala +++ b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessGroupSpecs.scala @@ -107,7 +107,7 @@ object ProcessGroupSpecs extends ZIOSpecDefault with ProxSpecHelpers { processGroup.start().flatMap { fiber => fiber.interrupt.unit } } - program.map(r => assertTrue(r == ())) + program.as(assertCompletes) } @@ TestAspect.timeout(5.seconds), test("can be terminated") { diff --git a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala index d8a9c7dd..92f2eb3c 100644 --- a/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala +++ b/prox-zstream-2/src/test/scala/io/github/vigoo/prox/tests/zstream/ProcessSpecs.scala @@ -97,7 +97,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .drainOutput(ZPipeline.utf8Decode >>> ZPipeline.splitLines) val program = process.run().map(_.output) - program.map(r => assertTrue(r == ())) + program.as(assertCompletes) }, test("can redirect output to stream and fold it") { val process = Process("echo", List("Hello\nworld!")).foldOutput( @@ -209,7 +209,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { .drainError(ZPipeline.utf8Decode >>> ZPipeline.splitLines) val program = process.run().map(_.error) - program.map(r => assertTrue(r == ())) + program.as(assertCompletes) }, test("can redirect error to stream and fold it") { val process = Process( @@ -356,7 +356,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { } } - program.map(r => assertTrue(r == ())) + program.as(assertCompletes) } @@ TestAspect.withLiveClock @@ TestAspect.timeout( 5.seconds ) @@ TestAspect.diagnose(2.seconds), @@ -369,7 +369,7 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { process.start() *> ZIO.sleep(250.millis) } - program.map(r => assertTrue(r == ())) + program.as(assertCompletes) } @@ TestAspect.withLiveClock @@ TestAspect.timeout(5.seconds), test("can be terminated") { val process = Process( @@ -461,12 +461,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { program.map(r => assertTrue(r == "Hello ! I am prox!\n")) }, test("is customizable with environment variables output is bound") { - val process = Process( + val process = (Process( "sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") ) ># ZPipeline.utf8Decode `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox") + `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) program.map(r => assertTrue(r == "Hello world! I am prox!\n")) @@ -502,12 +502,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) ) - val process = ((Process( + val process = (((Process( "sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") ) < source) ># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox") + `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) program.map(r => assertTrue(r == "Hello world! I am prox!\n")) @@ -531,12 +531,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { test( "is customizable with environment variables if output and error are bound" ) { - val process = ((Process( + val process = (((Process( "sh", List("-c", "echo \"Hello $TEST1! I am $TEST2!\"") ) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox") + `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) program.map(r => assertTrue(r == "Hello world! I am prox!\n")) @@ -547,12 +547,12 @@ object ProcessSpecs extends ZIOSpecDefault with ProxSpecHelpers { val source = ZStream("This is a test string").flatMap(s => ZStream.fromIterable(s.getBytes(StandardCharsets.UTF_8)) ) - val process = (((Process( + val process = ((((Process( "sh", List("-c", "cat > /dev/null; echo \"Hello $TEST1! I am $TEST2!\"") ) < source) !># ZPipeline.utf8Decode) ># ZPipeline.utf8Decode) `with` ("TEST1" -> "world") - `with` ("TEST2" -> "prox") + `with` ("TEST2" -> "prox")) val program = process.run().map(_.output) program.map(r => assertTrue(r == "Hello world! I am prox!\n")) From d4861b8b236dfe608366ddb6ca3150d374d1f607 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 20 Jan 2024 12:59:30 +0100 Subject: [PATCH 5/6] Fix mdoc --- docs/docs/docs/fs2/custom-runners.md | 5 +- docs/docs/docs/fs2/customize.md | 5 +- docs/docs/docs/fs2/index.md | 5 +- docs/docs/docs/fs2/processgroups.md | 9 +-- docs/docs/docs/fs2/redirection.md | 19 ++--- docs/docs/docs/fs2/running.md | 2 +- docs/docs/docs/zstream/custom-runners.md | 13 ++-- docs/docs/docs/zstream/processgroups.md | 9 +-- docs/docs/docs/zstream/redirection.md | 99 ++++++++++++------------ docs/docs/docs/zstream/running.md | 7 +- 10 files changed, 79 insertions(+), 94 deletions(-) diff --git a/docs/docs/docs/fs2/custom-runners.md b/docs/docs/docs/fs2/custom-runners.md index 5fe8352a..62ea841f 100644 --- a/docs/docs/docs/fs2/custom-runners.md +++ b/docs/docs/docs/fs2/custom-runners.md @@ -11,10 +11,7 @@ import cats.Traverse import scala.concurrent.ExecutionContext import io.github.vigoo.prox._ -implicit val contextShift = IO.contextShift(ExecutionContext.global) -val (blocker, _) = Blocker[IO].allocated.unsafeRunSync() - -val prox = ProxFS2[IO](blocker) +val prox = ProxFS2[IO] import prox._ ``` diff --git a/docs/docs/docs/fs2/customize.md b/docs/docs/docs/fs2/customize.md index 77890f8c..a78b7e5f 100644 --- a/docs/docs/docs/fs2/customize.md +++ b/docs/docs/docs/fs2/customize.md @@ -10,10 +10,7 @@ import cats.effect._ import scala.concurrent.ExecutionContext import io.github.vigoo.prox._ -implicit val contextShift = IO.contextShift(ExecutionContext.global) -val (blocker, _) = Blocker[IO].allocated.unsafeRunSync() - -val prox = ProxFS2[IO](blocker) +val prox = ProxFS2[IO] import prox._ ``` diff --git a/docs/docs/docs/fs2/index.md b/docs/docs/docs/fs2/index.md index c8ea1e27..30a3eece 100644 --- a/docs/docs/docs/fs2/index.md +++ b/docs/docs/docs/fs2/index.md @@ -24,13 +24,10 @@ the `Prox` module: import cats.effect._ import scala.concurrent.ExecutionContext import io.github.vigoo.prox._ - -implicit val contextShift = IO.contextShift(ExecutionContext.global) -val (blocker, _) = Blocker[IO].allocated.unsafeRunSync() ``` ```scala mdoc -val prox = ProxFS2[IO](blocker) +val prox = ProxFS2[IO] import prox._ ``` diff --git a/docs/docs/docs/fs2/processgroups.md b/docs/docs/docs/fs2/processgroups.md index 9ac8fc95..64945a6f 100644 --- a/docs/docs/docs/fs2/processgroups.md +++ b/docs/docs/docs/fs2/processgroups.md @@ -9,10 +9,7 @@ import cats.effect._ import scala.concurrent.ExecutionContext import io.github.vigoo.prox._ -implicit val contextShift = IO.contextShift(ExecutionContext.global) -val (blocker, _) = Blocker[IO].allocated.unsafeRunSync() - -val prox = ProxFS2[IO](blocker) +val prox = ProxFS2[IO] import prox._ ``` @@ -44,12 +41,12 @@ following not very useful example capitalizes each word coming through: ```scala mdoc:silent val customPipe: fs2.Pipe[IO, Byte, Byte] = (s: fs2.Stream[IO, Byte]) => s - .through(fs2.text.utf8Decode) // decode UTF-8 + .through(fs2.text.utf8.decode) // decode UTF-8 .through(fs2.text.lines) // split to lines .map(_.split(' ').toVector) // split lines to words .map(v => v.map(_.capitalize).mkString(" ")) .intersperse("\n") // remerge lines - .through(fs2.text.utf8Encode) // encode as UTF-8 + .through(fs2.text.utf8.encode) // encode as UTF-8 val group3 = Process("echo", List("hello world")).via(customPipe).to(Process("wc", List("-w"))) ``` \ No newline at end of file diff --git a/docs/docs/docs/fs2/redirection.md b/docs/docs/docs/fs2/redirection.md index ba1b255e..c8093e97 100644 --- a/docs/docs/docs/fs2/redirection.md +++ b/docs/docs/docs/fs2/redirection.md @@ -10,10 +10,7 @@ import cats.effect._ import scala.concurrent.ExecutionContext import io.github.vigoo.prox._ -implicit val contextShift = IO.contextShift(ExecutionContext.global) -val (blocker, _) = Blocker[IO].allocated.unsafeRunSync() - -val prox = ProxFS2[IO](blocker) +val prox = ProxFS2[IO] import prox._ ``` @@ -34,13 +31,13 @@ Let's see an example of this (redirection methods are described below on this pa import cats.implicits._ val proc1 = Process("echo", List("Hello world")) -val proc2 = proc1 ># fs2.text.utf8Decode +val proc2 = proc1 ># fs2.text.utf8.decode ``` It is no longer possible to redirect the output of `proc2`: ```scala mdoc:fail -val proc3 = proc2 >? fs2.text.utf8Decode[IO].andThen(fs2.text.lines) +val proc3 = proc2 >? fs2.text.utf8.decode[IO].andThen(fs2.text.lines) ``` Many redirection methods have an _operator_ version but all of them have alphanumberic @@ -136,17 +133,17 @@ process they came from: ```scala mdoc:silent -import fs2.concurrent.Queue +import cats.effect.std.Queue for { errors <- Queue.unbounded[IO, String] - parseLines = fs2.text.utf8Decode[IO].andThen(fs2.text.lines) + parseLines = fs2.text.utf8.decode[IO].andThen(fs2.text.lines) p1 = Process("proc1") p2 = Process("proc2") group = (p1 | p2).customizedPerProcess.errorsToSink { - case p if p == p1 => parseLines.andThen(_.map(s => "P1: " + s)).andThen(_.through(errors.enqueue)) - case p if p == p2 => parseLines.andThen(_.map(s => "P2: " + s)).andThen(_.through(errors.enqueue)) + case p if p == p1 => parseLines.andThen(_.map(s => "P1: " + s)).andThen(_.evalMap(errors.offer)) + case p if p == p2 => parseLines.andThen(_.map(s => "P2: " + s)).andThen(_.evalMap(errors.offer)) } } yield () ``` @@ -171,7 +168,7 @@ These type aliases can be used to define functions performing redirection on arb ```scala mdoc def logErrors[P <: Process.UnboundEProcess[_]](proc: P) = { - val target = fs2.text.utf8Decode[IO].andThen(fs2.text.lines).andThen(_.evalMap(line => IO(println(line)))) + val target = fs2.text.utf8.decode[IO].andThen(fs2.text.lines).andThen(_.evalMap(line => IO(println(line)))) proc !> target } diff --git a/docs/docs/docs/fs2/running.md b/docs/docs/docs/fs2/running.md index 549b6284..083d336b 100644 --- a/docs/docs/docs/fs2/running.md +++ b/docs/docs/docs/fs2/running.md @@ -24,7 +24,7 @@ implicit val runner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner val process = Process("echo", List("hello")) val result1 = process.run() -val result2 = process.start().use { fiber => +val result2 = process.start().flatMap { fiber => fiber.join } diff --git a/docs/docs/docs/zstream/custom-runners.md b/docs/docs/docs/zstream/custom-runners.md index 30e114da..a5a975e8 100644 --- a/docs/docs/docs/zstream/custom-runners.md +++ b/docs/docs/docs/zstream/custom-runners.md @@ -7,7 +7,6 @@ title: Custom runners ```scala mdoc:invisible import zio._ -import zio.blocking.Blocking import zio.stream._ import io.github.vigoo.prox._ import io.github.vigoo.prox.zstream._ @@ -39,7 +38,7 @@ class DockerizedProcessRunner[Info](processRunner: ProcessRunner[Info], image: DockerImage) extends ProcessRunner[DockerProcessInfo[Info]] { - override def startProcess[O, E](process: Process[O, E]): ZIO[Blocking, ProxError, RunningProcess[O, E, DockerProcessInfo[Info]]] = { + override def startProcess[O, E](process: Process[O, E]): ZIO[Any, ProxError, RunningProcess[O, E, DockerProcessInfo[Info]]] = { for { container <- generateContainerName runningProcess <- processRunner @@ -47,13 +46,13 @@ class DockerizedProcessRunner[Info](processRunner: ProcessRunner[Info], } yield runningProcess.mapInfo(info => DockerProcessInfo(container, info)) } - override def startProcessGroup[O, E](processGroup: ProcessGroup[O, E]): ZIO[Blocking, ProxError, RunningProcessGroup[O, E, DockerProcessInfo[Info]]] = { + override def startProcessGroup[O, E](processGroup: ProcessGroup[O, E]): ZIO[Any, ProxError, RunningProcessGroup[O, E, DockerProcessInfo[Info]]] = { ZIO.foreach(processGroup.originalProcesses.toVector)(key => generateContainerName.map(c => key -> c)).flatMap { keyAndNames => val nameMap = keyAndNames.toMap val names = keyAndNames.map(_._2) val modifiedProcessGroup = processGroup.map(new ProcessGroup.Mapper[O, E] { - def mapFirst[P <: Process[ZStream[Blocking, ProxError, Byte], E]](process: P): P = wrapInDocker(process, names.head).asInstanceOf[P] - def mapInnerWithIdx[P <: Process.UnboundIProcess[ZStream[Blocking, ProxError, Byte], E]](process: P, idx: Int): P = + def mapFirst[P <: Process[ZStream[Any, ProxError, Byte], E]](process: P): P = wrapInDocker(process, names.head).asInstanceOf[P] + def mapInnerWithIdx[P <: Process.UnboundIProcess[ZStream[Any, ProxError, Byte], E]](process: P, idx: Int): P = wrapInDocker(process, names(idx)).asInstanceOf[P] def mapLast[P <: Process.UnboundIProcess[O, E]](process: P): P = wrapInDocker(process, names.last).asInstanceOf[P] }) @@ -62,8 +61,8 @@ class DockerizedProcessRunner[Info](processRunner: ProcessRunner[Info], } } - private def generateContainerName: ZIO[Blocking, ProxError, DockerContainer] = - ZIO.effect(DockerContainer(UUID.randomUUID().toString)).mapError(UnknownProxError) + private def generateContainerName: ZIO[Any, ProxError, DockerContainer] = + ZIO.attempt(DockerContainer(UUID.randomUUID().toString)).mapError(UnknownProxError) private def wrapInDocker[O, E](process: Process[O, E], container: DockerContainer): Process[O, E] = { val envVars = process.environmentVariables.flatMap { case (key, value) => List("-e", s"$key=$value") }.toList diff --git a/docs/docs/docs/zstream/processgroups.md b/docs/docs/docs/zstream/processgroups.md index 02cdc262..5470bbe2 100644 --- a/docs/docs/docs/zstream/processgroups.md +++ b/docs/docs/docs/zstream/processgroups.md @@ -6,7 +6,6 @@ title: Process groups # Connecting processes together via pipes ```scala mdoc:invisible import zio._ -import zio.blocking.Blocking import zio.stream._ import zio.prelude._ import io.github.vigoo.prox._ @@ -36,14 +35,14 @@ val group1 = Process("grep", List("ERROR")) | Process("sort") val group2 = group1 | Process("uniq", List("-c")) ``` -A custom pipe (when using `via`) can be anything of the type `ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, Byte])`. +A custom pipe (when using `via`) can be anything of the type `ZStream[any, ProxError, Byte] => ZStream[any, ProxError, Byte])`. The following not very useful example capitalizes each word coming through: ```scala mdoc:silent val customPipe: ProxPipe[Byte, Byte] = - (s: ZStream[Blocking, ProxError, Byte]) => s - .transduce(ZTransducer.utf8Decode) // decode UTF-8 - .transduce(ZTransducer.splitLines) // split to lines + (s: ZStream[Any, ProxError, Byte]) => s + .via(ZPipeline.utf8Decode.mapError(UnknownProxError.apply)) // decode UTF-8 + .via(ZPipeline.splitLines) // split to lines .map(_.split(' ').toVector) // split lines to words .map(v => v.map(_.capitalize).mkString(" ")) .intersperse("\n") // remerge lines diff --git a/docs/docs/docs/zstream/redirection.md b/docs/docs/docs/zstream/redirection.md index 366d5e3d..b1f04bfd 100644 --- a/docs/docs/docs/zstream/redirection.md +++ b/docs/docs/docs/zstream/redirection.md @@ -25,18 +25,17 @@ Let's see an example of this (redirection methods are described below on this pa ```scala mdoc import zio._ -import zio.blocking.Blocking import zio.stream._ import zio.prelude._ val proc1 = Process("echo", List("Hello world")) -val proc2 = proc1 ># ZTransducer.utf8Decode +val proc2 = proc1 ># ZPipeline.utf8Decode ``` It is no longer possible to redirect the output of `proc2`: ```scala mdoc:fail -val proc3 = proc2 >? (ZTransducer.utf8Decode >>> ZTransducer.splitLines) +val proc3 = proc2 >? (ZPipeline.utf8Decode >>> ZPipeline.splitLines) ``` Many redirection methods have an _operator_ version but all of them have alphanumberic @@ -46,49 +45,49 @@ variants as well. Input redirection is enabled by the `RedirectableInput` trait. The following operations are supported: -| operator | alternative | parameter type | what it does | -|----------|--------------|-----------------------------------------|---------------| -| `<` | `fromFile` | `java.nio.file.Path` | Natively attach a source file to STDIN | -| `<` | `fromStream` | `ZStream[Blocking, ProxError, Byte]` | Attach a _ZIO byte stream_ to STDIN | -| `!<` | `fromStream` | `ZStream[Blocking, ProxError, Byte]` | Attach a _ZIO byte stream_ to STDIN and **flush** after each chunk | +| operator | alternative | parameter type | what it does | +|----------|--------------|---------------------------------|---------------| +| `<` | `fromFile` | `java.nio.file.Path` | Natively attach a source file to STDIN | +| `<` | `fromStream` | `ZStream[Any, ProxError, Byte]` | Attach a _ZIO byte stream_ to STDIN | +| `!<` | `fromStream` | `ZStream[Any, ProxError, Byte]` | Attach a _ZIO byte stream_ to STDIN and **flush** after each chunk | ### Output redirection Output redirection is enabled by the `RedirectableOutput` trait. The following operations are supported: -| operator | alternative | parameter type | result type | what it does | -|----------|----------------|------------------------------------------------------------------------------------------|-------------| --------------| -| `>` | `toFile` | `java.nio.file.Path` | `Unit` | Natively attach STDOUT to a file | -| `>>` | `appendToFile` | `java.nio.file.Path` | `Unit` | Natively attach STDOUT to a file in append mode | -| `>` | `toSink` | `TransformAndSink[Byte, _]` | `Unit` | Drains the STDOUT through the given sink | -| `>#` | `toFoldMonoid` | `[O: Identity](ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `O` | Sends STDOUT through the stream and folds the result using its _monoid_ instance -| `>?` | `toVector` | `ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `Vector[O]` | Sends STDOUT through the stream and collects the results | -| | `drainOutput` | `ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `Unit` | Drains the STDOUT through the given stream | -| | `foldOutput` | `ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O]), R, (R, O) => R` | `R` | Sends STDOUT through the stream and folds the result using a custom fold function | +| operator | alternative | parameter type | result type | what it does | +|----------|----------------|--------------------------------------------------------------------------------|-------------| --------------| +| `>` | `toFile` | `java.nio.file.Path` | `Unit` | Natively attach STDOUT to a file | +| `>>` | `appendToFile` | `java.nio.file.Path` | `Unit` | Natively attach STDOUT to a file in append mode | +| `>` | `toSink` | `TransformAndSink[Byte, _]` | `Unit` | Drains the STDOUT through the given sink | +| `>#` | `toFoldMonoid` | `[O: Identity](ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `O` | Sends STDOUT through the stream and folds the result using its _monoid_ instance +| `>?` | `toVector` | `ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `Vector[O]` | Sends STDOUT through the stream and collects the results | +| | `drainOutput` | `ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `Unit` | Drains the STDOUT through the given stream | +| | `foldOutput` | `ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O]), R, (R, O) => R` | `R` | Sends STDOUT through the stream and folds the result using a custom fold function | -All the variants that accept a _stream transformation_ (`ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])`) are also usable by directly passing -a `ZTransducer`. +All the variants that accept a _stream transformation_ (`ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])`) are also usable by directly passing +a `ZPipeline`. `TransformAndSink` encapsulates a _stream transformation_ and a _unit sink_. It is possible to use a sink directly if transformation is not needed. ```scala -case class TransformAndSink[A, B](transform: ZStream[Blocking, ProxError, A] => ZStream[Blocking, ProxError, B], - sink: ZSink[Blocking, ProxError, B, Any, Unit]) +case class TransformAndSink[A, B](transform: ZStream[Any, ProxError, A] => ZStream[Any, ProxError, B], + sink: ZSink[Any, ProxError, B, Any, Unit]) ``` ### Error redirection Error redirection is enabled by the `RedirectableError` trait. The following operations are supported: -| operator | alternative | parameter type | result type | what it does | -|-----------|---------------------|------------------------------------------------------------------------------------------|-------------| --------------| -| `!>` | `errorToFile` | `java.nio.file.Path` | `Unit` | Natively attach STDERR to a file | -| `!>>` | `appendErrorToFile` | `java.nio.file.Path` | `Unit` | Natively attach STDERR to a file in append mode | -| `!>` | `errorToSink` | `TransformAndSink[Byte, _]` | `Unit` | Drains the STDERR through the given sink | -| `!>#` | `errorToFoldMonoid` | `[O: Monoid](ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `O` | Sends STDERR through the pipe and folds the result using its _monoid_ instance -| `!>?` | `errorToVector` | `ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `Vector[O]` | Sends STDERR through the pipe and collects the results | -| | `drainError` | `ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `Unit` | Drains the STDERR through the given pipe | -| | `foldError` | `ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O]), R, (R, O) => R` | `R` | Sends STDERR through the pipe and folds the result using a custom fold function | +| operator | alternative | parameter type | result type | what it does | +|-----------|---------------------|--------------------------------------------------------------------------------|-------------| --------------| +| `!>` | `errorToFile` | `java.nio.file.Path` | `Unit` | Natively attach STDERR to a file | +| `!>>` | `appendErrorToFile` | `java.nio.file.Path` | `Unit` | Natively attach STDERR to a file in append mode | +| `!>` | `errorToSink` | `TransformAndSink[Byte, _]` | `Unit` | Drains the STDERR through the given sink | +| `!>#` | `errorToFoldMonoid` | `[O: Monoid](ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `O` | Sends STDERR through the pipe and folds the result using its _monoid_ instance +| `!>?` | `errorToVector` | `ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `Vector[O]` | Sends STDERR through the pipe and collects the results | +| | `drainError` | `ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `Unit` | Drains the STDERR through the given pipe | +| | `foldError` | `ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O]), R, (R, O) => R` | `R` | Sends STDERR through the pipe and folds the result using a custom fold function | ### Redirection for process groups [Process groups](processgroups) are two or more processes attached together through pipes. @@ -106,13 +105,13 @@ version described by the `RedirectableErrors` trait. The methods in this trait define error redirection for **all process in the group at once**: -| operator | alternative | parameter type | result type | what it does | -|-----------|----------------------|------------------------------------------------------------------------------------------|-------------| --------------| -| `!>` | `errorsToSink` | `TransformAndSink[Byte, _]` | `Unit` | Drains the STDERR through the given sink | -| `!>#` | `errorsToFoldMonoid` | `[O: Monoid](ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `O` | Sends STDERR through the stream and folds the result using its _monoid_ instance -| `!>?` | `errorsToVector` | `ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `Vector[O]` | Sends STDERR through the stream and collects the results | -| | `drainErrors` | `ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `Unit` | Drains the STDERR through the given stream | -| | `foldErrors` | `ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O]), R, (R, O) => R` | `R` | Sends STDERR through the stream and folds the result using a custom fold function | +| operator | alternative | parameter type | result type | what it does | +|-----------|----------------------|--------------------------------------------------------------------------------|-------------| --------------| +| `!>` | `errorsToSink` | `TransformAndSink[Byte, _]` | `Unit` | Drains the STDERR through the given sink | +| `!>#` | `errorsToFoldMonoid` | `[O: Monoid](ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `O` | Sends STDERR through the stream and folds the result using its _monoid_ instance +| `!>?` | `errorsToVector` | `ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `Vector[O]` | Sends STDERR through the stream and collects the results | +| | `drainErrors` | `ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `Unit` | Drains the STDERR through the given stream | +| | `foldErrors` | `ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O]), R, (R, O) => R` | `R` | Sends STDERR through the stream and folds the result using a custom fold function | Redirection to file is not possible through this interface as only a single path could be provided. @@ -123,15 +122,15 @@ By using the `RedirectableErrors.customizedPerProcess` interface (having the typ `RedirectableErrors.CustomizedPerProcess`) it is possible to customize the redirection targets per process while keeping their types uniform: -| operator | alternative | parameter type | result type | what it does | -|-----------|----------------------|-----------------------------------------------------------------------------------------------------|-------------| --------------| -| | `errorsToFile` | `Process => java.nio.file.Path` | `Unit` | Natively attach STDERR to a file | -| | `appendErrorsToFile` | `Process => java.nio.file.Path` | `Unit` | Natively attach STDERR to a file in append mode | -| | `errorsToSink` | `Process => TransformAndSink[Byte, _]` | `Unit` | Drains the STDERR through the given sink | -| | `errorsToFoldMonoid` | `Process => [O: Monoid](ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `O` | Sends STDERR through the stream and folds the result using its _monoid_ instance -| | `errorsToVector` | `Process => ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `Vector[O]` | Sends STDERR through the stream and collects the results | -| | `drainErrors` | `Process => ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O])` | `Unit` | Drains the STDERR through the given stream | -| | `foldErrors` | `Process => ZStream[Blocking, ProxError, Byte] => ZStream[Blocking, ProxError, O]), R, (R, O) => R` | `R` | Sends STDERR through the stream and folds the result using a custom fold function | +| operator | alternative | parameter type | result type | what it does | +|-----------|----------------------|-------------------------------------------------------------------------------------------|-------------| --------------| +| | `errorsToFile` | `Process => java.nio.file.Path` | `Unit` | Natively attach STDERR to a file | +| | `appendErrorsToFile` | `Process => java.nio.file.Path` | `Unit` | Natively attach STDERR to a file in append mode | +| | `errorsToSink` | `Process => TransformAndSink[Byte, _]` | `Unit` | Drains the STDERR through the given sink | +| | `errorsToFoldMonoid` | `Process => [O: Monoid](ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `O` | Sends STDERR through the stream and folds the result using its _monoid_ instance +| | `errorsToVector` | `Process => ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `Vector[O]` | Sends STDERR through the stream and collects the results | +| | `drainErrors` | `Process => ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O])` | `Unit` | Drains the STDERR through the given stream | +| | `foldErrors` | `Process => ZStream[Any, ProxError, Byte] => ZStream[Any, ProxError, O]), R, (R, O) => R` | `R` | Sends STDERR through the stream and folds the result using a custom fold function | Let's see an example of how this works! @@ -144,8 +143,8 @@ process they came from: ```scala mdoc:silent for { - errors <- ZQueue.unbounded[String] - parseLines = (s: ZStream[Blocking, ProxError, Byte]) => s.transduce(ZTransducer.utf8Decode >>> ZTransducer.splitLines) + errors <- Queue.unbounded[String] + parseLines = (s: ZStream[Any, ProxError, Byte]) => s.via(ZPipeline.utf8Decode.mapError(UnknownProxError.apply) >>> ZPipeline.splitLines) p1 = Process("proc1") p2 = Process("proc2") @@ -177,8 +176,8 @@ These type aliases can be used to define functions performing redirection on arb ```scala mdoc def logErrors[P <: Process.UnboundEProcess[_]](proc: P) = { val target = TransformAndSink( - ZTransducer.utf8Decode >>> ZTransducer.splitLines, - ZSink.foreach((line: String) => ZIO.effect(println(line)).mapError(UnknownProxError))) + ZPipeline.utf8Decode.mapError(UnknownProxError.apply) >>> ZPipeline.splitLines, + ZSink.foreach((line: String) => ZIO.debug(line))) proc !> target } diff --git a/docs/docs/docs/zstream/running.md b/docs/docs/docs/zstream/running.md index 748ec004..a2aa7a78 100644 --- a/docs/docs/docs/zstream/running.md +++ b/docs/docs/docs/zstream/running.md @@ -5,6 +5,7 @@ title: Running processes # Running processes and process groups ```scala mdoc:invisible +import zio._ import io.github.vigoo.prox._ import io.github.vigoo.prox.zstream._ ``` @@ -25,8 +26,10 @@ implicit val runner: ProcessRunner[JVMProcessInfo] = new JVMProcessRunner val process = Process("echo", List("hello")) val result1 = process.run() -val result2 = process.start().use { fiber => - fiber.join +val result2 = ZIO.scoped { + process.start().flatMap { fiber => + fiber.join + } } val result3 = From 1d00c60a18a4f355dc0c6cb3119e7f3417cf6bfa Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 20 Jan 2024 13:01:04 +0100 Subject: [PATCH 6/6] Update Scala version on CI --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e3991414..dbc2d53a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - scala: ['2.12.15', '2.13.8', '3.1.0'] + scala: ['2.12.18', '2.13.12', '3.3.1'] steps: - name: Checkout uses: actions/checkout@v2