diff --git a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala index eb316524cf..629a8d27bb 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala @@ -1907,74 +1907,78 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg } test("BSP respects JAVA_HOME") { - val javaVersion = "22" - val inputs = TestInputs(os.rel / "check-java.sc" -> - s"""assert(System.getProperty("java.version").startsWith("$javaVersion")) - |println(System.getProperty("java.home"))""".stripMargin) - inputs.fromRoot { root => - os.proc(TestUtil.cli, "bloop", "exit", "--power").call(cwd = root) - val java22Home = - os.Path( - os.proc(TestUtil.cs, "java-home", "--jvm", s"zulu:$javaVersion").call().out.trim(), - os.pwd - ) - os.proc(TestUtil.cli, "setup-ide", "check-java.sc") - .call(cwd = root, env = Map("JAVA_HOME" -> java22Home.toString())) - val ideOptionsPath = root / Constants.workspaceDirName / "ide-options-v2.json" - expect(ideOptionsPath.toNIO.toFile.exists()) - val ideEnvsPath = root / Constants.workspaceDirName / "ide-envs.json" - expect(ideEnvsPath.toNIO.toFile.exists()) - val jsonOptions = List("--json-options", ideOptionsPath.toString) - val envOptions = List("--envs-file", ideEnvsPath.toString) - withBsp(inputs, Seq("."), bspOptions = jsonOptions ++ envOptions, reuseRoot = Some(root)) { - (_, _, remoteServer) => - async { - val targets = await(remoteServer.workspaceBuildTargets().asScala) - .getTargets.asScala - .filter(!_.getId.getUri.contains("-test")) - .map(_.getId()) - val compileResult = - await(remoteServer.buildTargetCompile(new b.CompileParams(targets.asJava)).asScala) - expect(compileResult.getStatusCode == b.StatusCode.OK) - val runResult = - await(remoteServer.buildTargetRun(new b.RunParams(targets.head)).asScala) - expect(runResult.getStatusCode == b.StatusCode.OK) - } + TestUtil.retryOnCi() { + val javaVersion = "22" + val inputs = TestInputs(os.rel / "check-java.sc" -> + s"""assert(System.getProperty("java.version").startsWith("$javaVersion")) + |println(System.getProperty("java.home"))""".stripMargin) + inputs.fromRoot { root => + os.proc(TestUtil.cli, "bloop", "exit", "--power").call(cwd = root) + val java22Home = + os.Path( + os.proc(TestUtil.cs, "java-home", "--jvm", s"zulu:$javaVersion").call().out.trim(), + os.pwd + ) + os.proc(TestUtil.cli, "setup-ide", "check-java.sc") + .call(cwd = root, env = Map("JAVA_HOME" -> java22Home.toString())) + val ideOptionsPath = root / Constants.workspaceDirName / "ide-options-v2.json" + expect(ideOptionsPath.toNIO.toFile.exists()) + val ideEnvsPath = root / Constants.workspaceDirName / "ide-envs.json" + expect(ideEnvsPath.toNIO.toFile.exists()) + val jsonOptions = List("--json-options", ideOptionsPath.toString) + val envOptions = List("--envs-file", ideEnvsPath.toString) + withBsp(inputs, Seq("."), bspOptions = jsonOptions ++ envOptions, reuseRoot = Some(root)) { + (_, _, remoteServer) => + async { + val targets = await(remoteServer.workspaceBuildTargets().asScala) + .getTargets.asScala + .filter(!_.getId.getUri.contains("-test")) + .map(_.getId()) + val compileResult = + await(remoteServer.buildTargetCompile(new b.CompileParams(targets.asJava)).asScala) + expect(compileResult.getStatusCode == b.StatusCode.OK) + val runResult = + await(remoteServer.buildTargetRun(new b.RunParams(targets.head)).asScala) + expect(runResult.getStatusCode == b.StatusCode.OK) + } + } } } } test("BSP respects --java-home") { - val javaVersion = "22" - val inputs = TestInputs(os.rel / "check-java.sc" -> - s"""assert(System.getProperty("java.version").startsWith("$javaVersion")) - |println(System.getProperty("java.home"))""".stripMargin) - inputs.fromRoot { root => - os.proc(TestUtil.cli, "bloop", "exit", "--power").call(cwd = root) - val java22Home = - os.Path( - os.proc(TestUtil.cs, "java-home", "--jvm", s"zulu:$javaVersion").call().out.trim(), - os.pwd - ) - os.proc(TestUtil.cli, "setup-ide", "check-java.sc", "--java-home", java22Home.toString()) - .call(cwd = root) - val ideOptionsPath = root / Constants.workspaceDirName / "ide-options-v2.json" - expect(ideOptionsPath.toNIO.toFile.exists()) - val jsonOptions = List("--json-options", ideOptionsPath.toString) - withBsp(inputs, Seq("."), bspOptions = jsonOptions, reuseRoot = Some(root)) { - (_, _, remoteServer) => - async { - val targets = await(remoteServer.workspaceBuildTargets().asScala) - .getTargets.asScala - .filter(!_.getId.getUri.contains("-test")) - .map(_.getId()) - val compileResult = - await(remoteServer.buildTargetCompile(new b.CompileParams(targets.asJava)).asScala) - expect(compileResult.getStatusCode == b.StatusCode.OK) - val runResult = - await(remoteServer.buildTargetRun(new b.RunParams(targets.head)).asScala) - expect(runResult.getStatusCode == b.StatusCode.OK) - } + TestUtil.retryOnCi() { + val javaVersion = "22" + val inputs = TestInputs(os.rel / "check-java.sc" -> + s"""assert(System.getProperty("java.version").startsWith("$javaVersion")) + |println(System.getProperty("java.home"))""".stripMargin) + inputs.fromRoot { root => + os.proc(TestUtil.cli, "bloop", "exit", "--power").call(cwd = root) + val java22Home = + os.Path( + os.proc(TestUtil.cs, "java-home", "--jvm", s"zulu:$javaVersion").call().out.trim(), + os.pwd + ) + os.proc(TestUtil.cli, "setup-ide", "check-java.sc", "--java-home", java22Home.toString()) + .call(cwd = root) + val ideOptionsPath = root / Constants.workspaceDirName / "ide-options-v2.json" + expect(ideOptionsPath.toNIO.toFile.exists()) + val jsonOptions = List("--json-options", ideOptionsPath.toString) + withBsp(inputs, Seq("."), bspOptions = jsonOptions, reuseRoot = Some(root)) { + (_, _, remoteServer) => + async { + val targets = await(remoteServer.workspaceBuildTargets().asScala) + .getTargets.asScala + .filter(!_.getId.getUri.contains("-test")) + .map(_.getId()) + val compileResult = + await(remoteServer.buildTargetCompile(new b.CompileParams(targets.asJava)).asScala) + expect(compileResult.getStatusCode == b.StatusCode.OK) + val runResult = + await(remoteServer.buildTargetRun(new b.RunParams(targets.head)).asScala) + expect(runResult.getStatusCode == b.StatusCode.OK) + } + } } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/ConfigTests.scala b/modules/integration/src/test/scala/scala/cli/integration/ConfigTests.scala index 327f161350..6dfd22b6ef 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/ConfigTests.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/ConfigTests.scala @@ -175,87 +175,89 @@ class ConfigTests extends ScalaCliSuite { if (TestUtil.isNativeCli) test(s"Create a PGP key with external JVM process, java version too low") { - TestInputs().fromRoot { root => - val configFile = { - val dir = root / "config" - os.makeDir.all(dir, perms = if (Properties.isWin) null else "rwx------") - dir / "config.json" - } - - val java8Home = - os.Path(os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:8").call().out.trim(), os.pwd) - - val extraEnv = Map( - "JAVA_HOME" -> java8Home.toString, - "PATH" -> ((java8Home / "bin").toString + File.pathSeparator + System.getenv("PATH")), - "SCALA_CLI_CONFIG" -> configFile.toString - ) - - val pgpCreated = os.proc( - TestUtil.cli, - "--power", - "config", - "--create-pgp-key", - "--email", - "alex@alex.me", - "--pgp-password", - "none", - "--force-jvm-signing-cli", - "-v", - "-v", - "-v" - ) - .call(cwd = root, env = extraEnv, mergeErrIntoOut = true) + TestUtil.retryOnCi() { + TestInputs().fromRoot { root => + val configFile = { + val dir = root / "config" + os.makeDir.all(dir, perms = if (Properties.isWin) null else "rwx------") + dir / "config.json" + } + + val java8Home = + os.Path(os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:8").call().out.trim(), os.pwd) + + val extraEnv = Map( + "JAVA_HOME" -> java8Home.toString, + "PATH" -> ((java8Home / "bin").toString + File.pathSeparator + System.getenv("PATH")), + "SCALA_CLI_CONFIG" -> configFile.toString + ) + + val pgpCreated = os.proc( + TestUtil.cli, + "--power", + "config", + "--create-pgp-key", + "--email", + "alex@alex.me", + "--pgp-password", + "none", + "--force-jvm-signing-cli", + "-v", + "-v", + "-v" + ) + .call(cwd = root, env = extraEnv, mergeErrIntoOut = true) - val javaCommandLine = pgpCreated.out.text() - .linesIterator - .dropWhile(!_.equals(" Running")).slice(1, 2) - .toSeq + val javaCommandLine = pgpCreated.out.text() + .linesIterator + .dropWhile(!_.equals(" Running")).slice(1, 2) + .toSeq - expect(javaCommandLine.nonEmpty) - expect(javaCommandLine.head.contains("17")) + expect(javaCommandLine.nonEmpty) + expect(javaCommandLine.head.contains("17")) - val passwordInConfig = os.proc(TestUtil.cli, "--power", "config", "pgp.secret-key-password") - .call(cwd = root, env = extraEnv, stderr = os.Pipe) - expect(passwordInConfig.out.text().isEmpty()) + val passwordInConfig = + os.proc(TestUtil.cli, "--power", "config", "pgp.secret-key-password") + .call(cwd = root, env = extraEnv, stderr = os.Pipe) + expect(passwordInConfig.out.text().isEmpty()) - val secretKey = os.proc(TestUtil.cli, "--power", "config", "pgp.secret-key") - .call(cwd = root, env = extraEnv, stderr = os.Pipe) - .out.trim() - val rawPublicKey = - os.proc(TestUtil.cli, "--power", "config", "pgp.public-key", "--password-value") + val secretKey = os.proc(TestUtil.cli, "--power", "config", "pgp.secret-key") .call(cwd = root, env = extraEnv, stderr = os.Pipe) .out.trim() - - val tmpFile = root / "test-file" - val tmpFileAsc = root / "test-file.asc" - os.write(tmpFile, "Hello") - - val q = "\"" - - def maybeEscape(arg: String): String = - if (Properties.isWin) q + arg + q - else arg - - os.proc( - TestUtil.cli, - "--power", - "pgp", - "sign", - "--secret-key", - maybeEscape(secretKey), - tmpFile - ).call(cwd = root, stdin = os.Inherit, stdout = os.Inherit, env = extraEnv) - - val pubKeyFile = root / "key.pub" - os.write(pubKeyFile, rawPublicKey) - val verifyResult = - os.proc(TestUtil.cli, "--power", "pgp", "verify", "--key", pubKeyFile, tmpFileAsc) - .call(cwd = root, env = extraEnv, mergeErrIntoOut = true) - - expect(verifyResult.out.text().contains("valid signature")) + val rawPublicKey = + os.proc(TestUtil.cli, "--power", "config", "pgp.public-key", "--password-value") + .call(cwd = root, env = extraEnv, stderr = os.Pipe) + .out.trim() + + val tmpFile = root / "test-file" + val tmpFileAsc = root / "test-file.asc" + os.write(tmpFile, "Hello") + + val q = "\"" + + def maybeEscape(arg: String): String = + if (Properties.isWin) q + arg + q + else arg + + os.proc( + TestUtil.cli, + "--power", + "pgp", + "sign", + "--secret-key", + maybeEscape(secretKey), + tmpFile + ).call(cwd = root, stdin = os.Inherit, stdout = os.Inherit, env = extraEnv) + + val pubKeyFile = root / "key.pub" + os.write(pubKeyFile, rawPublicKey) + val verifyResult = + os.proc(TestUtil.cli, "--power", "pgp", "verify", "--key", pubKeyFile, tmpFileAsc) + .call(cwd = root, env = extraEnv, mergeErrIntoOut = true) + + expect(verifyResult.out.text().contains("valid signature")) + } } - } def createDefaultPgpKeyTest(pgpPasswordOption: String): Unit = { diff --git a/modules/integration/src/test/scala/scala/cli/integration/HadoopTests.scala b/modules/integration/src/test/scala/scala/cli/integration/HadoopTests.scala index aab866cf82..61ce9166f8 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/HadoopTests.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/HadoopTests.scala @@ -6,96 +6,98 @@ class HadoopTests extends munit.FunSuite { protected lazy val extraOptions: Seq[String] = TestUtil.extraOptions test("simple map-reduce") { - val inputs = TestInputs( - os.rel / "WordCount.java" -> - """//> using dep "org.apache.hadoop:hadoop-client-api:3.3.3" - | - |// from https://hadoop.apache.org/docs/r3.3.3/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html - | - |package foo; - | - |import java.io.IOException; - |import java.util.StringTokenizer; - | - |import org.apache.hadoop.conf.Configuration; - |import org.apache.hadoop.fs.Path; - |import org.apache.hadoop.io.IntWritable; - |import org.apache.hadoop.io.Text; - |import org.apache.hadoop.mapreduce.Job; - |import org.apache.hadoop.mapreduce.Mapper; - |import org.apache.hadoop.mapreduce.Reducer; - |import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; - |import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; - | - |public class WordCount { - | - | public static class TokenizerMapper - | extends Mapper{ - | - | private final static IntWritable one = new IntWritable(1); - | private Text word = new Text(); - | - | public void map(Object key, Text value, Context context - | ) throws IOException, InterruptedException { - | StringTokenizer itr = new StringTokenizer(value.toString()); - | while (itr.hasMoreTokens()) { - | word.set(itr.nextToken()); - | context.write(word, one); - | } - | } - | } - | - | public static class IntSumReducer - | extends Reducer { - | private IntWritable result = new IntWritable(); - | - | public void reduce(Text key, Iterable values, - | Context context - | ) throws IOException, InterruptedException { - | int sum = 0; - | for (IntWritable val : values) { - | sum += val.get(); - | } - | result.set(sum); - | context.write(key, result); - | } - | } - | - | public static void main(String[] args) throws Exception { - | Configuration conf = new Configuration(); - | Job job = Job.getInstance(conf, "word count"); - | job.setJarByClass(WordCount.class); - | job.setMapperClass(TokenizerMapper.class); - | job.setCombinerClass(IntSumReducer.class); - | job.setReducerClass(IntSumReducer.class); - | job.setOutputKeyClass(Text.class); - | job.setOutputValueClass(IntWritable.class); - | FileInputFormat.addInputPath(job, new Path(args[0])); - | FileOutputFormat.setOutputPath(job, new Path(args[1])); - | System.exit(job.waitForCompletion(true) ? 0 : 1); - | } - |} - |""".stripMargin - ) - inputs.fromRoot { root => - val res = os.proc( - TestUtil.cli, - "--power", - "run", - TestUtil.extraOptions, - ".", - "--hadoop", - "--command", - "--scratch-dir", - "tmp", - "--", - "foo" + TestUtil.retryOnCi() { + val inputs = TestInputs( + os.rel / "WordCount.java" -> + """//> using dep "org.apache.hadoop:hadoop-client-api:3.3.3" + | + |// from https://hadoop.apache.org/docs/r3.3.3/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html + | + |package foo; + | + |import java.io.IOException; + |import java.util.StringTokenizer; + | + |import org.apache.hadoop.conf.Configuration; + |import org.apache.hadoop.fs.Path; + |import org.apache.hadoop.io.IntWritable; + |import org.apache.hadoop.io.Text; + |import org.apache.hadoop.mapreduce.Job; + |import org.apache.hadoop.mapreduce.Mapper; + |import org.apache.hadoop.mapreduce.Reducer; + |import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; + |import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; + | + |public class WordCount { + | + | public static class TokenizerMapper + | extends Mapper{ + | + | private final static IntWritable one = new IntWritable(1); + | private Text word = new Text(); + | + | public void map(Object key, Text value, Context context + | ) throws IOException, InterruptedException { + | StringTokenizer itr = new StringTokenizer(value.toString()); + | while (itr.hasMoreTokens()) { + | word.set(itr.nextToken()); + | context.write(word, one); + | } + | } + | } + | + | public static class IntSumReducer + | extends Reducer { + | private IntWritable result = new IntWritable(); + | + | public void reduce(Text key, Iterable values, + | Context context + | ) throws IOException, InterruptedException { + | int sum = 0; + | for (IntWritable val : values) { + | sum += val.get(); + | } + | result.set(sum); + | context.write(key, result); + | } + | } + | + | public static void main(String[] args) throws Exception { + | Configuration conf = new Configuration(); + | Job job = Job.getInstance(conf, "word count"); + | job.setJarByClass(WordCount.class); + | job.setMapperClass(TokenizerMapper.class); + | job.setCombinerClass(IntSumReducer.class); + | job.setReducerClass(IntSumReducer.class); + | job.setOutputKeyClass(Text.class); + | job.setOutputValueClass(IntWritable.class); + | FileInputFormat.addInputPath(job, new Path(args[0])); + | FileOutputFormat.setOutputPath(job, new Path(args[1])); + | System.exit(job.waitForCompletion(true) ? 0 : 1); + | } + |} + |""".stripMargin ) - .call(cwd = root) - val command = res.out.lines() - pprint.err.log(command) - expect(command.take(2) == Seq("hadoop", "jar")) - expect(command.takeRight(2) == Seq("foo.WordCount", "foo")) + inputs.fromRoot { root => + val res = os.proc( + TestUtil.cli, + "--power", + "run", + TestUtil.extraOptions, + ".", + "--hadoop", + "--command", + "--scratch-dir", + "tmp", + "--", + "foo" + ) + .call(cwd = root) + val command = res.out.lines() + pprint.err.log(command) + expect(command.take(2) == Seq("hadoop", "jar")) + expect(command.takeRight(2) == Seq("foo.WordCount", "foo")) + } } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala index 23c5c331f8..46a7c819a2 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala @@ -526,51 +526,53 @@ abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersio } test("assembly") { - val fileName = "simple.sc" - val message = "Hello" - val inputs = TestInputs( - os.rel / fileName -> - s"""//> using dep "org.typelevel::cats-kernel:2.6.1" - |import cats.kernel._ - |val m = Monoid.instance[String]("", (a, b) => a + b) - |val msgStuff = m.combineAll(List("$message", "", "")) - |println(msgStuff) - |""".stripMargin - ) - val launcherName = fileName.stripSuffix(".sc") + ".jar" - inputs.fromRoot { root => - os.proc(TestUtil.cli, "--power", "package", extraOptions, "--assembly", fileName).call( - cwd = root, - stdin = os.Inherit, - stdout = os.Inherit + TestUtil.retryOnCi() { + val fileName = "simple.sc" + val message = "Hello" + val inputs = TestInputs( + os.rel / fileName -> + s"""//> using dep "org.typelevel::cats-kernel:2.6.1" + |import cats.kernel._ + |val m = Monoid.instance[String]("", (a, b) => a + b) + |val msgStuff = m.combineAll(List("$message", "", "")) + |println(msgStuff) + |""".stripMargin ) + val launcherName = fileName.stripSuffix(".sc") + ".jar" + inputs.fromRoot { root => + os.proc(TestUtil.cli, "--power", "package", extraOptions, "--assembly", fileName).call( + cwd = root, + stdin = os.Inherit, + stdout = os.Inherit + ) - val launcher = root / launcherName - expect(os.isFile(launcher)) - - var zf: ZipFile = null - try { - zf = new ZipFile(launcher.toIO) - expect(zf.getEntry("cats/kernel/Monoid.class") != null) - } - finally if (zf != null) zf.close() + val launcher = root / launcherName + expect(os.isFile(launcher)) - val runnableLauncher = - if (Properties.isWin) { - val bat = root / "assembly.bat" - os.copy(launcher, bat) - bat - } - else { - expect(Files.isExecutable(launcher.toNIO)) - launcher + var zf: ZipFile = null + try { + zf = new ZipFile(launcher.toIO) + expect(zf.getEntry("cats/kernel/Monoid.class") != null) } - val runnableLauncherSize = os.size(runnableLauncher) + finally if (zf != null) zf.close() - val output = TestUtil.maybeUseBash(runnableLauncher.toString)(cwd = root).out.trim() - val maxRunnableLauncherSize = 1024 * 1024 * 12 // should be smaller than 12MB - expect(output == message) - expect(runnableLauncherSize < maxRunnableLauncherSize) + val runnableLauncher = + if (Properties.isWin) { + val bat = root / "assembly.bat" + os.copy(launcher, bat) + bat + } + else { + expect(Files.isExecutable(launcher.toNIO)) + launcher + } + val runnableLauncherSize = os.size(runnableLauncher) + + val output = TestUtil.maybeUseBash(runnableLauncher.toString)(cwd = root).out.trim() + val maxRunnableLauncherSize = 1024 * 1024 * 12 // should be smaller than 12MB + expect(output == message) + expect(runnableLauncherSize < maxRunnableLauncherSize) + } } } @@ -1203,34 +1205,36 @@ abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersio if (actualScalaVersion.startsWith("2")) { test("resolution is kept for assemblies with provided spark deps (packaging.provided)") { - val msg = "Hello" - val inputPath = os.rel / "Hello.scala" - TestInputs( - inputPath -> - s"""//> using lib org.apache.spark::spark-sql:3.3.2 - |//> using lib org.apache.spark::spark-hive:3.3.2 - |//> using lib org.apache.spark::spark-sql-kafka-0-10:3.3.2 - |//> using packaging.packageType assembly - |//> using packaging.provided org.apache.spark::spark-sql - |//> using packaging.provided org.apache.spark::spark-hive - | - |object Main extends App { - | println("$msg") - |} - |""".stripMargin - ).fromRoot { root => - val outputJarPath = root / "Hello.jar" - val res = os.proc( - TestUtil.cli, - "--power", - "package", - inputPath, - "-o", - outputJarPath, - extraOptions - ).call(cwd = root, stderr = os.Pipe) - expect(os.isFile(outputJarPath)) - expect(res.err.trim().contains(s"Wrote $outputJarPath")) + TestUtil.retryOnCi() { + val msg = "Hello" + val inputPath = os.rel / "Hello.scala" + TestInputs( + inputPath -> + s"""//> using lib org.apache.spark::spark-sql:3.3.2 + |//> using lib org.apache.spark::spark-hive:3.3.2 + |//> using lib org.apache.spark::spark-sql-kafka-0-10:3.3.2 + |//> using packaging.packageType assembly + |//> using packaging.provided org.apache.spark::spark-sql + |//> using packaging.provided org.apache.spark::spark-hive + | + |object Main extends App { + | println("$msg") + |} + |""".stripMargin + ).fromRoot { root => + val outputJarPath = root / "Hello.jar" + val res = os.proc( + TestUtil.cli, + "--power", + "package", + inputPath, + "-o", + outputJarPath, + extraOptions + ).call(cwd = root, stderr = os.Pipe) + expect(os.isFile(outputJarPath)) + expect(res.err.trim().contains(s"Wrote $outputJarPath")) + } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/PackageTestsDefault.scala b/modules/integration/src/test/scala/scala/cli/integration/PackageTestsDefault.scala index c60a8c27ed..1a7781124f 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/PackageTestsDefault.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/PackageTestsDefault.scala @@ -4,27 +4,30 @@ import com.eed3si9n.expecty.Expecty.expect class PackageTestsDefault extends PackageTestDefinitions with TestDefault { test("reuse run native binary") { - val inputs = TestInputs( - os.rel / "Hello.scala" -> - """object Hello { - | def main(args: Array[String]): Unit = - | println("Hello") - |} - |""".stripMargin - ) - inputs.fromRoot { root => - val runRes = os.proc(TestUtil.cli, "run", "--native", ".", extraOptions) - .call(cwd = root) - val runOutput = runRes.out.trim().linesIterator.filter(!_.startsWith("[info] ")).toVector - expect(runOutput == Seq("Hello")) + TestUtil.retryOnCi() { + val inputs = TestInputs( + os.rel / "Hello.scala" -> + """object Hello { + | def main(args: Array[String]): Unit = + | println("Hello") + |} + |""".stripMargin + ) + inputs.fromRoot { root => + val runRes = os.proc(TestUtil.cli, "run", "--native", ".", extraOptions) + .call(cwd = root) + val runOutput = runRes.out.trim().linesIterator.filter(!_.startsWith("[info] ")).toVector + expect(runOutput == Seq("Hello")) - val packageRes = - os.proc(TestUtil.cli, "--power", "package", "--native", ".", "-o", "hello", extraOptions) - .call(cwd = root, mergeErrIntoOut = true) - val packageOutput = packageRes.out.trim() - val topPackageOutput = packageOutput.linesIterator.takeWhile(!_.startsWith("Wrote ")).toVector - // no compilation or Scala Native pipeline output, as this should just re-use what the run command wrote - expect(topPackageOutput.forall(!_.startsWith("[info] "))) + val packageRes = + os.proc(TestUtil.cli, "--power", "package", "--native", ".", "-o", "hello", extraOptions) + .call(cwd = root, mergeErrIntoOut = true) + val packageOutput = packageRes.out.trim() + val topPackageOutput = + packageOutput.linesIterator.takeWhile(!_.startsWith("Wrote ")).toVector + // no compilation or Scala Native pipeline output, as this should just re-use what the run command wrote + expect(topPackageOutput.forall(!_.startsWith("[info] "))) + } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/PublishTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/PublishTestDefinitions.scala index ad2892a091..434e3222d5 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/PublishTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/PublishTestDefinitions.scala @@ -153,107 +153,109 @@ abstract class PublishTestDefinitions extends ScalaCliSuite with TestScalaVersio } test("simple sign with external JVM process, java version too low") { - val publicKey = { - val uri = Thread.currentThread().getContextClassLoader - .getResource("test-keys/key.asc") - .toURI - os.Path(Paths.get(uri)) - } - val secretKey = { - val uri = Thread.currentThread().getContextClassLoader - .getResource("test-keys/key.skr") - .toURI - os.Path(Paths.get(uri)) - } - - val signingOptions = Seq( - "--secret-key", - s"file:$secretKey", - "--secret-key-password", - "value:1234", - "--signer", - "bc", - "--force-signing-externally", - "--force-jvm-signing-cli" - ) + TestUtil.retryOnCi() { + val publicKey = { + val uri = Thread.currentThread().getContextClassLoader + .getResource("test-keys/key.asc") + .toURI + os.Path(Paths.get(uri)) + } + val secretKey = { + val uri = Thread.currentThread().getContextClassLoader + .getResource("test-keys/key.skr") + .toURI + os.Path(Paths.get(uri)) + } - val java8Home = - os.Path(os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:8").call().out.trim(), os.pwd) + val signingOptions = Seq( + "--secret-key", + s"file:$secretKey", + "--secret-key-password", + "value:1234", + "--signer", + "bc", + "--force-signing-externally", + "--force-jvm-signing-cli" + ) - val extraEnv = Map( - "JAVA_HOME" -> java8Home.toString, - "PATH" -> ((java8Home / "bin").toString + File.pathSeparator + System.getenv("PATH")) - ) + val java8Home = + os.Path(os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:8").call().out.trim(), os.pwd) - TestCase.testInputs.fromRoot { root => - val publishRes = os.proc( - TestUtil.cli, - "--power", - "publish", - extraOptions, - signingOptions, - "project", - "-R", - "test-repo", - "-v", - "-v", - "-v" - ).call( - cwd = root, - mergeErrIntoOut = true, - env = extraEnv + val extraEnv = Map( + "JAVA_HOME" -> java8Home.toString, + "PATH" -> ((java8Home / "bin").toString + File.pathSeparator + System.getenv("PATH")) ) - val javaCommandOpt = publishRes.out.text() - .linesIterator - .find(_.contains("Running command ")) + TestCase.testInputs.fromRoot { root => + val publishRes = os.proc( + TestUtil.cli, + "--power", + "publish", + extraOptions, + signingOptions, + "project", + "-R", + "test-repo", + "-v", + "-v", + "-v" + ).call( + cwd = root, + mergeErrIntoOut = true, + env = extraEnv + ) - expect(javaCommandOpt.isDefined) - expect(javaCommandOpt.get.contains(" -cp,")) - expect(javaCommandOpt.get.split(" -cp,").headOption.exists(_.contains("17"))) + val javaCommandOpt = publishRes.out.text() + .linesIterator + .find(_.contains("Running command ")) - val files = os.walk(root / "test-repo") - .filter(os.isFile(_)) - .map(_.relativeTo(root / "test-repo")) - val notInDir = files.filter(!_.startsWith(TestCase.expectedArtifactsDir)) - expect(notInDir.isEmpty) + expect(javaCommandOpt.isDefined) + expect(javaCommandOpt.get.contains(" -cp,")) + expect(javaCommandOpt.get.split(" -cp,").headOption.exists(_.contains("17"))) - val files0 = files.map(_.relativeTo(TestCase.expectedArtifactsDir)).toSet + val files = os.walk(root / "test-repo") + .filter(os.isFile(_)) + .map(_.relativeTo(root / "test-repo")) + val notInDir = files.filter(!_.startsWith(TestCase.expectedArtifactsDir)) + expect(notInDir.isEmpty) - expect((files0 -- expectedArtifacts).isEmpty) - expect((expectedArtifacts -- files0).isEmpty) - expect(files0 == expectedArtifacts) // just in case… + val files0 = files.map(_.relativeTo(TestCase.expectedArtifactsDir)).toSet - val repoArgs = - Seq[os.Shellable]("-r", "!central", "-r", (root / "test-repo").toNIO.toUri.toASCIIString) - val dep = s"org.virtuslab.scalacli.test:simple${TestCase.scalaSuffix}:0.2.0-SNAPSHOT" - val res = os.proc(TestUtil.cs, "launch", repoArgs, dep).call(cwd = root) - val output = res.out.trim() - expect(output == "Hello") + expect((files0 -- expectedArtifacts).isEmpty) + expect((expectedArtifacts -- files0).isEmpty) + expect(files0 == expectedArtifacts) // just in case… - val sourceJarViaCsStr = - os.proc(TestUtil.cs, "fetch", repoArgs, "--sources", "--intransitive", dep) - .call(cwd = root) - .out.trim() - val sourceJarViaCs = os.Path(sourceJarViaCsStr, os.pwd) - val zf = new ZipFile(sourceJarViaCs.toIO) - val entries = zf.entries().asScala.toVector.map(_.getName).toSet - expect(entries == expectedSourceEntries) + val repoArgs = + Seq[os.Shellable]("-r", "!central", "-r", (root / "test-repo").toNIO.toUri.toASCIIString) + val dep = s"org.virtuslab.scalacli.test:simple${TestCase.scalaSuffix}:0.2.0-SNAPSHOT" + val res = os.proc(TestUtil.cs, "launch", repoArgs, dep).call(cwd = root) + val output = res.out.trim() + expect(output == "Hello") - val signatures = expectedArtifacts.filter(_.last.endsWith(".asc")) - assert(signatures.nonEmpty) - val verifyProc = os.proc( - TestUtil.cli, - "--power", - "pgp", - "verify", - "--key", - publicKey, - signatures.map(os.rel / "test-repo" / TestCase.expectedArtifactsDir / _) - ) - .call(cwd = root, mergeErrIntoOut = true) + val sourceJarViaCsStr = + os.proc(TestUtil.cs, "fetch", repoArgs, "--sources", "--intransitive", dep) + .call(cwd = root) + .out.trim() + val sourceJarViaCs = os.Path(sourceJarViaCsStr, os.pwd) + val zf = new ZipFile(sourceJarViaCs.toIO) + val entries = zf.entries().asScala.toVector.map(_.getName).toSet + expect(entries == expectedSourceEntries) - expect(!verifyProc.out.text().contains(s"invalid signature")) + val signatures = expectedArtifacts.filter(_.last.endsWith(".asc")) + assert(signatures.nonEmpty) + val verifyProc = os.proc( + TestUtil.cli, + "--power", + "pgp", + "verify", + "--key", + publicKey, + signatures.map(os.rel / "test-repo" / TestCase.expectedArtifactsDir / _) + ) + .call(cwd = root, mergeErrIntoOut = true) + + expect(!verifyProc.out.text().contains(s"invalid signature")) + } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/ReplTestsDefault.scala b/modules/integration/src/test/scala/scala/cli/integration/ReplTestsDefault.scala index 950e904ea7..f86fd5ce9f 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/ReplTestsDefault.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/ReplTestsDefault.scala @@ -10,26 +10,30 @@ class ReplTestsDefault extends ReplTestDefinitions with TestDefault { if (TestUtil.isNativeCli) test("not download java 17 when run repl without sources") { - TestInputs.empty.fromRoot { root => - val java8Home = - os.Path(os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:8").call().out.trim(), os.pwd) + TestUtil.retryOnCi() { + TestInputs.empty.fromRoot { root => + val java8Home = + os.Path(os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:8").call().out.trim(), os.pwd) - val res = - os.proc(TestUtil.cli, "--power", "repl", TestUtil.extraOptions, "--", "-version").call( - cwd = root, - mergeErrIntoOut = true, - env = Map( - "JAVA_HOME" -> java8Home.toString, - "COURSIER_ARCHIVE_CACHE" -> (root / "archive-cache").toString(), - "COURSIER_CACHE" -> (root / "cache").toString(), - "PATH" -> ((java8Home / "bin").toString + File.pathSeparator + System.getenv("PATH")) + val res = + os.proc(TestUtil.cli, "--power", "repl", TestUtil.extraOptions, "--", "-version").call( + cwd = root, + mergeErrIntoOut = true, + env = Map( + "JAVA_HOME" -> java8Home.toString, + "COURSIER_ARCHIVE_CACHE" -> (root / "archive-cache").toString(), + "COURSIER_CACHE" -> (root / "cache").toString(), + "PATH" -> ((java8Home / "bin").toString + File.pathSeparator + System.getenv( + "PATH" + )) + ) ) - ) - val output = res.out.trim().toLowerCase() + val output = res.out.trim().toLowerCase() - expect(!output.contains("jdk17")) - expect(!output.contains("jvm-index")) + expect(!output.contains("jdk17")) + expect(!output.contains("jvm-index")) + } } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunScalaNativeTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunScalaNativeTestDefinitions.scala index 142fa231fc..166863a29e 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunScalaNativeTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunScalaNativeTestDefinitions.scala @@ -44,7 +44,9 @@ trait RunScalaNativeTestDefinitions { _: RunTestDefinitions => } test("simple script native") { - simpleNativeTests() + TestUtil.retryOnCi() { + simpleNativeTests() + } } def scalaNativeLtoTests(): Unit = { @@ -67,133 +69,141 @@ trait RunScalaNativeTestDefinitions { _: RunTestDefinitions => if (!Properties.isMac) test("scala native with lto optimisation") { - scalaNativeLtoTests() + TestUtil.retryOnCi()(scalaNativeLtoTests()) } test("simple script native command") { - val fileName = "simple.sc" - val message = "Hello" - TestInputs(os.rel / fileName -> simpleNativeScriptCode(message)) - .fromRoot { root => + TestUtil.retryOnCi() { + val fileName = "simple.sc" + val message = "Hello" + TestInputs(os.rel / fileName -> simpleNativeScriptCode(message)) + .fromRoot { root => + val output = + os.proc(TestUtil.cli, extraOptions, fileName, "--native", "--command") + .call(cwd = root) + .out.trim() + val command = output.linesIterator.toVector.filter(!_.startsWith("[")) + val actualOutput = os.proc(command).call(cwd = root).out.trim() + expect(actualOutput == message) + } + } + } + + test("Resource embedding in Scala Native") { + TestUtil.retryOnCi() { + val projectDir = "nativeres" + val resourceContent = "resource contents" + val resourceFileName = "embeddedfile.txt" + val inputs = TestInputs( + os.rel / projectDir / "main.scala" -> + s"""|//> using platform "scala-native" + |//> using resourceDir "resources" + | + |import java.nio.charset.StandardCharsets + |import java.io.{BufferedReader, InputStreamReader} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val inputStream = getClass().getResourceAsStream("/$resourceFileName") + | val nativeResourceText = new BufferedReader( + | new InputStreamReader(inputStream, StandardCharsets.UTF_8) + | ).readLine() + | println(nativeResourceText) + | } + |} + |""".stripMargin, + os.rel / projectDir / "resources" / resourceFileName -> resourceContent + ) + inputs.fromRoot { root => val output = - os.proc(TestUtil.cli, extraOptions, fileName, "--native", "--command") + os.proc(TestUtil.cli, extraOptions, projectDir, "-q") .call(cwd = root) .out.trim() - val command = output.linesIterator.toVector.filter(!_.startsWith("[")) - val actualOutput = os.proc(command).call(cwd = root).out.trim() - expect(actualOutput == message) + println(output) + expect(output == resourceContent) } - } - - test("Resource embedding in Scala Native") { - val projectDir = "nativeres" - val resourceContent = "resource contents" - val resourceFileName = "embeddedfile.txt" - val inputs = TestInputs( - os.rel / projectDir / "main.scala" -> - s"""|//> using platform "scala-native" - |//> using resourceDir "resources" - | - |import java.nio.charset.StandardCharsets - |import java.io.{BufferedReader, InputStreamReader} - | - |object Main { - | def main(args: Array[String]): Unit = { - | val inputStream = getClass().getResourceAsStream("/$resourceFileName") - | val nativeResourceText = new BufferedReader( - | new InputStreamReader(inputStream, StandardCharsets.UTF_8) - | ).readLine() - | println(nativeResourceText) - | } - |} - |""".stripMargin, - os.rel / projectDir / "resources" / resourceFileName -> resourceContent - ) - inputs.fromRoot { root => - val output = - os.proc(TestUtil.cli, extraOptions, projectDir, "-q") - .call(cwd = root) - .out.trim() - println(output) - expect(output == resourceContent) } } test("Scala Native C Files are correctly handled as a regular Input") { - val projectDir = "native-interop" - val interopFileName = "bindings.c" - val interopMsg = "Hello C!" - val inputs = TestInputs( - os.rel / projectDir / "main.scala" -> - s"""|//> using platform "scala-native" - | - |import scala.scalanative.unsafe._ - | - |@extern - |object Bindings { - | @name("scalanative_print") - | def print(): Unit = extern - |} - | - |object Main { - | def main(args: Array[String]): Unit = { - | Bindings.print() - | } - |} - |""".stripMargin, - os.rel / projectDir / interopFileName -> - s"""|#include - | - |void scalanative_print() { - | printf("$interopMsg\\n"); - |} - |""".stripMargin - ) - inputs.fromRoot { root => - val output = - os.proc(TestUtil.cli, extraOptions, projectDir, "-q") - .call(cwd = root) - .out.trim() - expect(output == interopMsg) + TestUtil.retryOnCi() { + val projectDir = "native-interop" + val interopFileName = "bindings.c" + val interopMsg = "Hello C!" + val inputs = TestInputs( + os.rel / projectDir / "main.scala" -> + s"""|//> using platform "scala-native" + | + |import scala.scalanative.unsafe._ + | + |@extern + |object Bindings { + | @name("scalanative_print") + | def print(): Unit = extern + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | Bindings.print() + | } + |} + |""".stripMargin, + os.rel / projectDir / interopFileName -> + s"""|#include + | + |void scalanative_print() { + | printf("$interopMsg\\n"); + |} + |""".stripMargin + ) + inputs.fromRoot { root => + val output = + os.proc(TestUtil.cli, extraOptions, projectDir, "-q") + .call(cwd = root) + .out.trim() + expect(output == interopMsg) - os.move(root / projectDir / interopFileName, root / projectDir / "bindings2.c") - val output2 = - os.proc(TestUtil.cli, extraOptions, projectDir, "-q") - .call(cwd = root) - .out.trim() + os.move(root / projectDir / interopFileName, root / projectDir / "bindings2.c") + val output2 = + os.proc(TestUtil.cli, extraOptions, projectDir, "-q") + .call(cwd = root) + .out.trim() - // LLVM throws linking errors if scalanative_print is internally repeated. - // This can happen if a file containing it will be removed/renamed in src, - // but somehow those changes will not be reflected in the output directory, - // causing symbols inside linked files to be doubled. - // Because of that, the removed file should not be passed to linker, - // otherwise this test will fail. - expect(output2 == interopMsg) + // LLVM throws linking errors if scalanative_print is internally repeated. + // This can happen if a file containing it will be removed/renamed in src, + // but somehow those changes will not be reflected in the output directory, + // causing symbols inside linked files to be doubled. + // Because of that, the removed file should not be passed to linker, + // otherwise this test will fail. + expect(output2 == interopMsg) + } } } if (actualScalaVersion.startsWith("3.2")) test("Scala 3 in Scala Native") { - val message = "using Scala 3 Native" - val fileName = "scala3native.scala" - val inputs = TestInputs( - os.rel / fileName -> - s"""import scala.scalanative.libc._ - |import scala.scalanative.unsafe._ - | - |@main def main() = - | val message = "$message" - | Zone { implicit z => - | stdio.printf(toCString(message)) - | } - |""".stripMargin - ) - inputs.fromRoot { root => - val output = - os.proc(TestUtil.cli, extraOptions, fileName, "--native", "-q") - .call(cwd = root) - .out.trim() - expect(output == message) + TestUtil.retryOnCi() { + val message = "using Scala 3 Native" + val fileName = "scala3native.scala" + val inputs = TestInputs( + os.rel / fileName -> + s"""import scala.scalanative.libc._ + |import scala.scalanative.unsafe._ + | + |@main def main() = + | val message = "$message" + | Zone { implicit z => + | stdio.printf(toCString(message)) + | } + |""".stripMargin + ) + inputs.fromRoot { root => + val output = + os.proc(TestUtil.cli, extraOptions, fileName, "--native", "-q") + .call(cwd = root) + .out.trim() + expect(output == message) + } } } @@ -214,7 +224,7 @@ trait RunScalaNativeTestDefinitions { _: RunTestDefinitions => } test("Multiple scripts native") { - multipleScriptsNative() + TestUtil.retryOnCi()(multipleScriptsNative()) } def directoryNative(): Unit = { @@ -245,7 +255,7 @@ trait RunScalaNativeTestDefinitions { _: RunTestDefinitions => // TODO: make nice messages that the scenario is unsupported with 2.12 if (actualScalaVersion.startsWith("2.13")) test("Directory native") { - directoryNative() + TestUtil.retryOnCi()(directoryNative()) } test("help native") { @@ -259,40 +269,42 @@ trait RunScalaNativeTestDefinitions { _: RunTestDefinitions => } test("Take into account interactive main class when caching binaries") { - val inputs = TestInputs( - os.rel / "Main1.scala" -> - """package foo - | - |object Main1 { - | def main(args: Array[String]): Unit = - | println("Hello from Main1") - |} - |""".stripMargin, - os.rel / "Main2.scala" -> - """package foo - | - |object Main2 { - | def main(args: Array[String]): Unit = - | println("Hello from Main2") - |} - |""".stripMargin - ) - inputs.fromRoot { root => - val configDir = root / "config" - os.makeDir.all(configDir) - if (!Properties.isWin) - os.perms.set(configDir, "rwx------") - val configEnv = Map("SCALA_CLI_CONFIG" -> (configDir / "config.json").toString) - os.proc(TestUtil.cli, "config", "interactive", "true") - .call(cwd = root, env = configEnv) - val output1 = os.proc(TestUtil.cli, "run", "--native", ".", extraOptions) - .call(cwd = root, env = configEnv ++ Seq("SCALA_CLI_INTERACTIVE_INPUTS" -> "foo.Main1")) - .out.lines().last - expect(output1 == "Hello from Main1") - val output2 = os.proc(TestUtil.cli, "run", "--native", ".", extraOptions) - .call(cwd = root, env = configEnv ++ Seq("SCALA_CLI_INTERACTIVE_INPUTS" -> "foo.Main2")) - .out.lines().last - expect(output2 == "Hello from Main2") + TestUtil.retryOnCi() { + val inputs = TestInputs( + os.rel / "Main1.scala" -> + """package foo + | + |object Main1 { + | def main(args: Array[String]): Unit = + | println("Hello from Main1") + |} + |""".stripMargin, + os.rel / "Main2.scala" -> + """package foo + | + |object Main2 { + | def main(args: Array[String]): Unit = + | println("Hello from Main2") + |} + |""".stripMargin + ) + inputs.fromRoot { root => + val configDir = root / "config" + os.makeDir.all(configDir) + if (!Properties.isWin) + os.perms.set(configDir, "rwx------") + val configEnv = Map("SCALA_CLI_CONFIG" -> (configDir / "config.json").toString) + os.proc(TestUtil.cli, "config", "interactive", "true") + .call(cwd = root, env = configEnv) + val output1 = os.proc(TestUtil.cli, "run", "--native", ".", extraOptions) + .call(cwd = root, env = configEnv ++ Seq("SCALA_CLI_INTERACTIVE_INPUTS" -> "foo.Main1")) + .out.lines().last + expect(output1 == "Hello from Main1") + val output2 = os.proc(TestUtil.cli, "run", "--native", ".", extraOptions) + .call(cwd = root, env = configEnv ++ Seq("SCALA_CLI_INTERACTIVE_INPUTS" -> "foo.Main2")) + .out.lines().last + expect(output2 == "Hello from Main2") + } } } @@ -321,17 +333,59 @@ trait RunScalaNativeTestDefinitions { _: RunTestDefinitions => test( s"native ($nativeVersionStr) & typelevel toolkit ($typelevelToolkitVersion) $titleStr" ) { - val expectedMessage = "Hello" + TestUtil.retryOnCi() { + val expectedMessage = "Hello" + val cmdLineOpts = + if (useDirectives) Nil + else Seq( + "--toolkit", + s"typelevel:$typelevelToolkitVersion", + "--native" + ) ++ nativeVersionOpts + val directivesStr = + if (useDirectives) + s"""//> using toolkit typelevel:$typelevelToolkitVersion + |//> using platform native + |$nativeVersionDirectiveStr + |""".stripMargin + else "" + TestInputs( + os.rel / "toolkit.scala" -> + s"""$directivesStr + |import cats.effect._ + | + |object Hello extends IOApp.Simple { + | def run = IO.println("$expectedMessage") + |} + |""".stripMargin + ).fromRoot { root => + val result = os.proc(TestUtil.cli, "run", "toolkit.scala", cmdLineOpts, extraOptions) + .call(cwd = root, stderr = os.Pipe) + expect(result.out.trim() == expectedMessage) + if (actualNativeVersion != Constants.typelevelToolkitMaxScalaNative) { + val err = result.err.trim() + expect( + err.contains( + s"Scala Native default version ${Constants.scalaNativeVersion} is not supported in this build" + ) + ) + expect(err.contains(s"Using ${Constants.typelevelToolkitMaxScalaNative} instead.")) + expect(err.contains( + s"TypeLevel Toolkit ${Constants.typelevelToolkitVersion} does not support Scala Native ${Constants.scalaNativeVersion}" + )) + } + } + } + } + + test(s"native ($nativeVersionStr) & scala toolkit ($scalaToolkitVersion) $titleStr") { + TestUtil.retryOnCi() { val cmdLineOpts = if (useDirectives) Nil - else Seq( - "--toolkit", - s"typelevel:$typelevelToolkitVersion", - "--native" - ) ++ nativeVersionOpts + else Seq("--toolkit", scalaToolkitVersion, "--native") ++ nativeVersionOpts val directivesStr = if (useDirectives) - s"""//> using toolkit typelevel:$typelevelToolkitVersion + s"""//> using toolkit $scalaToolkitVersion |//> using platform native |$nativeVersionDirectiveStr |""".stripMargin @@ -339,66 +393,28 @@ trait RunScalaNativeTestDefinitions { _: RunTestDefinitions => TestInputs( os.rel / "toolkit.scala" -> s"""$directivesStr - |import cats.effect._ - | - |object Hello extends IOApp.Simple { - | def run = IO.println("$expectedMessage") + |object Hello extends App { + | println(os.pwd) |} |""".stripMargin ).fromRoot { root => val result = os.proc(TestUtil.cli, "run", "toolkit.scala", cmdLineOpts, extraOptions) .call(cwd = root, stderr = os.Pipe) - expect(result.out.trim() == expectedMessage) - if (actualNativeVersion != Constants.typelevelToolkitMaxScalaNative) { + expect(result.out.trim() == root.toString) + if (Constants.scalaNativeVersion != Constants.toolkiMaxScalaNative) { val err = result.err.trim() expect( err.contains( s"Scala Native default version ${Constants.scalaNativeVersion} is not supported in this build" ) ) - expect(err.contains(s"Using ${Constants.typelevelToolkitMaxScalaNative} instead.")) + expect(err.contains(s"Using ${Constants.toolkiMaxScalaNative} instead.")) expect(err.contains( - s"TypeLevel Toolkit ${Constants.typelevelToolkitVersion} does not support Scala Native ${Constants.scalaNativeVersion}" + s"Scala Toolkit does not support Scala Native ${Constants.scalaNativeVersion}" )) } } } - - test(s"native ($nativeVersionStr) & scala toolkit ($scalaToolkitVersion) $titleStr") { - val cmdLineOpts = - if (useDirectives) Nil - else Seq("--toolkit", scalaToolkitVersion, "--native") ++ nativeVersionOpts - val directivesStr = - if (useDirectives) - s"""//> using toolkit $scalaToolkitVersion - |//> using platform native - |$nativeVersionDirectiveStr - |""".stripMargin - else "" - TestInputs( - os.rel / "toolkit.scala" -> - s"""$directivesStr - |object Hello extends App { - | println(os.pwd) - |} - |""".stripMargin - ).fromRoot { root => - val result = os.proc(TestUtil.cli, "run", "toolkit.scala", cmdLineOpts, extraOptions) - .call(cwd = root, stderr = os.Pipe) - expect(result.out.trim() == root.toString) - if (Constants.scalaNativeVersion != Constants.toolkiMaxScalaNative) { - val err = result.err.trim() - expect( - err.contains( - s"Scala Native default version ${Constants.scalaNativeVersion} is not supported in this build" - ) - ) - expect(err.contains(s"Using ${Constants.toolkiMaxScalaNative} instead.")) - expect(err.contains( - s"Scala Toolkit does not support Scala Native ${Constants.scalaNativeVersion}" - )) - } - } } } @@ -424,37 +440,39 @@ trait RunScalaNativeTestDefinitions { _: RunTestDefinitions => test( s"Scala Native multithreading set to $expectedMultithreadingState $testDescriptionString" ) { - val fileName = "multithreading.sc" - val expectedOutput = "42" - val threadSleep = "100" - val threadAwait = "2.seconds" - val inputs = TestInputs( - os.rel / fileName -> - s"""$directive - |import scala.concurrent._ - |import scala.concurrent.duration._ - |import ExecutionContext.Implicits.global - |val promise = Promise[Int]() - |val thread = new Thread(new Runnable { - | def run(): Unit = { - | Thread.sleep($threadSleep) - | promise.success($expectedOutput) - | } - | }) - |thread.start() - |val result = Await.result(promise.future, $threadAwait) - |println(result) - |""".stripMargin - ) - inputs.fromRoot { root => - val r = os.proc(TestUtil.cli, extraOptions, fileName, "--native", cliOptions) - .call(cwd = root, stderr = os.Pipe, check = expectedMultithreadingState) - if (!expectedMultithreadingState) expect(r.exitCode == 1) - else { - expect(r.exitCode == 0) - expect(r.out.trim() == expectedOutput) + TestUtil.retryOnCi() { + val fileName = "multithreading.sc" + val expectedOutput = "42" + val threadSleep = "100" + val threadAwait = "2.seconds" + val inputs = TestInputs( + os.rel / fileName -> + s"""$directive + |import scala.concurrent._ + |import scala.concurrent.duration._ + |import ExecutionContext.Implicits.global + |val promise = Promise[Int]() + |val thread = new Thread(new Runnable { + | def run(): Unit = { + | Thread.sleep($threadSleep) + | promise.success($expectedOutput) + | } + | }) + |thread.start() + |val result = Await.result(promise.future, $threadAwait) + |println(result) + |""".stripMargin + ) + inputs.fromRoot { root => + val r = os.proc(TestUtil.cli, extraOptions, fileName, "--native", cliOptions) + .call(cwd = root, stderr = os.Pipe, check = expectedMultithreadingState) + if (!expectedMultithreadingState) expect(r.exitCode == 1) + else { + expect(r.exitCode == 0) + expect(r.out.trim() == expectedOutput) + } + expect(r.err.trim().contains(s"multithreadingEnabled=$expectedMultithreadingState")) } - expect(r.err.trim().contains(s"multithreadingEnabled=$expectedMultithreadingState")) } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunScriptTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunScriptTestDefinitions.scala index 712ba4a1b4..9f960a4987 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunScriptTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunScriptTestDefinitions.scala @@ -666,43 +666,50 @@ trait RunScriptTestDefinitions { _: RunTestDefinitions => } test("verify drive-relative JAVA_HOME works") { - val java8Home = - os.Path(os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:8").call().out.trim(), os.pwd) + TestUtil.retryOnCi() { + val java8Home = + os.Path(os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:8").call().out.trim(), os.pwd) - val dr = os.Path.driveRoot + val dr = os.Path.driveRoot - // forward slash is legal in `Windows` - val javaHome = java8Home.toString.replace('\\', '/') - expect(javaHome.drop(dr.length).startsWith("/")) + // forward slash is legal in `Windows` + val javaHome = java8Home.toString.replace('\\', '/') + expect(javaHome.drop(dr.length).startsWith("/")) - val sysPath: String = System.getenv("PATH").replace('\\', '/') - val newPath: String = s"$javaHome/bin" + File.pathSeparator + sysPath + val sysPath: String = System.getenv("PATH").replace('\\', '/') + val newPath: String = s"$javaHome/bin" + File.pathSeparator + sysPath - val extraEnv = Map( - "JAVA_HOME" -> java8Home.toString, - "PATH" -> newPath - ) + val extraEnv = Map( + "JAVA_HOME" -> java8Home.toString, + "PATH" -> newPath + ) - val inputs = TestInputs( - os.rel / "script-with-shebang" -> - s"""|#!/usr/bin/env -S ${TestUtil.cli.mkString(" ")} shebang -S 2.13 - |//> using scala "$actualScalaVersion" - |println(args.toList)""".stripMargin - ) - inputs.fromRoot { root => - printf("TestUtil.cli: [%s]\njavaHome: [%s]\nnewPath: [%s]\n", TestUtil.cli, javaHome, newPath) - val proc = if (!Properties.isWin) { - os.perms.set(root / "script-with-shebang", os.PermSet.fromString("rwx------")) - os.proc("./script-with-shebang", "1", "2", "3", "-v") - } - else - os.proc(TestUtil.cli, "shebang", "script-with-shebang", "1", "2", "3", "-v") + val inputs = TestInputs( + os.rel / "script-with-shebang" -> + s"""|#!/usr/bin/env -S ${TestUtil.cli.mkString(" ")} shebang -S 2.13 + |//> using scala "$actualScalaVersion" + |println(args.toList)""".stripMargin + ) + inputs.fromRoot { root => + printf( + "TestUtil.cli: [%s]\njavaHome: [%s]\nnewPath: [%s]\n", + TestUtil.cli, + javaHome, + newPath + ) + val proc = if (!Properties.isWin) { + os.perms.set(root / "script-with-shebang", os.PermSet.fromString("rwx------")) + os.proc("./script-with-shebang", "1", "2", "3", "-v") + } + else + os.proc(TestUtil.cli, "shebang", "script-with-shebang", "1", "2", "3", "-v") - val output = proc.call(cwd = root, env = extraEnv).out.trim() + val output = proc.call(cwd = root, env = extraEnv).out.trim() - val expectedOutput = "List(1, 2, 3, -v)" + val expectedOutput = "List(1, 2, 3, -v)" - expect(output == expectedOutput) + expect(output == expectedOutput) + } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala index 07261cf347..6115659413 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -1070,46 +1070,53 @@ abstract class RunTestDefinitions } test("return relevant error if multiple .scala main classes are present") { - val (scalaFile1, scalaFile2, scriptName) = ("ScalaMainClass1", "ScalaMainClass2", "ScalaScript") - val scriptsDir = "scripts" - val inputs = TestInputs( - os.rel / s"$scalaFile1.scala" -> s"object $scalaFile1 extends App { println() }", - os.rel / s"$scalaFile2.scala" -> s"object $scalaFile2 extends App { println() }", - os.rel / scriptsDir / s"$scriptName.sc" -> "println()" - ) - inputs.fromRoot { root => - val res = os.proc( - TestUtil.cli, - "run", - ".", - extraOptions + TestUtil.retryOnCi() { + val (scalaFile1, scalaFile2, scriptName) = + ("ScalaMainClass1", "ScalaMainClass2", "ScalaScript") + val scriptsDir = "scripts" + val inputs = TestInputs( + os.rel / s"$scalaFile1.scala" -> s"object $scalaFile1 extends App { println() }", + os.rel / s"$scalaFile2.scala" -> s"object $scalaFile2 extends App { println() }", + os.rel / scriptsDir / s"$scriptName.sc" -> "println()" ) - .call(cwd = root, mergeErrIntoOut = true, check = false) - expect(res.exitCode == 1) - val output = res.out.trim() - val errorMessage = - output.linesWithSeparators.toSeq.takeRight(6).mkString // dropping compilation logs - val extraOptionsString = extraOptions.mkString(" ") - val scriptMainClassName = if (actualScalaVersion.startsWith("3")) - s"$scriptsDir.${scriptName}_sc" - else - s"$scriptsDir.$scriptName" - - val expectedMainClassNames = Seq(scalaFile1, scalaFile2, scriptMainClassName).sorted - val expectedErrorMessage = - s"""[${Console.RED}error${Console.RESET}] Found several main classes: ${ - expectedMainClassNames.mkString( - ", " - ) - } - |You can run one of them by passing it with the --main-class option, e.g. - | ${Console.BOLD}${TestUtil.detectCliPath} run . $extraOptionsString --main-class ${expectedMainClassNames - .head}${Console.RESET} - | - |You can pick the main class interactively by passing the --interactive option. - | ${Console.BOLD}${TestUtil - .detectCliPath} run . $extraOptionsString --interactive${Console.RESET}""".stripMargin - expect(errorMessage == expectedErrorMessage) + inputs.fromRoot { root => + val res = os.proc( + TestUtil.cli, + "run", + ".", + extraOptions + ) + .call(cwd = root, mergeErrIntoOut = true, check = false) + expect(res.exitCode == 1) + val output = res.out.trim() + val errorMessage = + output.linesWithSeparators.toSeq.takeRight(6).mkString // dropping compilation logs + val extraOptionsString = extraOptions.mkString(" ") + val scriptMainClassName = if (actualScalaVersion.startsWith("3")) + s"$scriptsDir.${scriptName}_sc" + else + s"$scriptsDir.$scriptName" + + val expectedMainClassNames = Seq(scalaFile1, scalaFile2, scriptMainClassName).sorted + val expectedErrorMessage = + s"""[${Console.RED}error${Console.RESET}] Found several main classes: ${ + expectedMainClassNames.mkString( + ", " + ) + } + |You can run one of them by passing it with the --main-class option, e.g. + | ${Console.BOLD}${TestUtil.detectCliPath} run . $extraOptionsString --main-class ${ + expectedMainClassNames + .head + }${Console.RESET} + | + |You can pick the main class interactively by passing the --interactive option. + | ${Console.BOLD}${ + TestUtil + .detectCliPath + } run . $extraOptionsString --interactive${Console.RESET}""".stripMargin + expect(errorMessage == expectedErrorMessage) + } } } @@ -1621,42 +1628,44 @@ abstract class RunTestDefinitions } test("BuildInfo should take into account --project-version") { - val inputs = TestInputs( - os.rel / "Main.scala" -> - s"""//> using buildInfo - | - |import scala.cli.build.BuildInfo - | - |object Main extends App { - | assert(BuildInfo.projectVersion == Some("35.0.1")) - |} - |""".stripMargin - ) + TestUtil.retryOnCi() { + val inputs = TestInputs( + os.rel / "Main.scala" -> + s"""//> using buildInfo + | + |import scala.cli.build.BuildInfo + | + |object Main extends App { + | assert(BuildInfo.projectVersion == Some("35.0.1")) + |} + |""".stripMargin + ) - inputs.fromRoot { root => - TestUtil.initializeGit(root, "v1.0.0") + inputs.fromRoot { root => + TestUtil.initializeGit(root, "v1.0.0") - val res = - os.proc( - TestUtil.cli, - "--power", - extraOptions, - ".", - "--compute-version", - "git", - "--project-version", - "35.0.1" - ).call(cwd = root) - val output = res.out.trim() + val res = + os.proc( + TestUtil.cli, + "--power", + extraOptions, + ".", + "--compute-version", + "git", + "--project-version", + "35.0.1" + ).call(cwd = root) + val output = res.out.trim() - val projectDir = os.list(root / ".scala-build").filter( - _.baseName.startsWith(root.baseName + "_") - ) - expect(projectDir.size == 1) - val buildInfoPath = projectDir.head / "src_generated" / "main" / "BuildInfo.scala" - expect(os.isFile(buildInfoPath)) + val projectDir = os.list(root / ".scala-build").filter( + _.baseName.startsWith(root.baseName + "_") + ) + expect(projectDir.size == 1) + val buildInfoPath = projectDir.head / "src_generated" / "main" / "BuildInfo.scala" + expect(os.isFile(buildInfoPath)) - expect(output == "") + expect(output == "") + } } } @@ -1896,175 +1905,177 @@ abstract class RunTestDefinitions test( s"offline mode should fail on missing artifacts (with Scala $actualAnnouncedScalaVersion)" ) { - // Kill bloop deamon to test scalac fallback - os.proc(TestUtil.cli, "--power", "bloop", "exit") - .call(cwd = os.pwd) - - // ensure extra options use an announced Scala version - val customExtraOptions: Seq[String] = - if ( - scalaVersionOpt.isEmpty && - Constants.scala3Next != Constants.scala3NextAnnounced - ) - extraOptions ++ Seq("--scala", actualAnnouncedScalaVersion) - else if ( - actualScalaVersion == Constants.scala3Next && - actualScalaVersion != actualAnnouncedScalaVersion - ) - extraOptions - .map { - case opt if opt == Constants.scala3Next => actualAnnouncedScalaVersion - case opt => opt - } - else extraOptions - - val depScalaVersion = actualAnnouncedScalaVersion match { - case sv if sv.startsWith("2.12") => "2.12" - case sv if sv.startsWith("2.13") => "2.13" - case _ => "3" - } - - val dep = s"com.lihaoyi:os-lib_$depScalaVersion:0.10.6" - val inputs = TestInputs( - os.rel / "NoDeps.scala" -> - """//> using jvm zulu:11 - |object NoDeps extends App { - | println("Hello from NoDeps") - |} - |""".stripMargin, - os.rel / "WithDeps.scala" -> - s"""//> using jvm zulu:11 - |//> using dep $dep - | - |object WithDeps extends App { - | println("Hello from WithDeps") - |} - |""".stripMargin - ) - inputs.fromRoot { root => - val cachePath = root / ".cache" - os.makeDir(cachePath) + TestUtil.retryOnCi() { + // Kill bloop deamon to test scalac fallback + os.proc(TestUtil.cli, "--power", "bloop", "exit") + .call(cwd = os.pwd) + + // ensure extra options use an announced Scala version + val customExtraOptions: Seq[String] = + if ( + scalaVersionOpt.isEmpty && + Constants.scala3Next != Constants.scala3NextAnnounced + ) + extraOptions ++ Seq("--scala", actualAnnouncedScalaVersion) + else if ( + actualScalaVersion == Constants.scala3Next && + actualScalaVersion != actualAnnouncedScalaVersion + ) + extraOptions + .map { + case opt if opt == Constants.scala3Next => actualAnnouncedScalaVersion + case opt => opt + } + else extraOptions + + val depScalaVersion = actualAnnouncedScalaVersion match { + case sv if sv.startsWith("2.12") => "2.12" + case sv if sv.startsWith("2.13") => "2.13" + case _ => "3" + } - val extraEnv = Map("COURSIER_CACHE" -> cachePath.toString) + val dep = s"com.lihaoyi:os-lib_$depScalaVersion:0.10.6" + val inputs = TestInputs( + os.rel / "NoDeps.scala" -> + """//> using jvm zulu:11 + |object NoDeps extends App { + | println("Hello from NoDeps") + |} + |""".stripMargin, + os.rel / "WithDeps.scala" -> + s"""//> using jvm zulu:11 + |//> using dep $dep + | + |object WithDeps extends App { + | println("Hello from WithDeps") + |} + |""".stripMargin + ) + inputs.fromRoot { root => + val cachePath = root / ".cache" + os.makeDir(cachePath) - val emptyCacheWalkSize = os.walk(cachePath).size + val extraEnv = Map("COURSIER_CACHE" -> cachePath.toString) - val noArtifactsRes = os.proc( - TestUtil.cli, - "--power", - "NoDeps.scala", - customExtraOptions, - "--offline", - "--cache", - cachePath.toString - ) - .call(cwd = root, check = false, mergeErrIntoOut = true) - expect(noArtifactsRes.exitCode == 1) - - // Cache unchanged - expect(emptyCacheWalkSize == os.walk(cachePath).size) - - // Download the artifacts for scala - os.proc(TestUtil.cs, "install", s"scala:$actualAnnouncedScalaVersion") - .call(cwd = root, env = extraEnv) - os.proc(TestUtil.cs, "install", s"scalac:$actualAnnouncedScalaVersion") - .call(cwd = root, env = extraEnv) - (if (actualAnnouncedScalaVersion.startsWith("3")) Some("scala3-sbt-bridge") - else if ( - actualAnnouncedScalaVersion.startsWith("2.13.") && - actualAnnouncedScalaVersion.coursierVersion >= "2.13.12".coursierVersion - ) - Some("scala2-sbt-bridge") - else None) - .foreach { bridgeArtifactName => - os.proc( - TestUtil.cs, - "fetch", - s"org.scala-lang:$bridgeArtifactName:$actualAnnouncedScalaVersion" - ) - .call(cwd = root, env = extraEnv) - } + val emptyCacheWalkSize = os.walk(cachePath).size - // Download JVM that won't suit Bloop, also no Bloop artifacts are present - os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:11") - .call(cwd = root, env = extraEnv) + val noArtifactsRes = os.proc( + TestUtil.cli, + "--power", + "NoDeps.scala", + customExtraOptions, + "--offline", + "--cache", + cachePath.toString + ) + .call(cwd = root, check = false, mergeErrIntoOut = true) + expect(noArtifactsRes.exitCode == 1) - val scalaJvmCacheWalkSize = os.walk(cachePath).size + // Cache unchanged + expect(emptyCacheWalkSize == os.walk(cachePath).size) + + // Download the artifacts for scala + os.proc(TestUtil.cs, "install", s"scala:$actualAnnouncedScalaVersion") + .call(cwd = root, env = extraEnv) + os.proc(TestUtil.cs, "install", s"scalac:$actualAnnouncedScalaVersion") + .call(cwd = root, env = extraEnv) + (if (actualAnnouncedScalaVersion.startsWith("3")) Some("scala3-sbt-bridge") + else if ( + actualAnnouncedScalaVersion.startsWith("2.13.") && + actualAnnouncedScalaVersion.coursierVersion >= "2.13.12".coursierVersion + ) + Some("scala2-sbt-bridge") + else None) + .foreach { bridgeArtifactName => + os.proc( + TestUtil.cs, + "fetch", + s"org.scala-lang:$bridgeArtifactName:$actualAnnouncedScalaVersion" + ) + .call(cwd = root, env = extraEnv) + } - val scalaAndJvmRes = os.proc( - TestUtil.cli, - "--power", - "NoDeps.scala", - customExtraOptions, - "--offline", - "--cache", - cachePath.toString, - "-v", - "-v" - ) - .call(cwd = root, mergeErrIntoOut = true) - expect(scalaAndJvmRes.exitCode == 0) - expect(scalaAndJvmRes.out.trim().contains( - "Offline mode is ON and Bloop could not be fetched from the local cache, using scalac as fallback" - )) - expect(scalaAndJvmRes.out.trim().contains("Hello from NoDeps")) + // Download JVM that won't suit Bloop, also no Bloop artifacts are present + os.proc(TestUtil.cs, "java-home", "--jvm", "zulu:11") + .call(cwd = root, env = extraEnv) - // Cache unchanged - expect(scalaJvmCacheWalkSize == os.walk(cachePath).size) + val scalaJvmCacheWalkSize = os.walk(cachePath).size - // Missing dependencies - for { - (cliOption, extraEnvMode) <- Seq( - "--offline" -> Map.empty[String, String], - "-Dcoursier.mode=offline" -> Map.empty[String, String], - "" -> Map("COURSIER_MODE" -> "offline") - ) - } { - val missingDepsRes = os.proc( + val scalaAndJvmRes = os.proc( TestUtil.cli, "--power", - cliOption, - "WithDeps.scala", + "NoDeps.scala", customExtraOptions, + "--offline", "--cache", - cachePath.toString + cachePath.toString, + "-v", + "-v" ) - .call(cwd = root, check = false, mergeErrIntoOut = true, env = extraEnvMode) - expect(missingDepsRes.exitCode == 1) - expect(missingDepsRes.out.trim().contains("Error downloading com.lihaoyi:os-lib")) + .call(cwd = root, mergeErrIntoOut = true) + expect(scalaAndJvmRes.exitCode == 0) + expect(scalaAndJvmRes.out.trim().contains( + "Offline mode is ON and Bloop could not be fetched from the local cache, using scalac as fallback" + )) + expect(scalaAndJvmRes.out.trim().contains("Hello from NoDeps")) // Cache unchanged expect(scalaJvmCacheWalkSize == os.walk(cachePath).size) - } - // Download dependencies - os.proc(TestUtil.cs, "fetch", dep) - .call(cwd = root, env = extraEnv) + // Missing dependencies + for { + (cliOption, extraEnvMode) <- Seq( + "--offline" -> Map.empty[String, String], + "-Dcoursier.mode=offline" -> Map.empty[String, String], + "" -> Map("COURSIER_MODE" -> "offline") + ) + } { + val missingDepsRes = os.proc( + TestUtil.cli, + "--power", + cliOption, + "WithDeps.scala", + customExtraOptions, + "--cache", + cachePath.toString + ) + .call(cwd = root, check = false, mergeErrIntoOut = true, env = extraEnvMode) + expect(missingDepsRes.exitCode == 1) + expect(missingDepsRes.out.trim().contains("Error downloading com.lihaoyi:os-lib")) + + // Cache unchanged + expect(scalaJvmCacheWalkSize == os.walk(cachePath).size) + } - val withDependencyCacheWalkSize = os.walk(cachePath).size + // Download dependencies + os.proc(TestUtil.cs, "fetch", dep) + .call(cwd = root, env = extraEnv) - val depsRes = os.proc( - TestUtil.cli, - "--power", - "WithDeps.scala", - customExtraOptions, - "--offline", - "--cache", - cachePath.toString, - "-v", - "-v" - ) - .call(cwd = root, mergeErrIntoOut = true) - expect(depsRes.exitCode == 0) - expect( - depsRes.out.trim().contains( - "Offline mode is ON and Bloop could not be fetched from the local cache, using scalac as fallback" + val withDependencyCacheWalkSize = os.walk(cachePath).size + + val depsRes = os.proc( + TestUtil.cli, + "--power", + "WithDeps.scala", + customExtraOptions, + "--offline", + "--cache", + cachePath.toString, + "-v", + "-v" ) - ) - expect(depsRes.out.trim().contains("Hello from WithDeps")) + .call(cwd = root, mergeErrIntoOut = true) + expect(depsRes.exitCode == 0) + expect( + depsRes.out.trim().contains( + "Offline mode is ON and Bloop could not be fetched from the local cache, using scalac as fallback" + ) + ) + expect(depsRes.out.trim().contains("Hello from WithDeps")) - // Cache changed - expect(withDependencyCacheWalkSize == os.walk(cachePath).size) + // Cache changed + expect(withDependencyCacheWalkSize == os.walk(cachePath).size) + } } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala b/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala index 45cd3c5674..bcb5f257ee 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala @@ -815,19 +815,21 @@ class SipScalaTests extends ScalaCliSuite with SbtTestHelper with MillTestHelper } test("--cli-version and --cli-default-scala-version can be passed in tandem") { - TestInputs.empty.fromRoot { root => - val cliVersion = "1.3.1" - val scalaVersion = "3.5.1-RC1-bin-20240522-e0c030c-NIGHTLY" - val res = os.proc( - TestUtil.cli, - "--cli-version", - cliVersion, - "--cli-default-scala-version", - scalaVersion, - "version" - ).call(cwd = root) - expect(res.out.trim().contains(cliVersion)) - expect(res.out.trim().contains(scalaVersion)) + TestUtil.retryOnCi() { + TestInputs.empty.fromRoot { root => + val cliVersion = "1.3.1" + val scalaVersion = "3.5.1-RC1-bin-20240522-e0c030c-NIGHTLY" + val res = os.proc( + TestUtil.cli, + "--cli-version", + cliVersion, + "--cli-default-scala-version", + scalaVersion, + "version" + ).call(cwd = root) + expect(res.out.trim().contains(cliVersion)) + expect(res.out.trim().contains(scalaVersion)) + } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/TestTestDefinitions.scala index 7218d5b223..0e20753519 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestTestDefinitions.scala @@ -352,7 +352,7 @@ abstract class TestTestDefinitions extends ScalaCliSuite with TestScalaVersionAr if (actualScalaVersion.startsWith("2.")) test("successful test native") { - successfulNativeTest() + TestUtil.retryOnCi()(successfulNativeTest()) } test("failing test") { @@ -383,7 +383,7 @@ abstract class TestTestDefinitions extends ScalaCliSuite with TestScalaVersionAr if (actualScalaVersion.startsWith("2.")) test("failing test native") { - failingNativeTest() + TestUtil.retryOnCi()(failingNativeTest()) } test("failing test return code") { @@ -425,11 +425,13 @@ abstract class TestTestDefinitions extends ScalaCliSuite with TestScalaVersionAr } test("utest JS") { - successfulUtestJsInputs.fromRoot { root => - val output = os.proc(TestUtil.cli, "test", extraOptions, ".", "--js") - .call(cwd = root) - .out.text() - expect(output.contains("Hello from tests")) + TestUtil.retryOnCi() { + successfulUtestJsInputs.fromRoot { root => + val output = os.proc(TestUtil.cli, "test", extraOptions, ".", "--js") + .call(cwd = root) + .out.text() + expect(output.contains("Hello from tests")) + } } } @@ -452,7 +454,7 @@ abstract class TestTestDefinitions extends ScalaCliSuite with TestScalaVersionAr if (actualScalaVersion.startsWith("2.")) test("utest native") { - utestNative() + TestUtil.retryOnCi()(utestNative()) } test("junit") { @@ -495,58 +497,60 @@ abstract class TestTestDefinitions extends ScalaCliSuite with TestScalaVersionAr for ((platformName, platformArgs) <- platforms) test(s"test framework arguments $platformName") { - val inputs = TestInputs( - os.rel / "MyTests.test.scala" -> - """//> using dep org.scalatest::scalatest::3.2.18 - |import org.scalatest._ - |import org.scalatest.flatspec._ - |import org.scalatest.matchers._ - | - |class Tests extends AnyFlatSpec with should.Matchers { - | "A thing" should "thing" in { - | assert(2 + 2 == 4) - | } - |} - |""".stripMargin - ) - inputs.fromRoot { root => - val scalaTestExtraArgs = - if (platformName == "native") - // FIXME: revert to using default Scala Native version when scalatest supports 0.5.x - Seq("--native-version", "0.4.17") - else Nil - val baseRes = - os.proc(TestUtil.cli, "test", extraOptions, platformArgs, scalaTestExtraArgs, ".") - .call(cwd = root, check = false) - if (baseRes.exitCode != 0) { - println(baseRes.out.text()) - fail("scala-cli test falied", clues(baseRes.exitCode)) - } - val baseOutput = baseRes.out.text() - expect(baseOutput.contains("A thing")) - expect(baseOutput.contains("should thing")) - val baseShouldThingLine = baseRes.out - .lines() - .find(_.contains("should thing")) - .getOrElse(???) - expect(!baseShouldThingLine.contains("millisecond")) - - val res = os.proc( - TestUtil.cli, - "test", - extraOptions, - platformArgs, - scalaTestExtraArgs, - ".", - "--", - "-oD" + TestUtil.retryOnCi() { + val inputs = TestInputs( + os.rel / "MyTests.test.scala" -> + """//> using dep org.scalatest::scalatest::3.2.18 + |import org.scalatest._ + |import org.scalatest.flatspec._ + |import org.scalatest.matchers._ + | + |class Tests extends AnyFlatSpec with should.Matchers { + | "A thing" should "thing" in { + | assert(2 + 2 == 4) + | } + |} + |""".stripMargin ) - .call(cwd = root) - val output = res.out.text() - expect(output.contains("A thing")) - expect(output.contains("should thing")) - val shouldThingLine = res.out.lines().find(_.contains("should thing")).getOrElse(???) - expect(shouldThingLine.contains("millisecond")) + inputs.fromRoot { root => + val scalaTestExtraArgs = + if (platformName == "native") + // FIXME: revert to using default Scala Native version when scalatest supports 0.5.x + Seq("--native-version", "0.4.17") + else Nil + val baseRes = + os.proc(TestUtil.cli, "test", extraOptions, platformArgs, scalaTestExtraArgs, ".") + .call(cwd = root, check = false) + if (baseRes.exitCode != 0) { + println(baseRes.out.text()) + fail("scala-cli test falied", clues(baseRes.exitCode)) + } + val baseOutput = baseRes.out.text() + expect(baseOutput.contains("A thing")) + expect(baseOutput.contains("should thing")) + val baseShouldThingLine = baseRes.out + .lines() + .find(_.contains("should thing")) + .getOrElse(???) + expect(!baseShouldThingLine.contains("millisecond")) + + val res = os.proc( + TestUtil.cli, + "test", + extraOptions, + platformArgs, + scalaTestExtraArgs, + ".", + "--", + "-oD" + ) + .call(cwd = root) + val output = res.out.text() + expect(output.contains("A thing")) + expect(output.contains("should thing")) + val shouldThingLine = res.out.lines().find(_.contains("should thing")).getOrElse(???) + expect(shouldThingLine.contains("millisecond")) + } } } @@ -633,68 +637,70 @@ abstract class TestTestDefinitions extends ScalaCliSuite with TestScalaVersionAr } test("Cross-tests") { - val supportsNative = actualScalaVersion.startsWith("2.") - val platforms = { - var pf = Seq("\"jvm\"", "\"js\"") - if (supportsNative) - pf = pf :+ "\"native\"" - pf.mkString(", ") - } - val inputs = { - var inputs0 = TestInputs( - os.rel / "MyTests.scala" -> - s"""//> using dep org.scalameta::munit::$munitVersion - |//> using platform $platforms - | - |class MyTests extends munit.FunSuite { - | test("shared") { - | println("Hello from " + "shared") - | } - |} - |""".stripMargin, - os.rel / "MyJvmTests.scala" -> - """//> using target.platform "jvm" - | - |class MyJvmTests extends munit.FunSuite { - | test("jvm") { - | println("Hello from " + "jvm") - | } - |} - |""".stripMargin, - os.rel / "MyJsTests.scala" -> - """//> using target.platform "js" - | - |class MyJsTests extends munit.FunSuite { - | test("js") { - | println("Hello from " + "js") - | } - |} - |""".stripMargin - ) - if (supportsNative) - inputs0 = inputs0.add( - os.rel / "MyNativeTests.scala" -> - """//> using target.platform "native" + TestUtil.retryOnCi() { + val supportsNative = actualScalaVersion.startsWith("2.") + val platforms = { + var pf = Seq("\"jvm\"", "\"js\"") + if (supportsNative) + pf = pf :+ "\"native\"" + pf.mkString(", ") + } + val inputs = { + var inputs0 = TestInputs( + os.rel / "MyTests.scala" -> + s"""//> using dep org.scalameta::munit::$munitVersion + |//> using platform $platforms + | + |class MyTests extends munit.FunSuite { + | test("shared") { + | println("Hello from " + "shared") + | } + |} + |""".stripMargin, + os.rel / "MyJvmTests.scala" -> + """//> using target.platform "jvm" + | + |class MyJvmTests extends munit.FunSuite { + | test("jvm") { + | println("Hello from " + "jvm") + | } + |} + |""".stripMargin, + os.rel / "MyJsTests.scala" -> + """//> using target.platform "js" | - |class MyNativeTests extends munit.FunSuite { - | test("native") { - | println("Hello from " + "native") + |class MyJsTests extends munit.FunSuite { + | test("js") { + | println("Hello from " + "js") | } |} |""".stripMargin ) - inputs0 - } - inputs.fromRoot { root => - val res = - os.proc(TestUtil.cli, "--power", "test", extraOptions, ".", "--cross").call(cwd = root) - val output = res.out.text() - val expectedCount = 2 + (if (supportsNative) 1 else 0) - expect(countSubStrings(output, "Hello from shared") == expectedCount) - expect(output.contains("Hello from jvm")) - expect(output.contains("Hello from js")) - if (supportsNative) - expect(output.contains("Hello from native")) + if (supportsNative) + inputs0 = inputs0.add( + os.rel / "MyNativeTests.scala" -> + """//> using target.platform "native" + | + |class MyNativeTests extends munit.FunSuite { + | test("native") { + | println("Hello from " + "native") + | } + |} + |""".stripMargin + ) + inputs0 + } + inputs.fromRoot { root => + val res = + os.proc(TestUtil.cli, "--power", "test", extraOptions, ".", "--cross").call(cwd = root) + val output = res.out.text() + val expectedCount = 2 + (if (supportsNative) 1 else 0) + expect(countSubStrings(output, "Hello from shared") == expectedCount) + expect(output.contains("Hello from jvm")) + expect(output.contains("Hello from js")) + if (supportsNative) + expect(output.contains("Hello from native")) + } } }