| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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; |
| } |
| } |
| |
| } |