diff --git a/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala b/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala new file mode 100644 index 000000000000..5f0dffd15705 --- /dev/null +++ b/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala @@ -0,0 +1,91 @@ +package dotty.communitybuild + +import java.nio.file._ +import java.io.{PrintWriter, File} +import java.nio.charset.StandardCharsets.UTF_8 + +object CommunityBuildRunner: + + /** Depending on the mode of operation, either + * runs the test or updates the project. Updating + * means that all the dependencies are fetched but + * minimal other extra other work is done. Updating + * is necessary since we run tests each time on a fresh + * Docker container. We run the update on Docker container + * creation time to create the cache of the dependencies + * and avoid network overhead. See https://github.com/lampepfl/dotty-drone + * for more infrastructural details. + */ + extension (self: CommunityProject) def run()(using suite: CommunityBuildRunner): Unit = + if self.requiresExperimental && !compilerSupportExperimental then + log(s"Skipping ${self.project} - it needs experimental features unsupported in this build.") + return + self.dependencies.foreach(_.publish()) + self.testOnlyDependencies().foreach(_.publish()) + suite.runProject(self) + +trait CommunityBuildRunner: + + /** fails the current operation, can be specialised in a concrete Runner + * - overridden in `CommunityBuildTest` + */ + def failWith(msg: String): Nothing = throw IllegalStateException(msg) + + /** Build the given project with the published local compiler and sbt plugin. + * + * This test reads the compiler version from community-build/dotty-bootstrapped.version + * and expects community-build/sbt-dotty-sbt to set the compiler plugin. + * + * @param project The project name, should be a git submodule in community-build/ + * @param command The binary file of the program used to test the project – usually + * a build tool like SBT or Mill + * @param arguments Arguments to pass to the testing program + */ + def runProject(projectDef: CommunityProject): Unit = + val project = projectDef.project + val command = projectDef.binaryName + val arguments = projectDef.buildCommands + + @annotation.tailrec + def execTimes(task: () => Int, timesToRerun: Int): Boolean = + val exitCode = task() + if exitCode == 0 + then true + else if timesToRerun == 0 + then false + else + log(s"Rerunning tests in $project because of a previous run failure.") + execTimes(task, timesToRerun - 1) + + log(s"Building $project with dotty-bootstrapped $compilerVersion...") + + val projectDir = communitybuildDir.resolve("community-projects").resolve(project) + + if !Files.exists(projectDir.resolve(".git")) then + failWith(s""" + | + |Missing $project submodule. You can initialize this module using + | + | git submodule update --init community-build/community-projects/$project + | + |""".stripMargin) + + val testsCompletedSuccessfully = execTimes(projectDef.build, 3) + + if !testsCompletedSuccessfully then + failWith(s""" + | + |$command exited with an error code. To reproduce without JUnit, use: + | + | sbt community-build/prepareCommunityBuild + | cd community-build/community-projects/$project + | $command ${arguments.init.mkString(" ")} "${arguments.last}" + | + |For a faster feedback loop on SBT projects, one can try to extract a direct call to dotc + |using the sbt export command. For instance, for scalacheck, use + | sbt export jvm/test:compileIncremental + | + |""".stripMargin) + end runProject + +end CommunityBuildRunner diff --git a/community-build/src/scala/dotty/communitybuild/Main.scala b/community-build/src/scala/dotty/communitybuild/Main.scala index d135de12bdb0..852cee46af22 100644 --- a/community-build/src/scala/dotty/communitybuild/Main.scala +++ b/community-build/src/scala/dotty/communitybuild/Main.scala @@ -5,6 +5,7 @@ import java.nio.file.Path import java.nio.file.Files import scala.sys.process._ +import CommunityBuildRunner.run object Main: @@ -106,12 +107,15 @@ object Main: println(s"Documentation not found for ${failed.mkString(", ")}") sys.exit(1) + case "run" :: names if names.nonEmpty => + given CommunityBuildRunner() + withProjects(names, "Running")(_.run()) + case args => println("USAGE: ") - println("COMMAND is one of: publish doc run") + println("COMMAND is one of: publish, build, doc, doc all, run") println("Available projects are:") allProjects.foreach { k => println(s"\t${k.project}") } sys.exit(1) - diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index 62535a22f286..c1d748b4d3ad 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -7,93 +7,15 @@ import org.junit.{Ignore, Test} import org.junit.Assert.{assertEquals, fail} import org.junit.experimental.categories.Category -abstract class CommunityBuildTest: - given CommunityBuildTest = this - - /** Depending on the mode of operation, either - * runs the test or updates the project. Updating - * means that all the dependencies are fetched but - * minimal other extra other work is done. Updating - * is necessary since we run tests each time on a fresh - * Docker container. We run the update on Docker container - * creation time to create the cache of the dependencies - * and avoid network overhead. See https://github.com/lampepfl/dotty-drone - * for more infrastructural details. - */ - extension (self: CommunityProject) def run()(using suite: CommunityBuildTest): Unit = - if self.requiresExperimental && !compilerSupportExperimental then - println( - s"Skipping ${self.project} - it needs experimental features unsupported in this build." - ) - return - self.dependencies.foreach(_.publish()) - self.testOnlyDependencies().foreach(_.publish()) - suite.test(self) - - /** Build the given project with the published local compiler and sbt plugin. - * - * This test reads the compiler version from community-build/dotty-bootstrapped.version - * and expects community-build/sbt-dotty-sbt to set the compiler plugin. - * - * @param project The project name, should be a git submodule in community-build/ - * @param command The binary file of the program used to test the project – usually - * a build tool like SBT or Mill - * @param arguments Arguments to pass to the testing program - */ - def test(projectDef: CommunityProject): Unit = { - val project = projectDef.project - val command = projectDef.binaryName - val arguments = projectDef.buildCommands - - @annotation.tailrec - def execTimes(task: () => Int, timesToRerun: Int): Boolean = - val exitCode = task() - if exitCode == 0 - then true - else if timesToRerun == 0 - then false - else - log(s"Rerunning tests in $project because of a previous run failure.") - execTimes(task, timesToRerun - 1) - - log(s"Building $project with dotty-bootstrapped $compilerVersion...") - - val projectDir = communitybuildDir.resolve("community-projects").resolve(project) - - if (!Files.exists(projectDir.resolve(".git"))) { - fail(s""" - | - |Missing $project submodule. You can initialize this module using - | - | git submodule update --init community-build/community-projects/$project - | - |""".stripMargin) - } - - val testsCompletedSuccessfully = execTimes(projectDef.build, 3) - - if (!testsCompletedSuccessfully) { - fail(s""" - | - |$command exited with an error code. To reproduce without JUnit, use: - | - | sbt community-build/prepareCommunityBuild - | cd community-build/community-projects/$project - | $command ${arguments.init.mkString(" ")} "${arguments.last}" - | - |For a faster feedback loop on SBT projects, one can try to extract a direct call to dotc - |using the sbt export command. For instance, for scalacheck, use - | sbt export jvm/test:compileIncremental - | - |""".stripMargin) - } - } -end CommunityBuildTest +import CommunityBuildRunner.run class TestCategory +given testRunner: CommunityBuildRunner with + override def failWith(msg: String) = { fail(msg); ??? } + @Category(Array(classOf[TestCategory])) -class CommunityBuildTestA extends CommunityBuildTest: +class CommunityBuildTestA: @Test def izumiReflect = projects.izumiReflect.run() @Test def scalaSTM = projects.scalaSTM.run() @Test def scalatest = projects.scalatest.run() @@ -112,7 +34,7 @@ class CommunityBuildTestA extends CommunityBuildTest: end CommunityBuildTestA @Category(Array(classOf[TestCategory])) -class CommunityBuildTestB extends CommunityBuildTest: +class CommunityBuildTestB: @Test def cats = projects.cats.run() @Test def catsEffect3 = projects.catsEffect3.run() @Test def catsMtl = projects.catsMtl.run() @@ -132,7 +54,7 @@ class CommunityBuildTestB extends CommunityBuildTest: end CommunityBuildTestB @Category(Array(classOf[TestCategory])) -class CommunityBuildTestC extends CommunityBuildTest: +class CommunityBuildTestC: @Test def akka = projects.akka.run() @Test def algebra = projects.algebra.run() @Test def betterfiles = projects.betterfiles.run()