diff --git a/modules/integration/src/test/scala/scala/cli/integration/ReplAmmoniteTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/ReplAmmoniteTestDefinitions.scala index 0ddc5bc5f5..6beed19ef8 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/ReplAmmoniteTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/ReplAmmoniteTestDefinitions.scala @@ -2,14 +2,10 @@ package scala.cli.integration import com.eed3si9n.expecty.Expecty.expect -import scala.cli.integration.TestUtil.removeAnsiColors -import scala.util.Properties +import scala.cli.integration.TestUtil.{normalizeArgsForWindows, removeAnsiColors} trait ReplAmmoniteTestDefinitions { this: ReplTestDefinitions => - protected val retrieveScalaVersionCode: String = if (actualScalaVersion.startsWith("2.")) - "scala.util.Properties.versionNumberString" - else "dotty.tools.dotc.config.Properties.simpleVersionString" - + protected val ammonitePrefix: String = "Running in Ammonite REPL:" def expectedScalaVersionForAmmonite: String = actualScalaVersion match { case s @@ -32,81 +28,101 @@ trait ReplAmmoniteTestDefinitions { this: ReplTestDefinitions => } def actualMaxAmmoniteScalaVersion: String = - if (actualScalaVersion.startsWith(Constants.scala3LtsPrefix)) + if actualScalaVersion.startsWith(Constants.scala3LtsPrefix) then Constants.maxAmmoniteScala3LtsVersion - else if (actualScalaVersion.startsWith(Constants.scala3NextPrefix)) + else if actualScalaVersion.startsWith(Constants.scala3NextPrefix) then Constants.maxAmmoniteScala3Version - else if (actualScalaVersion.startsWith("2.13")) Constants.maxAmmoniteScala213Version + else if actualScalaVersion.startsWith("2.13") then Constants.maxAmmoniteScala213Version else Constants.maxAmmoniteScala212Version + def shouldUseMaxAmmoniteScalaVersion: Boolean = + actualScalaVersion.coursierVersion > actualMaxAmmoniteScalaVersion.coursierVersion + def ammoniteExtraOptions: Seq[String] = - Seq("--scala", actualMaxAmmoniteScalaVersion) ++ TestUtil.extraOptions + if !shouldUseMaxAmmoniteScalaVersion then extraOptions + else Seq("--scala", actualMaxAmmoniteScalaVersion) ++ TestUtil.extraOptions + + def runInAmmoniteRepl( + codeToRunInRepl: String = "", + testInputs: TestInputs = TestInputs.empty, + cliOptions: Seq[String] = Seq.empty, + extraAmmoniteOptions: Seq[String] = Seq.empty, + shouldPipeStdErr: Boolean = false, + check: Boolean = true, + env: Map[String, String] = Map.empty + )( + runAfterRepl: os.CommandResult => Unit, + runBeforeReplAndGetExtraCliOpts: () => Seq[os.Shellable] = () => Seq.empty + ): Unit = { + val ammArgs = + if codeToRunInRepl.nonEmpty then + (Seq("-c", codeToRunInRepl) ++ extraAmmoniteOptions) + .normalizeArgsForWindows + .flatMap(arg => Seq("--ammonite-arg", arg)) + else Seq.empty + testInputs.fromRoot { root => + val potentiallyExtraCliOpts = runBeforeReplAndGetExtraCliOpts() + runAfterRepl( + os.proc( + TestUtil.cli, + "--power", + "repl", + ".", + "--ammonite", + ammArgs, + ammoniteExtraOptions, + cliOptions, + potentiallyExtraCliOpts + ).call( + cwd = root, + env = env, + check = check, + stderr = if shouldPipeStdErr then os.Pipe else os.Inherit + ) + ) + } + } - def ammoniteTest(useMaxAmmoniteScalaVersion: Boolean): Unit = { + def ammoniteTest(): Unit = { TestInputs.empty.fromRoot { root => - val testExtraOptions = if (useMaxAmmoniteScalaVersion) ammoniteExtraOptions else extraOptions - val ammArgs = Seq( - "-c", - s"""println("Hello" + " from Scala " + $retrieveScalaVersionCode)""" - ) - .map { - if (Properties.isWin) - a => if (a.contains(" ")) "\"" + a.replace("\"", "\\\"") + "\"" else a - else - identity + runInAmmoniteRepl( + codeToRunInRepl = s"""println("Hello" + " from Scala " + $retrieveScalaVersionCode)""", + shouldPipeStdErr = true + ) { res => + val output = res.out.trim() + val expectedSv = + if shouldUseMaxAmmoniteScalaVersion then actualMaxAmmoniteScalaVersion + else expectedScalaVersionForAmmonite + expect(output == s"Hello from Scala $expectedSv") + if shouldUseMaxAmmoniteScalaVersion then { + // the maximum Scala version supported by ammonite is being used, so we shouldn't downgrade + val errOutput = res.err.trim() + expect(!errOutput.contains("not yet supported with this version of Ammonite")) } - .flatMap(arg => Seq("--ammonite-arg", arg)) - val res = - os.proc(TestUtil.cli, "--power", "repl", testExtraOptions, "--ammonite", ammArgs) - .call(cwd = root, stderr = os.Pipe) - val output = res.out.trim() - val expectedSv = - if (useMaxAmmoniteScalaVersion) actualMaxAmmoniteScalaVersion - else expectedScalaVersionForAmmonite - expect(output == s"Hello from Scala $expectedSv") - if (useMaxAmmoniteScalaVersion) { - // the maximum Scala version supported by ammonite is being used, so we shouldn't downgrade - val errOutput = res.err.trim() - expect(!errOutput.contains("not yet supported with this version of Ammonite")) } } } - def ammoniteTestScope(useMaxAmmoniteScalaVersion: Boolean): Unit = { + def ammoniteTestScope(): Unit = { val message = "something something ammonite" - TestInputs( - os.rel / "example" / "TestScopeExample.test.scala" -> - s"""package example - | - |object TestScopeExample { - | def message: String = "$message" - |} - |""".stripMargin - ).fromRoot { root => - val testExtraOptions = if (useMaxAmmoniteScalaVersion) ammoniteExtraOptions else extraOptions - val ammArgs = Seq("-c", "println(example.TestScopeExample.message)") - .map { - if (Properties.isWin) - a => if (a.contains(" ")) "\"" + a.replace("\"", "\\\"") + "\"" else a - else - identity - } - .flatMap(arg => Seq("--ammonite-arg", arg)) - val res = - os.proc( - TestUtil.cli, - "--power", - "repl", - ".", - testExtraOptions, - "--test", - "--ammonite", - ammArgs - ) - .call(cwd = root, stderr = os.Pipe) + runInAmmoniteRepl( + codeToRunInRepl = "println(example.TestScopeExample.message)", + testInputs = + TestInputs( + os.rel / "example" / "TestScopeExample.test.scala" -> + s"""package example + | + |object TestScopeExample { + | def message: String = "$message" + |} + |""".stripMargin + ), + cliOptions = Seq("--test"), + shouldPipeStdErr = true + ) { res => val output = res.out.trim() expect(output == message) - if (useMaxAmmoniteScalaVersion) { + if shouldUseMaxAmmoniteScalaVersion then { // the maximum Scala version supported by ammonite is being used, so we shouldn't downgrade val errOutput = res.err.trim() expect(!errOutput.contains("not yet supported with this version of Ammonite")) @@ -114,130 +130,83 @@ trait ReplAmmoniteTestDefinitions { this: ReplTestDefinitions => } } - def ammoniteScalapyTest(useMaxAmmoniteScalaVersion: Boolean): Unit = { - val testExtraOptions = if (useMaxAmmoniteScalaVersion) ammoniteExtraOptions else extraOptions - val inputs = TestInputs( - os.rel / "foo" / "something.py" -> - """messageStart = 'Hello from' - |messageEnd = 'ScalaPy' - |""".stripMargin - ) - inputs.fromRoot { root => - val ammArgs = Seq( - "-c", - s"""println("Hello" + " from Scala " + $retrieveScalaVersionCode) - |val sth = py.module("foo.something") - |py.Dynamic.global.applyDynamicNamed("print")("" -> sth.messageStart, "" -> sth.messageEnd, "flush" -> py.Any.from(true)) - |""".stripMargin - ) - .map { - if (Properties.isWin) - a => if (a.contains(" ")) "\"" + a.replace("\"", "\\\"") + "\"" else a - else - identity - } - .flatMap(arg => Seq("--ammonite-arg", arg)) - - val errorRes = os.proc( - TestUtil.cli, - "--power", - "repl", - testExtraOptions, - "--ammonite", - "--python", - ammArgs - ).call( - cwd = root, - env = Map("PYTHONSAFEPATH" -> "foo"), - mergeErrIntoOut = true, - check = false + def ammoniteScalapyTest(): Unit = { + val codeToRunInRepl = + s"""println("Hello" + " from Scala " + $retrieveScalaVersionCode) + |val sth = py.module("foo.something") + |py.Dynamic.global.applyDynamicNamed("print")("" -> sth.messageStart, "" -> sth.messageEnd, "flush" -> py.Any.from(true)) + |""".stripMargin + val inputs = + TestInputs( + os.rel / "foo" / "something.py" -> + """messageStart = 'Hello from' + |messageEnd = 'ScalaPy' + |""".stripMargin ) + runInAmmoniteRepl( + codeToRunInRepl = codeToRunInRepl, + testInputs = inputs, + cliOptions = Seq("--python"), + shouldPipeStdErr = true, + check = false, + env = Map("PYTHONSAFEPATH" -> "foo") + ) { errorRes => expect(errorRes.exitCode != 0) - val errorOutput = errorRes.out.text() + val errorOutput = errorRes.err.trim() + errorRes.out.trim() expect(errorOutput.contains("No module named 'foo'")) - - val res = os.proc( - TestUtil.cli, - "--power", - "repl", - testExtraOptions, - "--ammonite", - "--python", - ammArgs - ).call(cwd = root, stderr = os.Pipe) + } + runInAmmoniteRepl( + codeToRunInRepl = codeToRunInRepl, + testInputs = inputs, + cliOptions = Seq("--python"), + shouldPipeStdErr = true + ) { res => val expectedSv = - if (useMaxAmmoniteScalaVersion) actualMaxAmmoniteScalaVersion + if shouldUseMaxAmmoniteScalaVersion then actualMaxAmmoniteScalaVersion else expectedScalaVersionForAmmonite val lines = res.out.trim().linesIterator.toVector expect(lines == Seq(s"Hello from Scala $expectedSv", "Hello from ScalaPy")) - if (useMaxAmmoniteScalaVersion) + if shouldUseMaxAmmoniteScalaVersion then // the maximum Scala version supported by ammonite is being used, so we shouldn't downgrade expect(!res.err.trim().contains("not yet supported with this version of Ammonite")) } } def ammoniteMaxVersionString: String = - if (actualScalaVersion == actualMaxAmmoniteScalaVersion) "" + if actualScalaVersion <= actualMaxAmmoniteScalaVersion then s" with Scala $actualScalaVersion" else s" with Scala $actualMaxAmmoniteScalaVersion (the latest supported version)" - test(s"ammonite$ammoniteMaxVersionString") { - ammoniteTest(useMaxAmmoniteScalaVersion = true) - } - - test(s"ammonite scalapy$ammoniteMaxVersionString") { - ammoniteScalapyTest(useMaxAmmoniteScalaVersion = true) - } - - test("ammonite with test scope sources") { - ammoniteTestScope(useMaxAmmoniteScalaVersion = true) - } + test(s"$ammonitePrefix simple $ammoniteMaxVersionString")(ammoniteTest()) + test(s"$ammonitePrefix scalapy$ammoniteMaxVersionString")(ammoniteScalapyTest()) + test(s"$ammonitePrefix with test scope sources$ammoniteMaxVersionString")(ammoniteTestScope()) - test("default ammonite version in help") { - TestInputs.empty.fromRoot { root => - val res = os.proc(TestUtil.cli, "--power", "repl", extraOptions, "--help").call(cwd = root) - val lines = removeAnsiColors(res.out.trim()).linesIterator.toVector + test(s"$ammonitePrefix ammonite version in help$ammoniteMaxVersionString") { + runInAmmoniteRepl(cliOptions = Seq("--help")) { res => + val lines = removeAnsiColors(res.out.trim()).linesIterator.toVector val ammVersionHelp = lines.find(_.contains("--ammonite-ver")).getOrElse("") expect(ammVersionHelp.contains(s"(${Constants.ammoniteVersion} by default)")) } } def ammoniteWithExtraJarTest(): Unit = { - TestInputs.empty.fromRoot { root => - val ammArgs = Seq( - "-c", - """import shapeless._; println("Here's an HList: " + (2 :: true :: "a" :: HNil))""" - ) - .map { - if (Properties.isWin) - a => if (a.contains(" ")) "\"" + a.replace("\"", "\\\"") + "\"" else a - else - identity - } - .flatMap(arg => Seq("--ammonite-arg", arg)) - val shapelessJar = - os.proc(TestUtil.cs, "fetch", "--intransitive", "com.chuusai:shapeless_2.13:2.3.7") - .call() - .out - .text() - .trim - val cmd = Seq[os.Shellable]( - TestUtil.cli, - "--power", - "repl", - ammoniteExtraOptions, - "--jar", - shapelessJar, - "--ammonite", - ammArgs - ) - val res = os.proc(cmd).call(cwd = root) - val output = res.out.trim() - expect(output == "Here's an HList: 2 :: true :: a :: HNil") - } + runInAmmoniteRepl(codeToRunInRepl = + """import shapeless._; println("Here's an HList: " + (2 :: true :: "a" :: HNil))""" + )( + runBeforeReplAndGetExtraCliOpts = () => + val shapelessJar = + os.proc(TestUtil.cs, "fetch", "--intransitive", "com.chuusai:shapeless_2.13:2.3.7") + .call() + .out + .text() + .trim + Seq("--jar", shapelessJar) + , + runAfterRepl = res => expect(res.out.trim() == "Here's an HList: 2 :: true :: a :: HNil") + ) } - if (actualScalaVersion.startsWith("2.13")) - test(s"ammonite with extra JAR$ammoniteMaxVersionString") { + if actualScalaVersion.startsWith("2.13") then + test(s"$ammonitePrefix with extra JAR$ammoniteMaxVersionString") { ammoniteWithExtraJarTest() } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/ReplAmmoniteTests3StableDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/ReplAmmoniteTests3StableDefinitions.scala index dddf5b0e57..82cc9f900d 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/ReplAmmoniteTests3StableDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/ReplAmmoniteTests3StableDefinitions.scala @@ -2,54 +2,27 @@ package scala.cli.integration import com.eed3si9n.expecty.Expecty.expect -import scala.util.Properties - trait ReplAmmoniteTests3StableDefinitions { this: ReplTestDefinitions & ReplAmmoniteTestDefinitions => - if (!actualScalaVersion.equals(actualMaxAmmoniteScalaVersion)) { - lazy val defaultScalaVersionString = - s" with Scala $actualScalaVersion (the default version, may downgrade)" - test(s"ammonite$defaultScalaVersionString") { - ammoniteTest(useMaxAmmoniteScalaVersion = false) - } - - test(s"ammonite scalapy$defaultScalaVersionString") { - ammoniteScalapyTest(useMaxAmmoniteScalaVersion = false) - } - - test(s"ammonite with test scope sources$defaultScalaVersionString") { - ammoniteTestScope(useMaxAmmoniteScalaVersion = false) - } - } - - test("https://github.com/scala/scala3/issues/21229") { - TestInputs( - os.rel / "Pprint.scala" -> - """//> using dep com.lihaoyi::pprint::0.9.0 - |package stuff - |import scala.quoted.* - |def foo = pprint(1) - |inline def bar = pprint(1) - |inline def baz = ${ bazImpl } - |def bazImpl(using Quotes) = '{ pprint(1) } - |""".stripMargin - ).fromRoot { root => - val ammArgs = Seq("-c", "println(stuff.baz)") - .map { - if (Properties.isWin) - a => if (a.contains(" ")) "\"" + a.replace("\"", "\\\"") + "\"" else a - else - identity - } - .flatMap(arg => Seq("--ammonite-arg", arg)) - // FIXME: test this on standard Scala 3 REPL, rather than just Ammonite - val res = os.proc(TestUtil.cli, "repl", ".", "--power", "--amm", ammArgs, extraOptions) - .call(cwd = root) - expect(res.out.trim().nonEmpty) - } + test(s"$ammonitePrefix https://github.com/scala/scala3/issues/21229$ammoniteMaxVersionString") { + // FIXME: test this on standard Scala 3 REPL, rather than just Ammonite + runInAmmoniteRepl( + codeToRunInRepl = "println(stuff.baz)", + testInputs = TestInputs( + os.rel / "Pprint.scala" -> + """//> using dep com.lihaoyi::pprint::0.9.0 + |package stuff + |import scala.quoted.* + |def foo = pprint(1) + |inline def bar = pprint(1) + |inline def baz = ${ bazImpl } + |def bazImpl(using Quotes) = '{ pprint(1) } + |""".stripMargin + ) + )(res => expect(res.out.trim().nonEmpty)) } - test("as jar") { + test(s"$ammonitePrefix as jar$ammoniteMaxVersionString") { val inputs = TestInputs( os.rel / "CheckCp.scala" -> """//> using dep com.lihaoyi::os-lib:0.9.1 @@ -64,39 +37,12 @@ trait ReplAmmoniteTests3StableDefinitions { |} |""".stripMargin ) - - inputs.fromRoot { root => - val ammArgs = Seq( - "-c", - """println("hasDir=" + checkcp.CheckCp.hasDir)""" - ) - .map { - if (Properties.isWin) - a => if (a.contains(" ")) "\"" + a.replace("\"", "\\\"") + "\"" else a - else - identity - } - .flatMap(arg => Seq("--ammonite-arg", arg)) - - val output = - os.proc(TestUtil.cli, "--power", "repl", ".", TestUtil.extraOptions, "--ammonite", ammArgs) - .call(cwd = root) - .out.trim() - expect(output == "hasDir=true") - - val asJarOutput = os.proc( - TestUtil.cli, - "--power", - "repl", - ".", - TestUtil.extraOptions, - "--ammonite", - ammArgs, - "--as-jar" - ) - .call(cwd = root) - .out.trim() - expect(asJarOutput == "hasDir=false") + val code = """println("hasDir=" + checkcp.CheckCp.hasDir)""" + runInAmmoniteRepl(codeToRunInRepl = code, testInputs = inputs) { + res => expect(res.out.trim() == "hasDir=true") + } + runInAmmoniteRepl(codeToRunInRepl = code, testInputs = inputs, cliOptions = Seq("--as-jar")) { + res => expect(res.out.trim() == "hasDir=false") } } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/ReplTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/ReplTestDefinitions.scala index b5dd8c1bfa..7460946d60 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/ReplTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/ReplTestDefinitions.scala @@ -3,46 +3,108 @@ package scala.cli.integration import com.eed3si9n.expecty.Expecty.expect import scala.cli.integration.TestUtil.removeAnsiColors +import scala.util.Properties abstract class ReplTestDefinitions extends ScalaCliSuite with TestScalaVersionArgs { this: TestScalaVersion => protected lazy val extraOptions: Seq[String] = scalaVersionArgs ++ TestUtil.extraOptions - test("dry run (default)") { - TestInputs.empty.fromRoot { root => - os.proc(TestUtil.cli, "repl", extraOptions, "--repl-dry-run").call(cwd = root) + protected lazy val canRunInRepl: Boolean = + (actualScalaVersion.startsWith("3.3") && + actualScalaVersion.coursierVersion >= "3.3.7".coursierVersion) || + actualScalaVersion.startsWith("3.7") || + actualScalaVersion.coursierVersion >= "3.7.0-RC1".coursierVersion + + protected val dryRunPrefix: String = "Dry run:" + protected val runInReplPrefix: String = "Running in Scala REPL:" + + def runInRepl( + codeToRunInRepl: String, + testInputs: TestInputs = TestInputs.empty, + cliOptions: Seq[String] = Seq.empty, + shouldPipeStdErr: Boolean = false, + check: Boolean = true, + env: Map[String, String] = Map.empty + )( + runAfterRepl: os.CommandResult => Unit, + runBeforeReplAndGetExtraCliOpts: () => Seq[os.Shellable] = () => Seq.empty + ): Unit = { + testInputs.fromRoot { root => + val potentiallyExtraCliOpts = runBeforeReplAndGetExtraCliOpts() + runAfterRepl( + os.proc( + TestUtil.cli, + "repl", + ".", + "--repl-quit-after-init", + "--repl-init-script", + codeToRunInRepl, + extraOptions, + cliOptions, + potentiallyExtraCliOpts + ) + .call( + cwd = root, + stderr = if shouldPipeStdErr then os.Pipe else os.Inherit, + env = env, + check = check + ) + ) } } - test("dry run (with main scope sources)") { - TestInputs( - os.rel / "Example.scala" -> - """object Example extends App { - | println("Hello") - |} - |""".stripMargin - ).fromRoot { root => - os.proc(TestUtil.cli, "repl", ".", extraOptions, "--repl-dry-run").call(cwd = root) + def dryRun( + testInputs: TestInputs = TestInputs.empty, + cliOptions: Seq[String] = Seq.empty, + useExtraOptions: Boolean = true + ): Unit = { + testInputs.fromRoot { root => + os.proc( + TestUtil.cli, + "repl", + "--repl-dry-run", + cliOptions, + if useExtraOptions then extraOptions else Seq.empty + ) + .call(cwd = root) } } - test("dry run (with main and test scope sources, and the --test flag)") { - TestInputs( - os.rel / "Example.scala" -> - """object Example extends App { - | println("Hello") - |} - |""".stripMargin, - os.rel / "Example.test.scala" -> - s"""//> using dep org.scalameta::munit::${Constants.munitVersion} - | - |class Example extends munit.FunSuite { - | test("is true true") { assert(true) } - |} - |""".stripMargin - ).fromRoot { root => - os.proc(TestUtil.cli, "repl", ".", extraOptions, "--repl-dry-run", "--test").call(cwd = root) - } + test(s"$dryRunPrefix default")(dryRun()) + + test(s"$dryRunPrefix with main scope sources") { + dryRun( + TestInputs( + os.rel / "Example.scala" -> + """object Example extends App { + | println("Hello") + |} + |""".stripMargin + ) + ) + } + + test(s"$dryRunPrefix with main and test scope sources, and the --test flag") { + dryRun( + TestInputs( + os.rel / "Example.scala" -> + """object Example extends App { + | println("Hello") + |} + |""".stripMargin, + os.rel / "Example.test.scala" -> + s"""//> using dep org.scalameta::munit::${Constants.munitVersion} + | + |class Example extends munit.FunSuite { + | test("is true true") { assert(true) } + |} + |""".stripMargin + ) + ) + } + + test(s"$dryRunPrefix calling repl with a directory with no scala artifacts") { + dryRun(TestInputs(os.rel / "Testing.java" -> "public class Testing {}")) } test("default scala version in help") { @@ -69,44 +131,160 @@ abstract class ReplTestDefinitions extends ScalaCliSuite with TestScalaVersionAr expect(output.contains("typer")) } - test("calling repl with a directory with no scala artifacts") { - val inputs = TestInputs( - os.rel / "Testing.java" -> "public class Testing {}" - ) - val cmd = Seq[os.Shellable]( - TestUtil.cli, - "repl", - "--repl-dry-run", - ".", - extraOptions - ) - inputs.fromRoot { root => - val res = os.proc(cmd) - .call(cwd = root) - expect(res.exitCode == 0) + if canRunInRepl then { + test(s"$runInReplPrefix simple") { + val expectedMessage = "1337" + runInRepl(s"""println($expectedMessage)""")(r => + expect(r.out.trim() == expectedMessage) + ) } - } - if ( - (actualScalaVersion.startsWith("3.3") && - actualScalaVersion.coursierVersion >= "3.3.7".coursierVersion) || - actualScalaVersion.startsWith("3.7") || - actualScalaVersion.coursierVersion >= "3.7.0-RC1".coursierVersion - ) - test("run hello world from the REPL") { - TestInputs.empty.fromRoot { root => - val expectedMessage = "1337" - val code = s"""println($expectedMessage)""" - val r = os.proc( - TestUtil.cli, - "repl", - "--repl-quit-after-init", - "--repl-init-script", - code, - extraOptions + test(s"$runInReplPrefix verify Scala version from the REPL") { + val opts = if actualScalaVersion.startsWith("3") && !isScala38OrNewer then + Seq("--with-compiler") + else Seq.empty + runInRepl( + codeToRunInRepl = s"""println($retrieveScalaVersionCode)""", + cliOptions = opts + )(r => expect(r.out.trim() == actualScalaVersion)) + } + + test(s"$runInReplPrefix test scope") { + val message = "something something something REPL" + runInRepl( + codeToRunInRepl = "println(example.TestScopeExample.message)", + testInputs = TestInputs( + os.rel / "example" / "TestScopeExample.test.scala" -> + s"""package example + | + |object TestScopeExample { + | def message: String = "$message" + |} + |""".stripMargin + ), + cliOptions = Seq("--test") + )(r => expect(r.out.trim() == message)) + } + + test(s"$runInReplPrefix https://github.com/scala/scala3/issues/21229") { + runInRepl( + codeToRunInRepl = "println(stuff.baz)", + testInputs = TestInputs( + os.rel / "Pprint.scala" -> + """//> using dep com.lihaoyi::pprint::0.9.0 + |package stuff + |import scala.quoted.* + |def foo = pprint(1) + |inline def bar = pprint(1) + |inline def baz = ${ bazImpl } + |def bazImpl(using Quotes) = '{ pprint(1) } + |""".stripMargin ) - .call(cwd = root) - expect(r.out.trim() == expectedMessage) + )(res => expect(res.out.trim().nonEmpty)) + } + + if !Properties.isWin then { + test(s"$runInReplPrefix ScalaPy") { + val opts = + if actualScalaVersion.startsWith("3") && !isScala38OrNewer then Seq("--with-compiler") + else Seq.empty + runInRepl( + codeToRunInRepl = + s"""import me.shadaj.scalapy.py + |println("Hello" + " from Scala " + $retrieveScalaVersionCode) + |val sth = py.module("foo.something") + |py.Dynamic.global.applyDynamicNamed("print")("" -> sth.messageStart, "" -> sth.messageEnd, "flush" -> py.Any.from(true)) + |""".stripMargin, + testInputs = TestInputs( + os.rel / "foo" / "something.py" -> + """messageStart = 'Hello from' + |messageEnd = 'ScalaPy' + |""".stripMargin + ), + cliOptions = Seq("--python", "--power") ++ opts, + shouldPipeStdErr = true + ) { res => + val output = res.out.trim().linesIterator.toVector.take(2).mkString("\n") + expect(output == + s"""Hello from Scala $actualScalaVersion + |Hello from ScalaPy""".stripMargin) + } + } + + test(s"$runInReplPrefix ScalaPy with PYTHONSAFEPATH") { + val opts = + if actualScalaVersion.startsWith("3") && !isScala38OrNewer then Seq("--with-compiler") + else Seq.empty + runInRepl( + codeToRunInRepl = + s"""import me.shadaj.scalapy.py + |println("Hello" + " from Scala " + $retrieveScalaVersionCode) + |val sth = py.module("foo.something") + |py.Dynamic.global.applyDynamicNamed("print")("" -> sth.messageStart, "" -> sth.messageEnd, "flush" -> py.Any.from(true)) + |""".stripMargin, + testInputs = TestInputs( + os.rel / "foo" / "something.py" -> + """messageStart = 'Hello from' + |messageEnd = 'ScalaPy' + |""".stripMargin + ), + cliOptions = Seq("--python", "--power") ++ opts, + shouldPipeStdErr = true, + // check = false, // technically should be an error, but the REPL itself doesn't return it as such. + env = Map("PYTHONSAFEPATH" -> "foo") + ) { errorRes => + // expect(errorRes.exitCode != 0) // technically should be an error, but the REPL itself doesn't return it as such. + val errorOutput = TestUtil.removeAnsiColors(errorRes.err.trim() + errorRes.out.trim()) + expect(errorOutput.contains("No module named 'foo'")) + } } + + test(s"$runInReplPrefix with extra JAR") { + runInRepl(codeToRunInRepl = + """import shapeless._; println("Here's an HList: " + (2 :: true :: "a" :: HNil))""" + )( + runBeforeReplAndGetExtraCliOpts = () => + val shapelessJar = + os.proc(TestUtil.cs, "fetch", "--intransitive", "com.chuusai:shapeless_2.13:2.3.7") + .call() + .out + .text() + .trim + Seq("--jar", shapelessJar) + , + runAfterRepl = res => expect(res.out.trim() == "Here's an HList: 2 :: true :: a :: HNil") + ) + } + + if !isScala38OrNewer then + // TODO rewrite this test to work with Scala 3.8+ once 3.8.0 stable is out + test(s"$runInReplPrefix as jar") { + val inputs = TestInputs( + os.rel / "CheckCp.scala" -> + """//> using dep com.lihaoyi::os-lib:0.9.1 + |package checkcp + |class CheckCp + |object CheckCp { + | def hasDir: Boolean = { + | val uri: java.net.URI = classOf[checkcp.CheckCp].getProtectionDomain.getCodeSource.getLocation.toURI + | os.isDir(os.Path(java.nio.file.Paths.get(uri))) + | } + |} + |""".stripMargin + ) + val code = """println("hasDir=" + checkcp.CheckCp.hasDir)""" + runInRepl(codeToRunInRepl = code, testInputs = inputs) { + res => expect(res.out.trim().contains("hasDir=true")) + } + runInRepl( + codeToRunInRepl = code, + testInputs = inputs, + cliOptions = Seq("--as-jar", "--power") + ) { + res => expect(res.out.trim().contains("hasDir=false")) + } + + } } + } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/ReplTests213.scala b/modules/integration/src/test/scala/scala/cli/integration/ReplTests213.scala index d43d7b9c30..3112a0e15d 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/ReplTests213.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/ReplTests213.scala @@ -1,17 +1,24 @@ package scala.cli.integration class ReplTests213 extends ReplTestDefinitions with ReplAmmoniteTestDefinitions with Test213 { - test("repl custom repositories work".flaky) { - TestInputs.empty.fromRoot { root => - os.proc( - TestUtil.cli, - "repl", - "--repl-dry-run", - "--scala", - Constants.scalaSnapshot213, - "--repository", - "https://scala-ci.typesafe.com/artifactory/scala-integration" - ).call(cwd = root) - } + for { + withExplicitScala2SnapshotRepo <- Seq(true, false) + snapshotVersion = Constants.scalaSnapshot213 + scalaVersionOptions = Seq("--scala", snapshotVersion) + repoOptions = + if withExplicitScala2SnapshotRepo then + Seq( + "--repository", + "https://scala-ci.typesafe.com/artifactory/scala-2.13-snapshots" + ) + else + Seq.empty + repoString = if withExplicitScala2SnapshotRepo then " with Scala 2 snapshot repo" else "" } + test(s"$dryRunPrefix repl Scala 2 snapshots: $snapshotVersion$repoString") { + dryRun( + cliOptions = scalaVersionOptions ++ repoOptions, + useExtraOptions = false + ) + } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/ReplTests3NextRc.scala b/modules/integration/src/test/scala/scala/cli/integration/ReplTests3NextRc.scala index 7331dcbb31..b07d515498 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/ReplTests3NextRc.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/ReplTests3NextRc.scala @@ -1,25 +1,3 @@ package scala.cli.integration -import com.eed3si9n.expecty.Expecty.expect - -class ReplTests3NextRc extends ReplTestDefinitions with Test3NextRc { - test("run hello world from the 3.8 REPL") { - TestInputs.empty.fromRoot { root => - val expectedMessage = "1337" - val code = s"""println($expectedMessage)""" - val r = os.proc( - TestUtil.cli, - "repl", - "--repl-quit-after-init", - "--repl-init-script", - code, - "-S", - // TODO: switch this test to 3.8.0-RC1 once it's out - "3.8.0-RC1-bin-20251104-b83b3d9-NIGHTLY", - TestUtil.extraOptions - ) - .call(cwd = root) - expect(r.out.trim() == expectedMessage) - } - } -} +class ReplTests3NextRc extends ReplTestDefinitions with Test3NextRc 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 f86fd5ce9f..fc0d041f8d 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/ReplTestsDefault.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/ReplTestsDefault.scala @@ -1,39 +1,6 @@ package scala.cli.integration -import com.eed3si9n.expecty.Expecty.expect - -import java.io.File - class ReplTestsDefault extends ReplTestDefinitions with ReplAmmoniteTestDefinitions with ReplAmmoniteTests3StableDefinitions - with TestDefault { - if (TestUtil.isNativeCli) - test("not download java 17 when run repl without sources") { - 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 output = res.out.trim().toLowerCase() - - expect(!output.contains("jdk17")) - expect(!output.contains("jvm-index")) - } - } - } -} + with TestDefault diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestScalaVersionArgs.scala b/modules/integration/src/test/scala/scala/cli/integration/TestScalaVersionArgs.scala index fa30d73510..9f73f20d9e 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestScalaVersionArgs.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestScalaVersionArgs.scala @@ -23,6 +23,11 @@ trait TestScalaVersionArgs extends ScalaCliSuite { this: TestScalaVersion => Constants.scala38Versions .map(_.coursierVersion) .exists(_ <= actualScalaVersion.coursierVersion) + + def retrieveScalaVersionCode: String = + if actualScalaVersion.startsWith("2.") || isScala38OrNewer then + "scala.util.Properties.versionNumberString" + else "dotty.tools.dotc.config.Properties.simpleVersionString" } sealed trait TestScalaVersion { this: TestScalaVersionArgs => diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala b/modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala index 3386af0f09..f79748b54c 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala @@ -436,4 +436,11 @@ object TestUtil { def await: T = Await.result(f, Duration.Inf) def awaitWithTimeout(d: Duration): T = Await.result(f, d) } + + extension (args: Seq[String]) { + def normalizeArgsForWindows: Seq[String] = + if Properties.isWin then + args.map(a => if a.contains(" ") then "\"" + a.replace("\"", "\\\"") + "\"" else a) + else args + } } diff --git a/project/deps/package.mill.scala b/project/deps/package.mill.scala index 6de5621771..6b9a6934fd 100644 --- a/project/deps/package.mill.scala +++ b/project/deps/package.mill.scala @@ -107,7 +107,7 @@ object Java { object TestDeps { def pprint: Dep = Deps.pprint def munit: Dep = Deps.munit - def scalaSnapshot213: String = "2.13.8-bin-e814d78" + def scalaSnapshot213: String = "2.13.19-bin-efb7184" def archLinuxImage: String = "archlinux@sha256:b15db21228c7cd5fd3ab364a97193ba38abfad0e8b9593c15b71850b74738153"