FlexUnit4/src/org/flexunit/runner/notification/RunNotifier.as (233 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.flexunit.runner.notification {
import flash.utils.*;
import org.flexunit.runner.Description;
import org.flexunit.runner.IDescription;
import org.flexunit.runner.Result;
/**
* The <code>RunNotifier</code> is a class that FlexUnit4 uses to notify registered <code>IRunListeners</code>
* of an event that occurred during testing. There is generally only one <code>RunNotifier</code>
* used in a test run at a time. <code>RunNotifier</code> is used by the <code>IRunner</code>
* classes to notify others of the following conditions:
*
* <ul>
* <li>For the Test Run: Started/Finished</li>
* <li>For an Individual Test: Started/Failed/Ignored/Finished/AssumptionFailures</li>
* </ul><br/>
*
* Each <code>IRunListener</code> that is to be registered or unregistered to the <code>RunNotifier</code> needs
* to call either the <code>#addRunListener()</code> or the <code>#removeRunListener()</code> method. When
* the <code>RunNotifier</code> encounters one of the conditions stated above, all registered
* <code>IRunListeners</code> will be notified.<br/>
*
* If one writes an <code>IRunner</code>, they may need to notify FlexUnit4 of their progress while
* running tests. This is accomplished by invoking the <code>IRunNotifier</code> passed to the
* implementation of <code>org.flexunit.runner.IRunner#run(RunNotifier)</code>.
*
* @see org.flexunit.runner.IRunner#run()
* @see org.flexunit.runner.notification.IRunListener
*/
public class RunNotifier implements IRunNotifier {
/**
* @private
*/
private var listeners:Array = new Array();
/**
* @private
*/
private var startTime:Number;
/**
* Do not invoke.
*/
public function fireTestRunStarted( description:IDescription ):void {
var notifier:SafeNotifier = new SafeNotifier( this, listeners );
notifier.notifyListener = function( item:IRunListener ):void {
item.testRunStarted( description );
}
notifier.run();
}
/**
* Do not invoke.
*/
public function fireTestRunFinished( result:Result ):void {
var notifier:SafeNotifier = new SafeNotifier( this, listeners );
notifier.notifyListener = function( item:IRunListener ):void {
item.testRunFinished( result );
}
notifier.run();
}
/**
* Invoke to tell listeners that an atomic test is about to start.
*
* @param description The description of the atomic test (generally a class and method name).
*
* @throws org.flexunit.runner.notification.StoppedByUserException Thrown if a user has
* requested that the test run stop.
*/
public function fireTestStarted( description:IDescription ):void {
var notifier:SafeNotifier = new SafeNotifier( this, listeners );
notifier.notifyListener = function( item:IRunListener ):void {
item.testStarted( description );
}
notifier.run();
//Capture start time
startTime = getTimer();
}
/**
* Invoke to tell listeners that an atomic test failed.
*
* @param failure The description of the test that failed and the exception thrown.
*/
public function fireTestFailure( failure:Failure ):void {
//capture end time
var endTime:Number = getTimer() - startTime;
var notifier:SafeNotifier = new SafeNotifier( this, listeners );
notifier.notifyListener = function( item:IRunListener ):void {
if ( item is ITemporalRunListener ) {
( item as ITemporalRunListener ).testTimed( failure.description, endTime );
}
item.testFailure(failure);
}
notifier.run();
}
/**
* Invoke to tell listeners that an atomic test flagged that it assumed
* something false.
*
* @param failure The description of the test that failed and the
* <code>AssumptionViolatedException</code> thrown.
*/
public function fireTestAssumptionFailed( failure:Failure ):void {
//capture end time
var endTime:Number = getTimer() - startTime;
var notifier:SafeNotifier = new SafeNotifier( this, listeners );
notifier.notifyListener = function( item:IRunListener ):void {
if ( item is ITemporalRunListener ) {
( item as ITemporalRunListener ).testTimed( failure.description, endTime );
}
item.testAssumptionFailure(failure);
}
notifier.run();
}
/**
* Invoke to tell listeners that an atomic test was ignored.
*
* @param description The description of the ignored test.
*/
public function fireTestIgnored( description:IDescription ):void {
//capture end time
var endTime:Number = getTimer() - startTime;
var notifier:SafeNotifier = new SafeNotifier( this, listeners );
notifier.notifyListener = function( item:IRunListener ):void {
if ( item is ITemporalRunListener ) {
( item as ITemporalRunListener ).testTimed( description, endTime );
}
item.testIgnored(description);
}
notifier.run();
}
/**
* Invoke to tell listeners that an atomic test finished. Always invoke
* <code>#fireTestFinished(IDescription)</code> if you invoke <code>#fireTestStarted(Description)</code>
* as listeners are likely to expect them to come in pairs.
*
* @param description The description of the test that finished.
*
* @see #fireTestStarted()
*/
public function fireTestFinished( description:IDescription ):void {
var endTime:Number = getTimer() - startTime;
var notifier:SafeNotifier = new SafeNotifier( this, listeners );
notifier.notifyListener = function( item:IRunListener ):void {
if ( item is ITemporalRunListener ) {
( item as ITemporalRunListener ).testTimed( description, endTime );
}
item.testFinished(description);
}
notifier.run();
}
/**
* Internal use only.
*/
public function addListener( listener:IRunListener ):void {
listeners.push( listener );
}
/**
* Internal use only. The Result's listener must be first.
*/
public function addFirstListener( listener:IRunListener ):void {
listeners.unshift( listener );
}
/** Internal use only.
*/
public function removeListener( listener:IRunListener ):void {
for ( var i:int=0; i<listeners.length; i++ ) {
if ( listeners[ i ] == listener ) {
listeners.splice( i, 1 );
break;
}
}
}
public function removeAllListeners():void {
listeners = new Array();
}
/**
* Constructor.
*/
public function RunNotifier() {
}
}
}
import org.flexunit.runner.Description;
import org.flexunit.runner.Result;
import org.flexunit.runner.notification.Failure;
import org.flexunit.runner.notification.IRunListener;
import org.flexunit.runner.notification.IRunNotifier;
import org.flexunit.runner.notification.RunListener;
import org.flexunit.runner.notification.RunNotifier;
class SafeNotifier {
protected var notifier:IRunNotifier;
protected var listeners:Array;
public function SafeNotifier( notifier:IRunNotifier, listeners:Array ) {
this.notifier = notifier;
this.listeners = listeners;
}
/**
* This method attempts to notify each listener.
* Should a listener throw an error for any reason, it is assumed that the listener
* is bad and should no longer be called. The listener will be removed and will
* no longer recieve any callbacks for any future notfications. A "Test mechanism"
* failure will be thrown should be a listener fail.
*
* If a listener needs to be able to throw errors and continue to recieve callbacks,
* it's up to the listener to use try/catch statements to handle the error.
**/
public function run():void {
for ( var i:int=0; i<listeners.length; i++ ) {
try {
notifyListener( listeners[ i ] as IRunListener );
} catch ( e:Error ) {
listeners.splice( i, 1 );
notifier.fireTestFailure( new Failure( Description.TEST_MECHANISM, e));
//since we have deleted, we might need to repeat
i--;
}
}
}
public var notifyListener:Function;
}