Skip to content
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

Refactor common scripting test code #14108

Closed
wants to merge 2 commits into from
Closed
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
147 changes: 24 additions & 123 deletions compiler/test/dotty/tools/scripting/BashScriptsTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,28 @@ package dotty
package tools
package scripting

import java.io.File
import java.nio.file.{Path, Paths, Files}
import scala.sys.process._

import org.junit.Test
import org.junit.Assert.assertEquals

import vulpix.TestConfiguration

import dotty.tools.dotc.config.Properties._
import ScriptTestEnv.*

/** Verifies correct handling of command line arguments by `dist/bin/scala` and `dist/bin/scalac`.
* +. arguments following a script path must be treated as script arguments
* +. preserve script command line arguments.
* +. prevent SCALA_OPTS in build environment from infecting tests, via 'SCALA_OPTS= ' prefix
* +. test scripts must not throw execptions or exit with nonzero.
*/
class BashScriptsTests:
// classpath tests managed by scripting.ClasspathTests.scala
def testFiles = scripts("/scripting")
lazy val argsfile = createArgsFile() // avoid problems caused by drive letter

printf("osname[%s]\n", osname)
printf("using JAVA_HOME=%s\n", javaHome)
printf("using SCALA_HOME=%s\n", scalaHome)
printf("first 5 PATH entries:\n%s\n", pathEntries.take(5).mkString("\n"))
printf("using JAVA_HOME=%s\n", envJavaHome)
printf("using SCALA_HOME=%s\n", envScalaHome)
printf("first 5 PATH entries:\n%s\n", adjustedPathEntries.take(5).mkString("\n"))
printf("scala path: [%s]\n", scalaPath)
printf("scalac path: [%s]\n", scalacPath)

Expand All @@ -44,9 +43,9 @@ class BashScriptsTests:

/* verify `dist/bin/scalac` non-interference with command line args following script name */
@Test def verifyScalacArgs =
val commandline = (Seq(scalacPath, "-script", showArgsScript) ++ testScriptArgs).mkString(" ")
val commandline = (Seq("SCALA_OPTS= ", scalacPath, "-script", showArgsScript) ++ testScriptArgs).mkString(" ")
val (validTest, exitCode, stdout, stderr) = bashCommand(commandline)
if validTest then
if verifyValid(validTest) then
var fail = false
printf("\n")
for (line, expect) <- stdout zip expectedOutput do
Expand All @@ -59,41 +58,41 @@ class BashScriptsTests:

/* verify `dist/bin/scala` with -J setting */
@Test def verifyScJProperty =
val commandline = Seq(scalaPath, "-J-Dkey=World", testFiles.find(_.getName == "envtest.sc").get.absPath).mkString(" ")
val commandline = Seq("SCALA_OPTS= ", scalaPath, "-J-Dkey=World", testFiles.find(_.getName == "envtest.sc").get.absPath).mkString(" ")
val (validTest, exitCode, stdout, stderr) = bashCommand(commandline)
assertEquals(stdout.mkString("/n"), "Hello World")

/* verify `dist/bin/scala` with -J setting */
@Test def verifyScalaJProperty =
val commandline = Seq(scalaPath, "-J-Dkey=World3", testFiles.find(_.getName == "envtest.scala").get.absPath).mkString(" ")
val commandline = Seq("SCALA_OPTS= ", scalaPath, "-J-Dkey=World3", testFiles.find(_.getName == "envtest.scala").get.absPath).mkString(" ")
val (validTest, exitCode, stdout, stderr) = bashCommand(commandline)
assertEquals(stdout.mkString("/n"), "Hello World3")

/* verify `dist/bin/scala` with -D setting */
@Test def verifyScDProperty =
val commandline = Seq(scalaPath, "-Dkey=World3", testFiles.find(_.getName == "envtest.sc").get.absPath).mkString(" ")
val commandline = Seq("SCALA_OPTS= ", scalaPath, "-Dkey=World3", testFiles.find(_.getName == "envtest.sc").get.absPath).mkString(" ")
val (validTest, exitCode, stdout, stderr) = bashCommand(commandline)
assertEquals(stdout.mkString("/n"), "Hello World3")

