diff --git a/compiler/src/dotty/tools/MainGenericRunner.scala b/compiler/src/dotty/tools/MainGenericRunner.scala index 1dcab2d92a96..6f4366a00b77 100644 --- a/compiler/src/dotty/tools/MainGenericRunner.scala +++ b/compiler/src/dotty/tools/MainGenericRunner.scala @@ -15,7 +15,7 @@ import dotty.tools.io.Jar import dotty.tools.runner.ScalaClassLoader import java.nio.file.Paths import dotty.tools.dotc.config.CommandLineParser -import dotty.tools.scripting.StringDriver +import dotty.tools.scripting.{StringDriver, StringDriverException, ScriptingException} enum ExecuteMode: case Guess @@ -118,21 +118,21 @@ object MainGenericRunner { @sharable val scalaOption = raw"""@.*""".r @sharable val colorOption = raw"""-color:.*""".r @tailrec - def process(args: List[String], settings: Settings): Settings = args match + def processArgs(args: List[String], settings: Settings): Settings = args match case Nil => settings case "-run" :: fqName :: tail => - process(tail, settings.withExecuteMode(ExecuteMode.Run).withTargetToRun(fqName)) + processArgs(tail, settings.withExecuteMode(ExecuteMode.Run).withTargetToRun(fqName)) case ("-cp" | "-classpath" | "--class-path") :: cp :: tail => val (tailargs, newEntries) = processClasspath(cp, tail) - process(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty))) + processArgs(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty))) case ("-version" | "--version") :: _ => settings.copy( executeMode = ExecuteMode.Repl, residualArgs = List("-version") ) case ("-v" | "-verbose" | "--verbose") :: tail => - process( + processArgs( tail, settings.copy( verbose = true, @@ -140,18 +140,18 @@ object MainGenericRunner { ) ) case "-save" :: tail => - process(tail, settings.withSave) + processArgs(tail, settings.withSave) case "-nosave" :: tail => - process(tail, settings.noSave) + processArgs(tail, settings.noSave) case "-with-compiler" :: tail => - process(tail, settings.withCompiler) + processArgs(tail, settings.withCompiler) case (o @ javaOption(striped)) :: tail => - process(tail, settings.withJavaArgs(striped).withScalaArgs(o)) + processArgs(tail, settings.withJavaArgs(striped).withScalaArgs(o)) case (o @ scalaOption(_*)) :: tail => val remainingArgs = (CommandLineParser.expandArg(o) ++ tail).toList - process(remainingArgs, settings) + processArgs(remainingArgs, settings) case (o @ colorOption(_*)) :: tail => - process(tail, settings.withScalaArgs(o)) + processArgs(tail, settings.withScalaArgs(o)) case "-e" :: expression :: tail => val mainSource = s"@main def main(args: String *): Unit =\n ${expression}" settings @@ -169,13 +169,13 @@ object MainGenericRunner { .withScriptArgs(tail*) else val newSettings = if arg.startsWith("-") then settings else settings.withPossibleEntryPaths(arg).withModeShouldBePossibleRun - process(tail, newSettings.withResidualArgs(arg)) - end process + processArgs(tail, newSettings.withResidualArgs(arg)) + end processArgs - def main(args: Array[String]): Unit = + def process(args: Array[String]): Boolean = val scalaOpts = envOrNone("SCALA_OPTS").toArray.flatMap(_.split(" ")).filter(_.nonEmpty) val allArgs = scalaOpts ++ args - val settings = process(allArgs.toList, Settings()) + val settings = processArgs(allArgs.toList, Settings()) if settings.exitCode != 0 then System.exit(settings.exitCode) def removeCompiler(cp: Array[String]) = @@ -185,12 +185,13 @@ object MainGenericRunner { else cp - def run(settings: Settings): Unit = settings.executeMode match + def run(settings: Settings): Option[Throwable] = settings.executeMode match case ExecuteMode.Repl => val properArgs = List("-classpath", settings.classPath.mkString(classpathSeparator)).filter(Function.const(settings.classPath.nonEmpty)) ++ settings.residualArgs repl.Main.main(properArgs.toArray) + None case ExecuteMode.PossibleRun => val newClasspath = (settings.classPath :+ ".").flatMap(_.split(classpathSeparator).filter(_.nonEmpty)).map(File(_).toURI.toURL) @@ -207,10 +208,11 @@ object MainGenericRunner { case None => settings.withExecuteMode(ExecuteMode.Repl) run(newSettings) + case ExecuteMode.Run => val scalaClasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(classpathSeparator) val newClasspath = (settings.classPath.flatMap(_.split(classpathSeparator).filter(_.nonEmpty)) ++ removeCompiler(scalaClasspath) :+ ".").map(File(_).toURI.toURL) - val res = ObjectRunner.runAndCatch(newClasspath, settings.targetToRun, settings.residualArgs).flatMap { + ObjectRunner.runAndCatch(newClasspath, settings.targetToRun, settings.residualArgs).flatMap { case ex: ClassNotFoundException if ex.getMessage == settings.targetToRun => val file = settings.targetToRun Jar(file).mainClass match @@ -220,7 +222,7 @@ object MainGenericRunner { Some(IllegalArgumentException(s"No main class defined in manifest in jar: $file")) case ex => Some(ex) } - errorFn("", res) + case ExecuteMode.Script => val targetScript = Paths.get(settings.targetScript).toFile val targetJar = settings.targetScript.replaceAll("[.][^\\/]*$", "")+".jar" @@ -232,11 +234,10 @@ object MainGenericRunner { sys.props("script.path") = targetScript.toPath.toAbsolutePath.normalize.toString val scalaClasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(classpathSeparator) val newClasspath = (settings.classPath.flatMap(_.split(classpathSeparator).filter(_.nonEmpty)) ++ removeCompiler(scalaClasspath) :+ ".").map(File(_).toURI.toURL) - val res = if mainClass.nonEmpty then + if mainClass.nonEmpty then ObjectRunner.runAndCatch(newClasspath :+ File(targetJar).toURI.toURL, mainClass, settings.scriptArgs) else Some(IllegalArgumentException(s"No main class defined in manifest in jar: $precompiledJar")) - errorFn("", res) else val properArgs = @@ -246,7 +247,8 @@ object MainGenericRunner { ++ settings.scalaArgs ++ List("-script", settings.targetScript) ++ settings.scriptArgs - scripting.Main.main(properArgs.toArray) + scripting.Main.process(properArgs.toArray) + case ExecuteMode.Expression => val cp = settings.classPath match { case Nil => "" @@ -265,12 +267,17 @@ object MainGenericRunner { else run(settings.withExecuteMode(ExecuteMode.Repl)) - run(settings) + run(settings) match + case Some(ex: (StringDriverException | ScriptingException)) => errorFn(ex.getMessage) + case e @ Some(ex) => errorFn("", e) + case _ => true - - def errorFn(str: String, e: Option[Throwable] = None, isFailure: Boolean = true): Boolean = { + def errorFn(str: String, e: Option[Throwable] = None): Boolean = if (str.nonEmpty) Console.err.println(str) e.foreach(_.printStackTrace()) - !isFailure - } + false + + def main(args: Array[String]): Unit = + if (!process(args)) System.exit(1) + } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index f2650ee714ab..5876b69edfde 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -37,20 +37,22 @@ object TastyPrinter: for arg <- args do if arg == "-color:never" then () // skip else if arg.startsWith("-") then println(s"bad option '$arg' was ignored") - else if arg.endsWith(".tasty") then { + else if arg.endsWith(".tasty") then val path = Paths.get(arg) - if Files.exists(path) then printTasty(arg, Files.readAllBytes(path).nn) - else println("File not found: " + arg) - } - else if arg.endsWith(".jar") then { + if Files.exists(path) then + printTasty(arg, Files.readAllBytes(path).nn) + else + println("File not found: " + arg) + System.exit(1) + else if arg.endsWith(".jar") then val jar = JarArchive.open(Path(arg), create = false) try for file <- jar.iterator() if file.name.endsWith(".tasty") do printTasty(s"$arg ${file.path}", file.toByteArray) finally jar.close() - - } - else println(s"Not a '.tasty' or '.jar' file: $arg") + else + println(s"Not a '.tasty' or '.jar' file: $arg") + System.exit(1) if printLastLine then println(line) diff --git a/compiler/src/dotty/tools/scripting/Main.scala b/compiler/src/dotty/tools/scripting/Main.scala index f5259974f16f..8db12f400c64 100755 --- a/compiler/src/dotty/tools/scripting/Main.scala +++ b/compiler/src/dotty/tools/scripting/Main.scala @@ -33,10 +33,10 @@ object Main: (compilerArgs, file, scriptArgs, saveJar, invokeFlag) end distinguishArgs - def main(args: Array[String]): Unit = + def process(args: Array[String]): Option[Throwable] = val (compilerArgs, scriptFile, scriptArgs, saveJar, invokeFlag) = distinguishArgs(args) val driver = ScriptingDriver(compilerArgs, scriptFile, scriptArgs) - try driver.compileAndRun { (outDir:Path, classpathEntries:Seq[Path], mainClass: String) => + driver.compileAndRun { (outDir:Path, classpathEntries:Seq[Path], mainClass: String) => // write expanded classpath to java.class.path property, so called script can see it sys.props("java.class.path") = classpathEntries.map(_.toString).mkString(pathsep) if saveJar then @@ -44,13 +44,12 @@ object Main: writeJarfile(outDir, scriptFile, scriptArgs, classpathEntries, mainClass) invokeFlag } - catch - case ScriptingException(msg) => - println(s"Error: $msg") - sys.exit(1) - case e: java.lang.reflect.InvocationTargetException => - throw e.getCause + def main(args: Array[String]): Unit = + process(args).map { + case ScriptingException(msg) => println(msg) + case ex => ex.printStackTrace + }.foreach(_ => System.exit(1)) private def writeJarfile(outDir: Path, scriptFile: File, scriptArgs:Array[String], classpathEntries:Seq[Path], mainClassName: String): Unit = diff --git a/compiler/src/dotty/tools/scripting/ScriptingDriver.scala b/compiler/src/dotty/tools/scripting/ScriptingDriver.scala index 38765ac6dafa..e4c357494ff1 100755 --- a/compiler/src/dotty/tools/scripting/ScriptingDriver.scala +++ b/compiler/src/dotty/tools/scripting/ScriptingDriver.scala @@ -11,7 +11,7 @@ import dotty.tools.io.{ PlainDirectory, Directory, ClassPath } import Util.* class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: Array[String]) extends Driver: - def compileAndRun(pack:(Path, Seq[Path], String) => Boolean = null): Unit = + def compileAndRun(pack:(Path, Seq[Path], String) => Boolean = null): Option[Throwable] = val outDir = Files.createTempDirectory("scala3-scripting") outDir.toFile.deleteOnExit() setup(compilerArgs :+ scriptFile.getAbsolutePath, initCtx.fresh) match @@ -20,26 +20,25 @@ class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: new PlainDirectory(Directory(outDir))) if doCompile(newCompiler, toCompile).hasErrors then - throw ScriptingException("Errors encountered during compilation") - - try - val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}" - val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) } - val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scriptFile.toString) - val invokeMain: Boolean = - Option(pack) match - case Some(func) => - func(outDir, classpathEntries, mainClass) - case None => - true - end match - if invokeMain then mainMethod.invoke(null, scriptArgs) - catch - case e: java.lang.reflect.InvocationTargetException => - throw e.getCause - finally - deleteFile(outDir.toFile) - case None => + Some(ScriptingException("Errors encountered during compilation")) + else + try + val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}" + val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) } + detectMainClassAndMethod(outDir, classpathEntries, scriptFile.toString) match + case Right((mainClass, mainMethod)) => + val invokeMain: Boolean = Option(pack).map { func => + func(outDir, classpathEntries, mainClass) + }.getOrElse(true) + if invokeMain then mainMethod.invoke(null, scriptArgs) + None + case Left(ex) => Some(ex) + catch + case e: java.lang.reflect.InvocationTargetException => + Some(e.getCause) + finally + deleteFile(outDir.toFile) + case None => None end compileAndRun end ScriptingDriver diff --git a/compiler/src/dotty/tools/scripting/StringDriver.scala b/compiler/src/dotty/tools/scripting/StringDriver.scala index b8b64994f285..eccd8445c87d 100755 --- a/compiler/src/dotty/tools/scripting/StringDriver.scala +++ b/compiler/src/dotty/tools/scripting/StringDriver.scala @@ -8,38 +8,43 @@ import dotty.tools.dotc.Driver import dotty.tools.dotc.core.Contexts, Contexts.{ Context, ctx } import dotty.tools.io.{ PlainDirectory, Directory, ClassPath } import Util.* +import dotty.tools.dotc.util.SourceFile class StringDriver(compilerArgs: Array[String], scalaSource: String) extends Driver: override def sourcesRequired: Boolean = false - def compileAndRun(classpath: List[String] = Nil): Unit = + def compileAndRun(classpath: List[String] = Nil): Option[Throwable] = val outDir = Files.createTempDirectory("scala3-expression") outDir.toFile.deleteOnExit() setup(compilerArgs, initCtx.fresh) match case Some((toCompile, rootCtx)) => - given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir, - new PlainDirectory(Directory(outDir))) + given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir, new PlainDirectory(Directory(outDir))) val compiler = newCompiler - compiler.newRun.compileFromStrings(List(scalaSource)) + + val source = SourceFile.virtual("expression", scalaSource) + compiler.newRun.compileSources(List(source)) val output = ctx.settings.outputDir.value if ctx.reporter.hasErrors then - throw StringDriverException("Errors encountered during compilation") - - try - val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}" - val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) } - sys.props("java.class.path") = classpathEntries.map(_.toString).mkString(pathsep) - val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, classpathEntries, scalaSource) - mainMethod.invoke(null, Array.empty[String]) - catch - case e: java.lang.reflect.InvocationTargetException => - throw e.getCause - finally - deleteFile(outDir.toFile) - case None => + Some(StringDriverException("Errors encountered during compilation")) + else + try + val classpath = s"${ctx.settings.classpath.value}${pathsep}${sys.props("java.class.path")}" + val classpathEntries: Seq[Path] = ClassPath.expandPath(classpath, expandStar=true).map { Paths.get(_) } + sys.props("java.class.path") = classpathEntries.map(_.toString).mkString(pathsep) + detectMainClassAndMethod(outDir, classpathEntries, scalaSource) match + case Right((mainClass, mainMethod)) => + mainMethod.invoke(null, Array.empty[String]) + None + case Left(ex) => Some(ex) + catch + case e: java.lang.reflect.InvocationTargetException => + Some(e.getCause) + finally + deleteFile(outDir.toFile) + case None => None end compileAndRun end StringDriver diff --git a/compiler/src/dotty/tools/scripting/Util.scala b/compiler/src/dotty/tools/scripting/Util.scala index 71ae4413de55..fbbeb13ca13c 100755 --- a/compiler/src/dotty/tools/scripting/Util.scala +++ b/compiler/src/dotty/tools/scripting/Util.scala @@ -8,7 +8,6 @@ import java.net.{ URLClassLoader } import java.lang.reflect.{ Modifier, Method } object Util: - def deleteFile(target: File): Unit = if target.isDirectory then for member <- target.listFiles.toList @@ -16,7 +15,11 @@ object Util: target.delete() end deleteFile - def detectMainClassAndMethod(outDir: Path, classpathEntries: Seq[Path], srcFile: String): (String, Method) = + def detectMainClassAndMethod( + outDir: Path, + classpathEntries: Seq[Path], + srcFile: String + ): Either[Throwable, (String, Method)] = val classpathUrls = (classpathEntries :+ outDir).map { _.toUri.toURL } val cl = URLClassLoader(classpathUrls.toArray) @@ -48,11 +51,10 @@ object Util: mains match case Nil => - throw StringDriverException(s"No main methods detected for [${srcFile}]") + Left(StringDriverException(s"No main methods detected for [${srcFile}]")) case _ :: _ :: _ => - throw StringDriverException( - s"internal error: Detected the following main methods:\n${mains.mkString("\n")}") - case m :: Nil => m + Left(StringDriverException(s"Internal error: Detected the following main methods:\n${mains.mkString("\n")}")) + case mainMethod :: Nil => Right(mainMethod) end match end detectMainClassAndMethod diff --git a/compiler/test-resources/scripting/exit-code-tests/compileError.scala b/compiler/test-resources/scripting/exit-code-tests/compileError.scala new file mode 100644 index 000000000000..d703006dc2d4 --- /dev/null +++ b/compiler/test-resources/scripting/exit-code-tests/compileError.scala @@ -0,0 +1 @@ +@main def compileError = prin \ No newline at end of file diff --git a/compiler/test-resources/scripting/exit-code-tests/positiveTest.scala b/compiler/test-resources/scripting/exit-code-tests/positiveTest.scala new file mode 100644 index 000000000000..f3dbe50aa643 --- /dev/null +++ b/compiler/test-resources/scripting/exit-code-tests/positiveTest.scala @@ -0,0 +1 @@ +@main def positiveTest = println("Hello World!") \ No newline at end of file diff --git a/compiler/test-resources/scripting/exit-code-tests/runtimeError.scala b/compiler/test-resources/scripting/exit-code-tests/runtimeError.scala new file mode 100644 index 000000000000..f6bdba466455 --- /dev/null +++ b/compiler/test-resources/scripting/exit-code-tests/runtimeError.scala @@ -0,0 +1 @@ +@main def runtimeError = throw RuntimeException() diff --git a/compiler/test-resources/scripting/exit-code-tests/scriptCompileError.sc b/compiler/test-resources/scripting/exit-code-tests/scriptCompileError.sc new file mode 100644 index 000000000000..36a59d06f3c4 --- /dev/null +++ b/compiler/test-resources/scripting/exit-code-tests/scriptCompileError.sc @@ -0,0 +1 @@ +@main def main = prin \ No newline at end of file diff --git a/compiler/test-resources/scripting/exit-code-tests/scriptPositive.sc b/compiler/test-resources/scripting/exit-code-tests/scriptPositive.sc new file mode 100644 index 000000000000..a4dd0823f7a8 --- /dev/null +++ b/compiler/test-resources/scripting/exit-code-tests/scriptPositive.sc @@ -0,0 +1 @@ +@main def scriptPositive = println("Hello world!") \ No newline at end of file diff --git a/compiler/test-resources/scripting/exit-code-tests/scriptRuntimeError.sc b/compiler/test-resources/scripting/exit-code-tests/scriptRuntimeError.sc new file mode 100644 index 000000000000..0cd940cc20cf --- /dev/null +++ b/compiler/test-resources/scripting/exit-code-tests/scriptRuntimeError.sc @@ -0,0 +1 @@ +@main def scriptRuntimeError = throw RuntimeException() \ No newline at end of file diff --git a/compiler/test/dotty/tools/scripting/BashExitCodeTests.scala b/compiler/test/dotty/tools/scripting/BashExitCodeTests.scala new file mode 100644 index 000000000000..86d947cbc3bc --- /dev/null +++ b/compiler/test/dotty/tools/scripting/BashExitCodeTests.scala @@ -0,0 +1,188 @@ +package dotty +package tools +package scripting + +import scala.language.unsafeNulls + +import java.io.File +import java.nio.file.Files +import org.junit.Test +import org.junit.Assert.assertEquals +import org.junit.experimental.categories.Category + +import ScriptTestEnv.* + + +object BashExitCodeTests: + private def testFiles = scripts("/scripting/exit-code-tests") + + /* + * Compiles the class checking exit code + * + * @param testName name of the test + * @param expectedExitCode expected exit code from the output + */ + private def compileAndVerifyExitCode( + testName: String, + expectedExitCode: Int, + )(using temporaryDir: File): Unit = + assertTestExists(testName) { testFile => + val testFilePath = testFile.absPath + val commandline = (Seq(scalacPath, "-d", temporaryDir, testFilePath)).mkString(" ") + val (validTest, exitCode, _, _) = bashCommand(commandline) + if verifyValid(validTest) then + assertEquals(expectedExitCode, exitCode) + } + + /* + * Runs compiled code checking the exit code + * + * @param className name of compiled class + * @param runExitCode expected exit code from the runner + */ + private def runClassAndVerifyExitCode( + className: String, + expectedExitCode: Int + )(using temporaryDir: File): Unit = + val testClassFile = temporaryDir.files.find(_.getName == s"$className.class") + assert(testClassFile.isDefined) + val commandline = (Seq(scalaPath, "-classpath", temporaryDir.getAbsolutePath, className)).mkString(" ") + val (validTest, exitCode, o, e) = bashCommand(commandline) + if verifyValid(validTest) then + assertEquals(expectedExitCode, exitCode) + + /* + * Compiles and then runs code verifying runner status code + * + * @param testName name of the test + * @param className name of compiled class + * @param expectedRunExitCode expected exit code from the runner + */ + private def compileRunAndVerifyExitCode( + testName: String, + className: String, + expectedRunExitCode: Int, + )(using File): Unit = + compileAndVerifyExitCode(testName, 0) + runClassAndVerifyExitCode(className, expectedRunExitCode) + + /* + * Runs the command and checks the exit code + * + * @param args arguments for command line + * @param expectedExitCode expected exit code from the output + */ + private def testCommandExitCode(args: Seq[String], expectedExitCode: Int): Unit = + val commandline = args.mkString(" ") + val (validTest, exitCode, output, erroutput) = bashCommand(commandline) + if verifyValid(validTest) then + assertEquals(expectedExitCode, exitCode) + + /* + * Checks if scripting test resources contains test with given `testName` + * And then runs function `test` + * + * @param testName name of the test containing the extension + * @param test check to be run on found test file + */ + private def assertTestExists(testName: String)(test: File => Unit) = + val file = testFiles.find(_.getName == testName) + assert(file.isDefined) + test(file.get) + + /* + * Runs test for created temporary file + * and ensures it deletion after function execution + * + * @param test check to be run on found test file + */ + private def withTempFile(test: File => Unit) = + val tempFile = Files.createTempFile("temp-file", ".class").toFile + try { + test(tempFile) + } finally { + Util.deleteFile(tempFile) + } + + /* + * Runs test with implicit temporary directory + * and ensures it deletion after the function execution + * + * @param test test to be run with given temporary directory + */ + private def withTempDirectory(test: File ?=> Unit) = + given file: File = Files.createTempDirectory("exit-code-tests").toFile + try { test } finally { Util.deleteFile(file) } + + /* + * Returns path to the generated tasty file for given directory and classname + */ + private def getGeneratedTastyPath(className: String)(using temporaryDir: File): String = + temporaryDir.toPath.resolve(s"$className.tasty").toString + +@Category(Array(classOf[BootstrappedOnlyTests])) +class BashExitCodeTests: + import BashExitCodeTests.* + + @Test def verifyExitCodeOnCompileError: Unit = + withTempDirectory(compileAndVerifyExitCode("compileError.scala", 1)) + + @Test def verifyExitCodeOnRuntimeError: Unit = + withTempDirectory(compileRunAndVerifyExitCode("runtimeError.scala", "runtimeError", 1)) + + @Test def verifyExitCode: Unit = + withTempDirectory(compileRunAndVerifyExitCode("positiveTest.scala", "positiveTest", 0)) + + @Test def verifyExitCodeOnScriptError: Unit = + assertTestExists("scriptRuntimeError.sc"){ file => + testCommandExitCode(Seq(scalacPath, "-script", file.absPath), 1) + } + + @Test def verifyExitCodeOnScriptErrorCompiler: Unit = + assertTestExists("scriptRuntimeError.sc") { file => + testCommandExitCode(Seq(scalacPath, "-script", file.absPath), 1) + } + + @Test def verifyExitCodeOnScript: Unit = + assertTestExists("scriptPositive.sc") { file => + testCommandExitCode(Seq(scalaPath, file.absPath), 0) + } + + @Test def verifyExitCodeOnScriptCompiler: Unit = + assertTestExists("scriptPositive.sc") { file => + testCommandExitCode(Seq(scalacPath, "-script", file.absPath), 0) + } + + @Test def verifyExitCodeOnDecompilation: Unit = + withTempDirectory { + compileAndVerifyExitCode("positiveTest.scala", 0) + testCommandExitCode(Seq(scalacPath, "-decompile", getGeneratedTastyPath("positiveTest")), 0) + } + + @Test def verifyExitCodeOnPrintTasty: Unit = + withTempDirectory { + compileAndVerifyExitCode("positiveTest.scala", 0) + testCommandExitCode(Seq(scalacPath, "-print-tasty", getGeneratedTastyPath("positiveTest")), 0) + } + + @Test def verifyExitCodeOnDecompilationFailure: Unit = + withTempFile(file => testCommandExitCode(Seq(scalacPath, "-decompile", file.absPath), 1)) + testCommandExitCode(Seq(scalacPath, "-decompile", "non-existing-file.tasty"), 1) + + @Test def verifyExitCodeOnPrintTastyFailure: Unit = + withTempFile(file => testCommandExitCode(Seq(scalacPath, "-print-tasty", file.absPath), 1)) + testCommandExitCode(Seq(scalacPath, "-print-tasty", "non-existing-file.tasty"), 1) + + @Test def verifyExitCodeOnExpressionCompileError: Unit = + testCommandExitCode(Seq(scalaPath, "-e", "'prinln(10*10)'"), 1) + + @Test def verifyExitCodeOnExpressionRuntimeError: Unit = + testCommandExitCode(Seq(scalaPath, "-e", "'1/0'"), 1) + + @Test def verifyExitCodeOnExpression: Unit = + testCommandExitCode(Seq(scalaPath, "-e", "'println(10*10)'"), 0) + + @Test def verifyExitCodeOnInfo: Unit = + List("--help", "--version", "-Xplugin-list", "-Vphases").foreach { flag => + testCommandExitCode(Seq(scalaPath, flag), 0) + } diff --git a/compiler/test/dotty/tools/scripting/ScriptingTests.scala b/compiler/test/dotty/tools/scripting/ScriptingTests.scala index 2fb12b401754..16eeb48f0b2a 100644 --- a/compiler/test/dotty/tools/scripting/ScriptingTests.scala +++ b/compiler/test/dotty/tools/scripting/ScriptingTests.scala @@ -122,7 +122,7 @@ class ScriptingTests: /* * Compile touchFile.sc to create executable jar, verify jar execution succeeds. */ - @Test def scriptingNoCompileJar = + @Test def scriptingNoCompileJar: Unit = val scriptFile = touchFileScript showScriptUnderTest(scriptFile) val expectedJar = script2jar(scriptFile) @@ -146,6 +146,8 @@ class ScriptingTests: if touchedFile.exists then printf("success: executable jar created file %s\n", touchedFile) assert( touchedFile.exists, s"expected to find file ${touchedFile}" ) + touchedFile.delete + assert( !touchedFile.exists, s"unable to delete ${touchedFile}" ) /////////////////////////////////// def touchFileScript = testFiles.find(_.getName == "touchFile.sc").get