diff options
Diffstat (limited to 'contrib')
8 files changed, 469 insertions, 0 deletions
diff --git a/contrib/testng/src/mill/testng/ResultEvent.java b/contrib/testng/src/mill/testng/ResultEvent.java new file mode 100644 index 00000000..6e2a50d6 --- /dev/null +++ b/contrib/testng/src/mill/testng/ResultEvent.java @@ -0,0 +1,45 @@ + +package mill.testng; + +import sbt.testing.*; +import org.testng.ITestResult; + +public class ResultEvent { + static Event failure(ITestResult result){ return event(Status.Failure, result); } + static Event skipped(ITestResult result){ return event(Status.Skipped, result); } + static Event success(ITestResult result){ return event(Status.Success, result); } + + static Event event(Status result, ITestResult testNGResult) { + return new Event() { + public String fullyQualifiedName() { + return testNGResult.getTestClass().getName(); + } + + public Fingerprint fingerprint() { + return TestNGFingerprint.instance; + } + + public Selector selector() { + return new SuiteSelector(); + } + + public Status status() { + return result; + } + + public OptionalThrowable throwable() { + if (result != Status.Success){ + return new OptionalThrowable(testNGResult.getThrowable()); + }else { + return new OptionalThrowable(); + } + } + + @Override + public long duration() { + return testNGResult.getEndMillis() - testNGResult.getStartMillis(); + } + }; + } + static String classNameOf(ITestResult result){ return result.getTestClass().getName(); } +}
\ No newline at end of file diff --git a/contrib/testng/src/mill/testng/TestNGFramework.java b/contrib/testng/src/mill/testng/TestNGFramework.java new file mode 100644 index 00000000..6e993fcc --- /dev/null +++ b/contrib/testng/src/mill/testng/TestNGFramework.java @@ -0,0 +1,25 @@ +package mill.testng; + + + +import sbt.testing.*; + + +public class TestNGFramework implements Framework { + public String name(){ return "TestNG"; } + + public Fingerprint[] fingerprints() { + return new Fingerprint[]{TestNGFingerprint.instance}; + } + + @Override + public Runner runner(String[] args, String[] remoteArgs, ClassLoader classLoader) { + return new TestNGRunner(args, remoteArgs, classLoader); + } +} + +class TestNGFingerprint implements AnnotatedFingerprint{ + final public static TestNGFingerprint instance = new TestNGFingerprint(); + public String annotationName(){return "org.testng.annotations.Test";} + public boolean isModule(){return false;} +} diff --git a/contrib/testng/src/mill/testng/TestNGInstance.java b/contrib/testng/src/mill/testng/TestNGInstance.java new file mode 100644 index 00000000..4cf274d3 --- /dev/null +++ b/contrib/testng/src/mill/testng/TestNGInstance.java @@ -0,0 +1,67 @@ +package mill.testng; + + +import org.testng.*; +import sbt.testing.EventHandler; +import sbt.testing.Logger; + +import com.beust.jcommander.JCommander; + +import java.net.URLClassLoader; +import java.util.Arrays; + +class TestNGListener implements ITestListener{ + EventHandler basket; + String lastName = ""; + public TestNGListener(EventHandler basket){ + this.basket = basket; + } + public void onTestStart(ITestResult iTestResult) { + String newName = iTestResult.getTestClass().getName() + " " + iTestResult.getName() + " "; + if(!newName.equals(lastName)){ + if (!lastName.equals("")){ + System.out.println(); + } + lastName = newName; + System.out.print(lastName); + } + } + + public void onTestSuccess(ITestResult iTestResult) { + System.out.print('+'); + basket.handle(ResultEvent.success(iTestResult)); + } + + public void onTestFailure(ITestResult iTestResult) { + System.out.print('X'); + basket.handle(ResultEvent.failure(iTestResult)); + } + + public void onTestSkipped(ITestResult iTestResult) { + System.out.print('-'); + basket.handle(ResultEvent.skipped(iTestResult)); + } + + public void onTestFailedButWithinSuccessPercentage(ITestResult iTestResult) { + basket.handle(ResultEvent.failure(iTestResult)); + } + + public void onStart(ITestContext iTestContext) {} + + public void onFinish(ITestContext iTestContext) {} +} + +public class TestNGInstance extends TestNG{ + public TestNGInstance(Logger[] loggers, + ClassLoader testClassLoader, + CommandLineArgs args, + EventHandler eventHandler) { + addClassLoader(testClassLoader); + + this.addListener(new TestNGListener(eventHandler)); + + configure(args); + } +} + + diff --git a/contrib/testng/src/mill/testng/TestNGRunner.java b/contrib/testng/src/mill/testng/TestNGRunner.java new file mode 100644 index 00000000..0ad05f76 --- /dev/null +++ b/contrib/testng/src/mill/testng/TestNGRunner.java @@ -0,0 +1,76 @@ +package mill.testng; + +import com.beust.jcommander.JCommander; +import org.testng.CommandLineArgs; +import sbt.testing.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +class TestNGTask implements Task { + + TaskDef taskDef; + TestNGRunner runner; + CommandLineArgs cliArgs; + public TestNGTask(TaskDef taskDef, + TestNGRunner runner, + CommandLineArgs cliArgs){ + this.taskDef = taskDef; + this.runner = runner; + this.cliArgs = cliArgs; + } + + @Override + public String[] tags() { + return new String[0]; + } + + @Override + public Task[] execute(EventHandler eventHandler, Logger[] loggers) { + new TestNGInstance( + loggers, + runner.testClassLoader, + cliArgs, + eventHandler + ).run(); + return new Task[0]; + } + + @Override + public TaskDef taskDef() { + return taskDef; + } +} + +public class TestNGRunner implements Runner { + ClassLoader testClassLoader; + String[] args; + String[] remoteArgs; + public TestNGRunner(String[] args, String[] remoteArgs, ClassLoader testClassLoader) { + this.testClassLoader = testClassLoader; + this.args = args; + this.remoteArgs = remoteArgs; + } + + public Task[] tasks(TaskDef[] taskDefs) { + CommandLineArgs cliArgs = new CommandLineArgs(); + new JCommander(cliArgs, args); // args is an output parameter of the constructor! + if(cliArgs.testClass == null){ + String[] names = new String[taskDefs.length]; + for(int i = 0; i < taskDefs.length; i += 1){ + names[i] = taskDefs[i].fullyQualifiedName(); + } + cliArgs.testClass = String.join(",", names); + } + if (taskDefs.length == 0) return new Task[]{}; + else return new Task[]{new TestNGTask(taskDefs[0], this, cliArgs)}; + } + + public String done() { return null; } + + public String[] remoteArgs() { return remoteArgs; } + + public String[] args() { return args; } +} diff --git a/contrib/twirllib/src/mill/twirllib/TwirlModule.scala b/contrib/twirllib/src/mill/twirllib/TwirlModule.scala new file mode 100644 index 00000000..2df70a1f --- /dev/null +++ b/contrib/twirllib/src/mill/twirllib/TwirlModule.scala @@ -0,0 +1,56 @@ +package mill +package twirllib + +import coursier.{Cache, MavenRepository} +import mill.define.Sources +import mill.eval.PathRef +import mill.scalalib.Lib.resolveDependencies +import mill.scalalib._ +import mill.util.Loose + +import scala.io.Codec +import scala.util.Properties + +trait TwirlModule extends mill.Module { + + def twirlVersion: T[String] + + def twirlSources: Sources = T.sources { + millSourcePath / 'views + } + + def twirlClasspath: T[Loose.Agg[PathRef]] = T { + resolveDependencies( + Seq( + Cache.ivy2Local, + MavenRepository("https://repo1.maven.org/maven2") + ), + Lib.depToDependency(_, "2.12.4"), + Seq( + ivy"com.typesafe.play::twirl-compiler:${twirlVersion()}", + ivy"org.scala-lang.modules::scala-parser-combinators:1.1.0" + ) + ) + } + + // REMIND currently it's not possible to override these default settings + private def twirlAdditionalImports: Seq[String] = Nil + + private def twirlConstructorAnnotations: Seq[String] = Nil + + private def twirlCodec: Codec = Codec(Properties.sourceEncoding) + + private def twirlInclusiveDot: Boolean = false + + def compileTwirl: T[CompilationResult] = T.persistent { + TwirlWorkerApi.twirlWorker + .compile( + twirlClasspath().map(_.path), + twirlSources().map(_.path), + T.ctx().dest, + twirlAdditionalImports, + twirlConstructorAnnotations, + twirlCodec, + twirlInclusiveDot) + } +} diff --git a/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala b/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala new file mode 100644 index 00000000..f351ff2f --- /dev/null +++ b/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala @@ -0,0 +1,119 @@ +package mill +package twirllib + +import java.io.File +import java.lang.reflect.Method +import java.net.URLClassLoader + +import ammonite.ops.{Path, ls} +import mill.eval.PathRef +import mill.scalalib.CompilationResult + +import scala.io.Codec + +class TwirlWorker { + + private var twirlInstanceCache = Option.empty[(Long, TwirlWorkerApi)] + + private def twirl(twirlClasspath: Agg[Path]) = { + val classloaderSig = twirlClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum + twirlInstanceCache match { + case Some((sig, instance)) if sig == classloaderSig => instance + case _ => + val cl = new URLClassLoader(twirlClasspath.map(_.toIO.toURI.toURL).toArray) + val twirlCompilerClass = cl.loadClass("play.twirl.compiler.TwirlCompiler") + val compileMethod = twirlCompilerClass.getMethod("compile", + classOf[java.io.File], + classOf[java.io.File], + classOf[java.io.File], + classOf[java.lang.String], + cl.loadClass("scala.collection.Seq"), + cl.loadClass("scala.collection.Seq"), + cl.loadClass("scala.io.Codec"), + classOf[Boolean]) + + val defaultAdditionalImportsMethod = twirlCompilerClass.getMethod("compile$default$5") + val defaultConstructorAnnotationsMethod = twirlCompilerClass.getMethod("compile$default$6") + val defaultCodecMethod = twirlCompilerClass.getMethod("compile$default$7") + val defaultFlagMethod = twirlCompilerClass.getMethod("compile$default$8") + + val instance = new TwirlWorkerApi { + override def compileTwirl(source: File, + sourceDirectory: File, + generatedDirectory: File, + formatterType: String, + additionalImports: Seq[String], + constructorAnnotations: Seq[String], + codec: Codec, + inclusiveDot: Boolean) { + val o = compileMethod.invoke(null, source, + sourceDirectory, + generatedDirectory, + formatterType, + defaultAdditionalImportsMethod.invoke(null), + defaultConstructorAnnotationsMethod.invoke(null), + defaultCodecMethod.invoke(null), + defaultFlagMethod.invoke(null)) + } + } + twirlInstanceCache = Some((classloaderSig, instance)) + instance + } + } + + def compile(twirlClasspath: Agg[Path], + sourceDirectories: Seq[Path], + dest: Path, + additionalImports: Seq[String], + constructorAnnotations: Seq[String], + codec: Codec, + inclusiveDot: Boolean) + (implicit ctx: mill.util.Ctx): mill.eval.Result[CompilationResult] = { + val compiler = twirl(twirlClasspath) + + def compileTwirlDir(inputDir: Path) { + ls.rec(inputDir).filter(_.name.matches(".*.scala.(html|xml|js|txt)")) + .foreach { template => + val extFormat = twirlExtensionFormat(template.name) + compiler.compileTwirl(template.toIO, + inputDir.toIO, + dest.toIO, + s"play.twirl.api.$extFormat", + additionalImports, + constructorAnnotations, + codec, + inclusiveDot + ) + } + } + + sourceDirectories.foreach(compileTwirlDir) + + val zincFile = ctx.dest / 'zinc + val classesDir = ctx.dest / 'html + + mill.eval.Result.Success(CompilationResult(zincFile, PathRef(classesDir))) + } + + private def twirlExtensionFormat(name: String) = + if (name.endsWith("html")) "HtmlFormat" + else if (name.endsWith("xml")) "XmlFormat" + else if (name.endsWith("js")) "JavaScriptFormat" + else "TxtFormat" +} + +trait TwirlWorkerApi { + def compileTwirl(source: File, + sourceDirectory: File, + generatedDirectory: File, + formatterType: String, + additionalImports: Seq[String], + constructorAnnotations: Seq[String], + codec: Codec, + inclusiveDot: Boolean) +} + +object TwirlWorkerApi { + + def twirlWorker = new TwirlWorker() +} diff --git a/contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html b/contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html new file mode 100644 index 00000000..acadf615 --- /dev/null +++ b/contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html @@ -0,0 +1,6 @@ +@(title: String) +<html> + <body> + <h1>@title</h1> + </body> +</html>
\ No newline at end of file diff --git a/contrib/twirllib/test/src/mill/twirllib/HelloWorldTests.scala b/contrib/twirllib/test/src/mill/twirllib/HelloWorldTests.scala new file mode 100644 index 00000000..01576975 --- /dev/null +++ b/contrib/twirllib/test/src/mill/twirllib/HelloWorldTests.scala @@ -0,0 +1,75 @@ +package mill.twirllib + +import ammonite.ops.{Path, cp, ls, mkdir, pwd, rm, _} +import mill.util.{TestEvaluator, TestUtil} +import utest.framework.TestPath +import utest.{TestSuite, Tests, assert, _} + +object HelloWorldTests extends TestSuite { + + trait HelloBase extends TestUtil.BaseModule { + override def millSourcePath: Path = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.') + } + + trait HelloWorldModule extends mill.twirllib.TwirlModule { + def twirlVersion = "1.0.0" + } + + object HelloWorld extends HelloBase { + + object core extends HelloWorldModule { + override def twirlVersion = "1.3.15" + } + } + + val resourcePath: Path = pwd / 'contrib / 'twirllib / 'test / 'resources / "hello-world" + + def workspaceTest[T, M <: TestUtil.BaseModule](m: M, resourcePath: Path = resourcePath) + (t: TestEvaluator[M] => T) + (implicit tp: TestPath): T = { + val eval = new TestEvaluator(m) + rm(m.millSourcePath) + rm(eval.outPath) + mkdir(m.millSourcePath / up) + cp(resourcePath, m.millSourcePath) + t(eval) + } + + def compileClassfiles: Seq[RelPath] = Seq[RelPath]( + "hello.template.scala" + ) + + def tests: Tests = Tests { + 'twirlVersion - { + + 'fromBuild - workspaceTest(HelloWorld) { eval => + val Right((result, evalCount)) = eval.apply(HelloWorld.core.twirlVersion) + + assert( + result == "1.3.15", + evalCount > 0 + ) + } + } + 'compileTwirl - workspaceTest(HelloWorld) { eval => + val Right((result, evalCount)) = eval.apply(HelloWorld.core.compileTwirl) + + val outputFiles = ls.rec(result.classes.path) + val expectedClassfiles = compileClassfiles.map( + eval.outPath / 'core / 'compileTwirl / 'dest / 'html / _ + ) + assert( + result.classes.path == eval.outPath / 'core / 'compileTwirl / 'dest / 'html, + outputFiles.nonEmpty, + outputFiles.forall(expectedClassfiles.contains), + outputFiles.size == 1, + evalCount > 0 + ) + + // don't recompile if nothing changed + val Right((_, unchangedEvalCount)) = eval.apply(HelloWorld.core.compileTwirl) + + assert(unchangedEvalCount == 0) + } + } +} |