Skip to content

Commit 32d3437

Browse files
committed
fix: shutdown the environment properly
and remove the various 'default' methods creating default components with a default Environment. This is not safe since one might forget to shutdown the env afterwards and / or does not know who else is using it
1 parent 0cf9a36 commit 32d3437

File tree

17 files changed

+83
-56
lines changed

17 files changed

+83
-56
lines changed

common/shared/src/main/scala/org/specs2/concurrent/ExecutionEnv.scala common/js/src/main/scala/org/specs2/concurrent/ExecutionEnv.scala

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.specs2
22
package concurrent
33

44
import org.specs2.control.Logger
5+
import org.specs2.execute.*
56
import org.specs2.main.Arguments
67

78
import scala.concurrent.*, duration.*
@@ -10,7 +11,11 @@ import scala.concurrent.*, duration.*
1011
*/
1112
case class ExecutionEnv(executorServices: ExecutorServices, timeFactor: Int):
1213

13-
def shutdown(): Unit = ()
14+
def shutdown(): Unit =
15+
()
16+
17+
def await(future: Future[Result], timeout: Duration = Duration.Inf): Result =
18+
Success()
1419

1520
given executionContext: ExecutionContext = executorServices.executionContext
1621
given ec: ExecutionContext = executorServices.executionContext
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.specs2
2+
package concurrent
3+
4+
import org.specs2.control.Logger
5+
import org.specs2.execute.*
6+
import org.specs2.main.Arguments
7+
8+
import scala.concurrent.*, duration.*
9+
10+
/** Execution environment
11+
*/
12+
case class ExecutionEnv(executorServices: ExecutorServices, timeFactor: Int):
13+
14+
def shutdown(): Unit =
15+
executorServices.shutdownNow()
16+
17+
def await(future: Future[Result], timeout: Duration = Duration.Inf): Result =
18+
Await.result(future, timeout)
19+
20+
given executionContext: ExecutionContext = executorServices.executionContext
21+
given ec: ExecutionContext = executorServices.executionContext
22+
23+
lazy val scheduler = executorServices.scheduler
24+
25+
def schedule(action: =>Unit, duration: FiniteDuration): Unit =
26+
executorServices.schedule(action, duration)
27+
28+
object ExecutionEnv extends ExecutionEnvCompanionPlatform

core/jvm/src/main/scala/org/specs2/specification/core/EnvDefault.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ object EnvDefault:
1818
val statsDirectoryPath: DirectoryPath =
1919
"target" / "specs2-reports" / "stats"
2020

21-
def default: Env =
21+
lazy val default: Env =
2222
create(Arguments())
2323

2424
def create(arguments: Arguments): Env =

core/shared/src/main/scala/org/specs2/reporter/CustomIntances.scala

+9-9
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import reflect.Classes
99
import scala.reflect.ClassTag
1010
import Printer.*
1111

12-
case class CustomInstances(arguments: Arguments, loader: ClassLoader, logger: Logger):
12+
case class CustomInstances(env: Env, loader: ClassLoader, logger: Logger):
1313

