Browse files

Initial commit

  • Loading branch information...
0 parents commit 5e4c893165f83307f5bcf0da07438dbaf3d2eb83 Christoph Henkelmann committed Nov 12, 2010
55 .gitignore
@@ -0,0 +1,55 @@
+# use glob syntax.
+syntax: glob
+*.ser
+*.class
+*~
+*.bak
+*.off
+*.old
+.DS_Store
+
+# logs
+derby.log
+
+# eclipse conf file
+.settings
+.classpath
+.project
+.manager
+.scala_dependencies
+
+# building
+target
+build/target
+null
+tmp*
+dist
+test-output
+
+# sbt
+target
+lib_managed
+src_managed
+project/boot
+
+# db
+lift_proto*
+
+# other scm
+.svn
+.CVS
+.hg*
+
+# switch to regexp syntax.
+# syntax: regexp
+# ^\.pc/
+
+# IntelliJ
+*.iml
+*.ipr
+*.iws
+.idea
+
+# Pax Runner (for easy OSGi launching)
+runner
+
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010 Christoph Henkelmann
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 project/build.properties
@@ -0,0 +1,9 @@
+#Project properties
+#Sun Nov 07 17:43:48 CET 2010
+project.organization=eu.henkelmann
+project.name=junit_xml_listener
+sbt.version=0.7.4
+project.version=0.1
+def.scala.version=2.7.7
+build.scala.versions=2.7.7
+project.initialize=false
7 project/build/JUnitXmlListenerProject.scala
@@ -0,0 +1,7 @@
+import sbt._
+
+
+class JUnitXmlListenerProject(info: ProjectInfo) extends PluginProject(info) {
+ override def managedStyle = ManagedStyle.Maven
+ lazy val publishTo = Resolver.ssh("Christoph's Maven Repo", "taupo", "/srv/maven/")
+}
167 src/main/scala/eu/henkelmann/sbt/JUnitXmlTestsListener.scala
@@ -0,0 +1,167 @@
+package eu.henkelmann.sbt
+
+import _root_.sbt._
+import java.io.{StringWriter, PrintWriter, File}
+import java.net.InetAddress
+import scala.collection.mutable.ListBuffer
+import scala.xml.{Elem, Node, XML}
+import org.scalatools.testing.{Event => TEvent, Result => TResult, Logger => TLogger}
+/*
+The api for the test interface defining the results and events
+can be found here:
+https://github.com/harrah/test-interface
+*/
+
+/**
+ * A tests listener that outputs the results it receives in junit xml
+ * report format.
+ * @param outputDir path to the dir in which a folder with results is generated
+ */
+class JUnitXmlTestsListener(val outputDir:String) extends TestsListener
+{
+ /**Current hostname so we know which machine executed the tests*/
+ val hostname = InetAddress.getLocalHost.getHostName
+ /**The dir in which we put all result files. Is equal to the given dir + "/test-reports"*/
+ val targetDir = new File(outputDir + "/test-reports/")
+
+ /**all system properties as XML*/
+ val properties =
+ <properties> {
+ val iter = System.getProperties.entrySet.iterator
+ val props:ListBuffer[Node] = new ListBuffer()
+ while (iter.hasNext) {
+ val next = iter.next
+ props += <property name={next.getKey.toString} value={next.getValue.toString} />
+ }
+ props
+ }
+ </properties>
+
+ /** Gathers data for one Test Suite. We map test groups to TestSuites.
+ * Each TestSuite gets its own output file.
+ */
+ class TestSuite(val name:String) {
+ val events:ListBuffer[TEvent] = new ListBuffer()
+ val start = System.currentTimeMillis
+ var end = System.currentTimeMillis
+
+ /**Adds one test result to this suite.*/
+ def addEvent(e:TEvent) = e.result match {
+ case TResult.Skipped => {}
+ case _ => events += e
+ }
+
+ /** Returns a triplet with the number of errors, failures and the
+ * total numbers of tests in this suite.
+ */
+ def count():(Int, Int, Int) = {
+ var errors, failures = 0
+ for (e <- events) {
+ e.result match {
+ case TResult.Error => errors +=1
+ case TResult.Failure => failures +=1
+ case _ =>
+ }
+ }
+ (errors, failures, events.size)
+ }
+
+ /** Stops the time measuring and emits the XML for
+ * All tests collected so far.
+ */
+ def stop():Elem = {
+ end = System.currentTimeMillis
+ val duration = end - start
+
+ val (errors, failures, tests) = count()
+
+ val result = <testsuite hostname={hostname} name={name}
+ tests={tests + ""} errors={errors + ""} failures={failures + ""}
+ time={(duration/1000.0).toString} >
+ {properties}
+ {
+ for (e <- events) yield
+ <testcase classname={name} name={e.testName} time={"0.0"}> {
+ var trace:String = if (e.error!=null) {
+ val stringWriter = new StringWriter()
+ val writer = new PrintWriter(stringWriter)
+ e.error.printStackTrace(writer)
+ writer.flush()
+ stringWriter.toString
+ }
+ else {
+ ""
+ }
+ e.result match {
+ case TResult.Error if (e.error!=null) => <error message={e.error.getMessage} type={e.error.getClass.getName}>{trace}</error>
+ case TResult.Error => <error message={"No Exception or message provided"} />
+ case TResult.Failure if (e.error!=null) => <failure message={e.error.getMessage} type={e.error.getClass.getName}>{trace}</failure>
+ case TResult.Failure => <failure message={"No Exception or message provided"} />
+ case _ => {}
+ }
+ }
+ </testcase>
+
+ }
+ <system-out><![CDATA[]]></system-out>
+ <system-err><![CDATA[]]></system-err>
+ </testsuite>
+
+ result
+ }
+ }
+
+ /**The currently running test suite*/
+ var testSuite:TestSuite = null
+
+ /**Creates the output Dir*/
+ def doInit() = {targetDir.mkdirs()}
+
+ /** Starts a new, initially empty Suite with the given name.
+ */
+ def startGroup(name: String) {testSuite = new TestSuite(name)}
+
+ /** Adds all details for the given even to the current suite.
+ */
+ def testEvent(event: TestEvent): Unit = for (e <- event.detail) {testSuite.addEvent(e)}
+
+ /** called for each class or equivalent grouping
+ * We map one group to one Testsuite, so for each Group
+ * we create an XML like this:
+ * <?xml version="1.0" encoding="UTF-8" ?>
+ * <testsuite errors="x" failures="y" tests="z" hostname="example.com" name="eu.henkelmann.bla.SomeTest" time="0.23">
+ * <properties>
+ * <property name="os.name" value="Linux" />
+ * ...
+ * </properties>
+ * <testcase classname="eu.henkelmann.bla.SomeTest" name="testFooWorks" time="0.0" >
+ * <error message="the foo did not work" type="java.lang.NullPointerException">... stack ...</error>
+ * </testcase>
+ * <testcase classname="eu.henkelmann.bla.SomeTest" name="testBarThrowsException" time="0.0" />
+ * <testcase classname="eu.henkelmann.bla.SomeTest" name="testBaz" time="0.0">
+ * <failure message="the baz was no bar" type="junit.framework.AssertionFailedError">...stack...</failure>
+ * </testcase>
+ * <system-out><![CDATA[]]></system-out>
+ * <system-err><![CDATA[]]></system-err>
+ * </testsuite>
+ *
+ * I don't know how to measure the time for each testcase, so it has to remain "0.0" for now :(
+ */
+ def endGroup(name: String, t: Throwable) = {
+ System.err.println("Throwable escaped the test run of '" + name + "': " + t)
+ t.printStackTrace(System.err)
+ }
+
+ /** Ends the current suite, wraps up the result and writes it to an XML file
+ * in the output folder that is named after the suite.
+ */
+ def endGroup(name: String, result: Result.Value) = {
+ XML.saveFull (new File(targetDir, testSuite.name + ".xml").getAbsolutePath, testSuite.stop(), "UTF-8", true, null)
+ }
+
+ /**Does nothing, as we write each file after a suite is done.*/
+ def doComplete(finalResult: Result.Value): Unit = {}
+
+ /**Returns None*/
+ override def contentLogger: Option[TLogger] = None
+}

0 comments on commit 5e4c893

Please sign in to comment.