mirror of https://github.com/sbt/sbt.git
* Seth's patch for running javac in the same jvm
* Fixed LoggerWriter, added test * Added it test
This commit is contained in:
parent
19c70cf9d3
commit
5b833a6b72
|
|
@ -16,7 +16,7 @@ object CompileTest extends Specification
|
|||
WithCompiler( "2.7.5" )(testCompileAnalysis)
|
||||
WithCompiler( "2.7.7" )(testCompileAnalysis)
|
||||
WithCompiler( "2.8.0.Beta1" )(testCompileAnalysis)
|
||||
//WithCompiler( "2.8.0-SNAPSHOT" )(testCompileAnalysis)
|
||||
WithCompiler( "2.8.0-SNAPSHOT" )(testCompileAnalysis)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import FileUtilities.withTemporaryDirectory
|
|||
object TestCompile
|
||||
{
|
||||
// skip 2.7.3 and 2.7.4 for speed
|
||||
def allVersions = List("2.7.2", "2.7.5", "2.7.7", "2.8.0.Beta1")//List("2.7.2", "2.7.3", "2.7.4", "2.7.5", "2.8.0-SNAPSHOT")
|
||||
def allVersions = List("2.7.2", "2.7.5", "2.7.7", "2.8.0.Beta1", "2.8.0-SNAPSHOT")//List("2.7.2", "2.7.3", "2.7.4", "2.7.5", "2.8.0-SNAPSHOT")
|
||||
/** Tests running the compiler interface with the analyzer plugin with a test callback. The test callback saves all information
|
||||
* that the plugin sends it for post-compile analysis by the provided function.*/
|
||||
def apply[T](scalaVersion: String, sources: Set[File], outputDirectory: File, options: Seq[String], superclassNames: Seq[String], annotationNames: Seq[String])
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah
|
||||
* Copyright 2008, 2009 Mark Harrah, Seth Tisue
|
||||
*/
|
||||
package sbt
|
||||
|
||||
|
|
@ -79,11 +79,30 @@ final class Compile(maximumErrors: Int, compiler: AnalyzingCompiler, analysisCal
|
|||
{
|
||||
val augmentedClasspath = if(compiler.autoBootClasspath) classpath + compiler.scalaInstance.libraryJar else classpath
|
||||
val arguments = (new CompilerArguments(compiler.scalaInstance, false, compiler.compilerOnClasspath))(sources, augmentedClasspath, outputDirectory, options)
|
||||
log.debug("Calling 'javac' with arguments:\n\t" + arguments.mkString("\n\t"))
|
||||
def javac(argFile: File) = Process("javac", ("@" + normalizeSlash(argFile.getAbsolutePath)) :: Nil) ! log
|
||||
val code = withArgumentFile(arguments)(javac)
|
||||
log.debug("running javac with arguments:\n\t" + arguments.mkString("\n\t"))
|
||||
val code: Int =
|
||||
try { directJavac(arguments, log) }
|
||||
catch { case e: ClassNotFoundException => forkJavac(arguments, log) }
|
||||
log.debug("javac returned exit code: " + code)
|
||||
if( code != 0 ) throw new CompileFailed(arguments.toArray, "javac returned nonzero exit code")
|
||||
}
|
||||
private def forkJavac(arguments: Seq[String], log: Logger): Int =
|
||||
{
|
||||
log.debug("com.sun.tools.javac.Main not found; forking javac instead")
|
||||
def externalJavac(argFile: File) = Process("javac", ("@" + normalizeSlash(argFile.getAbsolutePath)) :: Nil) ! log
|
||||
withArgumentFile(arguments)(externalJavac)
|
||||
}
|
||||
private def directJavac(arguments: Seq[String], log: Logger): Int =
|
||||
{
|
||||
val writer = new java.io.PrintWriter(new LoggerWriter(log, Level.Error))
|
||||
val argsArray = arguments.toArray
|
||||
val javac = Class.forName("com.sun.tools.javac.Main")
|
||||
log.debug("Calling javac directly.")
|
||||
javac.getDeclaredMethod("compile", classOf[Array[String]], classOf[java.io.PrintWriter])
|
||||
.invoke(null, argsArray, writer)
|
||||
.asInstanceOf[java.lang.Integer]
|
||||
.intValue
|
||||
}
|
||||
}
|
||||
trait WithArgumentFile extends NotNull
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah
|
||||
* Copyright 2008, 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
|
|
@ -355,10 +355,38 @@ object Level extends Enumeration with NotNull
|
|||
/** Same as apply, defined for use in pattern matching. */
|
||||
private[sbt] def unapply(s: String) = apply(s)
|
||||
}
|
||||
final class LoggerWriter(delegate: Logger, level: Level.Value) extends java.io.Writer
|
||||
/** Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at `level`.
|
||||
* A line is delimited by `nl`, which is by default the platform line separator.*/
|
||||
final class LoggerWriter(delegate: Logger, level: Level.Value, nl: String) extends java.io.Writer
|
||||
{
|
||||
override def flush() {}
|
||||
override def close() {}
|
||||
def this(delegate: Logger, level: Level.Value) = this(delegate, level, FileUtilities.Newline)
|
||||
|
||||
private[this] val buffer = new StringBuilder
|
||||
|
||||
override def close() = flush()
|
||||
override def flush(): Unit =
|
||||
synchronized {
|
||||
if(buffer.length > 0)
|
||||
{
|
||||
log(buffer.toString)
|
||||
buffer.clear()
|
||||
}
|
||||
}
|
||||
override def write(content: Array[Char], offset: Int, length: Int): Unit =
|
||||
delegate.log(level, new String(content, offset, length))
|
||||
synchronized {
|
||||
buffer.append(content, offset, length)
|
||||
process()
|
||||
}
|
||||
|
||||
private[this] def process()
|
||||
{
|
||||
val i = buffer.indexOf(nl)
|
||||
if(i >= 0)
|
||||
{
|
||||
log(buffer.substring(0, i))
|
||||
buffer.delete(0, i + nl.length)
|
||||
process()
|
||||
}
|
||||
}
|
||||
private[this] def log(s: String): Unit = delegate.log(level, s)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import org.specs._
|
||||
|
||||
class A extends Specification
|
||||
{
|
||||
"this" should {
|
||||
"not work" in { 1 must_== 2 }
|
||||
}
|
||||
}
|
||||
|
||||
object A extends Specification
|
||||
{
|
||||
"this" should {
|
||||
"not work" in { 1 must_== 2 }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import org.specs._
|
||||
|
||||
class A extends Specification
|
||||
{
|
||||
"this" should {
|
||||
"not work" in { 1 must_== 2 }
|
||||
}
|
||||
}
|
||||
|
||||
object A extends Specification
|
||||
{
|
||||
"this" should {
|
||||
"work" in { 1 must_== 1 }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import org.specs._
|
||||
|
||||
class A extends Specification
|
||||
{
|
||||
"this" should {
|
||||
"work" in { 1 must_== 1 }
|
||||
}
|
||||
}
|
||||
|
||||
object A extends Specification
|
||||
{
|
||||
"this" should {
|
||||
"not work" in { 1 must_== 2 }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import org.specs._
|
||||
|
||||
class A extends Specification
|
||||
{
|
||||
"this" should {
|
||||
"work" in { 1 must_== 1 }
|
||||
}
|
||||
}
|
||||
|
||||
object A extends Specification
|
||||
{
|
||||
"this" should {
|
||||
"work" in { 1 must_== 1 }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
project.name=Specs Test Type Test
|
||||
project.version=1.0
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import sbt._
|
||||
|
||||
class TestProject(info: ProjectInfo) extends DefaultProject(info) with BasicScalaIntegrationTesting
|
||||
{
|
||||
val specs = "org.scala-tools.testing" %% "specs" % "1.6.1" intransitive()
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#> ++2.8.0.Beta1-RC7
|
||||
> ++2.7.7
|
||||
> update
|
||||
|
||||
> clean
|
||||
$ delete src/
|
||||
$ copy-file changes/ClassFailModuleSuccess.scala src/it/scala/Test.scala
|
||||
-> integration-test
|
||||
|
||||
> clean
|
||||
$ delete src/
|
||||
$ copy-file changes/ClassFailModuleFail.scala src/it/scala/Test.scala
|
||||
-> integration-test
|
||||
|
||||
> clean
|
||||
$ delete src/
|
||||
$ copy-file changes/ClassSuccessModuleFail.scala src/it/scala/Test.scala
|
||||
-> integration-test
|
||||
|
||||
|
||||
> clean
|
||||
$ delete src/
|
||||
$ copy-file changes/ClassSuccessModuleSuccess.scala src/it/scala/Test.scala
|
||||
> integration-test
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2010 Mark Harrah */
|
||||
|
||||
package sbt
|
||||
|
||||
import org.scalacheck._
|
||||
import Arbitrary.{arbitrary => arb, _}
|
||||
import Gen.{oneOf, value}
|
||||
import Prop._
|
||||
|
||||
import java.io.Writer
|
||||
|
||||
object LogWriterTest extends Properties("Log Writer")
|
||||
{
|
||||
/* Tests that content written through a LoggerWriter is properly passed to the underlying Logger.
|
||||
* Each line, determined by the specified newline separator, must be logged at the correct logging level. */
|
||||
property("properly logged") = forAll { (output: Output, newLine: NewLine) =>
|
||||
import output.{lines, level}
|
||||
val log = new RecordingLogger
|
||||
val writer = new LoggerWriter(log, level, newLine.str)
|
||||
logLines(writer, lines, newLine.str)
|
||||
val events = log.getEvents
|
||||
("Recorded:\n" + events.map(show).mkString("\n")) |:
|
||||
check( toLines(lines), events, level)
|
||||
}
|
||||
|
||||
/** Displays a LogEvent in a useful format for debugging. In particular, we are only interested in `Log` types
|
||||
* and non-printable characters should be escaped*/
|
||||
def show(event: LogEvent): String =
|
||||
event match
|
||||
{
|
||||
case l: Log => "Log('" + Escape(l.msg) + "', " + l.level + ")"
|
||||
case _ => "Not Log"
|
||||
}
|
||||
/** Writes the given lines to the Writer. `lines` is taken to be a list of lines, which are
|
||||
* represented as separately written segments (ToLog instances). ToLog.`byCharacter`
|
||||
* indicates whether to write the segment by character (true) or all at once (false)*/
|
||||
def logLines(writer: Writer, lines: List[List[ToLog]], newLine: String)
|
||||
{
|
||||
for(line <- lines; section <- line)
|
||||
{
|
||||
val content = section.content
|
||||
val normalized = Escape.newline(content, newLine)
|
||||
if(section.byCharacter)
|
||||
normalized.foreach { c => writer.write(c.toInt) }
|
||||
else
|
||||
writer.write(normalized)
|
||||
}
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
/** Converts the given lines in segments to lines as Strings for checking the results of the test.*/
|
||||
def toLines(lines: List[List[ToLog]]): List[String] =
|
||||
lines.map(_.map(_.contentOnly).mkString)
|
||||
/** Checks that the expected `lines` were recorded as `events` at level `Lvl`.*/
|
||||
def check(lines: List[String], events: List[LogEvent], Lvl: Level.Value): Boolean =
|
||||
(lines zip events) forall {
|
||||
case (line, log : Log) => log.level == Lvl && line == log.msg
|
||||
case _ => false
|
||||
}
|
||||
|
||||
/* The following are implicit generators to build up a write sequence.
|
||||
* ToLog represents a written segment. NewLine represents one of the possible
|
||||
* newline separators. A List[ToLog] represents a full line and always includes a
|
||||
* final ToLog with a trailing '\n'. Newline characters are otherwise not present in
|
||||
* the `content` of a ToLog instance.*/
|
||||
|
||||
implicit lazy val arbOut: Arbitrary[Output] = Arbitrary(genOutput)
|
||||
implicit lazy val arbLog: Arbitrary[ToLog] = Arbitrary(genLog)
|
||||
implicit lazy val arbLine: Arbitrary[List[ToLog]] = Arbitrary(genLine)
|
||||
implicit lazy val arbNewLine: Arbitrary[NewLine] = Arbitrary(genNewLine)
|
||||
implicit lazy val arbLevel : Arbitrary[Level.Value] = Arbitrary(genLevel)
|
||||
|
||||
implicit def genLine(implicit logG: Gen[ToLog]): Gen[List[ToLog]] =
|
||||
for(l <- arbList[ToLog].arbitrary; last <- logG) yield
|
||||
(addNewline(last) :: l.filter(!_.content.isEmpty)).reverse
|
||||
|
||||
implicit def genLog(implicit content: Arbitrary[String], byChar: Arbitrary[Boolean]): Gen[ToLog] =
|
||||
for(c <- content.arbitrary; by <- byChar.arbitrary) yield
|
||||
{
|
||||
assert(c != null)
|
||||
new ToLog(removeNewlines(c), by)
|
||||
}
|
||||
|
||||
implicit lazy val genNewLine: Gen[NewLine] =
|
||||
for(str <- oneOf("\n", "\r", "\r\n")) yield
|
||||
new NewLine(str)
|
||||
|
||||
implicit lazy val genLevel: Gen[Level.Value] =
|
||||
oneOf(levelsGen : _*)
|
||||
|
||||
implicit lazy val genOutput: Gen[Output] =
|
||||
for(ls <- arbList[List[ToLog]].arbitrary; lv <- genLevel) yield
|
||||
new Output(ls, lv)
|
||||
|
||||
def levelsGen: Seq[Gen[Level.Value]] = Level.elements.toList.map(x => value(x))
|
||||
|
||||
def removeNewlines(s: String) = s.replaceAll("""[\n\r]+""", "")
|
||||
def addNewline(l: ToLog): ToLog =
|
||||
new ToLog(l.content + "\n", l.byCharacter) // \n will be replaced by a random line terminator for all lines
|
||||
}
|
||||
|
||||
/* Helper classes*/
|
||||
|
||||
final class Output(val lines: List[List[ToLog]], val level: Level.Value) extends NotNull
|
||||
{
|
||||
override def toString =
|
||||
"Level: " + level + "\n" + lines.map(_.mkString).mkString("\n")
|
||||
}
|
||||
final class NewLine(val str: String) extends NotNull
|
||||
{
|
||||
override def toString = Escape(str)
|
||||
}
|
||||
final class ToLog(val content: String, val byCharacter: Boolean) extends NotNull
|
||||
{
|
||||
def contentOnly = Escape.newline(content, "")
|
||||
override def toString = if(content.isEmpty) "" else "ToLog('" + Escape(contentOnly) + "', " + byCharacter + ")"
|
||||
}
|
||||
/** Defines some utility methods for escaping unprintable characters.*/
|
||||
object Escape
|
||||
{
|
||||
/** Escapes characters with code less than 20 by printing them as unicode escapes.*/
|
||||
def apply(s: String): String =
|
||||
{
|
||||
val builder = new StringBuilder(s.length)
|
||||
for(c <- s)
|
||||
{
|
||||
def escaped = pad(c.toInt.toHexString.toUpperCase, 4, '0')
|
||||
if(c < 20) builder.append("\\u").append(escaped) else builder.append(c)
|
||||
}
|
||||
builder.toString
|
||||
}
|
||||
def pad(s: String, minLength: Int, extra: Char) =
|
||||
{
|
||||
val diff = minLength - s.length
|
||||
if(diff <= 0) s else List.make(diff, extra).mkString("", "", s)
|
||||
}
|
||||
/** Replaces a \n character at the end of a string `s` with `nl`.*/
|
||||
def newline(s: String, nl: String): String =
|
||||
if(s.endsWith("\n")) s.substring(0, s.length - 1) + nl else s
|
||||
}
|
||||
/** Records logging events for later retrieval.*/
|
||||
final class RecordingLogger extends BasicLogger
|
||||
{
|
||||
private var events: List[LogEvent] = Nil
|
||||
|
||||
def getEvents = events.reverse
|
||||
|
||||
override def ansiCodesSupported = true
|
||||
def trace(t: => Throwable) { events ::= new Trace(t) }
|
||||
def log(level: Level.Value, message: => String) { events ::= new Log(level, message) }
|
||||
def success(message: => String) { events ::= new Success(message) }
|
||||
def logAll(es: Seq[LogEvent]) { events :::= es.toList }
|
||||
def control(event: ControlEvent.Value, message: => String) { events ::= new ControlEvent(event, message) }
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008 Mark Harrah
|
||||
* Copyright 2008, 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue