@@ -3,17 +3,21 @@ package scala.build.internal
3
3
import coursier .jvm .Execve
4
4
import org .scalajs .jsenv .jsdomnodejs .JSDOMNodeJSEnv
5
5
import org .scalajs .jsenv .nodejs .NodeJSEnv
6
- import org .scalajs .jsenv .{Input , RunConfig }
6
+ import org .scalajs .jsenv .{Input , JSEnv , RunConfig }
7
+ import org .scalajs .testing .adapter .TestAdapter as ScalaJsTestAdapter
7
8
import sbt .testing .{Framework , Status }
8
9
9
10
import java .io .File
10
11
import java .nio .file .{Files , Path , Paths }
11
12
12
13
import scala .build .EitherCps .{either , value }
13
14
import scala .build .Logger
14
- import scala .build .errors ._
15
+ import scala .build .Ops .EitherSeqOps
16
+ import scala .build .errors .*
15
17
import scala .build .internals .EnvVar
18
+ import scala .build .testrunner .FrameworkUtils .*
16
19
import scala .build .testrunner .{AsmTestRunner , TestRunner }
20
+ import scala .scalanative .testinterface .adapter .TestAdapter as ScalaNativeTestAdapter
17
21
import scala .util .{Failure , Properties , Success }
18
22
19
23
object Runner {
@@ -238,22 +242,20 @@ object Runner {
238
242
sourceMap : Boolean = false ,
239
243
esModule : Boolean = false
240
244
): Either [BuildException , Process ] = either {
241
-
242
- import logger .{log , debug }
243
-
244
- val nodePath = value(findInPath(" node" ).map(_.toString).toRight(NodeNotFoundError ()))
245
-
246
- if (! jsDom && allowExecve && Execve .available()) {
247
-
245
+ val nodePath : String =
246
+ value(findInPath(" node" )
247
+ .map(_.toString)
248
+ .toRight(NodeNotFoundError ()))
249
+ if ! jsDom && allowExecve && Execve .available() then {
248
250
val command = Seq (nodePath, entrypoint.getAbsolutePath) ++ args
249
251
250
- log(
252
+ logger. log(
251
253
s " Running ${command.mkString(" " )}" ,
252
254
" Running" + System .lineSeparator() +
253
255
command.iterator.map(_ + System .lineSeparator()).mkString
254
256
)
255
257
256
- debug(" execve available" )
258
+ logger. debug(" execve available" )
257
259
Execve .execve(
258
260
command.head,
259
261
" node" +: command.tail.toArray,
@@ -262,40 +264,36 @@ object Runner {
262
264
sys.error(" should not happen" )
263
265
}
264
266
else {
265
-
266
267
val nodeArgs =
267
268
// Scala.js runs apps by piping JS to node.
268
269
// If we need to pass arguments, we must first make the piped input explicit
269
270
// with "-", and we pass the user's arguments after that.
270
- if (args.isEmpty) Nil
271
- else " -" :: args.toList
271
+ if args.isEmpty then Nil else " -" :: args.toList
272
272
val envJs =
273
- if ( jsDom)
273
+ if jsDom then
274
274
new JSDOMNodeJSEnv (
275
275
JSDOMNodeJSEnv .Config ()
276
276
.withExecutable(nodePath)
277
277
.withArgs(nodeArgs)
278
278
.withEnv(Map .empty)
279
279
)
280
- else new NodeJSEnv (
281
- NodeJSEnv .Config ()
282
- .withExecutable(nodePath)
283
- .withArgs(nodeArgs)
284
- .withEnv(Map .empty)
285
- .withSourceMap(sourceMap)
286
- )
280
+ else
281
+ new NodeJSEnv (
282
+ NodeJSEnv .Config ()
283
+ .withExecutable(nodePath)
284
+ .withArgs(nodeArgs)
285
+ .withEnv(Map .empty)
286
+ .withSourceMap(sourceMap)
287
+ )
287
288
288
- val inputs = Seq (
289
- if (esModule) Input .ESModule (entrypoint.toPath)
290
- else Input .Script (entrypoint.toPath)
291
- )
289
+ val inputs =
290
+ Seq (if esModule then Input .ESModule (entrypoint.toPath) else Input .Script (entrypoint.toPath))
292
291
293
292
val config = RunConfig ().withLogger(logger.scalaJsLogger)
294
293
val processJs = envJs.start(inputs, config)
295
294
296
295
processJs.future.value.foreach {
297
- case Failure (t) =>
298
- throw new Exception (t)
296
+ case Failure (t) => throw new Exception (t)
299
297
case Success (_) =>
300
298
}
301
299
@@ -346,32 +344,30 @@ object Runner {
346
344
347
345
private def runTests (
348
346
classPath : Seq [Path ],
349
- framework : Framework ,
347
+ frameworks : Seq [ Framework ] ,
350
348
requireTests : Boolean ,
351
349
args : Seq [String ],
352
350
parentInspector : AsmTestRunner .ParentInspector
353
- ): Either [NoTestsRun , Boolean ] = {
354
-
355
- val taskDefs =
356
- AsmTestRunner .taskDefs(
357
- classPath,
358
- keepJars = false ,
359
- framework.fingerprints().toIndexedSeq,
360
- parentInspector
361
- ).toArray
362
-
363
- val runner = framework.runner(args.toArray, Array (), null )
364
- val initialTasks = runner.tasks(taskDefs)
365
- val events = TestRunner .runTasks(initialTasks.toIndexedSeq, System .out)
366
-
367
- val doneMsg = runner.done()
368
- if (doneMsg.nonEmpty)
369
- System .out.println(doneMsg)
370
-
371
- if (requireTests && events.isEmpty)
372
- Left (new NoTestsRun )
373
- else
374
- Right {
351
+ ): Either [NoTestsRun , Boolean ] = frameworks
352
+ .flatMap { framework =>
353
+ val taskDefs =
354
+ AsmTestRunner .taskDefs(
355
+ classPath,
356
+ keepJars = false ,
357
+ framework.fingerprints().toIndexedSeq,
358
+ parentInspector
359
+ ).toArray
360
+
361
+ val runner = framework.runner(args.toArray, Array (), null )
362
+ val initialTasks = runner.tasks(taskDefs)
363
+ val events = TestRunner .runTasks(initialTasks.toIndexedSeq, System .out)
364
+
365
+ val doneMsg = runner.done()
366
+ if doneMsg.nonEmpty then System .out.println(doneMsg)
367
+ events
368
+ } match {
369
+ case events if requireTests && events.isEmpty => Left (new NoTestsRun )
370
+ case events => Right {
375
371
! events.exists { ev =>
376
372
ev.status == Status .Error ||
377
373
ev.status == Status .Failure ||
@@ -380,22 +376,30 @@ object Runner {
380
376
}
381
377
}
382
378
383
- def frameworkName (
379
+ def frameworkNames (
384
380
classPath : Seq [Path ],
385
- parentInspector : AsmTestRunner .ParentInspector
386
- ): Either [NoTestFrameworkFoundError , String ] = {
387
- val fwOpt = AsmTestRunner .findFrameworkService(classPath)
388
- .orElse {
389
- AsmTestRunner .findFramework(
390
- classPath,
391
- TestRunner .commonTestFrameworks,
392
- parentInspector
393
- )
394
- }
395
- fwOpt match {
396
- case Some (fw) => Right (fw.replace('/' , '.' ).replace('\\ ' , '.' ))
397
- case None => Left (new NoTestFrameworkFoundError )
398
- }
381
+ parentInspector : AsmTestRunner .ParentInspector ,
382
+ logger : Logger
383
+ ): Either [NoTestFrameworkFoundError , Seq [String ]] = {
384
+ logger.debug(" Looking for test framework services on the classpath..." )
385
+ val foundFrameworkServices =
386
+ AsmTestRunner .findFrameworkServices(classPath)
387
+ .map(_.replace('/' , '.' ).replace('\\ ' , '.' ))
388
+ logger.debug(s " Found ${foundFrameworkServices.length} test framework services. " )
389
+ if foundFrameworkServices.nonEmpty then
390
+ logger.debug(s " - ${foundFrameworkServices.mkString(" \n - " )}" )
391
+ logger.debug(" Looking for more test frameworks on the classpath..." )
392
+ val foundFrameworks =
393
+ AsmTestRunner .findFrameworks(classPath, TestRunner .commonTestFrameworks, parentInspector)
394
+ .map(_.replace('/' , '.' ).replace('\\ ' , '.' ))
395
+ logger.debug(s " Found ${foundFrameworks.length} additional test frameworks " )
396
+ if foundFrameworks.nonEmpty then
397
+ logger.debug(s " - ${foundFrameworks.mkString(" \n - " )}" )
398
+ val frameworks : Seq [String ] = foundFrameworkServices ++ foundFrameworks
399
+ logger.log(s " Found ${frameworks.length} test frameworks in total " )
400
+ if frameworks.nonEmpty then
401
+ logger.debug(s " - ${frameworks.mkString(" \n - " )}" )
402
+ if frameworks.nonEmpty then Right (frameworks) else Left (new NoTestFrameworkFoundError )
399
403
}
400
404
401
405
def testJs (
@@ -410,57 +414,72 @@ object Runner {
410
414
): Either [TestError , Int ] = either {
411
415
import org .scalajs .jsenv .Input
412
416
import org .scalajs .jsenv .nodejs .NodeJSEnv
413
- import org .scalajs .testing .adapter .TestAdapter
417
+ logger.debug(" Preparing to run tests with Scala.js..." )
418
+ logger.debug(s " Scala.js tests class path: $classPath" )
414
419
val nodePath = findInPath(" node" ).fold(" node" )(_.toString)
415
- val jsEnv =
416
- if (jsDom)
420
+ logger.debug(s " Node found at $nodePath" )
421
+ val jsEnv : JSEnv =
422
+ if jsDom then {
423
+ logger.log(" Loading JS environment with JS DOM..." )
417
424
new JSDOMNodeJSEnv (
418
425
JSDOMNodeJSEnv .Config ()
419
426
.withExecutable(nodePath)
420
427
.withArgs(Nil )
421
428
.withEnv(Map .empty)
422
429
)
423
- else new NodeJSEnv (
424
- NodeJSEnv .Config ()
425
- .withExecutable(nodePath)
426
- .withArgs(Nil )
427
- .withEnv(Map .empty)
428
- .withSourceMap(NodeJSEnv .SourceMap .Disable )
429
- )
430
- val adapterConfig = TestAdapter .Config ().withLogger(logger.scalaJsLogger)
431
- val inputs = Seq (
432
- if (esModule) Input .ESModule (entrypoint.toPath)
433
- else Input .Script (entrypoint.toPath)
434
- )
435
- var adapter : TestAdapter = null
430
+ }
431
+ else {
432
+ logger.log(" Loading JS environment with Node..." )
433
+ new NodeJSEnv (
434
+ NodeJSEnv .Config ()
435
+ .withExecutable(nodePath)
436
+ .withArgs(Nil )
437
+ .withEnv(Map .empty)
438
+ .withSourceMap(NodeJSEnv .SourceMap .Disable )
439
+ )
440
+ }
441
+ val adapterConfig = ScalaJsTestAdapter .Config ().withLogger(logger.scalaJsLogger)
442
+ val inputs =
443
+ Seq (if esModule then Input .ESModule (entrypoint.toPath) else Input .Script (entrypoint.toPath))
444
+ var adapter : ScalaJsTestAdapter = null
436
445
437
446
logger.debug(s " JS tests class path: $classPath" )
438
447
439
448
val parentInspector = new AsmTestRunner .ParentInspector (classPath)
440
- val frameworkName0 = testFrameworkOpt match {
441
- case Some (fw ) => fw
442
- case None => value(frameworkName (classPath, parentInspector))
449
+ val foundFrameworkNames : List [ String ] = testFrameworkOpt match {
450
+ case some @ Some (_ ) => some.toList
451
+ case None => value(frameworkNames (classPath, parentInspector, logger)).toList
443
452
}
444
453
445
454
val res =
446
455
try {
447
- adapter = new TestAdapter (jsEnv, inputs, adapterConfig)
448
-
449
- val frameworks = adapter.loadFrameworks(List (List (frameworkName0))).flatten
456
+ adapter = new ScalaJsTestAdapter (jsEnv, inputs, adapterConfig)
457
+
458
+ val loadedFrameworks =
459
+ adapter
460
+ .loadFrameworks(foundFrameworkNames.map(List (_)))
461
+ .flatten
462
+ .distinctBy(_.name())
463
+
464
+ val finalTestFrameworks =
465
+ loadedFrameworks
466
+ .filter(
467
+ ! _.name().toLowerCase.contains(" junit" ) ||
468
+ ! loadedFrameworks.exists(_.name().toLowerCase.contains(" munit" ))
469
+ )
470
+ if finalTestFrameworks.nonEmpty then
471
+ logger.log(
472
+ s """ Final list of test frameworks found:
473
+ | - ${finalTestFrameworks.map(_.description).mkString(" \n - " )}
474
+ | """ .stripMargin
475
+ )
450
476
451
- if (frameworks.isEmpty)
452
- Left (new NoFrameworkFoundByBridgeError )
453
- else if (frameworks.length > 1 )
454
- Left (new TooManyFrameworksFoundByBridgeError )
455
- else {
456
- val framework = frameworks.head
457
- runTests(classPath, framework, requireTests, args, parentInspector)
458
- }
477
+ if finalTestFrameworks.isEmpty then Left (new NoFrameworkFoundByBridgeError )
478
+ else runTests(classPath, finalTestFrameworks, requireTests, args, parentInspector)
459
479
}
460
- finally if ( adapter != null ) adapter.close()
480
+ finally if adapter != null then adapter.close()
461
481
462
- if (value(res)) 0
463
- else 1
482
+ if value(res) then 0 else 1
464
483
}
465
484
466
485
def testNative (
@@ -471,42 +490,61 @@ object Runner {
471
490
args : Seq [String ],
472
491
logger : Logger
473
492
): Either [TestError , Int ] = either {
474
-
475
- import scala .scalanative .testinterface .adapter .TestAdapter
476
-
493
+ logger.debug(" Preparing to run tests with Scala Native..." )
477
494
logger.debug(s " Native tests class path: $classPath" )
478
495
479
496
val parentInspector = new AsmTestRunner .ParentInspector (classPath)
480
- val frameworkName0 = frameworkNameOpt match {
481
- case Some (fw) => fw
482
- case None => value(frameworkName (classPath, parentInspector))
497
+ val foundFrameworkNames : List [ String ] = frameworkNameOpt match {
498
+ case Some (fw) => List (fw)
499
+ case None => value(frameworkNames (classPath, parentInspector, logger)).toList
483
500
}
484
501
485
- val config = TestAdapter .Config ()
502
+ val config = ScalaNativeTestAdapter .Config ()
486
503
.withBinaryFile(launcher)
487
- .withEnvVars(sys.env.toMap )
504
+ .withEnvVars(sys.env)
488
505
.withLogger(logger.scalaNativeTestLogger)
489
506
490
- var adapter : TestAdapter = null
507
+ var adapter : ScalaNativeTestAdapter = null
491
508
492
509
val res =
493
510
try {
494
- adapter = new TestAdapter (config)
511
+ adapter = new ScalaNativeTestAdapter (config)
512
+
513
+ val loadedFrameworks =
514
+ adapter
515
+ .loadFrameworks(foundFrameworkNames.map(List (_)))
516
+ .flatten
517
+ .distinctBy(_.name())
518
+
519
+ val finalTestFrameworks =
520
+ loadedFrameworks
521
+ // .filter(
522
+ // _.name() != "Scala Native JUnit test framework" ||
523
+ // !loadedFrameworks.exists(_.name() == "munit")
524
+ // )
525
+ // TODO: add support for JUnit and then only hardcode filtering it out when passed with munit
526
+ // https://github.com/VirtusLab/scala-cli/issues/3627
527
+ .filter(_.name() != " Scala Native JUnit test framework" )
528
+ if finalTestFrameworks.nonEmpty then
529
+ logger.log(
530
+ s """ Final list of test frameworks found:
531
+ | - ${finalTestFrameworks.map(_.description).mkString(" \n - " )}
532
+ | """ .stripMargin
533
+ )
495
534
496
- val frameworks = adapter.loadFrameworks(List (List (frameworkName0))).flatten
535
+ val skippedFrameworks = loadedFrameworks.diff(finalTestFrameworks)
536
+ if skippedFrameworks.nonEmpty then
537
+ logger.log(
538
+ s """ The following test frameworks have been filtered out:
539
+ | - ${skippedFrameworks.map(_.description).mkString(" \n - " )}
540
+ | """ .stripMargin
541
+ )
497
542
498
- if (frameworks.isEmpty)
499
- Left (new NoFrameworkFoundByBridgeError )
500
- else if (frameworks.length > 1 )
501
- Left (new TooManyFrameworksFoundByBridgeError )
502
- else {
503
- val framework = frameworks.head
504
- runTests(classPath, framework, requireTests, args, parentInspector)
505
- }
543
+ if finalTestFrameworks.isEmpty then Left (new NoFrameworkFoundByBridgeError )
544
+ else runTests(classPath, finalTestFrameworks, requireTests, args, parentInspector)
506
545
}
507
- finally if ( adapter != null ) adapter.close()
546
+ finally if adapter != null then adapter.close()
508
547
509
- if (value(res)) 0
510
- else 1
548
+ if value(res) then 0 else 1
511
549
}
512
550
}
0 commit comments