//////////////////////////////////////////////////////////////////////////////// // // 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.BitmapData; import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.display.InteractiveObject; import flash.display.Stage; import flash.events.Event; import flash.events.ErrorEvent; import flash.events.FocusEvent; import flash.events.EventDispatcher; import flash.events.IOErrorEvent; import flash.events.ProgressEvent; import flash.events.SecurityErrorEvent; import flash.events.UncaughtErrorEvent; import flash.geom.Point; import flash.geom.Matrix; import flash.geom.Rectangle; import flash.net.Socket; import flash.system.ApplicationDomain; import flash.system.Security; import flash.system.fscommand; import flash.utils.Dictionary; import flash.utils.getQualifiedClassName; import flash.utils.Timer; import flash.utils.setTimeout; import flash.net.URLRequest; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import mx.core.mx_internal; use namespace mx_internal; [Mixin] /** * The test engine for unit testing Flex framework components. * A UnitTester gets linked in as a mixin and when initialized * finds a set of tests to run and runs them. * * Test scripts are written as MXML components derived from * this class, and contain a bunch of TestCases with TestStep- * derived child tags. They must also be [Mixin] and call * setScript. */ public class UnitTester extends EventDispatcher { /** * This holds settings which are considered in ConditionalValue * work. **/ public static var cv:ConditionalValue; /** * This tells whether to write baselines to disk. * Set by MobileConfigWriter. **/ public static var writeBaselinesToDisk:Boolean = false; /** * This tells UnitTester where it can write files. * Set by MobileConfigWriter. **/ public static var mustellaWriteLocation:String = ""; /** * This is the name of the exclude file. * Set by MobileConfigWriter. **/ public static var excludeFile:String = ""; /** * This is a placeholder. We don't do portrait and landscape runs right now * and probablay won't. Delete. **/ //public static var deviceOrientation:String = null; /** * port number that will be used by tests that require a webserver. */ public static var portNumber : Number=80; /** * additional wait before exit for coverage */ public static var coverageTimeout : Number = 0; /** * Last executed step */ public static var lastStep:TestStep = null; /** * Step # of that last step */ public static var lastStepLine:int = -1; /** * IGNORE all failures and only report passing. * This allows creation of multiple .bad.png files in testcases * where there are many CompareBitmap tags * DANGEROUS flag, for obvious reasons */ public static var noFail:Boolean = false; /** * a pixel tolerance multiplier that CompareBitmap will use to judge comparisons */ public static var pixelToleranceMultiplier:Number = 1; /** * Mixin callback that gets everything ready to go. * The UnitTester waits for an event before starting */ public static function init(root:DisplayObject):void { // don't let child swfs override this if (!_root) _root = root; /// set device if not set. if (cv == null){ cv = new ConditionalValue(); } if (cv.os == null) { cv.os = DeviceNames.getFromOS(); } if(root.loaderInfo != null && root.loaderInfo.parameters != null) { for (var ix:String in root.loaderInfo.parameters) { if(ix == "port") { portNumber = Number(root.loaderInfo.parameters[ix]); } else if(ix == "pixelToleranceMultiplier") { pixelToleranceMultiplier = Number(root.loaderInfo.parameters[ix]); } } } // load a run id if not loaded (used in full runs) if (!run_id_loaded) { /// esp. for MP, avoid doing this twice: run_id_loaded=true; /// avoid 304 returns from the web server: var endBit:String = "?" + Math.random() + new Date().time; reader = new URLLoader(); var req:URLRequest = new URLRequest(); /// by convention, we use the /staging alias for the vetting run workspace if (isVettingRun) { req.url = "http://localhost:" + portNumber + "/staging/runid.properties" + endBit; } else { req.url = "http://localhost:" + portNumber + "/runid.properties" + endBit; } reader.dataFormat=URLLoaderDataFormat.TEXT; reader.addEventListener(Event.COMPLETE, readCompleteHandler); reader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, readErrorHandler); reader.addEventListener(IOErrorEvent.IO_ERROR, readErrorHandler); reader.load(req); } // load a run id if not loaded (used in full runs) if (!timeout_plus_loaded) { timeout_reader = new URLLoader(); var req2:URLRequest = new URLRequest(); /// by convention, we use the /staging alias for the vetting run workspace req2.url = "http://localhost:" + runnerPort + "/step_timeout"; timeout_reader.dataFormat=URLLoaderDataFormat.TEXT; timeout_reader.addEventListener(Event.COMPLETE, timeout_readCompleteHandler); timeout_reader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, timeout_readErrorHandler); timeout_reader.addEventListener(IOErrorEvent.IO_ERROR, timeout_readErrorHandler); timeout_reader.load(req2); } var mixins:Array = root["info"]()["mixins"]; var appdom:ApplicationDomain = root["info"]().currentDomain; if (!appdom) appdom = ApplicationDomain.currentDomain; for (var i:int = 0; i < mixins.length; i++) { var c:Class = Class(appdom.getDefinition(mixins[i])); var o:Object = new c(); if (o is UnitTester && mixins[i] != "UnitTester") { var eventScripts:Array = scripts[o.startEvent]; if (!eventScripts) { eventScripts = scripts[o.startEvent] = new Array(); root.addEventListener(o.startEvent, pre_startEventHandler); } eventScripts.push(o); } } // if we're sandboxed and have no scripts, assume we're passive. if (!(root.loaderInfo.parentAllowsChild && root.loaderInfo.childAllowsParent)) { if (eventScripts == null) { sandboxed = true; sandboxHelper = new UnitTester(); root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.STRING_TO_OBJECT, sandboxStringToObjectHandler); root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.GET_BITMAP, sandboxGetBitmapHandler); root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.GET_EFFECTS, sandboxGetEffectsHandler); root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.GET_OBJECTS_UNDER_POINT, sandboxObjectsUnderPointHandler); root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.RESET_COMPONENT, sandboxResetComponentHandler); root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.MOUSEXY, sandboxMouseXYHandler); root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.GET_FOCUS, sandboxGetFocusHandler); root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.APP_READY, sandboxAppReadyHandler); trace("sending mustellaStarted"); root.loaderInfo.sharedEvents.dispatchEvent(new MustellaSandboxEvent(MustellaSandboxEvent.MUSTELLA_STARTED)); root.addEventListener("applicationComplete", applicationCompleteHandler); root.addEventListener("enterFrame", enterFrameHandler, false, -9999); return; } } else if(root.parent != root.stage && root.loaderInfo.applicationDomain != root.parent.parent.loaderInfo.applicationDomain) { root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.GET_EFFECTS, sandboxGetEffectsHandler); root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.MOUSEXY, sandboxMouseXYHandler); root.addEventListener("applicationComplete", applicationCompleteHandler); root.loaderInfo.sharedEvents.addEventListener(MustellaSandboxEvent.APP_READY, sandboxAppReadyHandler); } root.loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, uncaughtErrorHandler); /* uncaught exceptions should be grabbd by the global handler, making this obsolete try { if (RTESocketAddress) { RTESocket = new Socket(); RTESocket.connect(RTESocketAddress, 2561); RTESocket.addEventListener(ProgressEvent.SOCKET_DATA, RTEDefaultHandler, false, -1); RTESocket.addEventListener(IOErrorEvent.IO_ERROR, RTEIOErrorHandler); RTESocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, RTEIOErrorHandler); } } catch (e:Error) { } */ var g:Class = Class(appdom.getDefinition("mx.core.UIComponentGlobals")); if (g) g["catchCallLaterExceptions"] = true; if (eventScripts != null) { try { _root.stage.addEventListener("enterFrame", enterFrameHandler, false, -9999); } catch (e:Error) { _root.addEventListener("enterFrame", enterFrameHandler, false, -9999); } _root.addEventListener("focusIn", focusBlockingHandler, true); _root.addEventListener("focusOut", focusBlockingHandler, true); _root.addEventListener("deactivate", activateBlockingHandler, true); _root.addEventListener("activate", activateBlockingHandler, true); } } /** * Repeat variables. Used in leak testing. Set by mixin. */ public static var repeat:int = 0; public static var repeatCount:int = 0; /** * A test run id. This is loaded from a web served file. No id by default. */ public static var run_id:String = "-1"; /** * Indicate the run as vetting */ public static var isVettingRun:Boolean = false; /** * flag to skip reloading run id */ public static var run_id_loaded:Boolean = false; /** * flag to skip rechecking for timeout extender */ public static var timeout_plus_loaded:Boolean = false; /** * value to extend each test step timeout */ public static var timeout_plus:int = 0; /** * the URL loader for the run id */ public static var reader:URLLoader; /** * the URL loader for the run id */ public static var timeout_reader:URLLoader; /** * The directory that this test lives in */ public var testDir:String; /** * Whether or not we've seen the applicationComplete event */ public static var applicationComplete:Boolean = false; /** * Whether or not we're subordinate to another UnitTester in another sandbox */ public static var sandboxed:Boolean = false; /** * UnitTester used for sandbox work */ public static var sandboxHelper:UnitTester; /** * cache of known swfLoaders */ public static var swfLoaders:Dictionary = new Dictionary(true); /** * note if an RTE has been detected */ public static var hasRTE:Boolean = false; /** * The RTE trace */ public static var RTEMsg:String = ""; /** * For mini_run, we want to show the RTE */ public static var showRTE:Boolean = false; private static function uncaughtErrorHandler(e:flash.events.UncaughtErrorEvent):void { hasRTE = true; /// Not yet seen if (e is Error) { RTEMsg = format(e.error.getStackTrace()); } else if (e is ErrorEvent) { RTEMsg = format(e.error.getStackTrace()); } e.stopImmediatePropagation(); // preventDefault will swallow the dialog pop up that shows the RTE // for mini run, we want to show that; for server runs, just swallow it if (!showRTE) { e.preventDefault(); } } private static function format(msg0:String):String { var tmp:Array = null; var ret:String = ""; /// collapse newlines in the messages: //// TEST ON MAC var culprit:String = "\n"; var replaceChar:String = "^"; var fileSepPat:RegExp = /\\/g; var msg:String = msg0.replace (fileSepPat, "/"); if (msg.indexOf (culprit) != -1) { tmp = msg.split (culprit); for (var i:int = 0;i 0) { arr = arr.concat(e.obj as Array); } } } return arr; } /** * hide all sandboxed SWFLoaders */ public static function hideSandboxes():void { for (var p:* in swfLoaders) { var swfLoader:Object = p; if (swfLoader) { swfLoader.removeChildAt(0); } } } /** * hide all sandboxed SWFLoaders */ public static function showSandboxes():void { for (var p:* in swfLoaders) { var swfLoader:Object = p; if (swfLoader) { swfLoader.addChildAt(swfLoader.contentHolder, 0); } } } private static function getSWFLoaderVisibleBounds(obj:DisplayObject):Object { var pt:Point = obj.localToGlobal(new Point(0, 0)); var rect:Rectangle = new Rectangle(pt.x, pt.y, obj.width, obj.height); var p:DisplayObject = obj.parent; while (rect.width && rect.height) { pt = p.localToGlobal(new Point(0, 0)); var prect:Rectangle = new Rectangle(pt.x, pt.y, p.width, p.height); if ("viewMetrics" in p) { var o:Object = p; o = o.viewMetrics; prect.x += o.left; prect.y += o.top; prect.width -= o.right; prect.height -= o.bottom; } rect = prect.intersection(rect); p = p.parent; if (p == _root) break; } return { width: rect.width, height: rect.height }; } /** * see if sandbox has focus */ private static function applicationCompleteHandler(event:Event):void { applicationComplete = true } /** * see if sandbox has focus */ private static function sandboxAppReadyHandler(event:Event):void { // if we sent it, ignore it if (event is MustellaSandboxEvent) return; if (applicationComplete) { event["obj"] = true; return; } } /** * see if sandbox has focus */ private static function sandboxGetFocusHandler(event:Event):void { // if we sent it, ignore it if (event is MustellaSandboxEvent) return; if (_root.stage.focus) { event["obj"] = _root.stage.focus; return; } var focus:InteractiveObject = getFocus(); if (focus) event["obj"] = focus; } /** * reset component in sandbox */ private static function sandboxMouseXYHandler(event:Event):void { // if we sent it, ignore it if (event is MustellaSandboxEvent) return; var eventObj:Object = event; var stagePt:Point = Point(eventObj.obj); if (stagePt) { var pt:Point = _root.globalToLocal(stagePt); _root[mouseX] = pt.x; _root[mouseY] = pt.y; } else { _root[mouseX] = undefined; _root[mouseY] = undefined; } } /** * reset component in sandbox */ private static function sandboxResetComponentHandler(event:Event):void { // if we sent it, ignore it if (event is MustellaSandboxEvent) return; var rc:ResetComponent = new ResetComponent(); rc.target = Object(event).string; rc.className = Object(event).obj; sandboxHelper.startTests(); rc.execute(_root, sandboxHelper, new TestCase(), new TestResult()); } /** * get bitmap as bytearray */ private static function sandboxGetBitmapHandler(event:Event):void { // if we sent it, ignore it if (event is MustellaSandboxEvent) return; var arr2:Array = []; var data:Object = {}; var pt:Point = _root.localToGlobal(new Point(0, 0)); data.x = pt.x; data.y = pt.y; var bm:BitmapData = new BitmapData(event["obj"].width, event["obj"].height); try { bm.draw(_root, new Matrix(1, 0, 0, 1, 0, 0)); } catch (se:SecurityError) { hideSandboxes(); bm = new BitmapData(event["obj"].width, event["obj"].height); showSandboxes(); arr2 = getSandboxBitmaps(); } var arr:Array = []; data.bits = bm.getPixels(new Rectangle(0, 0, bm.width, bm.height)); data.bits.position = 0; data.width = bm.width; data.height = bm.height; arr.push(data); event["obj"] = arr.concat(arr2); } /** * get bitmap as bytearray */ private static function sandboxGetEffectsHandler(event:Event):void { // if we sent it, ignore it if (event is MustellaSandboxEvent) return; var effects:Boolean = false; var effectMgr:Class = Class(_root["topLevelSystemManager"]["info"]().currentDomain.getDefinition("mx.effects.EffectManager")); if (effectMgr) { effects = effectMgr[effectsInEffect](); } if (!effects) { effectMgr = Class(_root["topLevelSystemManager"]["info"]().currentDomain.getDefinition("mx.effects.Tween")); if (effectMgr) { effects = effectMgr[activeTweens].length > 0; } } if (!effects) effects = UnitTester.getSandboxedEffects(); if (effects) event["obj"] = true; } /** * Handle request from main Mustella UnitTester */ private static function sandboxStringToObjectHandler(event:Event):void { // if we sent it, ignore it if (event is MustellaSandboxEvent) return; // we got here because someone tried to access .content which is the // systemManager so we prepend that since stringToObject always starts // with the root.document event["obj"] = sandboxHelper.stringToObject(event["string"].length == 0 ? "" : "systemManager." + event["string"]); } /** * Handle request from main Mustella UnitTester */ private static function sandboxObjectsUnderPointHandler(event:Event):void { // if we sent it, ignore it if (event is MustellaSandboxEvent) return; var pt:Point = Object(event).obj as Point; var arr:Array = new Array(); _getObjectsUnderPoint(_root, pt, arr); Object(event).obj = arr; } /** * Player doesn't handle this correctly so we have to do it ourselves */ private static function _getObjectsUnderPoint(obj:DisplayObject, pt:Point, arr:Array):void { if (!obj.visible) return; try { if (!obj[$visible]) return; } catch (e:Error) { } if (obj.hitTestPoint(pt.x, pt.y, true)) { arr.push(obj); if (obj is DisplayObjectContainer) { var doc:DisplayObjectContainer = obj as DisplayObjectContainer; if ("rawChildren" in doc) { var rc:Object = doc["rawChildren"]; n = rc.numChildren; for (i = 0; i < n; i++) { _getObjectsUnderPoint(rc.getChildAt(i), pt, arr); } } else { if (doc.numChildren) { var n:int = doc.numChildren; for (var i:int = 0; i < n; i++) { var child:DisplayObject = doc.getChildAt(i); if (swfLoaders[doc] && child is flash.display.Loader) { // if sandboxed then ask it for its targets var e:MustellaSandboxEvent = new MustellaSandboxEvent(MustellaSandboxEvent.GET_OBJECTS_UNDER_POINT); e.obj = pt; flash.display.Loader(child).contentLoaderInfo.sharedEvents.dispatchEvent(e); if (e.obj is Array && e.obj.length > 0) { // add them and we're done var objs:Array = e.obj as Array; while (objs.length) arr.push(objs.shift()); } else _getObjectsUnderPoint(child, pt, arr); } else _getObjectsUnderPoint(child, pt, arr); } } } } } } /** * Whether or not to block focus events */ public static var blockFocusEvents:Boolean = true; /** * Whether to wait for Excludes to load from a file */ public static var waitForExcludes:Boolean = false; /** * Whether to wait for Includes to load from a file */ public static var waitForIncludes:Boolean = false; /** * The handler for blocking focus events */ private static function focusBlockingHandler(event:FocusEvent):void { if (blockFocusEvents && event.relatedObject == null) { event.stopImmediatePropagation(); // attempt restore focus if (event.type == "focusOut") _root.stage.focus = InteractiveObject(event.target); } } /** * Whether or not to block activation events */ public static var blockActivationEvents:Boolean = true; /** * The handler for blocking activation events */ private static function activateBlockingHandler(event:Event):void { if (blockActivationEvents) { event.stopImmediatePropagation(); } } /** * A simplified callLater mechanism for running our tests */ public static var callback:Function; /** * The handler for the enter frame */ private static function enterFrameHandler(event:Event):void { if (callback != null) { var cb:Function = callback; callback = null; cb(); } } /** * holder of startEvent occurence */ public static var sawStartEvent:Boolean = false; /** * holder of the event to pass to the real start */ private static var saveEvent:Event = null; public static function pre_startEventHandler(event:Event):void { _root["topLevelSystemManager"].addEventListener("callLaterError", callLaterErrorDefaultHandler, false, -1); if (event.type == "applicationComplete") { sawStartEvent=true; saveEvent= event; } if (sawStartEvent && !waitForExcludes && !waitForIncludes) { startEventHandler (saveEvent); } } /** * The handler for the start event that starts the sequence * of tests. */ public static function startEventHandler(event:Event):void { var eventScripts:Array = scripts[event.type]; var actualScripts:Array = []; var n:int = eventScripts.length; for (var i:int = 0; i < n; i++) { var name:String = eventScripts[i].scriptName; if (includeList) { if (!includeList[name]) { TestOutput.logResult("Script: " + name + " not in include list but we don't care"); // continue; } } if (excludeList) { if (excludeList[name]) { TestOutput.logResult("Script: " + name + " in exclude list"); continue; } } actualScripts.push(eventScripts[i]); } var scriptRunner:ScriptRunner = new ScriptRunner(); scriptRunner.addEventListener("scriptsComplete", scriptsCompleteHandler); scriptRunner.scripts = actualScripts; if (isApollo && waitForWindow) { callback = waitForWindowFunction; waitForWindowScripts = scriptRunner.runScripts; } else callback = scriptRunner.runScripts; } /** * The handler for when the script runner finishes */ private static function scriptsCompleteHandler(event:Event):void { var allDone:Boolean = true; var n:int = scripts.length; for (var i:int = 0; i < n; i++) { if (!scripts[i].isDone()) { allDone = false; break; } } if (originalRoot) _root = originalRoot; TestOutput.logResult("ScriptComplete: completely done"); _root[mouseX] = undefined; _root[mouseY] = undefined; setMouseXY(null); _root.removeEventListener("focusIn", focusBlockingHandler, true); _root.removeEventListener("focusOut", focusBlockingHandler, true); _root.removeEventListener("deactivate", activateBlockingHandler, true); _root.removeEventListener("activate", activateBlockingHandler, true); /* try { if (RTESocket) RTESocket.close(); } catch (e:Error) { } */ if (exitWhenDone) { setTimeout(exit, UnitTester.coverageTimeout); } } private static var frameCounter:int = 0; /** * the callback that waits for an air window to be created */ private static function waitForWindowFunction():void { var window:Object = new UnitTester().stringToObject(waitForWindow); if (window) { window.addEventListener("windowComplete", windowCompleteHandler); callback = waitForWindowFunction; frameCounter++; if (frameCounter > 2) // see code in Window.as enterFrameHandler { callback = waitForWindowScripts; window.removeEventListener("windowComplete", windowCompleteHandler); originalRoot = _root; _root = window["systemManager"]; } } else callback = waitForWindowFunction; } /** * the callback that waits for an air window to be ready */ private static function windowCompleteHandler(event:Event):void { callback = waitForWindowScripts; event.target.removeEventListener("windowComplete", windowCompleteHandler); originalRoot = _root; _root = event.target["systemManager"]; } public static var exit:Function = function ():void { fscommand ("quit"); }; private static var layoutManager:QName = new QName(mx_internal, "layoutManager"); private static var getTextField:QName = new QName(mx_internal, "getTextField"); private static var getTextInput:QName = new QName(mx_internal, "getTextInput"); private static var getLabel:QName = new QName(mx_internal, "getLabel"); private static var mouseX:QName = new QName(mx_internal, "_mouseX"); private static var mouseY:QName = new QName(mx_internal, "_mouseY"); private static var $visible:QName = new QName(mx_internal, "$visible"); /** * The list of tests to run by start event */ private static var scripts:Array = new Array(); /** * Whether we're running on Apollo. If true, * a mixin will set this variable and * CompareBitmaps will use a static call to Apollo methods * to resolve baseline URLs */ public static var isApollo:Boolean = false; /** * If isApollo=true, then if this is set to a dot-path * we will wait for the expression to become valid * and wait for a windowComplete event from the * object before actually running the test */ public static var waitForWindow:String; /** * function to call to run scripts when window is ready */ private static var waitForWindowScripts:Function; /** * remember the original root */ private static var originalRoot:DisplayObject; /** * Whether to check to see if the test is using * embedded fonts. This is set by mixin and is expensive * so it should only be used when new tests are created. * This is checked by CompareBitmap. */ public static var checkEmbeddedFonts:Boolean = false; /** * Whether to save out the bitmaps or compare them * Default is false, bitmaps are read in from the * url and compared to the target. * Include the CreateBitmapReferences class to * cause all scripts to write out the target's bitmap * to the url. */ public static var createBitmapReferences:Boolean = false; /** * Whether to display additional information during the * running of a test */ public static var verboseMode:Boolean = false; /** * Which port to talk to the Runner on * */ public static var runnerPort:int = 9999; /** * Whether to close the Standalone player when done * running the tests */ public static var exitWhenDone:Boolean = false; /** * Control over the running of a test */ public static var playbackControl:String = "play"; /** * When saving out bitmaps, the server to talk to * to save them */ public static var bitmapServerPrefix:String; /** * To upload failed bitmaps, this is the url to talk. Set by SaveBitmapFailure mixin */ public static var serverCopy:String; /** * To upload failed bitmaps, this is function assembles the url */ public static function urlAssemble (type:String, testDir:String, testFile:String, testCase:String, run_id:String):String { testDir=encodeURIComponent(testDir); testFile = encodeURIComponent(testFile); testCase = encodeURIComponent(testCase); var back:String = "type=" + type + "&testFile="+ testDir + testFile + "&testCase=" + testCase + "&runid=" + run_id; return UnitTester.serverCopy + back; } /** * currentTestID - a holder for other guys to know what's current */ public static var currentTestID:String; /** * currentScript - a holder for other guys to know what's current */ public static var currentScript:String; /** * the root display object (SystemManager) */ public static var _root:DisplayObject; /** * the list of tests to run (if not specified, runs all tests) */ public static var includeList:Object; /** * the list of tests not to run (if not specified, runs all tests) */ public static var excludeList:Object; /** * constructor */ public function UnitTester() { super(); scriptName = getQualifiedClassName(this); if (scriptName.indexOf("::") >= 0) scriptName = scriptName.substring(scriptName.indexOf("::") + 2); } /** * The name of the script */ public var scriptName:String; /** * The name of the swf this script test */ public var testSWF:String; /** * The event to wait for before starting this script */ public var startEvent:String = "applicationComplete"; /** * The list of TestCases */ public var testCases:Array; /** * The last event object captured in an AssertEvent */ public var lastEvent:Event; /** * The index into the list of TestCases that we are currently running */ private var currentIndex:int = 0; /** * overall count of cases excluded, across scripts. Sent with ScriptDone to Runner * if used. */ public static var excludedCount:int = 0; /** * The total number of testCases to run */ private var numTests:int; /** * a shortcut to the application's variables */ public function get application():Object { return _root["document"]; } /** * take an expression, find the object. * handles mx_internal:propName * a.b.c * getChildAt() */ public function stringToObject(s:*):Object { if (s == null || s == "") return _root["document"]; var original:String = s; try { var propName:* = s; if (s.indexOf("mx_internal:") == 0) propName = new QName(mx_internal, s.substring(12)); if (s.indexOf("getChildAt(") == 0 && s.indexOf(".") == -1) { s = s.substring(11); s = s.substring(0, s.indexOf(")")); return _root["document"].getChildAt(parseInt(s)); } if (s.indexOf("getLayoutElementAt(") == 0 && s.indexOf(".") == -1) { s = s.substring(19); s = s.substring(0, s.indexOf(")")); return _root["document"].getLayoutElementAt(parseInt(s)); } if (s.indexOf("getElementAt(") == 0 && s.indexOf(".") == -1) { s = s.substring(13); s = s.substring(0, s.indexOf(")")); return _root["document"].getElementAt(parseInt(s)); } if (s.indexOf("script:") == 0) { propName = s.substring(7); return this[propName]; } return _root["document"][propName]; } catch (e:Error) { // maybe it is a class var dot:int; var test:Object; var c:int; var cc:int = s.indexOf("::"); var gd:int = -1; var className:String = s; var obj:Object = _root["document"]; if (cc > 0) { gd = s.indexOf("getDefinition"); if (gd == -1) { dot = s.indexOf(".", cc); if (dot >= 0) { className = s.substring(0, dot); s = s.substring(dot + 1); } else s = ""; } } else dot = s.indexOf("."); try { if (gd == -1) { var appdom:ApplicationDomain = _root["info"]().currentDomain; if (!appdom) appdom = ApplicationDomain.currentDomain; obj = appdom.getDefinition(className); } } catch (e:Error) { if (dot == -1) return null; } if (dot == -1 && gd == -1) return obj; var q:QName = new QName(mx_internal, "contentHolder"); var list:Array = s.split("."); if (list[0].indexOf("script:") == 0) { obj = this; list[0] = list[0].substring(7); } while (list.length) { try { s = list.shift(); if (s.indexOf("mx_internal:") == 0) s = new QName(mx_internal, s.substring(12)); if (s is String && s.indexOf("getChildAt(") == 0) { s = s.substring(11); s = s.substring(0, s.indexOf(")")); obj = obj.getChildAt(parseInt(s)); } else if (s is String && s.indexOf("getLayoutElementAt(") == 0) { s = s.substring(19); s = s.substring(0, s.indexOf(")")); obj = obj.getLayoutElementAt(parseInt(s)); } else if (s is String && s.indexOf("getElementAt(") == 0) { s = s.substring(13); s = s.substring(0, s.indexOf(")")); obj = obj.getElementAt(parseInt(s)); } else if (s is String && s == "getTextField()") { obj = obj[getTextField](); } else if (s is String && s == "getTextInput()") { obj = obj[getTextInput](); } else if (s is String && s == "getLabel()") { obj = obj[getLabel](); } else if (s is String && s == "getTextFormat()") { obj = obj.getTextFormat(); } else if (s is String && s == "info()") { obj = obj.info(); } else if (s is String && s.indexOf("getDefinition(") == 0) { s = s.substring(14); dot = s.indexOf(")"); while (dot == -1) { s += "." + list.shift(); dot = s.indexOf(")"); } s = s.substring(0, dot); obj = obj.getDefinition(s); } else obj = obj[s]; } catch (se:SecurityError) { try { test = obj[q]; } catch (e:Error) { return null; } var event:MustellaSandboxEvent = new MustellaSandboxEvent(MustellaSandboxEvent.STRING_TO_OBJECT); event.string = list.join("."); if (!swfLoaders[obj]) { // cache known swfloaders, associate with string path c = original.lastIndexOf(event.string); swfLoaders[obj] = original.substr(0, c); } test.contentLoaderInfo.sharedEvents.dispatchEvent(event); return event.obj; } catch (e:Error) { return null; } // hunt for other swfloaders with other application domains // we shouldn't get here if the object is in another security domain // unless the object is sandboxed but the loading app is coming from file:: // This also assumes that the test script will access the SWFLoader's contentHolder // before doing any steps that require the swfLoaders list to be set up properly try { test = obj[q]; if (test is flash.display.Loader) { if (!swfLoaders[obj]) { var path:String = list.join("."); // cache known swfloaders, associate with string path c = original.lastIndexOf(path); swfLoaders[obj] = original.substr(0, c) + "content"; } } } catch (e:Error) { } } return obj; } return null; } /** * storage for value property */ private var _value:Object; /** * A variable used to hold results from valueExpressions */ public function get value():Object { return _value; } /** * A variable used to hold results from valueExpressions */ public function set value(v:Object):void { _value = v; valueChanged = true; } /** * Whether or not the value changed */ mx_internal var valueChanged:Boolean; /** * Whether or not the value changed */ mx_internal function resetValue():void { valueChanged = false; _value = null; } /** * The set of display objects at the start of the script. * Used to clean up by ResetComponent */ public var knownDisplayObjects:Dictionary = new Dictionary(true); /** * A timer used by TestCases to know when to give up waiting for something */ private var timer:Timer; /** * Create a timer that can check every second to see * if we're hung, and then run the test cases */ public function startTests():void { var r:Object = _root; r = r["topLevelSystemManager"]; r = r.rawChildren; var n:int = r.numChildren; for (var i:int = 0; i < n; i++) { knownDisplayObjects[r.getChildAt(i)] = 1; } if (!timer) { timer = new Timer(1000); timer.start(); } // if (RTESocket) // RTESocket.addEventListener(ProgressEvent.SOCKET_DATA, RTEHandler); _root["topLevelSystemManager"].addEventListener("callLaterError", callLaterErrorHandler); if (testCases) numTests = testCases.length; TestOutput.logResult("LengthOfTestcases: " + numTests); if (runTests()) testComplete(); } /** * The current test that is running */ public function get currentTest():TestCase { return testCases[currentIndex]; } /** * Run the test cases * Returns false if we have to wait for the TestCase to complete. * Returns true if no tests required waiting. */ private function runTests():Boolean { if (testDir == null || testDir == "" ) { testDir=""; } while (currentIndex < numTests) { if (hasRTE) { break; } var testCase:TestCase = testCases[currentIndex]; currentTestID = testCase.testID; var testName:String = testDir + scriptName + "$" + testCase.testID; currentScript = scriptName; if (includeList) { if (!includeList[testName]) { currentIndex++; continue; } } if (excludeList) { if (excludeList[testName]) { currentIndex++; excludedCount++; continue; } } // TestOutput.logResult("TestCase Start: " + testCase.testID); TestOutput.logResult("TestCase Start: " + testName); // add listener early. If runTest catches an exception it will call // runCompleteHandler before returning testCase.addEventListener("runComplete", runCompleteHandler); if (testCase.runTest(_root, timer, this)) { testCase.removeEventListener("runComplete", runCompleteHandler); var tr:TestResult = currentTest.testResult; if (!tr.hasStatus()) tr.result = TestResult.PASS; tr.endTime = new Date().time; TestOutput.logResult (tr.toString()); if (hasRTE) return true; } else { return false; } currentIndex++; } return true; } /** * The handler that receives notice from the current TestCase that it * is done */ private function runCompleteHandler(event:Event):void { var tr:TestResult = currentTest.testResult; if (!tr.hasStatus()) tr.result = TestResult.PASS; tr.endTime = new Date().time; TestOutput.logResult (tr.toString()); currentIndex++; if (UnitTester.playbackControl == "play") UnitTester.callback = runMoreTests; else UnitTester.callback = pauseHandler; } private function runMoreTests():void { if (runTests()) testComplete(); } /** * called when test script is finished */ private function testComplete():void { // if (RTESocket) // RTESocket.removeEventListener(ProgressEvent.SOCKET_DATA, RTEHandler); _root["topLevelSystemManager"].removeEventListener("callLaterError", callLaterErrorHandler); TestOutput.logResult("testComplete"); dispatchEvent(new Event("testComplete")); } /** * Determines which set of steps (setup, body, cleanup) to run next */ private function pauseHandler():void { if (UnitTester.playbackControl == "step") { UnitTester.playbackControl = "pause"; runMoreTests(); } else if (UnitTester.playbackControl == "play") runMoreTests(); else UnitTester.callback = pauseHandler; } private function RTEHandler(event:Event):void { var s:String = RTESocket.readUTFBytes(RTESocket.bytesAvailable); TestOutput.logResult("Exception caught by RTE Monitor."); var tr:TestResult = currentTest.testResult; tr.doFail (s); event.stopImmediatePropagation(); } private static function RTEDefaultHandler(event:Event):void { var s:String = RTESocket.readUTFBytes(RTESocket.bytesAvailable); TestOutput.logResult("Exception caught by RTE Monitor when no tests running."); TestOutput.logResult(s); } private function callLaterErrorHandler(event:Event):void { var o:Object = event; var s:String = o["error"].getStackTrace(); TestOutput.logResult("Exception caught by CallLater Monitor."); var tr:TestResult = currentTest.testResult; tr.doFail (s); event.stopImmediatePropagation(); var appdom:ApplicationDomain = _root["info"]().currentDomain; if (!appdom) appdom = ApplicationDomain.currentDomain; var g:Class = Class(appdom.getDefinition("mx.core.UIComponentGlobals")); if (g) { o = g[layoutManager]; while (true) { try { o.validateNow(); break; } catch (e:Error) { } } } } private static function callLaterErrorDefaultHandler(event:Event):void { var o:Object = event; var s:String = o["error"].getStackTrace(); TestOutput.logResult("Exception caught by CallLater Monitor when no tests running."); TestOutput.logResult(s); } private static function RTEIOErrorHandler(event:Event):void { } private static var typeInfoCache:Dictionary; /** * Helper used for object introspection. */ public function getTypeInfo(object:*):TypeInfo { if (UnitTester.typeInfoCache == null) { UnitTester.typeInfoCache = new Dictionary(); } var className:String = flash.utils.getQualifiedClassName(object); var typeInfo:TypeInfo = UnitTester.typeInfoCache[className]; if (typeInfo == null) { typeInfo = new TypeInfo(className); UnitTester.typeInfoCache[className] = typeInfo; } return typeInfo; } /** * Socket used by RTE monitor */ public static var RTESocket:Socket; /** * Socket used by RTE monitor */ public static var RTESocketAddress:String; } }