From bbb9c271075e37ba61e0aa425404393e38dda837 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Wed, 2 Oct 2024 15:42:24 +0200 Subject: [PATCH 1/2] Refactor `--watch` tests --- .../RunWithWatchTestDefinitions.scala | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala index 84a4c70f43..b807212079 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala @@ -1,11 +1,24 @@ package scala.cli.integration import com.eed3si9n.expecty.Expecty.expect +import os.SubProcess -import scala.concurrent.duration.DurationInt +import scala.concurrent.ExecutionContext +import scala.concurrent.duration.{Duration, DurationInt} import scala.util.{Properties, Try} trait RunWithWatchTestDefinitions { _: RunTestDefinitions => + implicit class ProcOps(proc: SubProcess) { + def printStderrUntilRerun(timeout: Duration)(implicit ec: ExecutionContext): Unit = { + def rerunWasTriggered(): Boolean = { + val stderrOutput = TestUtil.readLine(proc.stderr, ec, timeout) + println(stderrOutput) + stderrOutput.contains("re-run") + } + while (!rerunWasTriggered()) Thread.sleep(100L) + } + } + if (!Properties.isMac || !TestUtil.isCI) // TODO make this pass reliably on Mac CI test("simple --watch .scala source") { @@ -21,8 +34,7 @@ trait RunWithWatchTestDefinitions { _: RunTestDefinitions => val output1 = TestUtil.readLine(proc.stdout, ec, timeout) expect(output1 == expectedMessage1) val expectedMessage2 = "World" - while (!TestUtil.readLine(proc.stderr, ec, timeout).contains("re-run")) - Thread.sleep(100L) + proc.printStderrUntilRerun(timeout)(ec) os.write.over( root / inputPath, s"""object Smth extends App { println("$expectedMessage2") }""" From d3f716393166ec136f945d6b97bf6a7d533b1d96 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Wed, 2 Oct 2024 16:53:52 +0200 Subject: [PATCH 2/2] Regenerate `CrossSources` on subsequent `--watch` builds --- .../src/main/scala/scala/build/Build.scala | 33 ++++--- .../RunWithWatchTestDefinitions.scala | 92 ++++++++++++++++--- 2 files changed, 96 insertions(+), 29 deletions(-) diff --git a/modules/build/src/main/scala/scala/build/Build.scala b/modules/build/src/main/scala/scala/build/Build.scala index 3b140fb8ef..d22552c71c 100644 --- a/modules/build/src/main/scala/scala/build/Build.scala +++ b/modules/build/src/main/scala/scala/build/Build.scala @@ -682,27 +682,30 @@ object Build { ) val threads = BuildThreads.create() val classesDir0 = classesRootDir(inputs.workspace, inputs.projectName) - val info = either { - val (crossSources: CrossSources, inputs0: Inputs) = value(allInputs(inputs, options, logger)) - val sharedOptions = crossSources.sharedOptions(options) - val compiler: ScalaCompiler = value { - compilerMaker.create( + + def info: Either[BuildException, (ScalaCompiler, Option[ScalaCompiler], CrossSources, Inputs)] = + either { + val (crossSources: CrossSources, inputs0: Inputs) = + value(allInputs(inputs, options, logger)) + val sharedOptions = crossSources.sharedOptions(options) + val compiler: ScalaCompiler = value { + compilerMaker.create( + inputs0.workspace / Constants.workspaceDirName, + classesDir0, + buildClient, + logger, + sharedOptions + ) + } + val docCompilerOpt: Option[ScalaCompiler] = docCompilerMakerOpt.map(_.create( inputs0.workspace / Constants.workspaceDirName, classesDir0, buildClient, logger, sharedOptions - ) + )).map(value) + (compiler, docCompilerOpt, crossSources, inputs0) } - val docCompilerOpt: Option[ScalaCompiler] = docCompilerMakerOpt.map(_.create( - inputs0.workspace / Constants.workspaceDirName, - classesDir0, - buildClient, - logger, - sharedOptions - )).map(value) - (compiler, docCompilerOpt, crossSources, inputs0) - } var res: Either[BuildException, Builds] = null diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala index b807212079..6021aa3e9e 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala @@ -19,31 +19,63 @@ trait RunWithWatchTestDefinitions { _: RunTestDefinitions => } } - if (!Properties.isMac || !TestUtil.isCI) - // TODO make this pass reliably on Mac CI - test("simple --watch .scala source") { - val expectedMessage1 = "Hello" - val inputPath = os.rel / "smth.scala" - TestInputs(inputPath -> s"""object Smth extends App { println("$expectedMessage1") }""") - .fromRoot { root => + // TODO make this pass reliably on Mac CI + if (!Properties.isMac || !TestUtil.isCI) { + val expectedMessage1 = "Hello" + val expectedMessage2 = "World" + for { + (inputPath, inputs, codeToWriteOver) <- + Seq( + { + val inputPath = os.rel / "raw.scala" + def code(message: String) = s"""object Smth extends App { println("$message") }""" + ( + inputPath, + TestInputs(inputPath -> code(expectedMessage1)), + code(expectedMessage2) + ) + }, { + val inputPath = os.rel / "script.sc" + def code(message: String) = s"""println("$message")""" + ( + inputPath, + TestInputs(inputPath -> code(expectedMessage1)), + code(expectedMessage2) + ) + }, { + val inputPath = os.rel / "markdown.md" + def code(message: String) = + s"""# Some random docs with a Scala snippet + |```scala + |println("$message") + |``` + |The snippet prints the message, of course. + |""".stripMargin + ( + inputPath, + TestInputs(inputPath -> code(expectedMessage1)), + code(expectedMessage2) + ) + } + ) + } + test(s"simple --watch ${inputPath.last}") { + inputs.fromRoot { root => TestUtil.withProcessWatching( - proc = os.proc(TestUtil.cli, "run", ".", "--watch", extraOptions) + proc = os.proc(TestUtil.cli, "run", inputPath.toString(), "--watch", extraOptions) .spawn(cwd = root, stderr = os.Pipe), timeout = 120.seconds ) { (proc, timeout, ec) => val output1 = TestUtil.readLine(proc.stdout, ec, timeout) expect(output1 == expectedMessage1) - val expectedMessage2 = "World" proc.printStderrUntilRerun(timeout)(ec) - os.write.over( - root / inputPath, - s"""object Smth extends App { println("$expectedMessage2") }""" - ) + os.write.over(root / inputPath, codeToWriteOver) val output2 = TestUtil.readLine(proc.stdout, ec, timeout) expect(output2 == expectedMessage2) } } - } + } + } test("watch with interactive, with multiple main classes") { val fileName = "watch.scala" @@ -232,4 +264,36 @@ trait RunWithWatchTestDefinitions { _: RunTestDefinitions => } } } + + // TODO make this pass reliably on Mac CI + if (!Properties.isMac || !TestUtil.isCI) + test("--watch .scala source with changing directives") { + val inputPath = os.rel / "smth.scala" + + def code(includeDirective: Boolean) = { + val directive = if (includeDirective) "//> using toolkit default" else "" + s"""$directive + |object Smth extends App { println(os.pwd) } + |""".stripMargin + } + + TestInputs(inputPath -> code(includeDirective = true)).fromRoot { root => + TestUtil.withProcessWatching( + os.proc(TestUtil.cli, "run", ".", "--watch", extraOptions) + .spawn(cwd = root, stderr = os.Pipe) + ) { (proc, timeout, ec) => + val output1 = TestUtil.readLine(proc.stdout, ec, timeout) + expect(output1 == root.toString) + proc.printStderrUntilRerun(timeout)(ec) + os.write.over(root / inputPath, code(includeDirective = false)) + TestUtil.readLine(proc.stderr, ec, timeout) + val output2 = TestUtil.readLine(proc.stderr, ec, timeout) + expect(output2.toLowerCase.contains("error")) + proc.printStderrUntilRerun(timeout)(ec) + os.write.over(root / inputPath, code(includeDirective = true)) + val output3 = TestUtil.readLine(proc.stdout, ec, timeout) + expect(output3 == root.toString) + } + } + } }