/* verify `dist/bin/scala` with -D setting */
@Test def verifyScalaDProperty =
val commandline = Seq(scalaPath, "-Dkey=World4", testFiles.find(_.getName == "envtest.scala").get.absPath).mkString(" ")
val commandline = Seq("SCALA_OPTS= ", scalaPath, "-Dkey=World4", testFiles.find(_.getName == "envtest.scala").get.absPath).mkString(" ")
val (validTest, exitCode, stdout, stderr) = bashCommand(commandline)
assertEquals(stdout.mkString("/n"), "Hello World4")

/* verify `dist/bin/scala` with -D setting */
@Test def saveAndRunWithDProperty =
val commandline = Seq(scalaPath, "-save", testFiles.find(_.getName == "envtest.scala").get.absPath).mkString(" ")
val commandline = Seq("SCALA_OPTS= ", scalaPath, "-save", testFiles.find(_.getName == "envtest.scala").get.absPath).mkString(" ")
val (_, _, _, _) = bashCommand(commandline)
val commandline2 = Seq(scalaPath, "-Dkey=World5", testFiles.find(_.getName == "envtest.jar").get.absPath).mkString(" ")
val commandline2 = Seq("SCALA_OPTS= ", scalaPath, "-Dkey=World5", testFiles.find(_.getName == "envtest.jar").get.absPath).mkString(" ")
val (validTest, exitCode, stdout, stderr) = bashCommand(commandline2)
assertEquals(stdout.mkString("/n"), "Hello World5")

/* verify `dist/bin/scala` non-interference with command line args following script name */
@Test def verifyScalaArgs =
val commandline = (Seq("SCALA_OPTS= ", scalaPath, showArgsScript) ++ testScriptArgs).mkString(" ")
val (validTest, exitCode, stdout, stderr) = bashCommand(commandline)
if validTest then
if verifyValid(validTest) then
var fail = false
printf("\n")
var mismatches = List.empty[(String, String)]
Expand All @@ -115,7 +114,7 @@ class BashScriptsTests:
printf("===> verify valid system property script.path is reported by script [%s]\n", scriptFile.getName)
printf("calling scriptFile: %s\n", scriptFile)
val (validTest, exitCode, stdout, stderr) = bashCommand(scriptFile.absPath)
if validTest then
if verifyValid(validTest) then
stdout.foreach { printf("stdout: [%s]\n", _) }
stderr.foreach { printf("stderr: [%s]\n", _) }
val valid = stdout.exists { _.endsWith(expected) }
Expand All @@ -131,113 +130,15 @@ class BashScriptsTests:
val envPairs = List(("SCALA_OPTS", s"@$argsfile"))
val (validTest, exitCode, stdout, stderr) = bashCommand(scriptFile.absPath, envPairs)
printf("stdout: %s\n", stdout.mkString("\n","\n",""))
if validTest then
if verifyValid(validTest) then
val expected = s"${workingDirectory.norm}"
val output = stdout.find( _.trim.startsWith("cwd") ).getOrElse("").dropWhile(_!=' ').trim
printf("output [%s]\n", output)
val cwdline = stdout.find( _.trim.startsWith("cwd") ).getOrElse("")
printf("cwdline [%s]\n", cwdline)
printf("expected[%s]\n", expected)
val valid = output.startsWith(expected)
val valid = cwdline.endsWith(expected)
if (!valid) then
stdout.foreach { printf("stdout[%s]\n", _) }
stderr.foreach { printf("stderr[%s]\n", _) }
if valid then printf(s"\n===> success: classpath begins with %s, as reported by [%s]\n", workingDirectory, scriptFile.getName)
assert(valid, s"script ${scriptFile.absPath} did not report valid java.class.path first entry")

def existingPath: String = envOrElse("PATH", "").norm
def adjustedPath = s"$javaHome/bin$psep$scalaHome/bin$psep$existingPath"
def pathEntries = adjustedPath.split(psep).toList

lazy val argsfile = createArgsFile() // avoid problems caused by drive letter
def createArgsFile(): String =
val utfCharset = java.nio.charset.StandardCharsets.UTF_8.name
val path = Files.createTempFile("scriptingTest", ".args")
val text = s"-classpath ${workingDirectory.absPath}"
Files.write(path, text.getBytes(utfCharset))
path.toFile.getAbsolutePath.norm

def fixHome(s: String): String =
s.startsWith("~") match {
case false => s
case true => s.replaceFirst("~", userHome)
}

extension(s: String) {
def toPath: Path = Paths.get(fixHome(s)) // .toAbsolutePath
def toFile: File = s.toPath.toFile
def absPath: String = s.toFile.absPath
def norm: String = s.replace('\\', '/') // bash expects forward slash
def isFile: Boolean = s.toFile.isFile
def exists: Boolean = s.toPath.toFile.exists
def name: String = s.toFile.getName
def dropExtension: String = s.reverse.dropWhile(_ != '.').drop(1).reverse
def parent(up: Int): String = s.norm.split("/").reverse.drop(up).reverse.mkString("/")
}

extension(p: Path) {
def listFiles: Seq[File] = p.toFile.listFiles.toList
def norm: String = p.normalize.toString.replace('\\', '/')
def name: String = p.toFile.getName
}

extension(f: File) {
def name = f.getName
def norm: String = f.toPath.normalize.norm
def absPath: String = f.getAbsolutePath.norm
}

lazy val psep: String = propOrElse("path.separator", "")
lazy val osname = propOrElse("os.name", "").toLowerCase

lazy val scalacPath = s"$workingDirectory/dist/target/pack/bin/scalac".norm
lazy val scalaPath = s"$workingDirectory/dist/target/pack/bin/scala".norm

// use optional working directory TEST_CWD, if defined
lazy val workingDirectory: String = envOrElse("TEST_CWD", userDir)

// use optional TEST_BASH if defined, otherwise, bash must be in PATH
lazy val bashExe: String = envOrElse("TEST_BASH", whichBash)

// test env SCALA_HOME is:
// dist/target/pack, if present
// else, SCALA_HOME if defined
// else, not defined
lazy val scalaHome =
if scalacPath.isFile then scalacPath.replaceAll("/bin/scalac", "")
else envOrElse("SCALA_HOME", "").norm

lazy val javaHome = whichJava.parent(2)

lazy val testEnvPairs = List(
("JAVA_HOME", javaHome),
("SCALA_HOME", scalaHome),
("PATH", adjustedPath),
).filter { case (name, valu) => valu.nonEmpty }

lazy val whichBash: String = whichExe("bash")
lazy val whichJava: String = whichExe("java")

def whichExe(basename: String): String =
val exeName = if (osname.toLowerCase.startsWith("windows")) s"$basename.exe" else basename
which(exeName)

def bashCommand(cmdstr: String, additionalEnvPairs: List[(String, String)] = Nil): (Boolean, Int, Seq[String], Seq[String]) = {
var (stdout, stderr) = (List.empty[String], List.empty[String])
if bashExe.toFile.exists then
val cmd = Seq(bashExe, "-c", cmdstr)
val envPairs = testEnvPairs ++ additionalEnvPairs
val proc = Process(cmd, None, envPairs *)
val exitVal = proc ! ProcessLogger (
(out: String) => stdout ::= out,
(err: String) => stderr ::= err
)
val validTest = exitVal == 0 && ! stderr.exists(_.contains("Permission denied"))
if ! validTest then
printf("\nunable to execute script, return value is %d\n", exitVal)
stderr.foreach { System.err.printf("stderr [%s]\n", _) }
(validTest, exitVal, stdout.reverse, stderr.reverse)
else
(false, -1, Nil, Nil)
}

def execCmd(command: String, options: String *): Seq[String] =
val cmd = (command :: options.toList).toSeq
for {
line <- Process(cmd).lazyLines_!
} yield line
115 changes: 3 additions & 112 deletions compiler/test/dotty/tools/scripting/ClasspathTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,12 @@ package dotty
package tools
package scripting

import java.io.File
import java.nio.file.{Files, Paths, Path}

import org.junit.Test

import vulpix.TestConfiguration

import scala.sys.process._
import scala.jdk.CollectionConverters._
import dotty.tools.dotc.config.Properties._
import ScriptTestEnv.*

/** Test java command line generated by bin/scala and bin/scalac */
class ClasspathTests:
val packBinDir = "dist/target/pack/bin"
val packLibDir = "dist/target/pack/lib"

def exists(scriptPath: Path): Boolean = Files.exists(scriptPath)
def packBinScalaExists:Boolean = exists(Paths.get(s"$packBinDir/scala"))

