package cbt package test import java.io.File import java.nio.file._ import java.net.URL import java.util.{Iterator=>_,_} import scala.concurrent._ import scala.concurrent.duration._ // micro framework object Main{ def cbtMain(context: Context): ExitCode = { import context._ val _args = context.args val args = new Stage1ArgsParser(_args.toVector) implicit val logger: Logger = new Logger(args.enabledLoggers, System.currentTimeMillis) val lib = new Lib(logger) val mavenCache = cache ++ "/maven" val slow = ( System.getenv("CIRCLECI") != null // enable only on circle || args.args.contains("slow") ) val compat = !args.args.contains("no-compat") val shellcheck = !args.args.contains("no-shellcheck") val fork = args.args.contains("fork") val direct = args.args.contains("direct") if(!slow) System.err.println( "Skipping slow tests" ) if(!compat) System.err.println( "Skipping cbt version compatibility tests" ) if(fork) System.err.println( "Forking tests" ) if(direct) System.err.println( "Running tests in direct mode" ) if(shellcheck){ val pb = new ProcessBuilder( "/usr/bin/env", "shellcheck", (cbtHome / "cbt").string ) val p = pb.start val out = new java.io.InputStreamReader(p.getInputStream) val errors = Iterator.continually(out.read).takeWhile(_ != -1).map(_.toChar).mkString p.waitFor() match { case 0 => () case 127 => // bash exit code: not found System.err.println( "Shellcheck not found. Your build may fail during ci-build" ) case _ => throw new Exception( "Linting error in ./cbt bash launcher script:\n" + errors ) } } else System.err.println( "Skipping shellcheck" ) var successes = 0 var failures = 0 def assertException[T:scala.reflect.ClassTag](msg: String = "")(code: => Unit)(implicit logger: Logger) = { try{ code assert(false, msg) }catch{ case _:AssertionError => } } def assert(condition: Boolean, msg: String = "")(implicit logger: Logger) = { scala.util.Try{ Predef.assert(condition, "["++msg++"]") }.map{ _ => print(".") successes += 1 }.recover{ case e: AssertionError => println("FAILED") e.printStackTrace failures += 1 }.get } def runCbt(path: String, _args: Seq[String])(implicit logger: Logger): Result = { import java.io._ val workingDirectory = cbtHome / "test" / path if( fork ){ val allArgs = Seq((cbtHome / "cbt").string) ++ (if(direct) Seq("direct") else Nil) ++ _args ++ args.propsRaw logger.test(allArgs.toString) val pb = new ProcessBuilder( allArgs :_* ) pb.directory( workingDirectory ) val p = pb.start val serr = new InputStreamReader(p.getErrorStream); val sout = new InputStreamReader(p.getInputStream); import scala.concurrent.ExecutionContext.Implicits.global val err = Future(blocking(Iterator.continually(serr.read).takeWhile(_ != -1).map(_.toChar).mkString)) val out = Future(blocking(Iterator.continually(sout.read).takeWhile(_ != -1).map(_.toChar).mkString)) p.waitFor p.exitValue Result( p.exitValue === 0, Await.result( out, Duration.Inf ), Await.result( err, Duration.Inf ) ) } else { val c = context.copy( workingDirectory = workingDirectory, args = _args.drop(1), transientCache = new java.util.HashMap() ) val ( outVar, errVar, _ ) = lib.getOutErrIn val oldOut = outVar.get val oldErr = errVar.get val out = new ByteArrayOutputStream val err = new ByteArrayOutputStream val out2 = new ByteArrayOutputStream val err2 = new ByteArrayOutputStream try{ outVar.set(new PrintStream(out)) errVar.set(new PrintStream(err)) val exitValue = try{ scala.Console.withOut(out2)( scala.Console.withErr(err2)( lib.trapExitCode( lib.callReflective( DirectoryDependency(c,None), _args.headOption, c ) ) ) ) } catch { case scala.util.control.NonFatal(e) => lib.redirectOutToErr( e.printStackTrace ) ExitCode.Failure } System.out.flush System.err.flush Result( exitValue.integer === 0, out.toString ++ out2.toString, err.toString ++ err2.toString ) } finally { outVar.set(oldOut) errVar.set(oldErr) } } } case class Result(exit0: Boolean, out: String, err: String) def assertSuccess(res: Result, msg: => String)(implicit logger: Logger) = { assert(res.exit0, msg ++ res.toString) } // tests def usage(path: String)(implicit logger: Logger) = { val usageString = "Methods provided by CBT" val res = runCbt(path, Seq()) logger.test(res.toString) val debugToken = "usage " ++ path ++ " " assertSuccess(res,debugToken) //assert(res.out == "", res.err.toString) assert(res.out contains usageString, usageString + " not found in " ++ res.toString) } def compile(path: String)(implicit logger: Logger) = task("compile", path) def run(path: String)(implicit logger: Logger) = task("run", path) def task(name: String, path: String)(implicit logger: Logger) = { val res = runCbt(path, Seq(name)) val debugToken = name ++ " " ++ path ++ " " assertSuccess(res,debugToken) res // assert(res.err == "", res.err) // FIXME: enable this } def clean(path: String)(implicit logger: Logger) = { val res = runCbt(path, Seq("clean", "dry-run", "force")) val debugToken = "\n"++lib.red("Deleting") ++ " " ++ (cbtHome ++("/test/"++path++"/target")).toPath.toAbsolutePath.toString++"\n" val debugToken2 = "\n"++lib.red("Deleting") ++ " " ++ (cbtHome ++("/test/"++path)).toPath.toAbsolutePath.toString++"\n" assertSuccess(res,debugToken) assert(res.out == "", "should be empty: " + res.out) assert(res.err.contains(debugToken), debugToken ++ " missing from " ++ res.err.toString) assert( !res.err.contains(debugToken2), "Tried to delete too much: " ++ debugToken2 ++ " found in " ++ res.err.toString ) res.err.split("\n").filter(_.startsWith(lib.red("Deleting"))).foreach{ line => assert( line.size >= debugToken2.trim.size, "Tried to delete too much: " ++ line ++" debugToken2: " ++ debugToken2 ) } } logger.test( "Running tests " ++ _args.toList.toString ) implicit val transientCache: java.util.Map[AnyRef,AnyRef] = new java.util.HashMap implicit val classLoaderCache: ClassLoaderCache = new ClassLoaderCache( new java.util.HashMap ) def Resolver(urls: URL*) = MavenResolver(cbtLastModified, mavenCache, urls: _*) { val b = new BasicBuild(context){ override def dependencies = Resolver(mavenCentral).bind( MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0"), MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0") ) } val cp = b.classpath assert(cp.strings.distinct == cp.strings, "duplicates in classpath: " ++ cp.string) } { def d = Resolver(mavenCentral).bindOne( MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0") ) assert(d === d) } // test that messed up artifacts crash with an assertion (which should tell the user what's up) assertException[AssertionError](){ Resolver(mavenCentral).bindOne( MavenDependency("com.jcraft", "jsch", " 0.1.53") ).classpath } assertException[AssertionError](){ Resolver(mavenCentral).bindOne( MavenDependency("com.jcraft", null, "0.1.53") ).classpath } assertException[AssertionError](){ Resolver(mavenCentral).bindOne( MavenDependency("com.jcraft", "", " 0.1.53") ).classpath } assertException[AssertionError](){ Resolver(mavenCentral).bindOne( MavenDependency("com.jcraft%", "jsch", " 0.1.53") ).classpath } assertException[AssertionError](){ Resolver(mavenCentral).bindOne( MavenDependency("", "jsch", " 0.1.53") ).classpath } ( Dependencies( Resolver( bintray("tpolecat"), mavenCentral ).bind( lib.ScalaDependency("org.tpolecat","tut-core","0.4.2", scalaMajorVersion="2.11", verifyHash = false) ) ).classpath.strings ++ Dependencies( Resolver( sonatypeReleases ).bind( MavenDependency("org.cvogt","scala-extensions_2.11","0.5.1") ) ).classpath.strings ++ Dependencies( Resolver( mavenCentral ).bind( MavenDependency("ai.x","lens_2.11","1.0.0") ) ).classpath.strings ).foreach{ path => assert(new File(path).exists, path) } ScaffoldTest.main(Array()) Seq( "1.0" -> "1.0", "1.0-M1" -> "1.0-M1", "1.0.0" -> "1.0.0", "[1.0.0]" -> "1.0.0", "[1.0.0)" -> "1.0.0", "(1.0.0]" -> "1.0.0", "(1.0.0)" -> "1.0.0", "(,1.0.0]" -> "1.0.0", "[,1.0.0]" -> "1.0.0", "[,1.0.0)" -> "1.0.0", "(,1.0.0)" -> "1.0.0", "(1.0.0,]" -> "1.0.0", "[1.0.0,]" -> "1.0.0", "[1.0.0,)" -> "1.0.0", "(1.0.0,)" -> "1.0.0", "(1.0.0,1.0.1]" -> "1.0.0", "[1.0.0,1.0.1]" -> "1.0.0", "[1.0.0,1.0.1)" -> "1.0.0", "(1.0.0,1.0.1)" -> "1.0.0" ).foreach{ case (input, expected) => val actual = BoundMavenDependency.extractVersion(input) assert( expected === actual, s"for $input expected $expected got $actual" ) } usage("nothing") compile("nothing") //clean("nothing") usage("multi-build") compile("multi-build") clean("multi-build") usage("simple") compile("simple") run("simple") clean("simple") if( compat ){ usage("simple-fixed") compile("simple-fixed") } compile("../plugins/sbt_layout") compile("../plugins/scalafmt") compile("../plugins/scalajs") compile("../plugins/scalariform") compile("../plugins/wartremover") compile("../plugins/uber-jar") compile("../plugins/scalafix-compiler-plugin") compile("../examples/fork-example") compile("../examples/scalafmt-example") compile("../examples/scalariform-example") compile("../examples/scalatest-example") compile("../plugins/scalastyle") if(slow){ compile("../examples/scalajs-react-example/js") compile("../examples/scalajs-react-example/jvm") compile("../examples/scalajs-plain-example/js") compile("../examples/scalajs-plain-example/jvm") } compile("../examples/multi-standalone-example") compile("../examples/multi-combined-example") if(sys.props("java.version").startsWith("1.7")){ System.err.println("\nskipping dotty tests on Java 7") } else { compile("../examples/dotty-example") task("run","../examples/dotty-example") if(slow){ task("dottydoc","../examples/dotty-example") } } if(slow){ task("compile","../examples/scalajs-react-example/js") task("fullOpt.compile","../examples/scalajs-react-example/js") } compile("../examples/uber-jar-example") if( compat && fork ){ // FIXME: this should not be excluded in forking val res = task("docJar","simple-fixed-cbt") assert( res.out endsWith "simple-fixed-cbt_2.11-0.1-javadoc.jar\n", res.out ) assert( res.err contains "model contains", res.err ) assert( res.err endsWith "documentable templates\n", res.err ) } { val res = runCbt("simple", Seq("printArgs","1","2","3")) assert(res.exit0) assert(res.out == "1 2 3\n", res.out) } { val res = runCbt("../examples/build-info-example", Seq("run")) assert(res.exit0) assert(res.out contains "version: 0.1", res.out) } { val res = runCbt("../examples/scalastyle", Seq("scalastyle")) assert(!res.exit0) assert(res.err contains "Line contains a tab", res.out ++ "\n" ++ res.err) assert(res.out.isEmpty, res.out ++ "\n" ++ res.err) } { val res = runCbt("../examples/scalapb-example", Seq("run")) assert(res.exit0) assert(res.out contains "age: 123", res.out ++ "\n--\n" ++ res.err) } { val res = runCbt("broken-build/build-class-with-wrong-arguments", Seq("run")) assert(!res.exit0) assert(res.err contains s"Expected class ${lib.buildClassName}(val context: Context), but found different constructor", res.err) assert(res.err contains s"${lib.buildClassName}(int, interface cbt.Context)", res.err) } { val res = runCbt("broken-build/build-class-with-wrong-parent", Seq("run")) assert(!res.exit0) assert(res.err contains s"You need to define a class ${lib.buildClassName} extending an appropriate super class", res.err) } { val res = runCbt("broken-build/no-build-file", Seq("run")) assert(!res.exit0) assert(res.err contains s"No file ${lib.buildFileName} (lower case) found", res.err) } { val res = runCbt("broken-build/empty-build-file", Seq("run")) assert(!res.exit0) assert(res.err contains s"You need to define a class ${lib.buildClassName}", res.err) } { val res = runCbt("../examples/wartremover-example", Seq("compile")) assert(!res.exit0) assert(res.err.contains("var is disabled"), res.err) assert(res.err.contains("null is disabled"), res.err) } { val res = runCbt("../examples/dynamic-overrides-example", Seq("with","""def dummy = "1.2.3" """, "dummy")) assert(res.exit0) assert(res.out == "1.2.3\n", res.out ++ res.err) } { val res = runCbt("../examples/dynamic-overrides-example", Seq("with","""def dummy = "1.2.3" """, "dummy")) assert(res.exit0) assert(res.out == "1.2.3\n", res.out ++ res.err) } { val res = runCbt("../examples/dynamic-overrides-example", Seq("eval",""" scalaVersion; 1 + 1 """)) assert(res.exit0) assert(res.out == "2\n", res.out ++ "\n--\n" ++ res.err) } { val res = runCbt("../examples/dynamic-overrides-example", Seq("foo")) assert(res.exit0) assert(res.out == "Build\n", res.out ++ res.err) } { val res = runCbt("../examples/dynamic-overrides-example", Seq("bar")) assert(res.exit0) assert(res.out startsWith "Bar: DynamicBuild", res.out) assert(res.out startsWith "", res.out ++ res.err) } { val res = runCbt("../examples/dynamic-overrides-example", Seq("baz")) assert(res.exit0) assert(res.out startsWith "Bar: DynamicBuild", res.out ++ res.err) } { val res = runCbt("../examples/dynamic-overrides-example", Seq("bam")) assert(res.exit0) assert(res.out startsWith "Baz: DynamicBuild", res.out ++ res.err) } { val res = runCbt("../examples/dynamic-overrides-example", Seq("foo2")) assert(res.exit0) assert(res.out == "Build\n", res.out ++ res.err) } { val res = runCbt("../examples/dynamic-overrides-example", Seq("bar2")) assert(res.exit0) assert(res.out startsWith "Bar2: Some(DynamicBuild", res.out ++ res.err) } { val res = runCbt("../examples/dynamic-overrides-example", Seq("baz2")) assert(res.exit0) assert(res.out startsWith "Bar2: Some(DynamicBuild", res.out ++ res.err) } { val res = runCbt("../examples/cross-build-example", Seq("cross.scalaVersion")) assert(res.exit0) assert(res.out == "2.10.5\n2.11.7\n", res.out) } { val res = runCbt("../libraries/eval", Seq("test.run")) assert(res.exit0) assert(res.out.contains("All tests passed"), res.out) } { val res = runCbt("../examples/new-style-macros-example", Seq("run")) assert(res.exit0) assert(res.out.contains("hello, world!"), res.out) } { val res = runCbt("../examples/resources-example", Seq("run")) assert(res.exit0) assert(res.out.contains("via parent.parent: false 0"), res.out) } { val res = runCbt("../examples/resources-example", Seq("runFlat")) assert(res.exit0) assert(res.out.contains("via parent.parent: true 2"), res.out) } { val res = runCbt("../examples/cross-rewrite-example", Seq("cross.exportedClasspath")) assert(res.exit0) Seq("cats","scalaz","2.11.8","2.12.1").foreach( s => assert(res.out contains s, res.out) ) } { val sourceFile = cbtHome / "examples" / "scalafix-compiler-plugin-example" / "Main.scala" val sourceBefore = sourceFile.readAsString runCbt("../examples/scalafix-compiler-plugin-example", Seq("clean","force")) val res = runCbt("../examples/scalafix-compiler-plugin-example", Seq("compile")) assert(res.exit0) val sourceAfter = sourceFile.readAsString assert(!(sourceBefore contains "@volatile")) assert(!(sourceBefore contains ": Unit")) assert(!(sourceBefore contains ": String ")) assert(!(sourceBefore contains "import scala.collection.immutable")) assert(sourceAfter contains "@volatile") assert(sourceAfter contains ": Unit") assert(sourceAfter contains ": String ") assert(sourceAfter contains "import scala.collection.immutable") lib.write(sourceFile, sourceBefore) } { val res = runCbt("../examples/kindprojector-example", Seq()) assert(res.exit0) } if (slow) { import scala.xml._ val expected = (cbtHome / "examples" / "export-build-information" / "expected.xml").readAsString.replaceAll("CBT_HOME", cbtHome.getPath) val expectedXml = Utility.trim(XML.loadString(expected)) val res = runCbt("../examples/export-build-information", Seq("buildInfoXml")) assert(res.exit0) val resultXml = Utility.trim(XML.loadString(res.out)) assert(resultXml == expectedXml) } /* // currently fails with // java.lang.UnsupportedOperationException: scalafix.rewrite.ScalafixMirror.fromMirror $anon#typeSignature requires the semantic api { val sourceFile = cbtHome / "examples" / "scalafix-example" / "Main.scala" val sourceBefore = sourceFile.readAsString runCbt("../examples/scalafix-example", Seq("clean","force")) val res = runCbt("../examples/scalafix-example", Seq("compile")) assert(res.exit0) val sourceAfter = sourceFile.readAsString assert(!(sourceBefore contains "@volatile")) assert(!(sourceBefore contains ": Unit")) assert(!(sourceBefore contains ": String ")) assert(!(sourceBefore contains "import scala.collection.immutable")) assert(sourceAfter contains "@volatile") assert(sourceAfter contains ": Unit") assert(sourceAfter contains ": String ") assert(sourceAfter contains "import scala.collection.immutable") lib.write(sourceFile, sourceBefore) } */ System.err.println(" DONE!") System.err.println( successes.toString ++ " succeeded, "++ failures.toString ++ " failed" ) if(failures > 0) ExitCode.Failure else ExitCode.Success } }