| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.ErrorEvent; |
| import flash.events.Event; |
| import flash.events.EventDispatcher; |
| import flash.events.FocusEvent; |
| import flash.events.IOErrorEvent; |
| import flash.events.ProgressEvent; |
| import flash.events.SecurityErrorEvent; |
| import flash.events.UncaughtErrorEvent; |
| import flash.geom.Matrix; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| import flash.net.Socket; |
| import flash.net.URLLoader; |
| import flash.net.URLLoaderDataFormat; |
| import flash.net.URLRequest; |
| import flash.system.ApplicationDomain; |
| import flash.system.Security; |
| import flash.system.fscommand; |
| import flash.utils.Dictionary; |
| import flash.utils.Timer; |
| import flash.utils.getQualifiedClassName; |
| import flash.utils.setTimeout; |
| |
| import mx.binding.Binding; |
| import mx.binding.BindingManager; |
| import mx.binding.FunctionReturnWatcher; |
| import mx.binding.PropertyWatcher; |
| import mx.binding.StaticPropertyWatcher; |
| import mx.binding.Watcher; |
| import mx.binding.XMLWatcher; |
| import mx.core.IMXMLObject; |
| 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 = ""; |
| |
| /** |
| * The location of the mustella test directory. |
| */ |
| public static var mustellaTestDir:String = ""; |
| |
| /** |
| * Set by either ExcludeFileLocation or ExcludeFileLocationApollo so they |
| * can be called back to load the exclude file after mustellaTestDir has |
| * been set in the init() method. |
| */ |
| public static var loadExcludeFile:Function; |
| |
| /** |
| * 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; |
| |
| /** |
| * waitEvent, if we're waiting for one. |
| */ |
| public static var waitEvent : String; |
| |
| /** |
| * UIComponentGlobals. |
| */ |
| public static var uiComponentGlobals : Object; |
| |
| /** |
| * 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 |
| { |
| |
| if (waitForExcludes && loadExcludeFile != null) |
| loadExcludeFile(); |
| |
| // 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 (cv.device == null) |
| { |
| if (Security.sandboxType == Security.APPLICATION) |
| cv.device = "air"; |
| } |
| |
| 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); |
| } |
| } |
| |
| var passive:Boolean; |
| try { |
| passive = !(root.loaderInfo.parentAllowsChild && root.loaderInfo.childAllowsParent); |
| } catch (e:Error) |
| { |
| // in single-frame apps, loaderInfo may not be ready |
| passive = false; |
| } |
| // if we're sandboxed and have no scripts, assume we're passive. |
| if (passive) |
| { |
| 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 = appdom.hasDefinition("mx.core.UIComponentGlobals") ? |
| Class(appdom.getDefinition("mx.core.UIComponentGlobals")) : null; |
| if (g) |
| { |
| g["catchCallLaterExceptions"] = true; |
| uiComponentGlobals = g; |
| } |
| |
| 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); |
| } |
| |
| if (appdom.hasDefinition("spark.components.supportClasses.RichEditableTextContainerManager")) |
| { |
| g = Class(appdom.getDefinition("spark.components.supportClasses.RichEditableTextContainerManager")); |
| if (g) |
| { |
| var q:QName = new QName(mx_internal, "hideCursor"); |
| g[q] = 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<tmp.length;i++) |
| { |
| |
| if (ret != "") |
| { |
| ret = ret + replaceChar + tmp[i]; |
| } |
| else |
| { |
| ret = tmp[i]; |
| } |
| |
| } |
| } |
| else |
| { |
| return msg; |
| } |
| |
| return ret; |
| |
| } |
| |
| |
| |
| |
| private static function readCompleteHandler(event:Event):void |
| { |
| run_id_loaded=true; |
| run_id = reader.data; |
| |
| if (run_id.indexOf ("=") != -1) |
| { |
| run_id = run_id.substring (run_id.indexOf ("=")+1); |
| } |
| |
| } |
| |
| private static function readErrorHandler(event:Event):void |
| { |
| trace ("runid.properties ERROR handler with: " + event); |
| run_id_loaded=true; |
| } |
| |
| |
| |
| private static function timeout_readCompleteHandler(event:Event):void |
| { |
| timeout_plus_loaded=true; |
| var tmp:String = timeout_reader.data; |
| |
| if (tmp.indexOf ("=") != -1) |
| { |
| timeout_plus = new int(tmp.substring (tmp.indexOf ("=")+1)) |
| } else { |
| timeout_plus = new int(tmp); |
| } |
| TestOutput.logResult("timeout_plus is: " + timeout_plus); |
| } |
| |
| private static function timeout_readErrorHandler(event:Event):void |
| { |
| timeout_plus_loaded=true; |
| } |
| |
| |
| /** |
| * set mouseXY in other SWFLoaders |
| */ |
| public static function getFocus():InteractiveObject |
| { |
| for (var p:* in swfLoaders) |
| { |
| var swfLoader:Object = p; |
| if (swfLoader) |
| { |
| var e:MustellaSandboxEvent = new MustellaSandboxEvent(MustellaSandboxEvent.GET_FOCUS); |
| swfLoader.contentHolder.contentLoaderInfo.sharedEvents.dispatchEvent(e); |
| if (e["obj"] != null) |
| return e["obj"]; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * set mouseXY in other SWFLoaders |
| */ |
| public static function setMouseXY(stagePt:Point):void |
| { |
| for (var p:* in swfLoaders) |
| { |
| var swfLoader:Object = p; |
| if (swfLoader) |
| { |
| try |
| { |
| if (stagePt) |
| { |
| var pt:Point = swfLoader.content.globalToLocal(stagePt); |
| swfLoader.content[mouseX] = pt.x; |
| swfLoader.content[mouseY] = pt.y; |
| } |
| else |
| { |
| swfLoader.content[mouseX] = undefined; |
| swfLoader.content[mouseY] = undefined; |
| } |
| } |
| catch (se:SecurityError) |
| { |
| var e:MustellaSandboxEvent = new MustellaSandboxEvent(MustellaSandboxEvent.MOUSEXY); |
| e.obj = stagePt; |
| swfLoader.contentHolder.contentLoaderInfo.sharedEvents.dispatchEvent(e); |
| } |
| catch (ee:Error) |
| { |
| } |
| } |
| } |
| } |
| |
| /** |
| * ask other sandboxes for objects underneath them |
| */ |
| public static function getObjectsUnderPoint(target:DisplayObject, pt:Point):Array |
| { |
| var arr:Array = []; |
| var root:DisplayObject = target.root; |
| if (root != _root && !(root is Stage)) |
| { |
| try |
| { |
| // Walk up as high as you can get |
| while (!(root.parent is Stage)) |
| { |
| root = root.parent.root; |
| } |
| } |
| catch (e:Error) |
| { |
| // in another sandbox, start from our root. |
| // probably won't work in an AIR window with |
| // loaded content. |
| root = _root; |
| } |
| } |
| else if (root != _root && (root is Stage)) |
| { |
| // seems to happen in AIR windows |
| root = target; |
| try |
| { |
| // Walk up as high as you can get |
| while (!(root.parent is Stage)) |
| { |
| root = root.parent; |
| } |
| } |
| catch (e:Error) |
| { |
| // in another sandbox, start from our root. |
| // probably won't work in an AIR window with |
| // loaded content. |
| root = _root; |
| } |
| } |
| _getObjectsUnderPoint(root, pt, arr); |
| |
| return arr; |
| } |
| |
| private static var effectsInEffect:QName = new QName(mx_internal, "effectsInEffect"); |
| private static var activeTweens:QName = new QName(mx_internal, "activeTweens"); |
| |
| /** |
| * ask other sandboxes if they are running effects |
| */ |
| public static function getSandboxedEffects():Boolean |
| { |
| for (var p:* in swfLoaders) |
| { |
| var swfLoader:Object = p; |
| if (swfLoader) |
| { |
| var e:MustellaSandboxEvent = new MustellaSandboxEvent(MustellaSandboxEvent.GET_EFFECTS); |
| if ("contentLoaderInfo" in swfLoader.contentHolder) |
| { |
| swfLoader.contentHolder.contentLoaderInfo.sharedEvents.dispatchEvent(e); |
| if (e.obj) |
| { |
| return e.obj; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * get the trusted appdom from one of the loaded swfs |
| */ |
| public static function getApplicationDomain(target:String, actualTarget:Object, className:String):ApplicationDomain |
| { |
| var appdom:ApplicationDomain = _root["info"]().currentDomain; |
| if (!appdom) |
| appdom = ApplicationDomain.currentDomain; |
| |
| if (appdom.hasDefinition(className)) |
| { |
| var c:Class = Class(appdom.getDefinition(className)); |
| if (actualTarget is c) |
| return appdom; |
| // try again to handle some cases in existing mustella tests that |
| // reset to a different class |
| var cn:String = getQualifiedClassName(actualTarget); |
| if (appdom.hasDefinition(cn)) |
| { |
| c = Class(appdom.getDefinition(cn)); |
| if (actualTarget is c) |
| return appdom; |
| } |
| } |
| |
| for (var p:* in swfLoaders) |
| { |
| var swfLoader:Object = p; |
| if (swfLoader) |
| { |
| try |
| { |
| // if parent is null, then this is a sandboxed app loaded from file:: |
| // and we don't want to return its appdom |
| if (swfLoader.content.parent) |
| { |
| if (swfLoader.content["info"]().currentDomain.hasDefinition(className)) |
| { |
| c = Class(swfLoader.content["info"]().currentDomain.getDefinition(className)); |
| if (actualTarget is c) |
| return swfLoader.content["info"]().currentDomain; |
| } |
| } |
| } |
| catch (se:SecurityError) |
| { |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * reset a component in another sandbox |
| */ |
| public static function resetSandboxedComponent(target:String, className:String):void |
| { |
| for (var p:* in swfLoaders) |
| { |
| var swfLoader:Object = p; |
| var path:String = swfLoaders[p]; |
| if (swfLoader) |
| { |
| // find the right one based on path |
| var c:int = target.indexOf(path); |
| if (c == 0) |
| { |
| // clip off swfloader from path |
| target = target.substr(path.length + 1, target.length); |
| var e:MustellaSandboxEvent = new MustellaSandboxEvent(MustellaSandboxEvent.RESET_COMPONENT); |
| e.string = target; |
| e.obj = className; |
| swfLoader.contentHolder.contentLoaderInfo.sharedEvents.dispatchEvent(e); |
| return; |
| } |
| } |
| } |
| } |
| |
| /** |
| * ask other sandboxes for their images |
| */ |
| public static function getSandboxBitmaps():Array |
| { |
| var arr:Array = new Array(); |
| |
| for (var p:* in swfLoaders) |
| { |
| var swfLoader:Object = p; |
| if (swfLoader) |
| { |
| var e:MustellaSandboxEvent = new MustellaSandboxEvent(MustellaSandboxEvent.GET_BITMAP); |
| e.obj = getSWFLoaderVisibleBounds(swfLoader as DisplayObject); |
| swfLoader.contentHolder.contentLoaderInfo.sharedEvents.dispatchEvent(e); |
| if (e.obj is Array && e.obj.length > 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 |
| { |
| // yes, there is a chance that you've clicked on the test |
| // just as it is waiting for a focusIn event or |
| // deferring focus assignment |
| // but I think that's the best we can do for now |
| if (waitEvent == "focusIn" || (uiComponentGlobals && uiComponentGlobals.nextFocusObject != null)) |
| return; |
| |
| 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 |
| { |
| if ("topLevelSystemManager" in _root) |
| _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 |
| { |
| cleanUpAndExit(); |
| } |
| |
| private static function cleanUpAndExit():void |
| { |
| if (pendingOutput > 0) |
| { |
| if (lastPendingOutput == 0) |
| lastPendingOutput = pendingOutput; |
| if (pendingOutput == lastPendingOutput) |
| { |
| if (frameWaitCount < 30) // wait about 30 frames to see if results come back |
| { |
| trace("waiting on pending output", pendingOutput); |
| frameWaitCount++; |
| callback = cleanUpAndExit; |
| return; |
| } |
| } |
| else |
| { |
| lastPendingOutput = pendingOutput; |
| frameWaitCount = 0; |
| callback = cleanUpAndExit; |
| return; |
| } |
| } |
| |
| 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"); |
| try { |
| _root[mouseX] = undefined; |
| _root[mouseY] = undefined; |
| setMouseXY(null); |
| } catch (e:Error) |
| { |
| // not all use cases support this |
| } |
| _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); |
| } |
| } |
| |
| public static var pendingOutput:int = 0; |
| public static var lastPendingOutput:int = 0; |
| public static var frameWaitCount:int = 0; |
| |
| 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 object to use for property lookups in stringToObject |
| */ |
| public static var contextFunction:Function; |
| |
| /** |
| * 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); |
| |
| } |
| |
| //---------------------------------- |
| // MXML Descriptor |
| //---------------------------------- |
| |
| /** |
| * The descriptor of MXML children. |
| */ |
| private var _MXMLDescriptor:Array; |
| |
| public function get MXMLDescriptor():Array |
| { |
| return _MXMLDescriptor; |
| } |
| |
| public function setMXMLDescriptor(value:Array):void |
| { |
| _MXMLDescriptor = value; |
| } |
| |
| //---------------------------------- |
| // MXML Properties |
| //---------------------------------- |
| |
| /** |
| * The attributes of MXML top tag. |
| */ |
| private var _MXMLProperties:Array; |
| |
| public function get MXMLProperties():Array |
| { |
| return _MXMLProperties; |
| } |
| |
| public function setMXMLProperties(value:Array):void |
| { |
| _MXMLProperties = value; |
| } |
| |
| /** |
| * 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 |
| { |
| var context:Object; |
| if (contextFunction != null) |
| context = contextFunction(); |
| else |
| context = _root["document"]; |
| |
| if (s == null || s == "") |
| return context; |
| |
| 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 context.getChildAt(parseInt(s)); |
| } |
| if (s.indexOf("getLayoutElementAt(") == 0 && s.indexOf(".") == -1) |
| { |
| s = s.substring(19); |
| s = s.substring(0, s.indexOf(")")); |
| return context.getLayoutElementAt(parseInt(s)); |
| } |
| if (s.indexOf("getElementAt(") == 0 && s.indexOf(".") == -1) |
| { |
| s = s.substring(13); |
| s = s.substring(0, s.indexOf(")")); |
| return context.getElementAt(parseInt(s)); |
| } |
| if (s.indexOf("script:") == 0) |
| { |
| propName = s.substring(7); |
| return this[propName]; |
| } |
| return context[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 = context; |
| 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 == "getInstance()") |
| { |
| obj = obj.getInstance(); |
| } |
| 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 children:Array = this.MXMLDescriptor; |
| if (children) |
| generateMXMLInstances(this, children); |
| |
| var r:Object = _root; |
| if ("topLevelSystemManager" in _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); |
| |
| if ("topLevelSystemManager" in _root) |
| _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; |
| if (hasRTE) |
| tr.result = TestResult.FAIL; |
| 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); |
| |
| if ("topLevelSystemManager" in _root) |
| _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 = appdom.hasDefinition("mx.core.UIComponentGlobals") ? |
| Class(appdom.getDefinition("mx.core.UIComponentGlobals")) : null; |
| if (g) |
| { |
| o = g[layoutManager]; |
| |
| while (true) |
| { |
| try |
| { |
| o.validateNow(); |
| break; |
| } |
| catch (e:Error) |
| { |
| } |
| } |
| } |
| |
| } |
| |
| protected function addMXMLChildren(comps:Array):void |
| { |
| } |
| |
| protected function generateMXMLObject(document:Object, data:Array):Object |
| { |
| var i:int = 0; |
| var cls:Class = data[i++]; |
| var comp:Object = new cls(); |
| |
| var m:int; |
| var j:int; |
| var name:String; |
| var simple:*; |
| var value:Object; |
| var id:String; |
| |
| m = data[i++]; // num props |
| for (j = 0; j < m; j++) |
| { |
| name = data[i++]; |
| simple = data[i++]; |
| value = data[i++]; |
| if (simple == null) |
| value = generateMXMLArray(document, value as Array); |
| else if (simple == false) |
| value = generateMXMLObject(document, value as Array); |
| if (name == "id") |
| { |
| document[value] = comp; |
| id = value as String; |
| } |
| else if (name == "_id") |
| { |
| document[value] = comp; |
| id = value as String; |
| continue; // skip assignment to comp |
| } |
| comp[name] = value; |
| } |
| if (comp is IMXMLObject) |
| comp.initialized(document, id); |
| return comp; |
| } |
| |
| public function generateMXMLArray(document:Object, data:Array, recursive:Boolean = true):Array |
| { |
| var comps:Array = []; |
| |
| var n:int = data.length; |
| var i:int = 0; |
| while (i < n) |
| { |
| var cls:Class = data[i++]; |
| var comp:Object = new cls(); |
| |
| var m:int; |
| var j:int; |
| var name:String; |
| var simple:*; |
| var value:Object; |
| var id:String = null; |
| |
| m = data[i++]; // num props |
| for (j = 0; j < m; j++) |
| { |
| name = data[i++]; |
| simple = data[i++]; |
| value = data[i++]; |
| if (simple == null) |
| value = generateMXMLArray(document, value as Array, recursive); |
| else if (simple == false) |
| value = generateMXMLObject(document, value as Array); |
| if (name == "id") |
| id = value as String; |
| if (name == "document" && !comp.document) |
| comp.document = document; |
| else if (name == "_id") |
| id = value as String; // and don't assign to comp |
| else |
| comp[name] = value; |
| } |
| m = data[i++]; // num styles |
| for (j = 0; j < m; j++) |
| { |
| name = data[i++]; |
| simple = data[i++]; |
| value = data[i++]; |
| if (simple == null) |
| value = generateMXMLArray(document, value as Array, recursive); |
| else if (simple == false) |
| value = generateMXMLObject(document, value as Array); |
| //comp.setStyle(name, value); |
| } |
| |
| m = data[i++]; // num effects |
| for (j = 0; j < m; j++) |
| { |
| name = data[i++]; |
| simple = data[i++]; |
| value = data[i++]; |
| if (simple == null) |
| value = generateMXMLArray(document, value as Array, recursive); |
| else if (simple == false) |
| value = generateMXMLObject(document, value as Array); |
| //comp.setStyle(name, value); |
| } |
| |
| m = data[i++]; // num events |
| for (j = 0; j < m; j++) |
| { |
| name = data[i++]; |
| value = data[i++]; |
| comp.addEventListener(name, value); |
| } |
| |
| var children:Array = data[i++]; |
| if (children) |
| { |
| if (recursive) |
| comp.generateMXMLInstances(document, children, recursive); |
| else |
| comp.setMXMLDescriptor(children); |
| } |
| |
| if (id) |
| { |
| document[id] = comp; |
| mx.binding.BindingManager.executeBindings(document, id, comp); |
| } |
| if (comp is IMXMLObject) |
| comp.initialized(document, id); |
| comps.push(comp); |
| } |
| return comps; |
| } |
| |
| protected function generateMXMLInstances(document:Object, data:Array, recursive:Boolean = true):void |
| { |
| var comps:Array = generateMXMLArray(document, data, recursive); |
| addMXMLChildren(comps); |
| } |
| |
| protected function generateMXMLAttributes(data:Array):void |
| { |
| var i:int = 0; |
| var m:int; |
| var j:int; |
| var name:String; |
| var simple:*; |
| var value:Object; |
| var id:String = null; |
| |
| m = data[i++]; // num props |
| for (j = 0; j < m; j++) |
| { |
| name = data[i++]; |
| simple = data[i++]; |
| value = data[i++]; |
| if (simple == null) |
| value = generateMXMLArray(this, value as Array, false); |
| else if (simple == false) |
| value = generateMXMLObject(this, value as Array); |
| if (name == "id") |
| id = value as String; |
| if (name == "_id") |
| id = value as String; // and don't assign |
| else |
| this[name] = value; |
| } |
| m = data[i++]; // num styles |
| for (j = 0; j < m; j++) |
| { |
| name = data[i++]; |
| simple = data[i++]; |
| value = data[i++]; |
| if (simple == null) |
| value = generateMXMLArray(this, value as Array, false); |
| else if (simple == false) |
| value = generateMXMLObject(this, value as Array); |
| // this.setStyle(name, value); |
| } |
| |
| m = data[i++]; // num effects |
| for (j = 0; j < m; j++) |
| { |
| name = data[i++]; |
| simple = data[i++]; |
| value = data[i++]; |
| if (simple == null) |
| value = generateMXMLArray(this, value as Array, false); |
| else if (simple == false) |
| value = generateMXMLObject(this, value as Array); |
| // this.setStyle(name, value); |
| } |
| |
| m = data[i++]; // num events |
| for (j = 0; j < m; j++) |
| { |
| name = data[i++]; |
| value = data[i++]; |
| this.addEventListener(name, value as Function); |
| } |
| } |
| |
| mx_internal function setupBindings(bindingData:Array):void |
| { |
| var fieldWatcher:Object; |
| var n:int = bindingData[0]; |
| var bindings:Array = []; |
| var i:int; |
| var index:int = 1; |
| for (i = 0; i < n; i++) |
| { |
| var source:Object = bindingData[index++]; |
| var destFunc:Object = bindingData[index++]; |
| var destStr:Object = bindingData[index++]; |
| var binding:Binding = new Binding(this, |
| (source is Function) ? source as Function : null, |
| (destFunc is Function) ? destFunc as Function : null, |
| (destStr is String) ? destStr as String : destStr.join("."), |
| (source is Function) ? null : (source is String) ? source as String : source.join(".")); |
| bindings.push(binding); |
| } |
| var watchers:Object = decodeWatcher(this, bindingData.slice(index), bindings); |
| this["_bindings"] = bindings; |
| this["_watchers"] = watchers; |
| } |
| |
| private function decodeWatcher(target:Object, bindingData:Array, bindings:Array):Array |
| { |
| var watcherMap:Object = {}; |
| var watchers:Array = []; |
| var n:int = bindingData.length; |
| var index:int = 0; |
| var watcherData:Object; |
| var theBindings:Array; |
| var bindingIndices:Array; |
| var bindingIndex:int; |
| var propertyName:String; |
| var eventNames:Array; |
| var eventName:String; |
| var eventObject:Object; |
| var getterFunction:Function; |
| var value:*; |
| var w:Watcher; |
| |
| while (index < n) |
| { |
| var watcherIndex:int = bindingData[index++]; |
| var type:int = bindingData[index++]; |
| switch (type) |
| { |
| case 0: |
| { |
| var functionName:String = bindingData[index++]; |
| var paramFunction:Function = bindingData[index++]; |
| value = bindingData[index++]; |
| if (value is String) |
| eventNames = [ value ]; |
| else |
| eventNames = value; |
| eventObject = {}; |
| for each (eventName in eventNames) |
| eventObject[eventName] = true; |
| value = bindingData[index++]; |
| if (value is Array) |
| bindingIndices = value; |
| else |
| bindingIndices = [ value ]; |
| theBindings = []; |
| for each (bindingIndex in bindingIndices) |
| theBindings.push(bindings[bindingIndex]); |
| w = new FunctionReturnWatcher(functionName, |
| this, |
| paramFunction, |
| eventObject, |
| theBindings); |
| break; |
| } |
| case 1: |
| { |
| propertyName = bindingData[index++]; |
| value = bindingData[index++]; |
| if (value is String) |
| eventNames = [ value ]; |
| else |
| eventNames = value; |
| eventObject = {}; |
| for each (eventName in eventNames) |
| eventObject[eventName] = true; |
| value = bindingData[index++]; |
| if (value is Array) |
| bindingIndices = value; |
| else |
| bindingIndices = [ value ]; |
| theBindings = []; |
| for each (bindingIndex in bindingIndices) |
| theBindings.push(bindings[bindingIndex]); |
| getterFunction = bindingData[index++]; |
| w = new StaticPropertyWatcher(propertyName, |
| eventObject, theBindings, getterFunction); |
| break; |
| } |
| case 2: |
| { |
| propertyName = bindingData[index++]; |
| value = bindingData[index++]; |
| if (value is String) |
| eventNames = [ value ]; |
| else |
| eventNames = value; |
| eventObject = {}; |
| for each (eventName in eventNames) |
| eventObject[eventName] = true; |
| value = bindingData[index++]; |
| if (value is Array) |
| bindingIndices = value; |
| else |
| bindingIndices = [ value ]; |
| theBindings = []; |
| for each (bindingIndex in bindingIndices) |
| theBindings.push(bindings[bindingIndex]); |
| getterFunction = bindingData[index++]; |
| w = new PropertyWatcher(propertyName, |
| eventObject, theBindings, getterFunction); |
| break; |
| } |
| case 3: |
| { |
| propertyName = bindingData[index++]; |
| value = bindingData[index++]; |
| if (value is Array) |
| bindingIndices = value; |
| else |
| bindingIndices = [ value ]; |
| theBindings = []; |
| for each (bindingIndex in bindingIndices) |
| theBindings.push(bindings[bindingIndex]); |
| w = new XMLWatcher(propertyName, theBindings); |
| break; |
| } |
| } |
| watchers.push(w); |
| w.updateParent(target); |
| if (target is Watcher) |
| { |
| if (w is FunctionReturnWatcher) |
| FunctionReturnWatcher(w).parentWatcher = Watcher(target); |
| Watcher(target).addChild(w); |
| } |
| |
| var children:Array = bindingData[index++]; |
| if (children != null) |
| { |
| children = decodeWatcher(w, children, bindings); |
| } |
| } |
| return watchers; |
| } |
| |
| 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; |
| |
| } |
| |
| } |