Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
etorreborre committed Jan 14, 2024
1 parent c370023 commit a116536
Show file tree
Hide file tree
Showing 28 changed files with 121 additions and 123 deletions.
43 changes: 31 additions & 12 deletions common/shared/src/main/scala/org/specs2/control/Action.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ case class Action[A](private[control] val runNow: ExecutionEnv => Future[A], las
Action[B](
runNow = ee => {
given ExecutionContext = ee.executionContext
runFuture(ee).flatMap { a =>
unsafeRunFuture(ee).flatMap { a =>
val otherAction = f(a)
otherLast = otherAction.last
otherAction.runNow(ee)
}
},
last = last ++ otherLast
last = last :+ Finalizer.create(Finalizer.runFinalizers(otherLast))
)

/** add a finalizer */
Expand All @@ -45,7 +45,7 @@ case class Action[A](private[control] val runNow: ExecutionEnv => Future[A], las

/** catch any exception resulting from running the action later */
def attempt: Action[Throwable `Either` A] =
Action(ee => runFuture(ee).transform(r => scala.util.Success(r.toEither))(ee.executionContext), last)
Action(ee => unsafeRunFuture(ee).transform(r => scala.util.Success(r.toEither))(ee.executionContext), last)

/** run another action if this one fails */
def orElse(other: Action[A]): Action[A] =
Expand All @@ -60,9 +60,22 @@ case class Action[A](private[control] val runNow: ExecutionEnv => Future[A], las

/** run as a Future and raise a timeout exception if necessary NOTE: this does not execute the finalizers!!!
*/
def runFuture(ee: ExecutionEnv, timeout: Option[FiniteDuration] = None): Future[A] =
private def unsafeRunFuture(ee: ExecutionEnv, timeout: Option[FiniteDuration] = None): Future[A] =
runActionToFuture(runNow, timeout, ee)

/** run as a Future and raise a timeout exception if necessary
*/
def runFuture(ee: ExecutionEnv, timeout: Option[FiniteDuration] = None): Future[A] =
runActionToFuture(
executionEnv => {
val f = runNow(executionEnv)
f.onComplete(_ => Try(Finalizer.runFinalizers(last)))(executionEnv.executionContext)
f
},
timeout,
ee
)

/** Run the action and return an exception if it fails Whatever happens run the finalizers
*/
def runAction(ee: ExecutionEnv, timeout: Option[FiniteDuration] = None): Throwable `Either` A =
Expand Down Expand Up @@ -132,10 +145,13 @@ object Action:
fa.flatMap(f)

override def ap[A, B](fa: =>Action[A])(ff: =>Action[A => B]): Action[B] =
Action { ee =>
given ExecutionContext = ee.executionContext
fa.runNow(ee).zip(ff.runNow(ee)).map { case (a, f) => f(a) }
}
Action(
runNow = { ee =>
given ExecutionContext = ee.executionContext
ff.runNow(ee).zip(fa.runNow(ee)).map { case (f, a) => f(a) }
},
last = fa.last ++ ff.last
)

override def toString: String =
"Monad[Action]"
Expand All @@ -145,10 +161,13 @@ object Action:
Action(_ => Future.successful(a))

def ap[A, B](fa: =>Action[A])(ff: =>Action[A => B]): Action[B] =
Action { ee =>
given ExecutionContext = ee.executionContext
fa.runNow(ee).zip(ff.runNow(ee)).map { case (a, f) => f(a) }
}
Action(
runNow = { ee =>
given ExecutionContext = ee.executionContext
ff.runNow(ee).zip(fa.runNow(ee)).map { case (f, a) => f(a) }
},
last = fa.last ++ ff.last
)

override def toString: String =
"Applicative[Action]"
Expand Down
15 changes: 8 additions & 7 deletions common/shared/src/main/scala/org/specs2/control/Operation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ case class Operation[A](operation: () => Throwable Either A, last: Vector[Finali
Operation[B](() => run.map(f), last)

def flatMap[B](f: A => Operation[B]): Operation[B] =
Operation.fromEither[B] {
runOperation match {
case Right(a) => f(a).runOperation
case Left(t) => Left(t)
}
val Operation(op, last) = attempt
op().flatten match {
case Right(a) =>
val o = f(a)
o.copy(last = last ++ o.last)
case Left(t) => Operation.fromEither(Left(t))
}

/** run this operation, to get back a result (possibly an exception) and run the finalizers when the operation has
Expand Down Expand Up @@ -131,7 +132,7 @@ object Operation:
fa.flatMap(f)

override def ap[A, B](fa: =>Operation[A])(ff: =>Operation[A => B]): Operation[B] =
Operation(() => ff.run.flatMap(fa.run.map))
ff.flatMap(f => fa.map(f))

override def tailrecM[A, B](a: A)(f: A => Operation[Either[A, B]]): Operation[B] =
Operation[B] { () =>
Expand All @@ -152,7 +153,7 @@ object Operation:
Operation(() => Right(a))

def ap[A, B](fa: =>Operation[A])(ff: =>Operation[A => B]): Operation[B] =
Operation(() => ff.run.flatMap(f => fa.run.map(f)))
ff.flatMap(f => fa.map(f))

override def toString: String =
"Applicative[Operation]"
Expand Down
5 changes: 1 addition & 4 deletions core/jvm/src/test/scala/org/specs2/io/LocationSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import user.io.{LocationSpecification, LocationUnitSpecification}
import Fragment.*
import org.specs2.concurrent.ExecutionEnv

class LocationSpec extends org.specs2.mutable.Spec with TypedEqual:
given ee: ExecutionEnv = Env().executionEnv
class LocationSpec(using ee: ExecutionEnv) extends org.specs2.mutable.Spec with TypedEqual:

"A unit specification must have correct locations for its fragments" >> {
given spec: LocationUnitSpecification = new LocationUnitSpecification(ee)
Expand Down Expand Up @@ -79,7 +78,5 @@ class LocationSpec extends org.specs2.mutable.Spec with TypedEqual:
def fragments(using spec: WithFragments): List[Fragment] =
spec.fragmentsList

step(ee.shutdown())

trait WithFragments:
def fragmentsList: List[Fragment]
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import io.StringOutput
import org.specs2.specification.{AfterSpec, Tables}
import specification.core.{Env, SpecificationStructure}
import runner.*
import org.specs2.specification.core.OwnEnv

class NotifierSpec extends Specification {
class NotifierSpec(val env: Env) extends Specification with OwnEnv {
def is = s2"""

Run a mutable spec with a Notifier $a1
Expand Down Expand Up @@ -84,13 +85,11 @@ class NotifierSpec extends Specification {

def report(spec: SpecificationStructure): TestNotifier =
val arguments = Arguments("notifier")
val env1 = Env(arguments = arguments)
val env1 = ownEnv.copy(arguments = arguments)
val notifier = new TestNotifier
val reporter = Reporter.create(List(NotifierPrinter(arguments).printer(notifier)), env1)

try reporter.report(spec.structure).runOption(env1.executionEnv)
finally env1.awaitShutdown()

reporter.report(spec.structure).runOption(env1.executionEnv)
notifier

}
Expand Down
24 changes: 12 additions & 12 deletions core/jvm/src/test/scala/org/specs2/reporter/TextPrinterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ class TextPrinterSpec(val env: Env) extends Specification with OwnEnv {
Whitespaces are respected $n1

"""
import TextPrinterSpecification.*
val textPrinterSpecification = TextPrinterSpecification(ownEnv)
import textPrinterSpecification.*
val factory = fragmentFactory; import factory.*

def a1 =
Expand Down Expand Up @@ -356,9 +357,10 @@ table ${"a" | "b" |>
*/
def error1 = { sys.error("boom"); ok }
def error2 = { throw new Exception("wrong", new IllegalArgumentException("boom")); ok }

}

object TextPrinterSpecification extends MustMatchers with FragmentsDsl with Debug:
class TextPrinterSpecification(ownEnv: Env) extends MustMatchers with FragmentsDsl with Debug:

extension (fragment: Fragment)
def contains(contained: String): Result =
Expand Down Expand Up @@ -412,20 +414,18 @@ object TextPrinterSpecification extends MustMatchers with FragmentsDsl with Debu
case Some(ownEnv) =>
ownEnv.copy(printerLogger = logger)
case _ =>
Env(
ownEnv.copy(
printerLogger = logger,
arguments = s.arguments.overrideWith(Arguments.split("sequential fullstacktrace"))
)
try
val printer = TextPrinter(env1)
printer.run(
s.setFragments(
s.fragments
.prepend(DefaultFragmentFactory.break) // add a newline after the title
.update(DefaultExecutor(env1).execute(s.arguments))
)
val printer = TextPrinter(env1)
printer.run(
s.setFragments(
s.fragments
.prepend(DefaultFragmentFactory.break) // add a newline after the title
.update(DefaultExecutor(env1).execute(s.arguments))
)
finally if optionalEnv.isEmpty then env1.awaitShutdown()
)

val messages = logger.messages
messages.map(_.removeEnd(" ")).mkString("\n").replace(" ", "_")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import org.specs2.reporter.PrinterLogger.*
import org.specs2.matcher.Matcher
import org.specs2.matcher.OperationMatchers.beOk
import Matcher.{given}
import org.specs2.matcher.ThrownExpectations

class SpecificationsFinderSpec extends Spec {
class SpecificationsFinderSpec(env: Env) extends Spec with ThrownExpectations {
def is = s2"""
It is possible to find specifications in the local test directory $e1
It is possible to find specifications in an absolute test directory $e2
Expand Down Expand Up @@ -41,13 +42,16 @@ class SpecificationsFinderSpec extends Spec {
val filter = (s: String) =>
s.contains("SourceFileSpec") || // SourceFileSpec cannot be instantiated
s.contains("SpecificationsFinderSpec")

DefaultSpecificationsFinder(EnvDefault.default.setSystemLogger(NoLogger))
val output = new StringOutput {}
val logger = StringOutputLogger(output)
val env1 = env.setSystemLogger(logger)
DefaultSpecificationsFinder(env1)
.findSpecifications(
basePath = DirectoryPath.unsafe(base) / "src" / "test" / "scala",
filter = filter
)
.runOption must beSome((l: List[?]) => l must haveSize(1))
.runOption must beSome((l: List[?]) => l must beEmpty)
output.messages.mkString(",") must beMatching(".*cannot create specification.*")

def findFiles: Matcher[Operation[List[FilePath]]] = (operation: Operation[List[FilePath]]) =>
operation must beOk((_: List[FilePath]) must not(beEmpty))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import core.Env
import dsl.*
import org.specs2.concurrent.ExecutionEnv

class AcceptanceDslSpec extends Spec with AcceptanceDsl {
class AcceptanceDslSpec(using ee: ExecutionEnv) extends Spec with AcceptanceDsl {
def is = s2"""

The caret ^ operator can be used to join fragments and build a Fragments object
Expand Down Expand Up @@ -45,11 +45,7 @@ class AcceptanceDslSpec extends Spec with AcceptanceDsl {
"description" ! result $g1
"description" ! (s: String) => result $g2
"description" ! (e: Env) => result $g3

${step(ee.shutdown())}
"""
given ee: ExecutionEnv =
Env().executionEnv

val factory = fragmentFactory; import factory.*

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import org.specs2.specification.core.{Env, SpecificationStructure, Fragment}
import org.specs2.specification.process.DefaultExecutor
import _root_.org.specs2.mutable.{Specification as Spec}
import fp.syntax.*
import org.specs2.specification.core.OwnEnv

class BeforeAfterAroundSpec extends Specification {
class BeforeAfterAroundSpec(val env: Env) extends Specification with OwnEnv {
def is = s2"""

The `Before/After/Around Example` traits are used to automatically insert contexts around examples bodies
Expand Down Expand Up @@ -55,10 +56,7 @@ class BeforeAfterAroundSpec extends Specification {
)

def executeContains(s: SpecificationStructure & StringOutput, messages: String*) =
val env = Env()
try
DefaultExecutor.executeFragments(s.structure.fragments)(env).traverse(_.executionResult).run(env.executionEnv)
s.messages must contain(allOf(messages*)).inOrder
finally env.awaitShutdown()
DefaultExecutor.executeFragments(s.structure.fragments)(env).traverse(_.executionResult).run(ownEnv.executionEnv)
s.messages must contain(allOf(messages*)).inOrder

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import reporter.*
import reporter.PrinterLogger.*
import specification.core.{SpecificationStructure, Env}
import scala.collection.mutable.ArrayBuffer
import org.specs2.specification.core.OwnEnv

class BeforeAfterSpecSpec extends Specification {
class BeforeAfterSpecSpec(val env: Env) extends Specification with OwnEnv {
def is = s2"""

Before and after all steps can be executed with the BeforeAfterSpec trait $beforeAfter
Expand Down Expand Up @@ -76,8 +77,7 @@ class BeforeAfterSpecSpec extends Specification {
messages.toList === List("before all", "e2", "after all")

def runSpec(s: SpecificationStructure, arguments: Arguments = Arguments()) =
val env = Env(arguments = arguments, printerLogger = NoPrinterLogger)
try Reporter.create(Nil, env).report(s.structure).runVoid(env.executionEnv)
finally env.awaitShutdown()
val env1 = ownEnv.copy(arguments = arguments, printerLogger = NoPrinterLogger)
Reporter.create(Nil, env1).report(s.structure).runVoid(env1.executionEnv)

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import ResultMatchers.*
import scala.concurrent.*

class ExecutorSpec(val env: Env) extends Specification with ThrownExpectations with OwnEnv {
def is = sequential ^ section("ci") ^ s2"""
def is = section("ci") ^ skipAll ^ s2"""

Steps
=====
Expand All @@ -39,7 +39,7 @@ class ExecutorSpec(val env: Env) extends Specification with ThrownExpectations w
with examples using an execution context $userEnv

Time
===
====

the timer must be started for each execution $e12
the timer must be started for each sequential execution $e13
Expand Down Expand Up @@ -177,9 +177,7 @@ class ExecutorSpec(val env: Env) extends Specification with ThrownExpectations w
Await.result(scala.concurrent.Future(1)(ee.executionContext), 5.second) === 1
}
}
val e = Env()
try execute(fragments, e) must contain(beSuccessful[Result]).forall
finally e.awaitShutdown()
execute(fragments, ownEnv) must contain(beSuccessful[Result]).forall

final lazy val factory = fragmentFactory

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import execute.*
import core.*, Execution.*
import matcher.Matcher
import specification.dsl.Online
import reporter.TextPrinterSpecification.*
import reporter.TextPrinterSpecification
import control.*
import producer.*, Producer.*

class OnlineSpecificationSpec extends Specification with Online {
class OnlineSpecificationSpec(val env: Env) extends Specification with Online with OwnEnv {
def is = s2"""

A specification can have examples returning a result and Fragments depending on the result value $e1

"""
val specification = TextPrinterSpecification(ownEnv)
import specification.*

val factory = fragmentFactory; import factory.*

def online(n: Int): Execution =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.specs2.specification.create.*
import org.specs2.specification.core.*
import org.specs2.specification.dsl.FragmentsDsl

class S2StringContextSpec extends Spec {
class S2StringContextSpec(using ee: ExecutionEnv) extends Spec {
def is = s2"""

Fragments can be interpolated from a s2 string
Expand All @@ -29,13 +29,8 @@ class S2StringContextSpec extends Spec {
when more than one lines are indented they are taken as the description ${desc.e2}
when more than one lines have a | margin they are taken as the description ${desc.e3}
for an auto-example (no text on the last line) ${desc.e4}

${step(ee.shutdown())}
"""

given ee: ExecutionEnv =
Env().executionEnv

object exs extends MustMatchers with StandardResults with S2StringContext with ThrownExpectations:

import DefaultFragmentFactory.*
Expand Down
Loading

0 comments on commit a116536

Please sign in to comment.