//////////////////////////////////////////////////////////////////////////////// // // 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 */ private 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 + UnitTester.timeout_plus; } /** * 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 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; } } }