1414
/** create a built-in specs2 printer */
1515
def createPrinterInstance(
@@ -18,15 +18,15 @@ case class CustomInstances(arguments: Arguments, loader: ClassLoader, logger: Lo
1818
failureMessage: String,
1919
noRequiredMessage: String
2020
): Operation[Option[Printer]] =
21-
if arguments.isSet(name.name) then
21+
if env.arguments.isSet(name.name) then
2222
createInstance[Printer](name.name, className, _ => failureMessage, noRequiredMessage)
2323
else noInstance(noRequiredMessage)
2424

2525
/** create a custom instance */
2626
def createCustomInstance[T <: AnyRef](name: String, failureMessage: String => String, noRequiredMessage: String)(using
2727
m: ClassTag[T]
2828
): Operation[Option[T]] =
29-
arguments.commandLine.value(name) match
29+
env.arguments.commandLine.value(name) match
3030
case Some(className) => createInstance[T](name, className, failureMessage, noRequiredMessage)
3131
case _ => noInstance(noRequiredMessage)
3232

@@ -37,7 +37,7 @@ case class CustomInstances(arguments: Arguments, loader: ClassLoader, logger: Lo
3737
noRequiredMessage: String
3838
)(using m: ClassTag[T]): Operation[Option[T]] =
3939
for
40-
instance <- Classes.createInstanceEither[T](className, loader, EnvDefault.create(arguments).defaultInstances)
40+
instance <- Classes.createInstanceEither[T](className, loader, env.defaultInstances)
4141
result <-
4242
instance match
4343
case Right(i) => Operation.ok(Option(i))
@@ -50,7 +50,7 @@ case class CustomInstances(arguments: Arguments, loader: ClassLoader, logger: Lo
5050
t: Throwable,
5151
forceVerbose: Option[Boolean] = None
5252
): Operation[Option[T]] =
53-
val verbose = forceVerbose.getOrElse(arguments.verbose)
53+
val verbose = forceVerbose.getOrElse(env.arguments.verbose)
5454
logger.info("", verbose) >>
5555
logger.info(message, verbose) >>
5656
logger.info("", verbose) >>
@@ -59,13 +59,13 @@ case class CustomInstances(arguments: Arguments, loader: ClassLoader, logger: Lo
5959

6060
/** print a message if a class can not be instantiated */
6161
def noInstance[T](message: String): Operation[Option[T]] =
62-
logger.info(message, arguments.verbose) >> Operation.ok(None)
62+
logger.info(message, env.arguments.verbose) >> Operation.ok(None)
6363

6464
object CustomInstances:
6565

6666
def default: CustomInstances =
67-
create(Arguments())
67+
create(EnvDefault.default)
6868

69-
def create(args: Arguments, logger: Logger = NoLogger): CustomInstances =
69+
def create(env: Env, logger: Logger = NoLogger): CustomInstances =
7070
val loader = Thread.currentThread.getContextClassLoader
71-
CustomInstances(args, loader, logger)
71+
CustomInstances(env, loader, logger)

core/shared/src/main/scala/org/specs2/reporter/PrinterFactory.scala

+1-4
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,5 @@ case class PrinterFactory(arguments: Arguments, customInstances: CustomInstances
7272

7373
object PrinterFactory:
7474

75-
def default: PrinterFactory =
76-
create(EnvDefault.default)
77-
7875
def create(env: Env): PrinterFactory =
79-
PrinterFactory(env.arguments, CustomInstances.create(env.arguments, env.systemLogger), env.systemLogger)
76+
PrinterFactory(env.arguments, CustomInstances.create(env, env.systemLogger), env.systemLogger)

core/shared/src/main/scala/org/specs2/reporter/TextPrinter.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,6 @@ case class TextPrinter(env: Env) extends Printer {
281281
}
282282

283283
object TextPrinter {
284-
val default: TextPrinter =
284+
lazy val default: TextPrinter =
285285
TextPrinter(Env())
286286
}

core/shared/src/main/scala/org/specs2/runner/ClassRunner.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,14 @@ trait ClassRunnerMain:
7171
yield stats
7272

7373
try execute(actions, env, exit)
74-
finally env.awaitShutdown()
74+
finally env.shutdown()
7575

7676
/** Create a ClassRunner from the default environment containing the command line arguments
7777
*/
7878
def createClassRunner(env: Env): Operation[ClassRunner] =
7979
val arguments = env.arguments
8080
val loader = env.getClass.getClassLoader
81-
val customInstances = CustomInstances(arguments, loader, env.systemLogger)
81+
val customInstances = CustomInstances(env, loader, env.systemLogger)
8282
val printerFactory = PrinterFactory(arguments, customInstances, env.systemLogger)
8383
val specFactory = DefaultSpecFactory(env, loader)
8484

@@ -101,7 +101,7 @@ object TextRunner extends ClassRunnerMain:
101101
val logger = PrinterLogger.stringPrinterLogger
102102
val env1 = env.setPrinterLogger(logger).setArguments(env.arguments.overrideWith(arguments))
103103
val loader = Thread.currentThread.getContextClassLoader
104-
val customInstances = CustomInstances(arguments, loader, StringOutputLogger(logger))
104+
val customInstances = CustomInstances(env1, loader, StringOutputLogger(logger))
105105

106106
val action =
107107
for

core/shared/src/main/scala/org/specs2/runner/FilesRunner.scala

+6-2
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ import specification.core.*
99
import reporter.*
1010
import runner.Runner.*
1111
import org.specs2.fp.syntax.*
12-
import SpecificationsFinder.*
1312
import main.FilesRunnerArguments.*
1413

1514
trait FilesRunner:
1615
/** run any specifications found via arguments */
1716
def run: Action[Stats]
1817

18+
object FilesRunner:
19+
20+
def create(env: Env, finder: SpecificationsFinder): FilesRunner =
21+
DefaultFilesRunner(env, finder)
22+
1923
case class DefaultFilesRunner(env: Env, specificationsFinder: SpecificationsFinder) extends FilesRunner:
2024

2125
val logger = env.systemLogger
@@ -79,4 +83,4 @@ trait FilesRunnerMain:
7983
val env = EnvDefault.create(Arguments(args*))
8084
val specificationsFinder = DefaultSpecificationsFinder(env)
8185
try execute(DefaultFilesRunner(env, specificationsFinder).run, env, exit)
82-
finally env.awaitShutdown()
86+
finally env.shutdown()

core/shared/src/main/scala/org/specs2/runner/SbtRunner.scala

+6-10
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ abstract class BaseSbtRunner(args: Array[String], remoteArgs: Array[String], loa
3030

3131
lazy val commandLineArguments = Arguments(args ++ remoteArgs*)
3232

33-
lazy val env = EnvDefault.create(commandLineArguments).setCustomClassLoader(loader)
33+
lazy val env: Env =
34+
EnvDefault.create(commandLineArguments).setCustomClassLoader(loader)
3435

3536
def tasks(taskDefs: Array[TaskDef]): Array[Task] =
3637
taskDefs.toList.map(newTask).toArray
@@ -40,13 +41,8 @@ abstract class BaseSbtRunner(args: Array[String], remoteArgs: Array[String], loa
4041
SbtTask(aTaskDef, env, loader, this)
4142

4243
def done =
43-
env.shutdown
44-
.onComplete {
45-
case Failure(e) =>
46-
loggers.foreach(_.error("error while finalizing resources: " + e.getMessage))
47-
case Success(failures) =>
48-
failures.toList.foreach { result => loggers.foreach(_.error(result.message)) }
49-
}(using env.specs2ExecutionContext)
44+
val result = env.shutdown()
45+
if !result.isSuccess then loggers.foreach(_.error(result.message))
5046
""
5147

5248
def deserializeTask(task: String, deserializer: String => TaskDef): Task =
@@ -75,7 +71,7 @@ object sbtRun extends MasterSbtRunner(Array(), Array(), Thread.currentThread.get
7571
given ee: ExecutionEnv = env.specs2ExecutionEnv
7672

7773
try exit(start(arguments*))
78-
finally Action.future(env.shutdown).runVoid(ee)
74+
finally env.shutdown()
7975

8076
def exit(action: Action[Stats])(using ee: ExecutionEnv): Unit =
8177
action
@@ -176,7 +172,7 @@ case class SbtTask(aTaskDef: TaskDef, env: Env, loader: ClassLoader, base: BaseS
176172
loggers: Array[Logger]
177173
): Action[Stats] =
178174

179-
val customInstances = CustomInstances(arguments, loader, env.systemLogger)
175+
val customInstances = CustomInstances(env, loader, env.systemLogger)
180176
val specFactory = DefaultSpecFactory(env, loader)
181177
val makeSpecs =
182178
if arguments.isSet("all") then

core/shared/src/main/scala/org/specs2/runner/SpecFactory.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ trait SpecFactory:
1111

1212
object SpecFactory:
1313

14-
def default: SpecFactory =
15-
DefaultSpecFactory(Env(), Thread.currentThread.getContextClassLoader)
14+
def create(env: Env): SpecFactory =
15+
DefaultSpecFactory(env, Thread.currentThread.getContextClassLoader)
1616

1717
case class DefaultSpecFactory(env: Env, classLoader: ClassLoader) extends SpecFactory:
1818

core/shared/src/main/scala/org/specs2/runner/SpecificationsFinder.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,5 @@ case class DefaultSpecificationsFinder(env: Env) extends SpecificationsFinder:
202202

203203
object SpecificationsFinder:
204204

205-
val default: SpecificationsFinder =
206-
DefaultSpecificationsFinder(EnvDefault.default)
205+
def create(env: Env): SpecificationsFinder =
206+
DefaultSpecificationsFinder(env)

core/shared/src/main/scala/org/specs2/specification/core/Env.scala

+9-12
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,13 @@ case class Env(
8585
copy(arguments = arguments.setTimeout(duration))
8686

8787
/** @return a list of finalization failures by resource key if any */
88-
def shutdown: Future[List[Result]] =
88+
private def startShutdown: Future[List[Result]] =
8989
given ExecutionContext = specs2ExecutionContext
9090
val results: Action[List[(String, Result)]] = resources.toList.traverse { case (key, resource) =>
9191
resource.finalizer.startExecution(this).executionResult.map(r => (key, r))
9292
}
9393

94-
val failures = results
94+
results
9595
.map { (rs: List[(String, Result)]) =>
9696
rs
9797
.filter(_._2.isIssue)
@@ -101,19 +101,16 @@ case class Env(
101101
}
102102
.runFuture(specs2ExecutionEnv)
103103

104-
failures.onComplete { _ =>
105-
try specs2ExecutionEnv.shutdown()
106-
finally executionEnv.shutdown()
107-
}
108-
failures
109-
110-
def shutdownResult: Future[Result] =
104+
private def startShutdownResult: Future[Result] =
111105
given ExecutionContext = specs2ExecutionContext
112-
shutdown.map(vs => AsResult(vs))
106+
startShutdown.map(vs => AsResult(vs))
113107

114108
/** be sure to only call this method on the JVM! */
115-
def awaitShutdown(): Unit =
116-
Await.result(shutdown, Duration.Inf)
109+
def shutdown(timeout: Duration = Duration.Inf): Result =
110+
val result = specs2ExecutionEnv.await(startShutdownResult, timeout)
111+
try executionEnv.shutdown()
112+
finally specs2ExecutionEnv.shutdown()
113+
result
117114

118115
/** set new PrinterLogger */
119116
def setPrinterLogger(logger: PrinterLogger) =

core/shared/src/main/scala/org/specs2/specification/core/OwnEnv.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@ trait OwnEnv extends AfterSpec:
3737
ownEnv.executionContext
3838

3939
def afterSpec: Fragments =
40-
step(ownEnv.shutdownResult)
40+
step(ownEnv.shutdown())

core/shared/src/main/scala/org/specs2/specification/core/OwnExecutionEnv.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ trait OwnExecutionEnv extends AfterSpec:
3232
ownEnv.executionContext
3333

3434
def afterSpec: Fragments =
35-
step(ownEnv.shutdownResult)
35+
step(ownEnv.shutdown())

core/shared/src/main/scala/specs2/files.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ object files:
1616
val arguments = Arguments(args.drop(1)*)
1717
val env = EnvDefault.create(arguments)
1818

19-
val filesRunner = DefaultFilesRunner(env, SpecificationsFinder.default)
19+
val filesRunner = FilesRunner.create(env, SpecificationsFinder.create(env))
2020
try Runner.execute(filesRunner.run, env, exit)
21-
finally env.awaitShutdown()
21+
finally env.shutdown()

core/shared/src/main/scala/specs2/run.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ object run extends ClassRunnerMain:
2020
yield result
2121

2222
try Runner.execute(action, env, exit = false)
23-
finally env.awaitShutdown()
23+
finally env.shutdown()
2424

2525
/** main method for the command line */
2626
def main(args: Array[String]) =

junit/src/main/scala/org/specs2/runner/JUnitRunner.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class JUnitRunner(klass: Class[?]) extends org.junit.runner.Runner with Filterab
3333

3434
def getDescription(env: Env): org.junit.runner.Description =
3535
try JUnitDescriptions.createDescription(specStructure)(env.specs2ExecutionEnv)
36-
catch { case NonFatal(t) => env.awaitShutdown(); throw t; }
36+
catch { case NonFatal(t) => env.shutdown(); throw t; }
3737

3838
/** specification structure for the environment */
3939
lazy val specStructure: SpecStructure =
@@ -45,13 +45,13 @@ class JUnitRunner(klass: Class[?]) extends org.junit.runner.Runner with Filterab
4545
runWithEnv(n, env).runAction(env.specs2ExecutionEnv) match
4646
case Right(_) => ()
4747
case Left(t) => n.fireTestFailure(new Failure(getDescription, new RuntimeException(t)))
48-
finally env.awaitShutdown()
48+
finally env.shutdown()
4949

5050
/** run the specification with a Notifier and an environment */
5151
def runWithEnv(runNotifier: RunNotifier, env: Env): Action[Stats] =
5252
val loader = Thread.currentThread.getContextClassLoader
5353
val arguments = env.arguments
54-
val customInstances = CustomInstances(arguments, loader, env.systemLogger)
54+
val customInstances = CustomInstances(env, loader, env.systemLogger)
5555
val printerFactory = PrinterFactory(arguments, customInstances, env.systemLogger)
5656
val junitPrinter = JUnitPrinter(env, runNotifier)
5757

@@ -64,7 +64,7 @@ class JUnitRunner(klass: Class[?]) extends org.junit.runner.Runner with Filterab
6464
stats <-
6565
if arguments.isSet("all") then
6666
for
67-
ss <- SpecFactory.default.createLinkedSpecs(specStructure).toAction
67+
ss <- SpecFactory.create(env).createLinkedSpecs(specStructure).toAction
6868
sorted <- Action.pure(SpecStructure.topologicalSort(ss)(env.specs2ExecutionEnv).getOrElse(ss))
6969
stats <- reporter.report(sorted.toList)
7070
yield stats

0 commit comments

Comments
 (0)