Skip to content

Cleanup BashExitCodeTests #15657

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

229 changes: 53 additions & 176 deletions compiler/test/dotty/tools/scripting/BashExitCodeTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,187 +4,64 @@ package scripting

import scala.language.unsafeNulls

import java.io.File
import java.nio.file.Files
import org.junit.Test
import java.nio.file.Files, java.nio.charset.StandardCharsets.UTF_8
import org.junit.{ After, 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.absPath, 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.absPath, className)).mkString(" ")
val (validTest, exitCode, _, _) = 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, _, _) = 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 =
val file = temporaryDir.files.find(_.getName == s"$className.tasty")
assert(file.isDefined)
file.get.absPath

@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))
private var myTmpDir: String | Null = null
private lazy val tmpDir = { myTmpDir = Files.createTempDirectory("exit-code-tests").toFile.absPath; myTmpDir }
@After def cleanup(): Unit = if myTmpDir != null then io.Directory(myTmpDir).deleteRecursively()

@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)
}
/** Verify the exit code of running `cmd args*`. */
def verifyExit(cmd: String, args: String*)(expectedExitCode: Int): Unit =
val (validTest, exitCode, stdout, stderr) = bashCommand((cmd +: args).mkString(" "))
if verifyValid(validTest) then
assertEquals({
def pp(n: String, ss: Seq[String]) = if ss.isEmpty then "" else s"\nstd$n:${ss.map("\n " + _).mkString}"
s"expected $expectedExitCode but got $exitCode${pp("out", stdout)}${pp("err", stderr)}"
}, expectedExitCode, exitCode)

// Helpers for running scala, scalac, and scalac without the the output directory ("raw")
def scala(args: String*) = verifyExit(scalaPath, args*)
def scalacRaw(args: String*) = verifyExit(scalacPath, args*)
def scalac(args: String*) = scalacRaw(("-d" +: tmpDir +: args)*)

/** The path to the test file for this class. */
def f(body: String, suffix: String = ".scala"): String =
Files.write(Files.createTempFile(tmpDir.toPath, getClass.getSimpleName, suffix), body.getBytes(UTF_8)).absPath

@Test def neg = scalac(f("@main def Test = prin"))(1)
@Test def run = scalac(f("@main def Test = ???"))(0) & scala("-classpath", tmpDir, "Test")(1)
@Test def pos = scalac(f("@main def Test = ()"))(0) & scala("-classpath", tmpDir, "Test")(0)

@Test def runNeg = scala(f("@main def Test = prin", ".sc"))(1)
@Test def runRun = scala(f("@main def Test = ???", ".sc"))(1)
@Test def runPos = scala(f("@main def Test = ()", ".sc"))(0)

@Test def scNeg = scalac("-script", f("@main def Test = prin", ".sc"))(1)
@Test def scRun = scalac("-script", f("@main def Test = ???", ".sc"))(1)
@Test def scPos = scalac("-script", f("@main def Test = ()", ".sc"))(0)

@Test def evalNeg = scala("-e", "'prinln(10*10)'")(1)
@Test def evalRun = scala("-e", "'1/0'")(1)
@Test def evalPos = scala("-e", "'println(10*10)'")(0)

@Test def decompileNeg = scalac("-decompile", "non-existing-file.tasty")(1)
@Test def decompilePos = scalac(f("class Test"))(0) & scalacRaw("-decompile", s"$tmpDir/Test.tasty")(0)

@Test def printTastyNeg = scalac("-print-tasty", "non-existing-file.tasty")(1)
@Test def printTastyPos = scalac(f("class Test"))(0) & scalacRaw("-print-tasty", s"$tmpDir/Test.tasty")(0)

@Test def help = scala("--help")(0)
@Test def version = scala("--version")(0)
@Test def xPluginList = scala("-Xplugin-list")(0)
@Test def vPhases = scala("-Vphases")(0)

/** A utility for running two commands in a row, like you do in bash. */
extension (inline u1: Unit) inline def & (inline u2: Unit): Unit = { u1; u2 }
end BashExitCodeTests