/*
* verify classpath reported by called script.
*/
Expand Down Expand Up @@ -64,7 +51,7 @@ class ClasspathTests:
*/
@Test def unglobClasspathVerifyTest = {
val testScriptName = "unglobClasspath.sc"
val testScript = scripts("/scripting").find { _.getName.matches(testScriptName) } match
val testScript = scripts("/scripting").find { _.name.matches(testScriptName) } match
case None => sys.error(s"test script not found: ${testScriptName}")
case Some(file) => file

Expand All @@ -81,105 +68,9 @@ class ClasspathTests:
// test script reports the classpath it sees
val scriptOutput = exec(cmd:_*)
val scriptCp = findTaggedLine("unglobbed classpath", scriptOutput)
printf("%s\n", scriptCp)
val classpathJars = scriptCp.split(psep).map { _.getName }.sorted.distinct
//classpathJars.foreach { printf("%s\n", _) }
assert(classpathJars.size > 1)
}


//////////////// end of tests ////////////////
lazy val cwd = Paths.get(".").toAbsolutePath
lazy val wildcardEntry = "dist/target/pack/lib/*"

def listJars(dir: String) =
val packlibDir = Paths.get(dir).toFile
if packlibDir.isDirectory then
packlibDir.listFiles.toList.map { _.getName }.filter { _.endsWith(".jar") }
else
Nil

import scala.jdk.CollectionConverters._
lazy val env:Map[String, String] = System.getenv.asScala.toMap

// script output expected as "<tag>: <value>"
def findTaggedLine(tag: String, lines: Seq[String]): String =
lines.find { _.startsWith(tag) } match
case None =>
lines.foreach { System.err.printf("line[%s]\n", _) }
sys.error(s"no $tag: found in script output")
case Some(cwd) => cwd.dropWhile( _ != ' ').trim // discard tag

def exec(cmd: String *): Seq[String] = Process(cmd).lazyLines_!.toList

def which(str:String) =
var out = ""
path.find { entry =>
val it = Paths.get(entry).toAbsolutePath
it.toFile.isDirectory && {
var testpath = s"$it/$str".norm
val test = Paths.get(testpath)
if test.toFile.exists then
out = testpath
true
else
val test = Paths.get(s"$it/$str.exe".norm)
if test.toFile.exists then
out = testpath
true
else
false
}
}

out

def bashExe = which("bash")
def unameExe = which("uname")
def path = envOrElse("PATH", "").split(psep).toList
def psep = sys.props("path.separator")

def cygwin = ostype == "cygwin"
def mingw = ostype == "mingw"
def msys = ostype == "msys"
def winshell = cygwin || mingw || msys

def ostypeFull = if unameExe.nonEmpty then exec(unameExe).mkString else ""
def ostype = ostypeFull.toLowerCase.takeWhile{ cc => cc >= 'a' && cc <='z' || cc >= 'A' && cc <= 'Z' }

extension(p:Path)
def relpath: Path = cwd.relativize(p)
def norm: String = p.toString.replace('\\', '/')

extension(path: String)
def getName: String = norm.replaceAll(".*/", "")

// Normalize path separator, convert relative path to absolute
def norm: String =
path.replace('\\', '/') match {
case s if s.secondChar == ":" => s // .drop(2) // path without drive letter
case s if s.startsWith("./") => s.drop(2)
case s => s
}

def parent: String = norm.replaceAll("/[^/]*$", "")

// convert to absolute path relative to cwd.
def absPath: String = norm match
case str if str.isAbsolute => norm
case _ => Paths.get(userDir, norm).toString.norm

def isDir: Boolean = Files.isDirectory(Paths.get(path))

def toUrl: String = Paths.get(absPath).toUri.toURL.toString

// Treat norm paths with a leading '/' as absolute.
// Windows java.io.File#isAbsolute treats them as relative.
def isAbsolute = path.norm.startsWith("/") || (isWin && path.secondChar == ":")
def secondChar: String = path.take(2).drop(1).mkString("")

extension (str: String) def dropExtension =
str.reverse.dropWhile(_ != '.').drop(1).reverse

//extension(f: File) def absPath =
//f.getAbsolutePath.replace('\\', '/')

Loading