Skip to content

Commit ea68a9a

Browse files
authored
Merge pull request #4281 from Gedochao/fix/4017-2
Improve error for when JVM-only dependencies are not found on other platforms
1 parent 72008ba commit ea68a9a

5 files changed

Lines changed: 130 additions & 22 deletions

File tree

modules/build/src/main/scala/scala/build/internal/Runner.scala

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,25 @@ object Runner {
413413
if frameworks.nonEmpty then Right(frameworks) else Left(new NoTestFrameworkFoundError)
414414
}
415415

416+
private def jvmOnlyScalaJarsOnPlatformClassPath(
417+
classPath: Seq[Path],
418+
scalaBinaryVersion: String,
419+
platformSuffix: String,
420+
userDeclaredDepNames: Set[String]
421+
): Seq[String] =
422+
if userDeclaredDepNames.isEmpty then Nil
423+
else {
424+
val jvmSuffix = s"_$scalaBinaryVersion"
425+
val platformMarker = s"_${platformSuffix}_"
426+
val prefixes = userDeclaredDepNames.map(n => s"$n$jvmSuffix")
427+
classPath.iterator
428+
.map(_.getFileName.toString)
429+
.filter(_.endsWith(".jar"))
430+
.filterNot(_.contains(platformMarker))
431+
.filter(name => prefixes.exists(p => name.startsWith(s"$p-") || name.startsWith(s"$p.")))
432+
.toList
433+
}
434+
416435
def testJs(
417436
classPath: Seq[Path],
418437
entrypoint: File,
@@ -421,7 +440,10 @@ object Runner {
421440
predefinedTestFrameworks: Seq[String],
422441
logger: Logger,
423442
jsDom: Boolean,
424-
esModule: Boolean
443+
esModule: Boolean,
444+
scalaBinaryVersion: String,
445+
platformSuffix: String,
446+
userDeclaredDepNames: Set[String]
425447
): Either[TestError, Int] = either {
426448
import org.scalajs.jsenv.Input
427449
import org.scalajs.jsenv.nodejs.NodeJSEnv
@@ -485,7 +507,15 @@ object Runner {
485507
|""".stripMargin
486508
)
487509

488-
if finalTestFrameworks.isEmpty then Left(new NoFrameworkFoundByBridgeError)
510+
if finalTestFrameworks.isEmpty then
511+
Left(new NoFrameworkFoundByBridgeError(
512+
jvmOnlyScalaJarsOnPlatformClassPath(
513+
classPath,
514+
scalaBinaryVersion,
515+
platformSuffix,
516+
userDeclaredDepNames
517+
)
518+
))
489519
else runTests(classPath, finalTestFrameworks, requireTests, args, parentInspector, logger)
490520
}
491521
finally if adapter != null then adapter.close()
@@ -499,7 +529,10 @@ object Runner {
499529
predefinedTestFrameworks: Seq[String],
500530
requireTests: Boolean,
501531
args: Seq[String],
502-
logger: Logger
532+
logger: Logger,
533+
scalaBinaryVersion: String,
534+
platformSuffix: String,
535+
userDeclaredDepNames: Set[String]
503536
): Either[TestError, Int] = either {
504537
logger.debug("Preparing to run tests with Scala Native...")
505538
logger.debug(s"Native tests class path: $classPath")
@@ -551,7 +584,15 @@ object Runner {
551584
|""".stripMargin
552585
)
553586

554-
if finalTestFrameworks.isEmpty then Left(new NoFrameworkFoundByNativeBridgeError)
587+
if finalTestFrameworks.isEmpty then
588+
Left(new NoFrameworkFoundByNativeBridgeError(
589+
jvmOnlyScalaJarsOnPlatformClassPath(
590+
classPath,
591+
scalaBinaryVersion,
592+
platformSuffix,
593+
userDeclaredDepNames
594+
)
595+
))
555596
else runTests(classPath, finalTestFrameworks, requireTests, args, parentInspector, logger)
556597
}
557598
finally if adapter != null then adapter.close()

modules/cli/src/main/scala/scala/cli/commands/test/Test.scala

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -210,14 +210,19 @@ object Test extends ScalaCommand[TestOptions] {
210210
esModule
211211
) { js =>
212212
Runner.testJs(
213-
build.fullClassPath.map(_.toNIO),
214-
js.toIO,
215-
requireTests,
216-
args,
217-
predefinedTestFrameworks.map(_.value),
218-
logger,
219-
build.options.scalaJsOptions.dom.getOrElse(false),
220-
esModule
213+
classPath = build.fullClassPath.map(_.toNIO),
214+
entrypoint = js.toIO,
215+
requireTests = requireTests,
216+
args = args,
217+
predefinedTestFrameworks = predefinedTestFrameworks.map(_.value),
218+
logger = logger,
219+
jsDom = build.options.scalaJsOptions.dom.getOrElse(false),
220+
esModule = esModule,
221+
scalaBinaryVersion = build.scalaParams.map(_.scalaBinaryVersion).getOrElse("3"),
222+
platformSuffix = build.scalaParams.flatMap(_.platform).getOrElse("sjs1"),
223+
userDeclaredDepNames =
224+
build.options.classPathOptions.allExtraDependencies.toSeq.iterator
225+
.map(_.value.name).toSet
221226
)
222227
}.flatten
223228
}
@@ -229,12 +234,17 @@ object Test extends ScalaCommand[TestOptions] {
229234
logger
230235
) { launcher =>
231236
Runner.testNative(
232-
build.fullClassPath.map(_.toNIO),
233-
launcher.toIO,
234-
predefinedTestFrameworks.map(_.value),
235-
requireTests,
236-
args,
237-
logger
237+
classPath = build.fullClassPath.map(_.toNIO),
238+
launcher = launcher.toIO,
239+
predefinedTestFrameworks = predefinedTestFrameworks.map(_.value),
240+
requireTests = requireTests,
241+
args = args,
242+
logger = logger,
243+
scalaBinaryVersion = build.scalaParams.map(_.scalaBinaryVersion).getOrElse("3"),
244+
platformSuffix = build.scalaParams.flatMap(_.platform).getOrElse("native0.5"),
245+
userDeclaredDepNames =
246+
build.options.classPathOptions.allExtraDependencies.toSeq.iterator
247+
.map(_.value.name).toSet
238248
)
239249
}.flatten
240250
}
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
11
package scala.build.errors
22

