blob: db6e9377a8b0b45d47aa1a753fe03b49285da997 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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 {
import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.utils.getQualifiedClassName;
import flash.utils.getTimer;
import flash.utils.Timer;
/**
* A Test. A Test script comprises several of these
* TestCases. Each TestCase consists of a
* setup, body and cleanup section which are sets
* of TestStep-derived classes like SetProperty,
* RunCode, AssertPropertyValue, etc.
*
* MXML Properties (not attributes)
* setup
* body
* cleanup
*/
public class TestCase extends EventDispatcher
{
/**
* The history of bugs that this test case has encountered
*/
public var bugs:Array;
/**
* The sequence of TestSteps that comprise the test setup
*/
public var setup:Array;
/**
* The sequence of TestSteps that comprise the test
*/
public var body:Array;
/**
* The sequence of TestSteps that restore the test environment
*/
public var cleanup:Array;
/**
* An identifier for the test
*/
public var testID:String;
/**
* A description of the test
*/
public var description:String;
/**
* keywords, summarizing the tests
*/
public var keywords:String;
/**
* frequency, an estimate of the tests's intersection with real-world
* usage. 1 = most frequent usage; 2 somewhat common; 3 = unusual
*/
public var frequency:String;
/**
* The current set of steps (setup, body, cleanup) we are executing
*/
private var currentSteps:Array;
/**
* Which step we're currently executing (or waiting on an event for)
*/
private var currentIndex:int = 0;
/**
* Number of steps in currentSteps
*/
private var numSteps:int = 0;
/**
* The root of the SWF (SystemManager)
*/
private var root:DisplayObject;
/**
* The shared timer we listen to
*/
private var timer:Timer;
/**
* The unit tester
*/
private var context:UnitTester;
/**
* Whether we need to emit a runComplete event or not
*/
private var needCompleteEvent:Boolean = false;
/**
* If non-zero, the time when we'll give up on waiting
*/
public var expirationTime:int;
/**
* the last expected Error thrown by SetProperty
*/
public var lastError:Error;
/**
* Called by test steps looking for a timeout indicator
*/
public function setExpirationTime(time:int):void
{
expirationTime = (time > 0) ? time + UnitTester.timeout_plus : 0;
}
/**
* Storage for the cleanupAsserts
*/
private var _cleanupAsserts:Array;
/**
* Steps we have to review at the end of the body to see if
* they failed or not. These steps monitor activity like
* checking for duplicate events or making sure unwanted events
* don't fire.
*/
public function get cleanupAsserts():Array
{
return _cleanupAsserts;
}
/**
* Storage for this tests's result
*/
private var _testResult:TestResult;
/**
* This tests's result
*/
public function get testResult():TestResult
{
_testResult.testID = testID;
return _testResult;
}
/**
* Constructor. Create the TestResult associated with this TestCase
*/
public function TestCase() {
_testResult = new TestResult();
_testResult.testID = testID;
_cleanupAsserts = [];
}
/**
* Called by the UnitTester when it is time to execute
* this test case.
*
* @param root The SystemManager
* @param timer The shared Timer;
* @param context the UnitTester that contains these tests
*/
public function runTest(root:DisplayObject, timer:Timer, context:UnitTester):Boolean
{
_testResult.beginTime = new Date().time;
_testResult.context = context;
this.timer = timer;
this.timer.addEventListener("timer", timerHandler);
this.root = root;
this.context = context;
if (UnitTester.hasRTE)
{
return true;
}
// do a sanity test here
if (UnitTester.verboseMode)
{
var needWaitEvent:Boolean = false;
var i:int;
if (setup)
{
for (i = 0; i < setup.length; i++)
{
if (setup[i] is ResetComponent)
needWaitEvent = true;
if (needWaitEvent)
{
if (setup[i].waitEvent)
{
needWaitEvent = false;
break;
}
}
}
if (needWaitEvent)
TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " ResetComponent used without any setup steps using a waitEvent\n");
}
var allAsserts:Boolean = true;
needWaitEvent = false;
for (i = 0; i < body.length; i++)
{
if (body[i] is SetProperty || body[i] is SetStyle)
needWaitEvent = true;
if (!(body[i] is Assert))
allAsserts = false;
if (allAsserts && body[i] is AssertEvent)
{
TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " no non-Assert steps in body before AssertEvent\n");
}
if (needWaitEvent)
{
if (body[i].waitEvent)
{
needWaitEvent = false;
break;
}
}
}
if (needWaitEvent)
TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " tests setting values without a waitEvent\n");
}
return runSetup();
}
/**
* Execute the setup portion of the test
*
* @param continuing If true, don't reset the counter to zero
* and just continue on with the test at the currentIndex
*/
private function runSetup(continuing:Boolean = false):Boolean
{
if (!testResult.hasStatus())
{
if (setup)
{
testResult.phase = TestResult.SETUP;
currentSteps = setup;
if (!continuing)
currentIndex = 0;
numSteps = setup.length;
// return if we need to wait for something
if (!runSteps())
return false;
}
}
return runBody();
}
/**
* Execute the body portion of the test
*
* @param continuing If true, don't reset the counter to zero
* and just continue on with the test at the currentIndex
*/
private function runBody(continuing:Boolean = false):Boolean
{
if (!testResult.hasStatus())
{
if (body)
{
testResult.phase = TestResult.BODY;
currentSteps = body;
if (!continuing)
currentIndex = 0;
numSteps = body.length;
// return if we need to wait for something
if (!runSteps())
return false;
}
}
return runCleanup();
}
/**
* Execute the cleanup portion of the test
*
* @param continuing If true, don't reset the counter to zero
* and just continue on with the test at the currentIndex
*/
private function runCleanup(continuing:Boolean = false):Boolean
{
if (!testResult.hasStatus())
{
if (cleanup)
{
testResult.phase = TestResult.CLEANUP;
currentSteps = cleanup;
if (!continuing)
currentIndex = 0;
numSteps = cleanup.length;
// return if we need to wait for something
if (!runSteps())
return false;
}
}
return runComplete();
}
/**
* Clean up when all three phases are done. Sends an event
* to the UnitTester harness to tell it that it can run
* the next test case.
*/
private function runComplete():Boolean
{
var n:int = cleanupAsserts.length;
for (var i:int = 0; i < n; i++)
{
var assert:Assert = cleanupAsserts[i];
assert.cleanup();
}
timer.removeEventListener("timer", timerHandler);
if (needCompleteEvent)
dispatchEvent(new Event("runComplete"));
return true;
}
/**
* Go through the currentSteps, executing each one.
* Returns true if no test steps required waiting.
* Returns false if we have to wait for an event before
* continuing.
*/
private function runSteps():Boolean
{
while (currentIndex < numSteps)
{
// return if a step failed
if (testResult.hasStatus())
return true;
/*
The following lets you step each step but makes tests
more sensitive to which step actually sends the event.
For example, cb.mouseDown/mouseUp, the tween starts on
mousedown and fires its event before you step. Another
example is asserting with waitEvent=updateComplete. It
could have fired a long time before you step
*/
if (UnitTester.playbackControl == "pause")
{
UnitTester.callback = pauseHandler;
needCompleteEvent = true;
return false;
}
else if (UnitTester.playbackControl == "step")
UnitTester.playbackControl = "pause";
var step:TestStep = currentSteps[currentIndex];
if (!(step is Assert))
{
// look at subsequent steps for Asserts and set them up early
for (var j:int = currentIndex + 1; j < numSteps; j++)
{
// scan following asserts for AssertEvents and set them up early
var nextStep:TestStep = currentSteps[j];
if (nextStep is Assert)
{
Assert(nextStep).preview(root, context, this, testResult);
// do a check to be sure folks are using AssertEventPropertyValue correctly
if (nextStep is AssertEventPropertyValue)
{
// AEPV must follow an AssertEvent or another AEPV
if (j == 0 || !(currentSteps[j-1] is AssertEvent || currentSteps[j-1] is AssertEventPropertyValue))
TestOutput.logResult("WARNING: AssertEventPropertyValue may be missing preceding AssertEvent");
}
else if (nextStep is AssertError)
{
if (step is SetProperty)
SetProperty(step).expectError = true;
}
}
else
break;
}
}
if (UnitTester.verboseMode)
{
TestOutput.logResult(step.toString());
}
UnitTester.lastStep = step;
UnitTester.lastStepLine = currentIndex;
step.addEventListener("stepComplete", stepCompleteHandler);
if (!step.execute(root, context, this, testResult))
{
needCompleteEvent = true;
return false;
}
step.removeEventListener("stepComplete", stepCompleteHandler);
currentIndex++;
}
return true;
}
private function stepCompleteHandler(event:Event):void
{
var step:TestStep = currentSteps[currentIndex];
step.removeEventListener("stepComplete", stepCompleteHandler);
if (UnitTester.playbackControl == "play")
UnitTester.callback = runNextSteps;
else
{
currentIndex++;
UnitTester.callback = pauseHandler;
}
}
/**
* Handler for timer events to see if we've waited too long
*/
private function timerHandler(event:Event):void
{
if (expirationTime > 0)
if (expirationTime < getTimer())
{
var step:TestStep = currentSteps[currentIndex];
step.timeoutCallback();
}
}
/**
* Determines which set of steps (setup, body, cleanup) to run next
*/
private function runNextSteps():void
{
currentIndex++;
if (currentSteps == setup)
runSetup(true);
else if (currentSteps == body)
runBody(true);
else if (currentSteps == cleanup)
runCleanup(true);
else
runComplete();
}
/**
* Determines which set of steps (setup, body, cleanup) to run next
*/
private function continueSteps():void
{
if (currentSteps == setup)
runSetup(true);
else if (currentSteps == body)
runBody(true);
else if (currentSteps == cleanup)
runCleanup(true);
else
runComplete();
}
/**
* Determines which set of steps (setup, body, cleanup) to run next
*/
private function pauseHandler():void
{
if (UnitTester.playbackControl == "step")
continueSteps();
else if (UnitTester.playbackControl == "play")
continueSteps();
else
UnitTester.callback = pauseHandler;
}
}
}