Skip to content

Commit 479fb8c

Browse files
committed
add exit code tests, fix exit code for tastyprinter flag, improve temp file creation for expression for better error messages
1 parent 01c1855 commit 479fb8c

File tree

11 files changed

+183
-18
lines changed

11 files changed

+183
-18
lines changed

compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,22 @@ object TastyPrinter:
3737
for arg <- args do
3838
if arg == "-color:never" then () // skip
3939
else if arg.startsWith("-") then println(s"bad option '$arg' was ignored")
40-
else if arg.endsWith(".tasty") then {
40+
else if arg.endsWith(".tasty") then
4141
val path = Paths.get(arg)
42-
if Files.exists(path) then printTasty(arg, Files.readAllBytes(path).nn)
43-
else println("File not found: " + arg)
44-
}
45-
else if arg.endsWith(".jar") then {
42+
if Files.exists(path) then
43+
printTasty(arg, Files.readAllBytes(path).nn)
44+
else
45+
println("File not found: " + arg)
46+
System.exit(1)
47+
else if arg.endsWith(".jar") then
4648
val jar = JarArchive.open(Path(arg), create = false)
4749
try
4850
for file <- jar.iterator() if file.name.endsWith(".tasty") do
4951
printTasty(s"$arg ${file.path}", file.toByteArray)
5052
finally jar.close()
51-
52-
}
53-
else println(s"Not a '.tasty' or '.jar' file: $arg")
53+
else
54+
println(s"Not a '.tasty' or '.jar' file: $arg")
55+
System.exit(1)
5456

5557
if printLastLine then
5658
println(line)

compiler/src/dotty/tools/io/Path.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ class Path private[io] (val jpath: JPath) {
170170
// returns the filename without the extension.
171171
def stripExtension: String = name stripSuffix ("." + extension)
172172
// returns the Path with the extension.
173-
def addExtension(ext: String): Path = new Path(jpath.resolveSibling(name + ext))
173+
def addExtension(ext: String): Path = new Path(jpath.resolveSibling(name + "." + ext))
174174
// changes the existing extension out for a new one, or adds it
175175
// if the current path has none.
176176
def changeExtension(ext: String): Path =

compiler/src/dotty/tools/scripting/Main.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,10 @@ object Main:
4343
// write a standalone jar to the script parent directory
4444
writeJarfile(outDir, scriptFile, scriptArgs, classpathEntries, mainClass)
4545
invokeFlag
46-
} match
47-
case Some(ex) =>
48-
println(ex.getMessage)
49-
sys.exit(1)
50-
case _ =>
46+
}.map {
47+
case ScriptingException(msg) => println(msg)
48+
case ex => ex.printStackTrace
49+
}.foreach(_ => System.exit(1))
5150

5251
private def writeJarfile(outDir: Path, scriptFile: File, scriptArgs:Array[String],
5352
classpathEntries:Seq[Path], mainClassName: String): Unit =

compiler/src/dotty/tools/scripting/StringDriver.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import dotty.tools.dotc.Driver
88
import dotty.tools.dotc.core.Contexts, Contexts.{ Context, ctx }
99
import dotty.tools.io.{ PlainDirectory, Directory, ClassPath }
1010
import Util.*
11+
import dotty.tools.dotc.util.SourceFile
1112

1213
class StringDriver(compilerArgs: Array[String], scalaSource: String) extends Driver:
1314
override def sourcesRequired: Boolean = false
@@ -18,11 +19,12 @@ class StringDriver(compilerArgs: Array[String], scalaSource: String) extends Dri
1819

1920
setup(compilerArgs, initCtx.fresh) match
2021
case Some((toCompile, rootCtx)) =>
21-
given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir,
22-
new PlainDirectory(Directory(outDir)))
22+
given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir, new PlainDirectory(Directory(outDir)))
2323

2424
val compiler = newCompiler
25-
compiler.newRun.compileFromStrings(List(scalaSource))
25+
26+
val source = SourceFile.virtual("expression", scalaSource)
27+
compiler.newRun.compileSources(List(source))
2628

2729
val output = ctx.settings.outputDir.value
2830
if ctx.reporter.hasErrors then
@@ -39,7 +41,7 @@ class StringDriver(compilerArgs: Array[String], scalaSource: String) extends Dri
3941
case Left(ex) => Some(ex)
4042
catch
4143
case e: java.lang.reflect.InvocationTargetException =>
42-
throw e.getCause
44+
Some(e.getCause)
4345
finally
4446
deleteFile(outDir.toFile)
4547
case None => None
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def compileError = prin
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def positiveTest = println("Hello World!")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def runtimeError = throw RuntimeException()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def main = prin
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def scriptPositive = println("Hello world!")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def scriptRuntimeError = throw RuntimeException()
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package dotty
2+
package tools
3+
package scripting
4+
5+
import java.io.File
6+
import java.nio.file.Files
7+
import org.junit.Test
8+
import org.junit.Assert.assertEquals
9+
10+
import ScriptTestEnv.*
11+
12+
13+
object BashExitCodeTests:
14+
def testFiles = scripts("/scripting")
15+
16+
/*
17+
* Compiles the class checking exit code
18+
*
19+
* @param testName name of the test containing the extension
20+
* @param expectedExitCode expected exit code from the output
21+
* @param deleteTempDir if temporary directory created for compilation output should delete in this scope
22+
*
23+
* @return Created temporary directory
24+
*/
25+
private def testCompilationExitCode(testName: String, expectedExitCode: Int, deleteTempDir: Boolean = false): File =
26+
val temporaryDir = Files.createTempDirectory(testName)
27+
assertTestExists(testName) { testFile =>
28+
try {
29+
val testFilePath = testFile.absPath
30+
val commandline = (Seq(scalacPath, "-d", temporaryDir, testFilePath)).mkString(" ")
31+
val (validTest, exitCode, _, _) = bashCommand(commandline)
32+
if verifyValid(validTest) then
33+
assertEquals(expectedExitCode, exitCode)
34+
} finally {
35+
if deleteTempDir then Util.deleteFile(temporaryDir.toFile)
36+
}
37+
}
38+
temporaryDir.toFile
39+
40+
/*
41+
* Runs the command and checks the exit code
42+
*
43+
* @param args arguments for command line
44+
* @param expectedExitCode expected exit code from the output
45+
*/
46+
private def testCommandExitCode(args: Seq[String], expectedExitCode: Int): Unit =
47+
val commandline = args.mkString(" ")
48+
val (validTest, exitCode, output, erroutput) = bashCommand(commandline)
49+
if verifyValid(validTest) then
50+
assertEquals(expectedExitCode, exitCode)
51+
52+
53+
/* Compiles and then runs the code checking the exit code
54+
*
55+
* @param testFileName name of the test
56+
* @param runExitCode expected exit code from the runner output
57+
*/
58+
private def testCompileAndRun(testFileName: String, runExitCode: Int): Unit =
59+
val outputDirectory = testCompilationExitCode(testFileName, 0)
60+
61+
def testRuntimeExitCode(className: String, expectedExitCode: Int): Unit =
62+
val testClassFile = outputDirectory.files.find(_.getName == s"$className.class")
63+
assert(testClassFile.isDefined)
64+
val commandline = (Seq(scalaPath, "-classpath", outputDirectory.getAbsolutePath, className)).mkString(" ")
65+
val (validTest, exitCode, o, e) = bashCommand(commandline)
66+
if verifyValid(validTest) then
67+
assertEquals(expectedExitCode, exitCode)
68+
69+
try {
70+
val generatedClassName = testFileName.split('.').head
71+
testRuntimeExitCode(generatedClassName, runExitCode)
72+
} finally {
73+
Util.deleteFile(outputDirectory)
74+
}
75+
76+
/*
77+
* Checks if scripting test resources contains test with given `testName`
78+
* And then runs function `test`
79+
*
80+
* @param testName name of the test containing the extension
81+
* @param test check to be run on found test file
82+
*/
83+
private def assertTestExists(testName: String)(test: File => Unit) =
84+
val file = testFiles.find(_.getName == testName)
85+
assert(file.isDefined)
86+
test(file.get)
87+
88+
class BashExitCodeTests:
89+
import BashExitCodeTests.*
90+
91+
@Test def verifyExitCodeOnCompileError: Unit =
92+
testCompilationExitCode("compileError.scala", 1, true)
93+
94+
@Test def verifyExitCodeOnRuntimeError: Unit =
95+
testCompileAndRun("runtimeError.scala", 1)
96+
97+
@Test def verifyExitCode: Unit =
98+
testCompileAndRun("positiveTest.scala", 0)
99+
100+
@Test def verifyExitCodeOnScriptError: Unit =
101+
assertTestExists("scriptRuntimeError.sc") { file =>
102+
testCommandExitCode(Seq(scalacPath, "-script", file.absPath), 1)
103+
}
104+
105+
@Test def verifyExitCodeOnScriptErrorCompiler: Unit =
106+
assertTestExists("scriptRuntimeError.sc") { file =>
107+
testCommandExitCode(Seq(scalacPath, "-script", file.absPath), 1)
108+
}
109+
110+
@Test def verifyExitCodeOnScript: Unit =
111+
assertTestExists("scriptPositive.sc") { file =>
112+
testCommandExitCode(Seq(scalaPath, file.absPath), 0)
113+
}
114+
115+
@Test def verifyExitCodeOnScriptCompiler: Unit =
116+
assertTestExists("scriptPositive.sc") { file =>
117+
testCommandExitCode(Seq(scalacPath, "-script", file.absPath), 0)
118+
}
119+
120+
@Test def verifyExitCodeOnDecompilation: Unit =
121+
val testName = "positiveTest"
122+
val outputDirectory = testCompilationExitCode(s"$testName.scala", 0)
123+
val tastyFilePath = outputDirectory.toPath.resolve(s"$testName.tasty").toString
124+
testCommandExitCode(Seq(scalacPath, "-decompile", tastyFilePath), 0)
125+
126+
@Test def verifyExitCodeOnPrintTasty: Unit =
127+
val testName = "positiveTest"
128+
val outputDirectory = testCompilationExitCode(s"$testName.scala", 0)
129+
val tastyFilePath = outputDirectory.toPath.resolve(s"$testName.tasty").toString
130+
testCommandExitCode(Seq(scalacPath, "-print-tasty", tastyFilePath), 0)
131+
132+
@Test def verifyExitCodeOnDecompilationFailure: Unit =
133+
val tempFile = Files.createTempFile("temp-file", ".class").toFile
134+
testCommandExitCode(Seq(scalacPath, "-decompile", "non-existing-file.tasty"), 1)
135+
testCommandExitCode(Seq(scalacPath, "-decompile", tempFile.absPath), 1)
136+
Util.deleteFile(tempFile)
137+
138+
@Test def verifyExitCodeOnPrintTastyFailure: Unit =
139+
val tempFile = Files.createTempFile("temp-file", ".class").toFile
140+
testCommandExitCode(Seq(scalacPath, "-print-tasty", "non-existing-file.tasty"), 1)
141+
testCommandExitCode(Seq(scalacPath, "-print-tasty", tempFile.absPath), 1)
142+
Util.deleteFile(tempFile)
143+
144+
@Test def verifyExitCodeOnExpressionCompileError: Unit =
145+
testCommandExitCode(Seq(scalaPath, "-e", "'prinln(10*10)'"), 1)
146+
147+
@Test def verifyExitCodeOnExpressionRuntimeError: Unit =
148+
testCommandExitCode(Seq(scalaPath, "-e", "'1/0'"), 1)
149+
150+
@Test def verifyExitCodeOnExpression: Unit =
151+
testCommandExitCode(Seq(scalaPath, "-e", "'println(10*10)'"), 0)
152+
153+
@Test def verifyExitCodeOnInfo: Unit =
154+
List("--help", "--version", "-Xplugin-list", "-Vphases").foreach { flag =>
155+
testCommandExitCode(Seq(scalaPath, flag), 0)
156+
}

0 commit comments

Comments
 (0)