3-
final class NoFrameworkFoundByBridgeError
4-
extends TestError("No framework found by Scala.js test bridge")
3+
final class NoFrameworkFoundByBridgeError(
4+
suspiciousJvmOnlyJars: Seq[String] = Nil
5+
) extends TestError(
6+
NoFrameworkFoundByBridgeError.message(suspiciousJvmOnlyJars)
7+
)
8+
9+
object NoFrameworkFoundByBridgeError {
10+
private def message(jars: Seq[String]): String =
11+
if jars.isEmpty then "No framework found by Scala.js test bridge"
12+
else
13+
s"""No framework found by Scala.js test bridge.
14+
|The following JVM-only Scala dependencies are on the Scala.js classpath and likely caused the failure:
15+
|${jars.map(" - " + _).mkString("\n")}
16+
|Use the platform-aware dependency syntax (extra ':' before the version, e.g. 'org::name::version' / '//> using dep org::name::version') so the Scala.js artifact is fetched instead of the JVM one.""".stripMargin
17+
}
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
11
package scala.build.errors
22

3-
final class NoFrameworkFoundByNativeBridgeError
4-
extends TestError("No framework found by Scala Native test bridge")
3+
final class NoFrameworkFoundByNativeBridgeError(
4+
suspiciousJvmOnlyJars: Seq[String] = Nil
5+
) extends TestError(
6+
NoFrameworkFoundByNativeBridgeError.message(suspiciousJvmOnlyJars)
7+
)
8+
9+
object NoFrameworkFoundByNativeBridgeError {
10+
private def message(jars: Seq[String]): String =
11+
if jars.isEmpty then "No framework found by Scala Native test bridge"
12+
else
13+
s"""No framework found by Scala Native test bridge.
14+
|The following JVM-only Scala dependencies are on the Scala Native classpath and likely caused the failure:
15+
|${jars.map(" - " + _).mkString("\n")}
16+
|Use the platform-aware dependency syntax (extra ':' before the version, e.g. 'org::name::version' / '//> using dep org::name::version') so the Scala Native artifact is fetched instead of the JVM one.""".stripMargin
17+
}

modules/integration/src/test/scala/scala/cli/integration/TestTestDefinitions.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,37 @@ abstract class TestTestDefinitions extends ScalaCliSuite with TestScalaVersionAr
518518
TestUtil.retryOnCi()(utestNative())
519519
}
520520

521+
for {
522+
(platformArgs, platformName) <- Seq(
523+
(Seq("--native"), "Scala Native"),
524+
(Seq("--js"), "Scala.js")
525+
)
526+
} test(s"utest on $platformName with JVM-only dep syntax produces an informative error") {
527+
TestUtil.retryOnCi() {
528+
TestInputs(
529+
os.rel / "MyTests.test.scala" ->
530+
s"""//> using dep com.lihaoyi::utest:$utestVersion
531+
|import utest._
532+
|
533+
|object MyTests extends TestSuite {
534+
| val tests = Tests {
535+
| test("foo") { assert(2 + 2 == 4) }
536+
| }
537+
|}
538+
|""".stripMargin
539+
).fromRoot { root =>
540+
val res = os.proc(TestUtil.cli, "test", extraOptions, ".", platformArgs)
541+
.call(cwd = root, check = false, mergeErrIntoOut = true)
542+
expect(res.exitCode != 0)
543+
val out = res.out.text()
544+
expect(out.contains(s"No framework found by $platformName test bridge"))
545+
expect(out.contains("JVM-only Scala dependencies"))
546+
expect(out.contains("utest"))
547+
expect(out.contains("::"))
548+
}
549+
}
550+
}
551+
521552
test("junit") {
522553
successfulJunitInputs.fromRoot { root =>
523554
val output = os.proc(TestUtil.cli, "test", extraOptions, ".").call(cwd = root).out.text()

0 commit comments

Comments
 (0)