Skip to content

Instantly share code, notes, and snippets.

@wolever
Created May 17, 2010 22:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wolever/404321 to your computer and use it in GitHub Desktop.
Save wolever/404321 to your computer and use it in GitHub Desktop.
package utils.testlisteners {
import flash.external.ExternalInterface;
import mx.events.DynamicEvent;
/**
* Reports the results of a TextTestListener into the browser, using HTML
* instad of Flash.
*/
public class HTMLTestReporter {
protected static var bootstrapJavascript:String = (<![CDATA[
(function() {
// Note: The Flash player will restart if it's resized or moved,
// which will break the connection to the debugger and other
// unhappy things.
// So, instead of trying to move it, we just slap this <pre>
// over top of it.
var logTarget = document.createElement("pre");
logTarget.id = "logTarget";
logTarget.style.position = "fixed";
logTarget.style.left = 0;
logTarget.style.top = 0;
logTarget.style.margin = 0;
logTarget.style.width = "100%";
logTarget.style.height = "100%";
logTarget.style.overflow = "auto";
document.body.appendChild(logTarget);
window.writeText = function(message) {
logTarget.textContent += message;
logTarget.scrollTop = logTarget.scrollHeight;
};
})();
]]>);
public static function setup(listener:TextTestListener):void {
ExternalInterface.call("eval", bootstrapJavascript);
listener.addEventListener("gotText",
function(event:DynamicEvent):void {
ExternalInterface.call("writeText", event.text);
});
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:adobe="http://www.adobe.com/2009/flexUnitUIRunner"
creationComplete="runTests();"
layout="absolute" xmlns:testlisteners="utils.testlisteners.*">
<mx:Script><![CDATA[
import tests.UtilsTestSuite;
import org.flexunit.runner.FlexUnitCore;
import utils.logger.Log;
import utils.logger.LogEvent;
import utils.testlisteners.HTMLTestReporter;
import utils.testlisteners.TextTestListener;
public function runTests():void {
var listener:TextTestListener = new TextTestListener();
HTMLTestReporter.setup(listener);
Log.addListener(function(event:LogEvent):void {
var msg:String = event.levelString + ": " + event.message + "\n";
listener.injectLogMessage(msg);
});
var core:FlexUnitCore;
core = new FlexUnitCore();
core.addListener(listener);
core.run(UtilsTestSuite);
}
]]></mx:Script>
</mx:Application>
package utils.testlisteners {
import flash.events.EventDispatcher;
import flex.lang.reflect.metadata.MetaDataAnnotation;
import mx.events.DynamicEvent;
import org.flexunit.runner.IDescription;
import org.flexunit.runner.Result;
import org.flexunit.runner.notification.Failure;
import org.flexunit.runner.notification.IRunListener;
import utils.create;
import utils.filter;
import utils.forEach;
import utils.printf;
/**
* A FlexUnit RunListener which emits plain text describing the status
* of the test run.
* Note: See the 'injectLogMessage' method, which allows log messages
* to be injected into the test run then displated if a test fails.
*/
[Event(name="gotText", type="mx.events.DynamicEvent")]
public class TextTestListener extends EventDispatcher implements IRunListener {
public static const QUIET:int = 0;
public static const VERBOSE:int = 1;
public var verbosity:int = VERBOSE;
public var stacktraceLimit:int = 20;
public var stacktraceShortenPaths:Boolean = true;
public var stacktraceShortenPathRe:RegExp = /\[.*([\/\\].*?[\/\\].*?)\]/
protected var startTime:Date;
protected var failures:Array;
protected var ignored:Array;
protected var logMessages:Array;
public function injectLogMessage(message:String):void {
// Just incase we get log messages before the tests start
if (logMessages)
logMessages.push(message);
}
protected function gotText(txtVerbosityOrTxt:*, ...args):void {
var txtVerbosity:int = verbosity;
var text:String;
if (txtVerbosityOrTxt is int) {
txtVerbosity = txtVerbosityOrTxt;
text = args[0];
args.splice(0, 1);
} else {
text = txtVerbosityOrTxt;
}
if (verbosity >= txtVerbosity) {
text = printf.apply(null, [text].concat(args));
dispatchEvent(create(DynamicEvent, "gotText", { text: text }));
}
}
/**
* Called before any tests have been run and the test run is starting.
*
* @param description The <code>IDescription</code> of the top most
* <code>IRunner</code>.
*/
public function testRunStarted(description:IDescription):void {
startTime = new Date();
failures = [];
ignored = [];
trace("ASDFSDF");
gotText(VERBOSE, "Starting tests...\n");
}
protected static function getMetadata(description:IDescription,
name:String):MetaDataAnnotation {
for each (var metadata:MetaDataAnnotation in description.getAllMetadata()) {
if (metadata.name == name)
return metadata;
}
throw Error("Metadata with name '" + name + "' not found.");
}
protected function showIgnored():void {
if (ignored.length <= 0)
return;
gotText("===================================" +
"===================================\n");
gotText("IGNORED\n");
gotText("-----------------------------------" +
"-----------------------------------\n");
for each (var description:IDescription in ignored) {
var ignore:MetaDataAnnotation = getMetadata(description, "Ignore");
var desc:String = "";
if (ignore.arguments.length > 0)
desc = ": " + ignore.arguments[0].key;
gotText(description.displayName + desc + "\n");
}
gotText("\n");
}
protected function showFailures():void {
if (failures.length <= 0)
return;
for each (var failureObj:Object in failures) {
var failure:Failure = failureObj.failure;
gotText("===================================" +
"===================================\n");
gotText("ERROR: %s\n", failure.description.displayName);
gotText("-----------------------------------" +
"-----------------------------------\n");
// Fix up the stack trace
// Shorten it up a bit...
var stackTrace:Array = failure.stackTrace.split("\n");
if (stackTrace.length > stacktraceLimit) {
var removed:int = stackTrace.length - stacktraceLimit;
stackTrace = stackTrace.slice(0, stacktraceLimit);
stackTrace.push("... and " + removed + " more ...");
}
// Shorten the paths
if (stacktraceShortenPaths) {
stackTrace = stackTrace.map(function(line:String, ..._):String {
return line.replace(stacktraceShortenPathRe, "[...$1]");
});
}
// Split out the error portion
stackTrace.reverse();
var _inTrace:Boolean = true;
// Find the error second to reduce the chance that the error
// will look like a stack trace.
var error:Array = filter(function(line:String):Boolean {
if (_inTrace && !line.match(/^\tat /))
_inTrace = false;
return !_inTrace;
}, stackTrace.slice(1));
stackTrace.splice(stackTrace.length - error.length, error.length);
error.reverse();
// Send out the stack trace
gotText(stackTrace.join("\n") + "\n");
gotText(error.join("\n") + "\n");
// Print any log messages associated with this failure
if (failureObj.logMessages.length > 0) {
gotText("---------------------- >> " +
"begin captured log" +
" << ----------------------\n");
forEach(failureObj.logMessages, gotText);
gotText("----------------------- >> " +
"end captured log" +
" << -----------------------\n");
}
gotText("\n");
}
}
/**
* Called when all tests have finished and the test run is done.
*
* @param result The <code>Result</code> of the test run, including all the tests
* that have failed.
*/
public function testRunFinished(result:Result):void {
showIgnored();
showFailures();
// Since 'quiet' just spits out a string of periods, we've got to
// manually stick a newline after it.
if (verbosity == QUIET)
gotText("\n");
gotText("-----------------------------------" +
"-----------------------------------\n");
var runTime:Number = ((new Date()).time - startTime.time)/1000;
gotText("Ran %s tests in %0.3fs\n\n", result.runCount, runTime);
var summary:Array = []
if (result.failureCount) {
summary.push(printf("failures=%s", result.failureCount));
}
if (result.ignoreCount) {
summary.push(printf("ignored=%s", result.ignoreCount));
}
var summaryStr:String = "";
if (summary.length > 0) {
summaryStr = printf(" (%s)", summary.join(", "));
}
if (result.failureCount > 0) {
gotText("FAILED%s\n", summaryStr);
} else {
gotText("OK%s\n", summaryStr);
}
}
/**
* Called when an atomic test is about to be begin.
*
* @param description The <code>IDescription</code> of the test that is about
* to be run (generally a class and method name).
*/
public function testStarted(description:IDescription):void {
logMessages = [];
gotText(VERBOSE, description.displayName + "... ");
}
/**
* Called when an atomic test has finished, whether the test succeeds or fails.
*
* @param description The <code>IDescription</code> of the test that finished.
*/
protected var _lastDidntSucceed:Boolean = false;
public function testFinished(description:IDescription):void {
if (_lastDidntSucceed) {
_lastDidntSucceed = false;
return;
}
if (verbosity >= VERBOSE)
gotText("ok\n");
else
gotText(".");
}
/**
* Called when an atomic test fails.
*
* @param failure The <code>Failure</code> indicating why the test has failed.
*/
public function testFailure(failure:Failure):void {
failures.push({ failure: failure, logMessages: logMessages });
_lastDidntSucceed = true;
if (verbosity >= VERBOSE)
gotText("FAILED\n");
else
gotText("F");
}
/**
* Called when an atomic test flags that it assumes a condition that is false.
*
* @param failure The <code>Failure</code> indicating the
* <code>AssumptionViolatedException</code> that was thrown.
*/
public function testAssumptionFailure(failure:Failure):void {
// do nothing - I don't like assumptions
}
/**
* Called when a test will not be run, generally because a test method is annotated
* with the <code>Ignore</code> tag.
*
* @param description The <code>IDescription</code> of the test that will be
* ignored.
*/
public function testIgnored(description:IDescription):void {
ignored.push(description);
_lastDidntSucceed = false;
if (verbosity >= VERBOSE)
gotText(description.displayName + "... IGNORED\n");
else
gotText("I");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment