Skip to content

Fix for 14106 cygwin failed tests #14113

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 13 commits into from
Dec 16, 2021
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
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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-introduces bootstrapping the compiler in "test_non_bootstrapped", which is annoying (can't see the results of running the tests in CI while the compiler fails to bootstrap).

If these tests need a bootstrapped compiler, can't they be in bootstrap-only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these tests need a bootstrapped compiler, can't they be in bootstrap-only?

I don't know enough about the test architecture to offer a reply, perhaps this is a question for @BarkingBad ?

./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