Skip to content

Commit

Permalink
Fix for 14106 cygwin failed tests (#14113)
Browse files Browse the repository at this point in the history
* refactor common code from scripting tests; invalid tests fail by default

* dump stdout/stderr if BashScriptsTests.verifyScalaOpts fails

* fix for failing cygwin tests for #14106

* normalize scala and scalac paths; set proper SHELLOPTS in cygwin bashCommandline env

* improved detection of scalacPath and scalaPath; additional logging

* print warnings; remove unused code

* strip ansi colors from bash command line output, to fix windows tests

* dist/pack before sbt test in test_windows_full and test_non_bootstrapped

* squeeze redundancy from env-var-setting tests, add more log messages

* further reduced redundancy; additional log messages

* remove trailing java from JAVA_HOME value; shorten comand lines with relpath

* Update BashScriptsTests.scala

remove duplicate

* Update BashScriptsTests.scala

Fix not-updating property

Co-authored-by: Andrzej Ratajczak <[email protected]>
  • Loading branch information
philwalk and BarkingBad authored Dec 16, 2021
1 parent 3f7a7ae commit 0857285
Show file tree
Hide file tree
Showing 8 changed files with 483 additions and 321 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:

- name: Test
run: |
./project/scripts/sbt ";compile ;test"
./project/scripts/sbt ";dist/pack; compile ;test"
./project/scripts/cmdTests
test:
Expand Down Expand Up @@ -181,7 +181,7 @@ jobs:
uses: actions/checkout@v2

- name: Test
run: sbt ";scala3-bootstrapped/compile ;scala3-bootstrapped/test"
run: sbt ";dist/pack ;scala3-bootstrapped/compile ;scala3-bootstrapped/test"
shell: cmd

- name: Scala.js Test
Expand Down
2 changes: 1 addition & 1 deletion compiler/test-resources/scripting/classpathReport.sc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env -S bin/scala -classpath 'dist/target/pack/lib/*'
#!bin/scala -classpath 'dist/target/pack/lib/*'

import java.nio.file.Paths

Expand Down
2 changes: 1 addition & 1 deletion compiler/test-resources/scripting/unglobClasspath.sc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env -S bin/scala -classpath 'dist/target/pack/lib/*'
#!bin/scala -classpath 'dist/target/pack/lib/*'

// won't compile unless the hashbang line sets classpath
import org.jline.terminal.Terminal
Expand Down
257 changes: 102 additions & 155 deletions compiler/test/dotty/tools/scripting/BashScriptsTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,38 @@ 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.{Test, AfterClass}
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
object BashScriptsTests:
lazy val argsfile = createArgsFile() // avoid problems caused by drive letter
def testFiles = scripts("/scripting")

@AfterClass def cleanup: Unit = {
val af = argsfile.toFile
if (af.exists) {
af.delete()
}
}
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("uname[%s]\n", ostypeFull)
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)

lazy val expectedOutput = List(
val expectedOutput = List(
"arg 0:[a]",
"arg 1:[b]",
"arg 2:[c]",
Expand All @@ -37,16 +42,88 @@ class BashScriptsTests:
"arg 5:[-script]",
"arg 6:[-debug]",
)
lazy val testScriptArgs = Seq(
val testScriptArgs = Seq(
"a", "b", "c", "-repl", "-run", "-script", "-debug"
)
val showArgsScript = testFiles.find(_.getName == "showArgs.sc").get.absPath

def testFile(name: String): String =
val file = testFiles.find(_.getName == name) match {
case Some(f) =>
val ff = f.absPath
printf("test file [%s] is [%s]\n", name, ff)
ff
case None =>
printf("test file [%s] not found!\n", name)
name.absPath
}
file

val Seq(envtestSc, envtestScala) = Seq("envtest.sc", "envtest.scala").map { testFile(_) }

// create command line with given options, execute specified script, return stdout
def callScript(tag: String, script: String, keyPre: String): String =
val keyArg = s"$keyPre=$tag"
printf("pass tag [%s] via [%s] to script [%s]\n", tag, keyArg, script)
val cmd: String = Seq("SCALA_OPTS= ", scalaPath, keyArg, script).mkString(" ")
printf("cmd: [%s]\n", cmd)
val (validTest, exitCode, stdout, stderr) = bashCommand(cmd)
stderr.filter { !_.contains("Inappropriate ioctl") }.foreach { System.err.printf("stderr [%s]\n", _) }
stdout.mkString("\n")


class BashScriptsTests:
import BashScriptsTests.*
// classpath tests managed by scripting.ClasspathTests.scala

////////////////////////// begin tests //////////////////////

/* verify that `dist/bin/scala` correctly passes args to the jvm via -J-D for script envtest.sc */
@Test def verifyScJProperty =
val tag = "World1"
val stdout = callScript(tag, envtestSc, s"-J-Dkey")
assertEquals( s"Hello $tag", stdout)

/* verify that `dist/bin/scala` correctly passes args to the jvm via -J-D for script envtest.scala */
@Test def verifyScalaJProperty =
val tag = "World2"
val stdout = callScript(tag, envtestScala, s"-J-Dkey")
assertEquals(s"Hello $tag", stdout)

/* verify that `dist/bin/scala` can set system properties via -D for envtest.sc */
@Test def verifyScDProperty =
val tag = "World3"
val stdout = callScript(tag, envtestSc, s"-Dkey")
assertEquals(s"Hello $tag", stdout)

/* verify that `dist/bin/scala` can set system properties via -D for envtest.scala */
@Test def verifyScalaDProperty =
val tag = "World4"
val stdout = callScript(tag, envtestScala, s"-Dkey")
assertEquals(s"Hello $tag", stdout)

/* verify that `dist/bin/scala` can set system properties via -D when executing compiled script via -jar envtest.jar */
@Test def saveAndRunWithDProperty =
val commandline = Seq("SCALA_OPTS= ", scalaPath.relpath, "-save", envtestScala.relpath).mkString(" ")
val (_, _, _, _) = bashCommand(commandline) // compile jar, discard output
val testJar = testFile("envtest.jar") // jar is created by the previous bashCommand()
if (testJar.isFile){
printf("compiled envtest.scala to %s\n", testJar.norm)
} else {
sys.error(s"error: unable to compile envtest.scala to ${testJar.norm}")
}

val tag = "World5"
val commandline2 = Seq("SCALA_OPTS= ", scalaPath.relpath, s"-Dkey=$tag", testJar.relpath)
printf("cmd[%s]\n", commandline2.mkString(" "))
val (validTest, exitCode, stdout, stderr) = bashCommand(commandline2.mkString(" "))
assertEquals(s"Hello $tag", stdout.mkString("/n"))

/* 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 @@ -57,46 +134,13 @@ class BashScriptsTests:
if fail then
assert(stdout == expectedOutput)

/* 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 (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 (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 (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 (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 (_, _, _, _) = bashCommand(commandline)
val commandline2 = Seq(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)]
for (line, expect) <- stdout zip expectedOutput do
printf("expected: %-17s\nactual : %s\n", expect, line)
if line != expect then
Expand All @@ -115,7 +159,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 +175,16 @@ 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)
// stdout might be polluted with an ANSI color prefix, so be careful
val cwdline = stdout.find( _.trim.matches(".*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
Loading

0 comments on commit 0857285

Please sign in to comment.