From 014a7654db71b151198164da65ef610f539cdac1 Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Tue, 23 Jan 2024 08:22:27 +1100 Subject: [PATCH 1/2] Add scalafmt --- .github/workflows/format.yml | 23 +++++++++++++++++++++++ .scalafmt.conf | 26 ++++++++++++++++++++++++++ project/plugins.sbt | 1 + 3 files changed, 50 insertions(+) create mode 100644 .github/workflows/format.yml create mode 100644 .scalafmt.conf diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 0000000..c7698ac --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,23 @@ +name: Scalafmt + +permissions: {} + +on: + pull_request: + branches: ['**'] + +jobs: + build: + name: Code is formatted + runs-on: ubuntu-latest + steps: + - name: Checkout current branch (full) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Check project is formatted + uses: jrouly/scalafmt-native-action@v3 + with: + arguments: '--list --mode diff-ref=origin/main' diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..75e1b1f --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,26 @@ +version = 3.7.17 +runner.dialect = scala212 +maxColumn = 120 +project.git = true + +# http://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style. +# scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala +docstrings.style = Asterisk + +# This also seems more idiomatic to include whitespace in import x.{ yyy } +spaces.inImportCurlyBraces = true + +align.tokens."+" = [ + { + code = "%" + owners = [ + { regex = "Term.ApplyInfix" } + ] + }, + { + code = "%%" + owners = [ + { regex = "Term.ApplyInfix" } + ] + } +] diff --git a/project/plugins.sbt b/project/plugins.sbt index 7a811dd..23658cb 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,3 @@ addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.22.0") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") From 808a92246bccf181033771c095e55793cd7c9bcf Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Tue, 23 Jan 2024 08:31:14 +1100 Subject: [PATCH 2/2] Apply scalafmt --- build.sbt | 3 +- project/plugins.sbt | 4 +- .../com/typesafe/sbt/MultiJvmPlugin.scala | 390 ++++++++++++------ .../scala/com/typesafe/sbt/multijvm/Jvm.scala | 42 +- 4 files changed, 298 insertions(+), 141 deletions(-) diff --git a/build.sbt b/build.sbt index e2f7e3f..b3160b6 100644 --- a/build.sbt +++ b/build.sbt @@ -36,7 +36,8 @@ scalacOptions ++= List( "-unchecked", "-deprecation", "-language:_", - "-encoding", "UTF-8" + "-encoding", + "UTF-8" ) // publish settings diff --git a/project/plugins.sbt b/project/plugins.sbt index 23658cb..0183bbb 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,3 @@ addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.22.0") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") diff --git a/src/main/scala/com/typesafe/sbt/MultiJvmPlugin.scala b/src/main/scala/com/typesafe/sbt/MultiJvmPlugin.scala index baf4679..a954e9c 100644 --- a/src/main/scala/com/typesafe/sbt/MultiJvmPlugin.scala +++ b/src/main/scala/com/typesafe/sbt/MultiJvmPlugin.scala @@ -4,7 +4,7 @@ package com.typesafe.sbt -import com.typesafe.sbt.multijvm.{Jvm, JvmLogger} +import com.typesafe.sbt.multijvm.{ Jvm, JvmLogger } import sbt._ import Keys._ import java.io.File @@ -14,7 +14,7 @@ import scala.Console.{ GREEN, RESET } import scala.sys.process.Process import sbtassembly.AssemblyPlugin.assemblySettings -import sbtassembly.{MergeStrategy, AssemblyKeys} +import sbtassembly.{ MergeStrategy, AssemblyKeys } import sjsonnew.BasicJsonProtocol._ import AssemblyKeys._ @@ -27,7 +27,7 @@ object MultiJvmPlugin extends AutoPlugin { import autoImport._ trait MultiJvmKeys { - val MultiJvm = config("multi-jvm") extend(Test) + val MultiJvm = config("multi-jvm") extend Test val multiJvmMarker = SettingKey[String]("multi-jvm-marker") @@ -70,7 +70,8 @@ object MultiJvmPlugin extends AutoPlugin { val multiNodeJavaName = SettingKey[String]("multi-node-java-name") // TODO fugly workaround for now - val multiNodeWorkAround = TaskKey[(String, (IndexedSeq[String], IndexedSeq[String]), String)]("multi-node-workaround") + val multiNodeWorkAround = + TaskKey[(String, (IndexedSeq[String], IndexedSeq[String]), String)]("multi-node-workaround") } @deprecated("Use MultiJvmPlugin.autoImport instead", "0.6.0") @@ -87,11 +88,13 @@ object MultiJvmPlugin extends AutoPlugin { private[this] def noTestsMessage(scoped: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): String = "No tests to run for " + display.show(scoped) - lazy val multiJvmSettings: Seq[Def.Setting[_]] = inConfig(MultiJvm)(Defaults.configSettings ++ internalMultiJvmSettings) + lazy val multiJvmSettings: Seq[Def.Setting[_]] = + inConfig(MultiJvm)(Defaults.configSettings ++ internalMultiJvmSettings) // https://github.com/sbt/sbt/blob/v0.13.15/main/actions/src/main/scala/sbt/Tests.scala#L296-L298 private[this] def showResults(log: Logger, results: Tests.Output, noTestsMessage: => String): Unit = - TestResultLogger.Default.copy(printNoTests = TestResultLogger.const(_ info noTestsMessage)) + TestResultLogger.Default + .copy(printNoTests = TestResultLogger.const(_ info noTestsMessage)) .run(log, results, "") private def internalMultiJvmSettings = assemblySettings ++ Seq( @@ -104,20 +107,24 @@ object MultiJvmPlugin extends AutoPlugin { multiJvmAppNames := (multiJvmApps.map(_.keys.toSeq) storeAs multiJvmAppNames triggeredBy compile).value, multiJvmJavaCommand := javaCommand(javaHome.value, "java"), jvmOptions := Seq.empty, - extraOptions := { (name: String) => Seq.empty }, + extraOptions := { (_: String) => Seq.empty }, multiJvmCreateLogger := { (name: String) => new JvmLogger(name) }, scalatestRunner := "org.scalatest.tools.Runner", scalatestOptions := defaultScalatestOptions, scalatestClasspath := managedClasspath.value.filter(_.data.name.contains("scalatest")), multiRunCopiedClassLocation := new File(target.value, "multi-run-copied-libraries"), - scalatestScalaOptions := scalaOptionsForScalatest(scalatestRunner.value, scalatestOptions.value, fullClasspath.value, multiRunCopiedClassLocation.value), + scalatestScalaOptions := scalaOptionsForScalatest( + scalatestRunner.value, + scalatestOptions.value, + fullClasspath.value, + multiRunCopiedClassLocation.value + ), scalatestMultiNodeScalaOptions := scalaMultiNodeOptionsForScalatest(scalatestRunner.value, scalatestOptions.value), multiTestOptions := Options(jvmOptions.value, extraOptions.value, scalatestScalaOptions.value), multiNodeTestOptions := Options(jvmOptions.value, extraOptions.value, scalatestMultiNodeScalaOptions.value), appScalaOptions := scalaOptionsForApps(fullClasspath.value), connectInput := true, multiRunOptions := Options(jvmOptions.value, extraOptions.value, appScalaOptions.value), - executeTests := multiJvmExecuteTests.value, testOnly := multiJvmTestOnly.evaluated, test := showResults(streams.value.log, executeTests.value, "No tests to run for MultiJvm"), @@ -127,15 +134,20 @@ object MultiJvmPlugin extends AutoPlugin { // TODO try to make sure that this is only generated on a need to have basis multiJvmTestJar := (assembly / assemblyOutputPath).map(_.getAbsolutePath).dependsOn(assembly).value, multiJvmTestJarName := (assembly / assemblyOutputPath).value.getAbsolutePath, - multiNodeTest := { implicit val display = Project.showContextKey(state.value) - showResults(streams.value.log, multiNodeExecuteTests.value, noTestsMessage(resolvedScoped.value)) }, + showResults(streams.value.log, multiNodeExecuteTests.value, noTestsMessage(resolvedScoped.value)) + }, multiNodeExecuteTests := multiNodeExecuteTestsTask.value, multiNodeTestOnly := multiNodeTestOnlyTask.evaluated, multiNodeHosts := Seq.empty, multiNodeHostsFileName := "multi-node-test.hosts", - multiNodeProcessedHosts := processMultiNodeHosts(multiNodeHosts.value, multiNodeHostsFileName.value, multiNodeJavaName.value, streams.value), + multiNodeProcessedHosts := processMultiNodeHosts( + multiNodeHosts.value, + multiNodeHostsFileName.value, + multiNodeJavaName.value, + streams.value + ), multiNodeTargetDirName := "multi-node-test", multiNodeJavaName := "java", // TODO there must be a way get at keys in the tasks that I just don't get @@ -151,12 +163,11 @@ object MultiJvmPlugin extends AutoPlugin { // the first class wins just like a classpath // just concatenate conflicting text files assembly / assemblyMergeStrategy := { - case n if n.endsWith(".class") => MergeStrategy.first - case n if n.endsWith(".txt") => MergeStrategy.concat - case n if n.endsWith("NOTICE") => MergeStrategy.concat - case n => (assembly / assemblyMergeStrategy).value.apply(n) + case n if n.endsWith(".class") => MergeStrategy.first + case n if n.endsWith(".txt") => MergeStrategy.concat + case n if n.endsWith("NOTICE") => MergeStrategy.concat + case n => (assembly / assemblyMergeStrategy).value.apply(n) }, - assembly / assemblyJarName := { name.value + "_" + scalaVersion.value + "-" + version.value + "-multi-jvm-assembly.jar" } @@ -164,12 +175,11 @@ object MultiJvmPlugin extends AutoPlugin { def collectMultiJvm(discovered: Seq[String], marker: String): Map[String, Seq[String]] = { val found = discovered filter (_.contains(marker)) groupBy (multiName(_, marker)) - found map { - case (key, values) => - val totalNodes = sys.props.get(marker + "." + key + ".nrOfNodes").getOrElse(values.size.toString).toInt - val sortedClasses = values.sorted - val totalClasses = sortedClasses.padTo(totalNodes, sortedClasses.last) - (key, totalClasses) + found map { case (key, values) => + val totalNodes = sys.props.get(marker + "." + key + ".nrOfNodes").getOrElse(values.size.toString).toInt + val sortedClasses = values.sorted + val totalClasses = sortedClasses.padTo(totalNodes, sortedClasses.last) + (key, totalClasses) } } @@ -182,20 +192,29 @@ object MultiJvmPlugin extends AutoPlugin { new File(new File(home, "bin"), name) } - def defaultScalatestOptions: Seq[String] = { + def defaultScalatestOptions: Seq[String] = if (getBoolean("sbt.log.noformat")) Seq("-oW") else Seq("-o") - } - def scalaOptionsForScalatest(runner: String, options: Seq[String], fullClasspath: Classpath, multiRunCopiedClassDir: File) = { + def scalaOptionsForScalatest( + runner: String, + options: Seq[String], + fullClasspath: Classpath, + multiRunCopiedClassDir: File + ) = { val directoryBasedClasspathEntries = fullClasspath.files.filter(_.isDirectory) // Copy over just the jars to this folder. - fullClasspath.files.filter(_.isFile).foreach(classpathFile => IO.copyFile(classpathFile, new File(multiRunCopiedClassDir, classpathFile.getName), true)) - val cp = directoryBasedClasspathEntries.absString + File.pathSeparator + multiRunCopiedClassDir.getAbsolutePath + File.separator + "*" + fullClasspath.files + .filter(_.isFile) + .foreach(classpathFile => + IO.copyFile(classpathFile, new File(multiRunCopiedClassDir, classpathFile.getName), true) + ) + val cp = + directoryBasedClasspathEntries.absString + File.pathSeparator + multiRunCopiedClassDir.getAbsolutePath + File.separator + "*" (testClass: String) => { Seq("-cp", cp, runner, "-s", testClass) ++ options } } - def scalaMultiNodeOptionsForScalatest(runner: String, options: Seq[String]) = { - (testClass: String) => { Seq(runner, "-s", testClass) ++ options } + def scalaMultiNodeOptionsForScalatest(runner: String, options: Seq[String]) = { (testClass: String) => + { Seq(runner, "-s", testClass) ++ options } } def scalaOptionsForApps(classpath: Classpath) = { @@ -204,7 +223,15 @@ object MultiJvmPlugin extends AutoPlugin { } def multiJvmExecuteTests: Def.Initialize[sbt.Task[Tests.Output]] = Def.task { - runMultiJvmTests(multiJvmTests.value, multiJvmMarker.value, multiJvmJavaCommand.value, multiTestOptions.value, sourceDirectory.value, multiJvmCreateLogger.value, streams.value.log) + runMultiJvmTests( + multiJvmTests.value, + multiJvmMarker.value, + multiJvmJavaCommand.value, + multiTestOptions.value, + sourceDirectory.value, + multiJvmCreateLogger.value, + streams.value.log + ) } def multiJvmTestOnly: Def.Initialize[sbt.InputTask[Unit]] = InputTask.createDyn( @@ -217,90 +244,131 @@ object MultiJvmPlugin extends AutoPlugin { val filters = selection.map(GlobFilter(_)) val tests = multiJvmTests.value.filterKeys(name => filters.exists(_.accept(name))) Def.task { - val results = runMultiJvmTests(tests, multiJvmMarker.value, multiJvmJavaCommand.value, opts, sourceDirectory.value, multiJvmCreateLogger.value, s.log) + val results = runMultiJvmTests( + tests, + multiJvmMarker.value, + multiJvmJavaCommand.value, + opts, + sourceDirectory.value, + multiJvmCreateLogger.value, + s.log + ) showResults(s.log, results, "No tests to run for MultiJvm") } } } - def runMultiJvmTests(tests: Map[String, Seq[String]], marker: String, javaBin: File, options: Options, - srcDir: File, createLogger: String => Logger, log: Logger): Tests.Output = { + def runMultiJvmTests( + tests: Map[String, Seq[String]], + marker: String, + javaBin: File, + options: Options, + srcDir: File, + createLogger: String => Logger, + log: Logger + ): Tests.Output = { val results = if (tests.isEmpty) List() - else tests.map { - case (_name, classes) => multi(_name, classes, marker, javaBin, options, srcDir, false, createLogger, log) - } - Tests.Output(Tests.overall(results.map(_._2)), Map.empty, results.map(result => Tests.Summary("multi-jvm", result._1))) + else + tests.map { case (_name, classes) => + multi(_name, classes, marker, javaBin, options, srcDir, false, createLogger, log) + } + Tests.Output( + Tests.overall(results.map(_._2)), + Map.empty, + results.map(result => Tests.Summary("multi-jvm", result._1)) + ) } def multiJvmRun: Def.Initialize[sbt.InputTask[Unit]] = InputTask.createDyn( loadForParser(multiJvmAppNames)((s, i) => runParser(s, i getOrElse Nil)) ) { - Def.task { - val s = streams.value - val apps = multiJvmApps.value - val j = multiJvmJavaCommand.value - val c = connectInput.value - val dir = sourceDirectory.value - val options = multiRunOptions.value - val marker = multiJvmMarker.value - val createLogger = multiJvmCreateLogger.value - - result => { - val classes = apps.getOrElse(result, Seq.empty) - Def.task { - if (classes.isEmpty) s.log.info("No apps to run.") - else multi(result, classes, marker, j, options, dir, c, createLogger, s.log) - } + Def.task { + val s = streams.value + val apps = multiJvmApps.value + val j = multiJvmJavaCommand.value + val c = connectInput.value + val dir = sourceDirectory.value + val options = multiRunOptions.value + val marker = multiJvmMarker.value + val createLogger = multiJvmCreateLogger.value + + result => { + val classes = apps.getOrElse(result, Seq.empty) + Def.task { + if (classes.isEmpty) s.log.info("No apps to run.") + else multi(result, classes, marker, j, options, dir, c, createLogger, s.log) } } } + } def runParser: (State, Seq[String]) => complete.Parser[String] = { import complete.DefaultParsers._ (state, appClasses) => Space ~> token(NotSpace examples appClasses.toSet) } - def multi(name: String, classes: Seq[String], marker: String, javaBin: File, options: Options, srcDir: File, - input: Boolean, createLogger: String => Logger, log: Logger): (String, TestResult) = { + def multi( + name: String, + classes: Seq[String], + marker: String, + javaBin: File, + options: Options, + srcDir: File, + input: Boolean, + createLogger: String => Logger, + log: Logger + ): (String, TestResult) = { val logName = "* " + name log.info(if (log.ansiCodesSupported) GREEN + logName + RESET else logName) val classesHostsJavas = getClassesHostsJavas(classes, IndexedSeq.empty, IndexedSeq.empty, "") val hosts = classesHostsJavas.map(_._2) - val processes = classes.zipWithIndex map { - case (testClass, index) => - val className = multiSimpleName(testClass) - val jvmName = "JVM-" + (index + 1) + "-" + className - val jvmLogger = createLogger(jvmName) - val optionsFile = (srcDir ** (className + ".opts")).get.headOption - val optionsFromFile = optionsFile map (IO.read(_)) map (_.trim.replace("\\n", " ").split("\\s+").toList) getOrElse (Seq.empty[String]) - val multiNodeOptions = getMultiNodeCommandLineOptions(hosts, index, classes.size) - val allJvmOptions = options.jvm ++ multiNodeOptions ++ optionsFromFile ++ options.extra(className) - val runOptions = options.run(testClass) - val connectInput = input && index == 0 - log.debug("Starting %s for %s" format (jvmName, testClass)) - log.debug(" with JVM options: %s" format allJvmOptions.mkString(" ")) - (testClass, Jvm.startJvm(javaBin, allJvmOptions, runOptions, jvmLogger, connectInput)) + val processes = classes.zipWithIndex map { case (testClass, index) => + val className = multiSimpleName(testClass) + val jvmName = "JVM-" + (index + 1) + "-" + className + val jvmLogger = createLogger(jvmName) + val optionsFile = (srcDir ** (className + ".opts")).get.headOption + val optionsFromFile = + optionsFile map (IO.read(_)) map (_.trim.replace("\\n", " ").split("\\s+").toList) getOrElse Seq.empty[String] + val multiNodeOptions = getMultiNodeCommandLineOptions(hosts, index, classes.size) + val allJvmOptions = options.jvm ++ multiNodeOptions ++ optionsFromFile ++ options.extra(className) + val runOptions = options.run(testClass) + val connectInput = input && index == 0 + log.debug("Starting %s for %s" format (jvmName, testClass)) + log.debug(" with JVM options: %s" format allJvmOptions.mkString(" ")) + (testClass, Jvm.startJvm(javaBin, allJvmOptions, runOptions, jvmLogger, connectInput)) } processExitCodes(name, processes, log) } def processExitCodes(name: String, processes: Seq[(String, Process)], log: Logger): (String, TestResult) = { - val exitCodes = processes map { - case (testClass, process) => (testClass, process.exitValue()) + val exitCodes = processes map { case (testClass, process) => + (testClass, process.exitValue()) } val failures = exitCodes flatMap { case (testClass, exit) if exit > 0 => Some("Failed: " + testClass) - case _ => None + case _ => None } - failures foreach(log.error(_)) - (name, if(failures.nonEmpty) TestResult.Failed else TestResult.Passed) + failures foreach (log.error(_)) + (name, if (failures.nonEmpty) TestResult.Failed else TestResult.Passed) } def multiNodeExecuteTestsTask: Def.Initialize[sbt.Task[Tests.Output]] = Def.task { val (_jarName, (hostsAndUsers, javas), targetDir) = multiNodeWorkAround.value - runMultiNodeTests(multiJvmTests.value, multiJvmMarker.value, multiNodeJavaName.value, multiNodeTestOptions.value, sourceDirectory.value, _jarName, hostsAndUsers, javas, targetDir, multiJvmCreateLogger.value, streams.value.log) + runMultiNodeTests( + multiJvmTests.value, + multiJvmMarker.value, + multiNodeJavaName.value, + multiNodeTestOptions.value, + sourceDirectory.value, + _jarName, + hostsAndUsers, + javas, + targetDir, + multiJvmCreateLogger.value, + streams.value.log + ) } def multiNodeTestOnlyTask: Def.Initialize[InputTask[Unit]] = InputTask.createDyn( @@ -313,62 +381,122 @@ object MultiJvmPlugin extends AutoPlugin { val opts = options.copy(extra = (s: String) => { options.extra(s) ++ _extraOptions }) val tests = selected flatMap { name => multiJvmTests.value.get(name) map ((name, _)) } Def.task { - val results = runMultiNodeTests(tests.toMap, multiJvmMarker.value, multiNodeJavaName.value, opts, sourceDirectory.value, _jarName, hostsAndUsers, javas, targetDir, multiJvmCreateLogger.value, s.log) + val results = runMultiNodeTests( + tests.toMap, + multiJvmMarker.value, + multiNodeJavaName.value, + opts, + sourceDirectory.value, + _jarName, + hostsAndUsers, + javas, + targetDir, + multiJvmCreateLogger.value, + s.log + ) showResults(s.log, results, "No tests to run for MultiNode") } } } - def runMultiNodeTests(tests: Map[String, Seq[String]], marker: String, java: String, options: Options, - srcDir: File, jarName: String, hostsAndUsers: IndexedSeq[String], - javas: IndexedSeq[String], targetDir: String, - createLogger: String => Logger, log: Logger): Tests.Output = { + def runMultiNodeTests( + tests: Map[String, Seq[String]], + marker: String, + java: String, + options: Options, + srcDir: File, + jarName: String, + hostsAndUsers: IndexedSeq[String], + javas: IndexedSeq[String], + targetDir: String, + createLogger: String => Logger, + log: Logger + ): Tests.Output = { val results = if (tests.isEmpty) List() - else tests.map { - case (_name, classes) => multiNode(_name, classes, marker, java, options, srcDir, false, jarName, - hostsAndUsers, javas, targetDir, createLogger, log) - } - Tests.Output(Tests.overall(results.map(_._2)), Map.empty, results.map(result => Tests.Summary("multi-jvm", result._1))) + else + tests.map { case (_name, classes) => + multiNode( + _name, + classes, + marker, + java, + options, + srcDir, + false, + jarName, + hostsAndUsers, + javas, + targetDir, + createLogger, + log + ) + } + Tests.Output( + Tests.overall(results.map(_._2)), + Map.empty, + results.map(result => Tests.Summary("multi-jvm", result._1)) + ) } - def multiNode(name: String, classes: Seq[String], marker: String, defaultJava: String, options: Options, srcDir: File, - input: Boolean, testJar: String, hostsAndUsers: IndexedSeq[String], javas: IndexedSeq[String], targetDir: String, - createLogger: String => Logger, log: Logger): (String, TestResult) = { + def multiNode( + name: String, + classes: Seq[String], + marker: String, + defaultJava: String, + options: Options, + srcDir: File, + input: Boolean, + testJar: String, + hostsAndUsers: IndexedSeq[String], + javas: IndexedSeq[String], + targetDir: String, + createLogger: String => Logger, + log: Logger + ): (String, TestResult) = { val logName = "* " + name log.info(if (log.ansiCodesSupported) GREEN + logName + RESET else logName) val classesHostsJavas = getClassesHostsJavas(classes, hostsAndUsers, javas, defaultJava) val hosts = classesHostsJavas.map(_._2) // TODO move this out, maybe to the hosts string as well? - val syncProcesses = classesHostsJavas.map { - case ((testClass, hostAndUser, java)) => - (testClass + " sync", Jvm.syncJar(testJar, hostAndUser, targetDir, log)) + val syncProcesses = classesHostsJavas.map { case (testClass, hostAndUser, _) => + (testClass + " sync", Jvm.syncJar(testJar, hostAndUser, targetDir, log)) } val syncResult = processExitCodes(name, syncProcesses, log) if (syncResult._2 == TestResult.Passed) { - val processes = classesHostsJavas.zipWithIndex map { - case ((testClass, hostAndUser, java), index) => { - val jvmName = "JVM-" + (index + 1) - val jvmLogger = createLogger(jvmName) - val className = multiSimpleName(testClass) - val optionsFile = (srcDir ** (className + ".opts")).get.headOption - val optionsFromFile = optionsFile map (IO.read(_)) map (_.trim.replace("\\n", " ").split("\\s+").toList) getOrElse (Seq.empty[String]) - val multiNodeOptions = getMultiNodeCommandLineOptions(hosts, index, classes.size) - val allJvmOptions = options.jvm ++ optionsFromFile ++ options.extra(className) ++ multiNodeOptions - val runOptions = options.run(testClass) - val connectInput = input && index == 0 - log.debug("Starting %s for %s" format (jvmName, testClass)) - log.debug(" with JVM options: %s" format allJvmOptions.mkString(" ")) - (testClass, Jvm.forkRemoteJava(java, allJvmOptions, runOptions, testJar, hostAndUser, targetDir, - jvmLogger, connectInput, log)) - } + val processes = classesHostsJavas.zipWithIndex map { case ((testClass, hostAndUser, java), index) => + val jvmName = "JVM-" + (index + 1) + val jvmLogger = createLogger(jvmName) + val className = multiSimpleName(testClass) + val optionsFile = (srcDir ** (className + ".opts")).get.headOption + val optionsFromFile = + optionsFile map (IO.read(_)) map (_.trim.replace("\\n", " ").split("\\s+").toList) getOrElse Seq + .empty[String] + val multiNodeOptions = getMultiNodeCommandLineOptions(hosts, index, classes.size) + val allJvmOptions = options.jvm ++ optionsFromFile ++ options.extra(className) ++ multiNodeOptions + val runOptions = options.run(testClass) + val connectInput = input && index == 0 + log.debug("Starting %s for %s" format (jvmName, testClass)) + log.debug(" with JVM options: %s" format allJvmOptions.mkString(" ")) + ( + testClass, + Jvm.forkRemoteJava( + java, + allJvmOptions, + runOptions, + testJar, + hostAndUser, + targetDir, + jvmLogger, + connectInput, + log + ) + ) } processExitCodes(name, processes, log) - } - else { + } else syncResult - } } private def padSeqOrDefaultTo(seq: IndexedSeq[String], default: String, max: Int): IndexedSeq[String] = { @@ -379,33 +507,49 @@ object MultiJvmPlugin extends AutoPlugin { (realSeq /: (0 until (max - realSeq.size)))((mySeq, pos) => mySeq :+ realSeq(pos % realSeq.size)) } - private def getClassesHostsJavas(classes: Seq[String], hostsAndUsers: IndexedSeq[String], javas: IndexedSeq[String], - defaultJava: String): IndexedSeq[(String, String, String)] = { + private def getClassesHostsJavas( + classes: Seq[String], + hostsAndUsers: IndexedSeq[String], + javas: IndexedSeq[String], + defaultJava: String + ): IndexedSeq[(String, String, String)] = { val max = classes.length - val tuple = (classes.toIndexedSeq, padSeqOrDefaultTo(hostsAndUsers, "localhost", max), padSeqOrDefaultTo(javas, defaultJava, max)) + val tuple = ( + classes.toIndexedSeq, + padSeqOrDefaultTo(hostsAndUsers, "localhost", max), + padSeqOrDefaultTo(javas, defaultJava, max) + ) tuple.zipped.map { case (className: String, hostAndUser: String, _java: String) => (className, hostAndUser, _java) } } private def getMultiNodeCommandLineOptions(hosts: Seq[String], index: Int, maxNodes: Int): Seq[String] = { - Seq("-Dmultinode.max-nodes=" + maxNodes, "-Dmultinode.server-host=" + hosts(0).split("@").last, - "-Dmultinode.host=" + hosts(index).split("@").last, "-Dmultinode.index=" + index) + Seq( + "-Dmultinode.max-nodes=" + maxNodes, + "-Dmultinode.server-host=" + hosts(0).split("@").last, + "-Dmultinode.host=" + hosts(index).split("@").last, + "-Dmultinode.index=" + index + ) } - private def processMultiNodeHosts(hosts: Seq[String], hostsFileName: String, defaultJava: String, s: Types.Id[Keys.TaskStreams]): - (IndexedSeq[String], IndexedSeq[String]) = { + private def processMultiNodeHosts( + hosts: Seq[String], + hostsFileName: String, + defaultJava: String, + s: Types.Id[Keys.TaskStreams] + ): (IndexedSeq[String], IndexedSeq[String]) = { val hostsFile = new File(hostsFileName) val theHosts: IndexedSeq[String] = if (hosts.isEmpty) { if (hostsFile.exists && hostsFile.canRead) { s.log.info("Using hosts defined in file " + hostsFile.getAbsolutePath) IO.readLines(hostsFile).map(_.trim).filter(_.length > 0).toIndexedSeq - } - else + } else hosts.toIndexedSeq - } - else { + } else { if (hostsFile.exists && hostsFile.canRead) - s.log.info("Hosts from setting " + multiNodeHosts.key.label + " is overrriding file " + hostsFile.getAbsolutePath) + s.log.info( + "Hosts from setting " + multiNodeHosts.key.label + " is overrriding file " + hostsFile.getAbsolutePath + ) hosts.toIndexedSeq } diff --git a/src/main/scala/com/typesafe/sbt/multijvm/Jvm.scala b/src/main/scala/com/typesafe/sbt/multijvm/Jvm.scala index 2ca5446..0671a53 100644 --- a/src/main/scala/com/typesafe/sbt/multijvm/Jvm.scala +++ b/src/main/scala/com/typesafe/sbt/multijvm/Jvm.scala @@ -5,13 +5,19 @@ package com.typesafe.sbt.multijvm import java.io.File -import java.lang.{ProcessBuilder => JProcessBuilder} +import java.lang.{ ProcessBuilder => JProcessBuilder } import sbt._ import scala.sys.process.Process object Jvm { - def startJvm(javaBin: File, jvmOptions: Seq[String], runOptions: Seq[String], logger: Logger, connectInput: Boolean) = { + def startJvm( + javaBin: File, + jvmOptions: Seq[String], + runOptions: Seq[String], + logger: Logger, + connectInput: Boolean + ) = { forkJava(javaBin, jvmOptions ++ runOptions, logger, connectInput) } @@ -24,37 +30,43 @@ object Jvm { /** * check if the current operating system is some OS - **/ - def isOS(os:String) = try { + */ + def isOS(os: String) = try { System.getProperty("os.name").toUpperCase startsWith os.toUpperCase } catch { - case _ : Throwable => false + case _: Throwable => false } /** * convert to proper path for the operating system - **/ - def osPath(path:String) = if (isOS("WINDOWS")) Process(Seq("cygpath", path)).lineStream.mkString else path + */ + def osPath(path: String) = if (isOS("WINDOWS")) Process(Seq("cygpath", path)).lineStream.mkString else path - def syncJar(jarName: String, hostAndUser: String, remoteDir: String, sbtLogger: Logger) : Process = { + def syncJar(jarName: String, hostAndUser: String, remoteDir: String, sbtLogger: Logger): Process = { val command: Array[String] = Array("ssh", hostAndUser, "mkdir -p " + remoteDir) val builder = new JProcessBuilder(command: _*) sbtLogger.debug("Jvm.syncJar about to run " + command.mkString(" ")) val process = Process(builder).run(sbtLogger, false) if (process.exitValue() == 0) { - val command: Array[String] = Array("rsync", "-ace", "ssh", osPath(jarName), hostAndUser +":" + remoteDir +"/") + val command: Array[String] = Array("rsync", "-ace", "ssh", osPath(jarName), hostAndUser + ":" + remoteDir + "/") val builder = new JProcessBuilder(command: _*) sbtLogger.debug("Jvm.syncJar about to run " + command.mkString(" ")) Process(builder).run(sbtLogger, false) - } - else { + } else process - } } - def forkRemoteJava(java: String, jvmOptions: Seq[String], appOptions: Seq[String], jarName: String, - hostAndUser: String, remoteDir: String, logger: Logger, connectInput: Boolean, - sbtLogger: Logger): Process = { + def forkRemoteJava( + java: String, + jvmOptions: Seq[String], + appOptions: Seq[String], + jarName: String, + hostAndUser: String, + remoteDir: String, + logger: Logger, + connectInput: Boolean, + sbtLogger: Logger + ): Process = { sbtLogger.debug("About to use java " + java) val shortJarName = new File(jarName).getName val javaCommand = List(List(java), jvmOptions, List("-cp", shortJarName), appOptions).flatten