From ee06f5150fd77f1bc7703306b95e9703ce50bd4d Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 15 Feb 2024 19:37:22 +0100 Subject: [PATCH] ? --- build.sc | 4 +- .../scala/almond/amm/AmmInterpreter.scala | 23 +--- .../test/scala/almond/ScalaKernelTests.scala | 22 +--- project/settings.sc | 110 ++++++++++++++++++ 4 files changed, 124 insertions(+), 35 deletions(-) diff --git a/build.sc b/build.sc index 57915255d..ad4edf9b6 100644 --- a/build.sc +++ b/build.sc @@ -237,7 +237,8 @@ trait ScalaInterpreter extends Cross.Module[String] with AlmondModule with Bloop Deps.directiveHandler, Deps.jansi, Deps.ammoniteCompiler(crossScalaVersion).exclude(("net.java.dev.jna", "jna")), - Deps.ammoniteRepl(crossScalaVersion).exclude(("net.java.dev.jna", "jna")) + Deps.ammoniteRepl(crossScalaVersion).exclude(("net.java.dev.jna", "jna")), + ivy"io.github.alexarchambault::class-path-inspector:0.1.1" ) } def scalacOptions = super.scalacOptions() ++ { @@ -247,7 +248,6 @@ trait ScalaInterpreter extends Cross.Module[String] with AlmondModule with Bloop scala213Options } object test extends CrossSbtModuleTests with AlmondTestModule { - override def testUseArgsFile = false def moduleDeps = { val rx = if (crossScalaVersion.startsWith("2.12.")) Seq(scala.`almond-rx`()) diff --git a/modules/scala/scala-interpreter/src/main/scala/almond/amm/AmmInterpreter.scala b/modules/scala/scala-interpreter/src/main/scala/almond/amm/AmmInterpreter.scala index a949b3c94..01ab8ca53 100644 --- a/modules/scala/scala-interpreter/src/main/scala/almond/amm/AmmInterpreter.scala +++ b/modules/scala/scala-interpreter/src/main/scala/almond/amm/AmmInterpreter.scala @@ -13,8 +13,6 @@ import coursier.util.ModuleMatcher import scala.jdk.CollectionConverters._ import scala.language.reflectiveCalls -import java.net.URLClassLoader -import scala.annotation.tailrec object AmmInterpreter { @@ -113,21 +111,12 @@ object AmmInterpreter { val log = logCtx(getClass) - @tailrec - def printCl(cl: ClassLoader): Unit = - if (cl != null) { - System.err.println(s"Loader $cl") - cl match { - case u: URLClassLoader => - for (url <- u.getURLs) - System.err.println(s" $url") - case _ => - } - System.err.println() - printCl(cl.getParent) - } - - printCl(frames0().head.classloader) + for ((cl, cp) <- classpath.Inspector.classPath(frames0().head.classloader).toVector) { + System.err.println(s"$cp") + for (elem <- cp) + System.err.println(s" $elem") + System.err.println() + } try { diff --git a/modules/scala/scala-interpreter/src/test/scala/almond/ScalaKernelTests.scala b/modules/scala/scala-interpreter/src/test/scala/almond/ScalaKernelTests.scala index 723cbe8f8..c1b7f069e 100644 --- a/modules/scala/scala-interpreter/src/test/scala/almond/ScalaKernelTests.scala +++ b/modules/scala/scala-interpreter/src/test/scala/almond/ScalaKernelTests.scala @@ -24,7 +24,6 @@ import utest._ import scala.collection.JavaConverters._ import scala.collection.compat._ -import scala.annotation.tailrec object ScalaKernelTests extends TestSuite { @@ -76,21 +75,12 @@ object ScalaKernelTests extends TestSuite { test("stdin") { - @tailrec - def printCl(cl: ClassLoader): Unit = - if (cl != null) { - System.err.println(s"Loader $cl") - cl match { - case u: URLClassLoader => - for (url <- u.getURLs) - System.err.println(s" $url") - case _ => - } - System.err.println() - printCl(cl.getParent) - } - - printCl(Thread.currentThread().getContextClassLoader) + for ((cl, cp) <- classpath.Inspector.classPath().toVector) { + System.err.println(s"$cp") + for (elem <- cp) + System.err.println(s" $elem") + System.err.println() + } val interpreter = new ScalaInterpreter( params = ScalaInterpreterParams( diff --git a/project/settings.sc b/project/settings.sc index 67eb6cf40..27ca4d3e7 100644 --- a/project/settings.sc +++ b/project/settings.sc @@ -262,6 +262,116 @@ trait AlmondTestModule with AlmondScalacOptions { // with AlmondScala2Or3Module { + // originally based on https://github.com/com-lihaoyi/mill/blob/3335d2a2f7f33766a680e30df6a7d0dc0fbe08b3/scalalib/src/mill/scalalib/TestModule.scala#L80-L146 + // goal here is to use a coursier bootstrap rather than a manifest JAR when testUseArgsFile is true + protected def testTask( + args: Task[Seq[String]], + globSelectors: Task[Seq[String]] + ): Task[(String, Seq[mill.testrunner.TestResult])] = + T.task { + val outputPath = T.dest / "out.json" + val useArgsFile = testUseArgsFile() + + val (jvmArgs, props: Map[String, String]) = + if (useArgsFile) { + val (props, jvmArgs) = forkArgs().partition(_.startsWith("-D")) + val sysProps = + props + .map(_.drop(2).split("[=]", 2)) + .map { + case Array(k, v) => k -> v + case Array(k) => k -> "" + } + .toMap + + jvmArgs -> sysProps + } else { + forkArgs() -> Map() + } + + val testArgs = mill.testrunner.TestArgs( + framework = testFramework(), + classpath = runClasspath().map(_.path), + arguments = args(), + sysProps = props, + outputPath = outputPath, + colored = T.log.colored, + testCp = compile().classes.path, + home = T.home, + globSelectors = globSelectors() + ) + + val testRunnerClasspathArg = zincWorker().scalalibClasspath() + .map(_.path.toNIO.toUri.toURL) + .mkString(",") + + val argsFile = T.dest / "testargs" + os.write(argsFile, upickle.default.write(testArgs)) + val mainArgs = Seq(testRunnerClasspathArg, argsFile.toString) + + runSubprocess( + mainClass = "mill.testrunner.entrypoint.TestRunnerMain", + classPath = (runClasspath() ++ zincWorker().testrunnerEntrypointClasspath()).map(_.path), + jvmArgs = jvmArgs, + envArgs = forkEnv(), + mainArgs = mainArgs, + workingDir = forkWorkingDir(), + useCpPassingJar = useArgsFile + ) + + if (!os.exists(outputPath)) mill.api.Result.Failure("Test execution failed.") + else + try { + val jsonOutput = ujson.read(outputPath.toIO) + val (doneMsg, results) = + upickle.default.read[(String, Seq[mill.testrunner.TestResult])](jsonOutput) + TestModule.handleResults(doneMsg, results, Some(T.ctx())) + } catch { + case e: Throwable => + mill.api.Result.Failure("Test reporting failed: " + e) + } + } + + private def createBootstrapJar(dest: os.Path, classPath: Agg[os.Path], mainClass: String): Unit = { + import coursier.launcher._ + val content = Seq(ClassLoaderContent.fromUrls(classPath.toSeq.map(_.toNIO.toUri.toASCIIString))) + val params = Parameters.Bootstrap(content, mainClass) + BootstrapGenerator.generate(params, dest.toNIO) + } + + // originally based on https://github.com/com-lihaoyi/mill/blob/3335d2a2f7f33766a680e30df6a7d0dc0fbe08b3/main/util/src/mill/util/Jvm.scala#L117-L154 + // goal is to use coursier bootstraps rather than manifest JARs + // the former load JARs via standard URLClassLoader-s, which is better dealt with in Ammonite and almond + private def runSubprocess( + mainClass: String, + classPath: Agg[os.Path], + jvmArgs: Seq[String] = Seq.empty, + envArgs: Map[String, String] = Map.empty, + mainArgs: Seq[String] = Seq.empty, + workingDir: os.Path = null, + useCpPassingJar: Boolean = false + )(implicit ctx: mill.api.Ctx): Unit = { + + val (cp, mainClass0) = + if (useCpPassingJar && !classPath.iterator.isEmpty) { + val passingJar = os.temp(prefix = "run-", suffix = ".jar", deleteOnExit = false) + ctx.log.debug(s"Creating classpath passing jar '$passingJar' with Class-Path: ${classPath.iterator.map(_.toNIO.toUri.toURL.toExternalForm).mkString(" ")}") + createBootstrapJar(passingJar, classPath, mainClass) + (Agg(passingJar), "coursier.bootstrap.launcher.Launcher") + } else + (classPath, mainClass) + + val args = + Vector(mill.util.Jvm.javaExe) ++ + jvmArgs ++ + Vector("-cp", cp.iterator.mkString(java.io.File.pathSeparator), mainClass0) ++ + mainArgs + + ctx.log.debug(s"Run subprocess with args: ${args.map(a => s"'$a'").mkString(" ")}") + + mill.util.Jvm.runSubprocess(args, envArgs, workingDir) + } + def ivyDeps = Agg(Deps.utest) def testFramework = "utest.runner.Framework"