http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/SnifferRemoteClient.mxml ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/SnifferRemoteClient.mxml b/mustella/src/main/flex/SnifferRemoteClient.mxml new file mode 100644 index 0000000..d73ec02 --- /dev/null +++ b/mustella/src/main/flex/SnifferRemoteClient.mxml @@ -0,0 +1,514 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + 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. + +--> +<s:Application + xmlns:fx="http://ns.adobe.com/mxml/2009" + xmlns:s="library://ns.adobe.com/flex/spark" + xmlns:mx="library://ns.adobe.com/flex/mx" + width="100%" height="800" + initialize="initApp()"> + + <fx:Script> + <![CDATA[ + + import mx.core.UIComponent; + import mx.collections.*; + import mx.core.mx_internal; + use namespace mx_internal; + + + [Bindable] private var theList:ArrayCollection = new ArrayCollection(); + [Bindable] private var theListView:ListCollectionView = new ListCollectionView (theList); + private var iterator:int = 0; + private var connection:LocalConnection; + private var commandconnection:LocalConnection; + private var pixelconnection:LocalConnection; + private var pixelcommandconnection:LocalConnection; + private var mouseconnection:LocalConnection; + private var mousecommandconnection:LocalConnection; + private var objectconnection:LocalConnection; + private var objectcommandconnection:LocalConnection; + private var playbackconnection:LocalConnection; + private var playbackcommandconnection:LocalConnection; + + private function initApp():void + { + + theListView.filterFunction = filterData; + + connection = new LocalConnection(); + connection.allowDomain("*"); + connection.client = this; + + commandconnection = new LocalConnection(); + commandconnection.allowDomain("*"); + commandconnection.addEventListener(StatusEvent.STATUS, statusHandler); + + pixelconnection = new LocalConnection(); + pixelconnection.allowDomain("*"); + pixelconnection.client = this; + + pixelcommandconnection = new LocalConnection(); + pixelcommandconnection.allowDomain("*"); + pixelcommandconnection.addEventListener(StatusEvent.STATUS, statusHandler); + + mouseconnection = new LocalConnection(); + mouseconnection.allowDomain("*"); + mouseconnection.client = this; + + mousecommandconnection = new LocalConnection(); + mousecommandconnection.allowDomain("*"); + mousecommandconnection.addEventListener(StatusEvent.STATUS, statusHandler); + + objectconnection = new LocalConnection(); + objectconnection.allowDomain("*"); + objectconnection.client = this; + + objectcommandconnection = new LocalConnection(); + objectcommandconnection.allowDomain("*"); + objectcommandconnection.addEventListener(StatusEvent.STATUS, statusHandler); + + playbackconnection = new LocalConnection(); + playbackconnection.allowDomain("*"); + playbackconnection.client = this; + + playbackcommandconnection = new LocalConnection(); + playbackcommandconnection.allowDomain("*"); + playbackcommandconnection.addEventListener(StatusEvent.STATUS, statusHandler); + + connect(); + + toggleSniffersEnabled(); + } + + private function connect():void + { + try + { + connection.connect("_EventSniffer"); + } + catch (e:Error) + { + appendLog("connection failed"); + } + + try + { + pixelconnection.connect("_PixelSniffer"); + } + catch (e:Error) + { + appendLog("pixel connection failed"); + } + + try + { + mouseconnection.connect("_MouseSniffer"); + } + catch (e:Error) + { + appendLog("mouse connection failed"); + } + + try + { + objectconnection.connect("_ObjectSniffer"); + } + catch (e:Error) + { + appendLog("object connection failed"); + } + + try + { + playbackconnection.connect("_PlaybackSniffer"); + } + catch (e:Error) + { + appendLog("playback connection failed"); + } + } + + private function statusHandler(event:Event):void + { + } + + [Bindable] + public var enableLogging:Boolean = true; + + public function enableSniffer():void + { + enableLogging = true; + } + + public function disableSniffer():void + { + enableLogging = false; + } + + /** + * Called by the sniffer. + **/ + public function appendLog(info:Object):void + { + if (!enableLogging) + return; + + /** + if (eventName==null) + { + eventName=" "; + } + if (event==null) + { + event=" "; + } + **/ + + iterator++; + +/** + trace ("RECEIVED item " + iterator + ":"); + trace (" dataSource: " + info.dataSource); // Event, Object, Mouse... + trace (" target: " + info.target); + trace (" eventName: " + info.eventName); + trace (" event: " + info.event); +**/ + + theList.addItem ({sequence:iterator, dataSource:info.dataSource, target:info.target, event:info.event, eventName:info.eventName}); + /// call refresh + theListView.refresh(); + } + + public function filterData (o:Object):Boolean { + + // Do the easy event check before the more expensive ones. + if (o.dataSource == "Event"){ + if (!eventsList.selected) + return false; + + if (rbIncludeEvents.selected && (eventMap[o.eventName] != 1 && !match(o.eventName, eventWildCards))) + return false; + + if (rbExcludeEvents.selected && (eventMap[o.eventName] == 1 || match(o.eventName, eventWildCards))) + return false; + } + + if (!mouseList.selected && (o.dataSource == "Mouse" || o.dataSource == "Pixel" )) + return false; + + if (!mustellaOutputList.selected && o.dataSource == "Mustella_Output") + return false; + + if (!objectList.selected && o.dataSource == "Object") + return false; + + if (rbIncludeTargets.selected && (targetMap[o.target] != 1 && !match(o.target, targetWildCards))) + return false; + + if (rbExcludeTargets.selected && (targetMap[o.target] == 1 || match(o.target, targetWildCards))) + return false; + + return true; + } + + /** + * Enable or disable sniffers based on the checkboxes. + * This is called locally when the checkboxes are changed, + * and remotely by other sniffers when the SWF under test + * starts up. + **/ + public function toggleSniffersEnabled():void{ + + if(eventsList.selected == true){ + commandconnection.send("_EventSnifferCommands", "enableSniffer"); + }else{ + commandconnection.send("_EventSnifferCommands", "disableSniffer"); + } + + if(mouseList.selected == true){ + mousecommandconnection.send("_MouseSnifferCommands", "enableSniffer"); + pixelcommandconnection.send("_PixelSnifferCommands", "enableSniffer"); + }else{ + mousecommandconnection.send("_MouseSnifferCommands", "disableSniffer"); + pixelcommandconnection.send("_PixelSnifferCommands", "disableSniffer"); + } + } + + private function scrollLog():void + { + if (log.maxVerticalScrollPosition != log.verticalScrollPosition) + log.verticalScrollPosition = log.maxVerticalScrollPosition; + } + + private var _targetList:String; + private var targetListChanged:Boolean = false; + + public function get targetList():String + { + var s:String = targets.text; + s = s.replace("\r", ","); + return s; + } + + public function set targetList(s:String):void + { + _targetList = s; + targetListChanged = true; + invalidateProperties(); + } + + private var _eventList:String; + private var eventListChanged:Boolean = false; + + public function get eventList():String + { + var s:String = events.text; + s = s.replace("\r", ","); + return s; + } + + public function set eventList(s:String):void + { + _eventList = s; + eventListChanged = true; + invalidateProperties(); + } + + private var filterChanged:Boolean = false; + + /** + * If the target or event include/exclude lists + * changed, process the new settings. + **/ + override protected function commitProperties():void + { + var s:String; + var n:int; + var i:int; + + if (eventListChanged) + { + eventListChanged = false; + s = _eventList.replace(",", "\r"); + events.text = s; + filterChanged = true; + } + + if (targetListChanged) + { + targetListChanged = false; + s = _targetList.replace(",", "\r"); + targets.text = s; + filterChanged = true; + } + + if (filterChanged) + { + filterChanged = false; + + s = events.text; + var names:Array = s.split("\r"); + n = names.length; + eventMap = {}; + eventWildCards = []; + for (i = 0; i < n; i++) + { + s = names[i]; + if (s.indexOf('*') >= 0) + eventWildCards.push(s); + else + eventMap[names[i]] = 1; + } + + s = targets.text; + names = s.split("\r"); + n = names.length; + targetMap = {}; + targetWildCards = []; + for (i = 0; i < n; i++) + { + s = names[i]; + if (s.indexOf('*') >= 0) + targetWildCards.push(new RegExp(s.replace("*", ".*"))); + else + targetMap[names[i]] = 1; + } + + } + + super.commitProperties(); + theListView.refresh(); + } + + private var eventMap:Object = {}; + private var targetMap:Object = {}; + + private var eventWildCards:Array = []; + private var targetWildCards:Array = []; + + private function changeFilter():void + { + filterChanged = true; + invalidateProperties(); + } + + private function match(target:String, wildCards:Array):Boolean + { + var n:int = wildCards.length; + for (var i:int = 0; i < n; i++) + { + if (RegExp(wildCards[i]).test(target)) + return true; + } + return false; + } + + private function getAProperty():void + { + objectcommandconnection.send("_ObjectSnifferCommands", "dumpObject", target.text); + } + + private function listAllProperties():void + { + objectcommandconnection.send("_ObjectSnifferCommands", "listProperties", target.text); + } + + [Bindable] + public var paused:Boolean = true; + + public function getPausedState():void + { + if (paused) + pausePlayback(); + } + + public function pausePlayback():void + { + paused = true; + playbackcommandconnection.send("_PlaybackCommands", "pause") + } + + public function playback():void + { + paused = false; + playbackcommandconnection.send("_PlaybackCommands", "playback") + } + + public function stepit():void + { + playbackcommandconnection.send("_PlaybackCommands", "step") + } + + /** + * Someone has changed the Events, Mouse Stuff, + * Test Output, or Objects checkbox(es). + **/ + private function handleSnifferListChange(e:Event):void{ + theListView.refresh(); + toggleSniffersEnabled(); + } + ]]> + </fx:Script> + + <s:layout> + <s:VerticalLayout /> + </s:layout> + + <mx:Spacer width="1024" /> + + <!-- Each row of data looks like this: {sequence, dataSource, target, event, eventName} --> + <mx:DataGrid id="log" dataProvider="{theListView}" rowCount="20" width="100%" updateComplete="scrollLog()" > + <mx:columns> + <mx:DataGridColumn dataField="sequence" dataTipField="sequence" headerText="#" width="80" editable="false"/> + <mx:DataGridColumn dataField="target" dataTipField="target" headerText="Target" width="1000" editable="false" itemRenderer="mx.controls.Label"/> + <mx:DataGridColumn dataField="eventName" dataTipField="eventName" headerText="Event Name" width="1000" editable="false" itemRenderer="SnifferCellRenderer"> + </mx:DataGridColumn> + </mx:columns> + </mx:DataGrid> + + <mx:HBox width="100%" defaultButton="{events}" borderStyle="solid" paddingTop="1" paddingBottom="1" paddingLeft="1" paddingRight="1"> + <mx:Label text="Filter by Type: "/> + <mx:CheckBox id="eventsList" selected="true" label="Events" change="handleSnifferListChange(event)" /> + <mx:CheckBox id="mustellaOutputList" selected="true" label="Test Output" change="handleSnifferListChange(event)" /> + <mx:CheckBox id="objectList" selected="true" label="Objects" change="handleSnifferListChange(event)" /> + <mx:CheckBox id="mouseList" selected="false" label="Mouse Stuff" change="handleSnifferListChange(event)" /> + </mx:HBox> + + <mx:Box width="100%" borderStyle="solid" paddingTop="1" paddingBottom="1" + paddingLeft="1" paddingRight="1" enabled="{eventsList.selected}" > + <mx:Label text="Filter by Target/Event: " /> + <mx:HBox width="100%" > + <mx:HBox width="50%" borderStyle="solid" paddingTop="1" paddingBottom="1" paddingLeft="1" paddingRight="1"> + <mx:Box width="20%"> + <mx:RadioButton id="rbIncludeTargets" groupName="targetRB" label="include targets"/> + <mx:RadioButton id="rbExcludeTargets" groupName="targetRB" label="exclude targets" selected="true" /> + </mx:Box> + <mx:Box width="80%"> + <mx:TextArea id="targets" width="100%" height="100" focusOut="changeFilter()"/> + </mx:Box> + </mx:HBox> + <mx:HBox width="50%" borderStyle="solid" paddingTop="1" paddingBottom="1" paddingLeft="1" paddingRight="1"> + <mx:Box width="20%"> + <mx:RadioButton id="rbIncludeEvents" groupName="eventRB" label="include events"/> + <mx:RadioButton id="rbExcludeEvents" groupName="eventRB" label="exclude events" selected="true"/> + </mx:Box> + <mx:Box width="80%"> + <mx:TextArea id="events" width="100%" height="100" focusOut="changeFilter()"/> + </mx:Box> + </mx:HBox> + </mx:HBox> + + <mx:Button label="Apply Filters" click="changeFilter()" /> + </mx:Box> + + <mx:HBox width="100%" defaultButton="{getProperty}" enabled="{objectList.selected}" > + <mx:TextInput width="100%" id="target"/> + <mx:Button id="getProperty" label="Get Property" click="getAProperty()" /> + <mx:Button id="listProperties" label="List Properties" click="listAllProperties()" /> + </mx:HBox> + + <s:Group width="100%"> + <s:Rect id="headerBackground" left="0" right="0" top="0" height="24"> + <s:fill> + <s:SolidColor color="0xCCCCCC" /> + </s:fill> + </s:Rect> + + <s:HGroup width="100%"> + <s:HGroup width="100%" paddingLeft="5"> + <s:Label text="Playback Control:" paddingTop="5"/> + <s:Button id="pause" label="Pause" enabled="{!paused}" click="pausePlayback()" skinClass="skins.pauseButtonSkin" /> + <s:Button id="play" label="Play" enabled="{paused}" click="playback()" skinClass="skins.playButtonSkin" /> + <s:Button id="step" label="Step" enabled="{paused}" click="stepit()" skinClass="skins.stepButtonSkin" /> + </s:HGroup> + + <s:HGroup width="100%" > + <s:Label text="Logging:" paddingTop="5" /> + <s:Button label="start" enabled="{!enableLogging}" click="enableSniffer()" /> + <s:Button label="stop" enabled="{enableLogging}" click="disableSniffer()" /> + <s:Button label="mark" click="appendLog({dataSource:'Marker', target:'------------ ' + getTimer() + ' -------------', event:'', eventName:''})" /> + <s:Button label="clear" click="theList.removeAll(); iterator=0;" /> + <s:Label id="pixel" text="Current Pixel Color" visible="false" /> + </s:HGroup> + + </s:HGroup> + </s:Group> + +</s:Application>
http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/SocketAddress.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/SocketAddress.as b/mustella/src/main/flex/SocketAddress.as new file mode 100644 index 0000000..6b19dac --- /dev/null +++ b/mustella/src/main/flex/SocketAddress.as @@ -0,0 +1,52 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package { + +import flash.display.DisplayObject; +import mx.core.UIComponentGlobals; +import mx.core.mx_internal; + +use namespace mx_internal; + +[Mixin] +/** + * A "marker" class that causes test scripts to write out + * bitmaps to the urls instead of reading and comparing + * so that baselines/reference-points can be created for + * future comparing. + */ +public class SocketAddress +{ + + /** + * Mixin callback that gets everything ready to go. + * The UnitTester waits for an event before starting + */ + public static function init(root:DisplayObject):void + { + } + + function SocketAddress() + { + UnitTester.RTESocketAddress = "127.0.0.1"; + // UnitTester.RTESocketAddress = "10.132.64.64"; + } +} + +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/TargetConfigurations.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/TargetConfigurations.as b/mustella/src/main/flex/TargetConfigurations.as new file mode 100644 index 0000000..711d0aa --- /dev/null +++ b/mustella/src/main/flex/TargetConfigurations.as @@ -0,0 +1,114 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 { + + +/** + * Configurations of environments where tests are run + * are mapped to IDs. + * + * This can't be database stuff because we don't know + * some of this info. until we are actually running. + * + * Most tests will only differ with deviceDensity and OS, with a few differing by screenDPI. + * We aren't embedding in this release, so the same deviceDensity basesline will differ across + * OSs. But that will probably change in Ultra, which is why the DPI is used in the config ID. + * + * The color depth is just theoretical, and the osVersion has not been needed. + * + * DEVICES: + * + * Device deviceDensity os screenDPI widthxheight color osVersion + * win 160 win 160 320x455 - - + * Playbook 160 qnx 170 1024x600 - - + * win 240 win 240 480x762 - - + * Droid 2: 240 android 240 480x854 - - + * Droid X: 240 android 240 480x816 - - + * Nexus One: 240 android 240 480x762 - - + * Desire: 240 android 240 480x762 - - + * Nexus S: 240 android 240 480x762 - - + * win 320 win 320 640x960 - - + * iPodTouch4G: 320 ios 326 640x960 - - + * + */ + +import mx.core.FlexGlobals; + +public class TargetConfigurations +{ + //TODO add some larger screens? + public static var configs:Array = [ + { configID: "160_01", deviceDensity: 160, os: DeviceNames.WIN, screenDPI: 160, deviceWidth: 320, deviceHeight: 455, color: null, osVersion: null}, + { configID: "160_02", deviceDensity: 160, os: DeviceNames.MAC, screenDPI: 160, deviceWidth: 320, deviceHeight: 455, color: null, osVersion: null}, + { configID: "160_03", deviceDensity: 160, os: DeviceNames.QNX, screenDPI: 170, deviceWidth: 1024, deviceHeight: 600, color: null, osVersion: null}, + { configID: "240_01", deviceDensity: 240, os: DeviceNames.WIN, screenDPI: 240, deviceWidth: 480, deviceHeight: 762, color: null, osVersion: null}, + { configID: "240_02", deviceDensity: 240, os: DeviceNames.MAC, screenDPI: 240, deviceWidth: 480, deviceHeight: 762, color: null, osVersion: null}, + { configID: "240_03", deviceDensity: 240, os: DeviceNames.ANDROID, screenDPI: 240, deviceWidth: 480, deviceHeight: 816, color: null, osVersion: null}, + { configID: "240_04", deviceDensity: 240, os: DeviceNames.ANDROID, screenDPI: 240, deviceWidth: 480, deviceHeight: 854, color: null, osVersion: null}, + { configID: "240_05", deviceDensity: 240, os: DeviceNames.ANDROID, screenDPI: 240, deviceWidth: 480, deviceHeight: 762, color: null, osVersion: null}, + { configID: "320_01", deviceDensity: 320, os: DeviceNames.WIN, screenDPI: 320, deviceWidth: 640, deviceHeight: 960, color: null, osVersion: null}, + { configID: "320_02", deviceDensity: 320, os: DeviceNames.MAC, screenDPI: 320, deviceWidth: 640, deviceHeight: 960, color: null, osVersion: null}, + { configID: "320_03", deviceDensity: 320, os: DeviceNames.IOS, screenDPI: 326, deviceWidth: 640, deviceHeight: 960, color: null, osVersion: null}, + { configID: "480_01", deviceDensity: 480, os: DeviceNames.ANDROID, screenDPI: 441, deviceWidth: 1080, deviceHeight: 1920, color: null, osVersion: null} + ]; + + /** + * Returns the config ID which best matches the given ConditionalValue. + * This config ID will get added to baselines. e.g. MyGroovyTest_160_01.png + **/ + public static function getTargetConfigID( cv:ConditionalValue ):String{ + var i:int = 0; + + if( cv == null ){ + return null; + } + + // Get these when a test is actually running. + if( cv.deviceWidth == -1 ){ + cv.deviceWidth = FlexGlobals.topLevelApplication.width; + } + + if( cv.deviceHeight == -1 ){ + cv.deviceHeight = FlexGlobals.topLevelApplication.height; + } + + for( i = 0; i < configs.length; ++i ){ + if( cv.deviceDensity == configs[ i ].deviceDensity && + cv.os == configs[ i ].os && + cv.screenDPI == configs[ i ].screenDPI && + cv.deviceWidth == configs[ i ].deviceWidth && + cv.deviceHeight == configs[ i ].deviceHeight + ){ + return configs[ i ].configID; + }else{ + //trace( "*********This did not match:**********" ); + //trace( "deviceDensity: " + configs[ i ].deviceDensity + "!=" + cv.deviceDensity ); + //trace( "os: " + configs[ i ].os + "!=" + cv.os ); + //trace( "screenDPI: " + configs[ i ].screenDPI + "!=" + cv.screenDPI ); + //trace( "deviceWidth: " + configs[ i ].deviceWidth + "!=" + cv.deviceWidth ); + //trace( "deviceHeight: " + configs[ i ].deviceHeight + "!=" + cv.deviceHeight ); + } + } + + // No matches. + return null; + + } +} // end TargetConfigurations class +} // end package http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/TestCase.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/TestCase.as b/mustella/src/main/flex/TestCase.as new file mode 100644 index 0000000..7a063f1 --- /dev/null +++ b/mustella/src/main/flex/TestCase.as @@ -0,0 +1,525 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 { + +COMPILE::SWF +{ +import flash.display.DisplayObject; +import flash.events.Event; +import flash.events.EventDispatcher; +import flash.events.IEventDispatcher; +import flash.utils.getQualifiedClassName; +import flash.utils.getTimer; +import flash.utils.Timer; +} +COMPILE::JS +{ + import org.apache.flex.events.EventDispatcher; + import org.apache.flex.utils.Timer; +} +/** + * A Test. A Test script comprises several of these + * TestCases. Each TestCase consists of a + * setup, body and cleanup section which are sets + * of TestStep-derived classes like SetProperty, + * RunCode, AssertPropertyValue, etc. + * + * MXML Properties (not attributes) + * setup + * body + * cleanup + */ +public class TestCase extends EventDispatcher +{ + /** + * The history of bugs that this test case has encountered + */ + public var bugs:Array; + + /** + * The sequence of TestSteps that comprise the test setup + */ + public var setup:Array; + + /** + * The sequence of TestSteps that comprise the test + */ + public var body:Array; + + /** + * The sequence of TestSteps that restore the test environment + */ + public var cleanup:Array; + + /** + * An identifier for the test + */ + public var testID:String; + + /** + * A description of the test + */ + public var description:String; + + /** + * keywords, summarizing the tests + */ + public var keywords:String; + + /** + * frequency, an estimate of the tests's intersection with real-world + * usage. 1 = most frequent usage; 2 somewhat common; 3 = unusual + */ + public var frequency:String; + + /** + * The current set of steps (setup, body, cleanup) we are executing + */ + private var currentSteps:Array; + + /** + * Which step we're currently executing (or waiting on an event for) + */ + private var currentIndex:int = 0; + + /** + * Number of steps in currentSteps + */ + private var numSteps:int = 0; + + /** + * The root of the SWF (SystemManager) + */ + COMPILE::SWF + private var root:DisplayObject; + + /** + * The shared timer we listen to + */ + private var timer:Timer; + + /** + * The unit tester + */ + private var context:UnitTester; + + /** + * Whether we need to emit a runComplete event or not + */ + private var needCompleteEvent:Boolean = false; + + /** + * If non-zero, the time when we'll give up on waiting + */ + public var expirationTime:int; + + /** + * the last expected Error thrown by SetProperty + */ + public var lastError:Error; + + /** + * Called by test steps looking for a timeout indicator + */ + public function setExpirationTime(time:int):void + { + expirationTime = (time > 0) ? time + UnitTester.timeout_plus : 0; + } + + /** + * Storage for the cleanupAsserts + */ + private var _cleanupAsserts:Array; + + /** + * Steps we have to review at the end of the body to see if + * they failed or not. These steps monitor activity like + * checking for duplicate events or making sure unwanted events + * don't fire. + */ + public function get cleanupAsserts():Array + { + return _cleanupAsserts; + } + + /** + * Storage for this tests's result + */ + private var _testResult:TestResult; + + /** + * This tests's result + */ + public function get testResult():TestResult + { + _testResult.testID = testID; + return _testResult; + } + + /** + * Constructor. Create the TestResult associated with this TestCase + */ + public function TestCase() { + + _testResult = new TestResult(); + _testResult.testID = testID; + + _cleanupAsserts = []; + } + + /** + * Called by the UnitTester when it is time to execute + * this test case. + * + * @param root The SystemManager + * @param timer The shared Timer; + * @param context the UnitTester that contains these tests + */ + public function runTest(root:Object, timer:Timer, context:UnitTester):Boolean + { + COMPILE::SWF + { + _testResult.beginTime = new Date().time; + _testResult.context = context; + this.timer = timer; + this.timer.addEventListener("timer", timerHandler); + this.root = root; + this.context = context; + + if (UnitTester.hasRTE) + { + return true; + + } + + // do a sanity test here + if (UnitTester.verboseMode) + { + var needWaitEvent:Boolean = false; + var i:int; + if (setup) + { + for (i = 0; i < setup.length; i++) + { + if (setup[i] is ResetComponent) + needWaitEvent = true; + if (needWaitEvent) + { + if (setup[i].waitEvent) + { + needWaitEvent = false; + break; + } + } + } + if (needWaitEvent) + TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " ResetComponent used without any setup steps using a waitEvent\n"); + } + var allAsserts:Boolean = true; + needWaitEvent = false; + for (i = 0; i < body.length; i++) + { + if (body[i] is SetProperty || body[i] is SetStyle) + needWaitEvent = true; + if (!(body[i] is Assert)) + allAsserts = false; + if (allAsserts && body[i] is AssertEvent) + { + TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " no non-Assert steps in body before AssertEvent\n"); + } + if (needWaitEvent) + { + if (body[i].waitEvent) + { + needWaitEvent = false; + break; + } + } + } + if (needWaitEvent) + TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " tests setting values without a waitEvent\n"); + + } + + return runSetup(); + } + COMPILE::JS + { + return true; + } + } + + /** + * Execute the setup portion of the test + * + * @param continuing If true, don't reset the counter to zero + * and just continue on with the test at the currentIndex + */ + COMPILE::SWF + private function runSetup(continuing:Boolean = false):Boolean + { + if (!testResult.hasStatus()) + { + if (setup) + { + testResult.phase = TestResult.SETUP; + currentSteps = setup; + if (!continuing) + currentIndex = 0; + numSteps = setup.length; + // return if we need to wait for something + if (!runSteps()) + return false; + + } + } + return runBody(); + } + + /** + * Execute the body portion of the test + * + * @param continuing If true, don't reset the counter to zero + * and just continue on with the test at the currentIndex + */ + COMPILE::SWF + private function runBody(continuing:Boolean = false):Boolean + { + if (!testResult.hasStatus()) + { + if (body) + { + testResult.phase = TestResult.BODY; + currentSteps = body; + if (!continuing) + currentIndex = 0; + numSteps = body.length; + // return if we need to wait for something + if (!runSteps()) + return false; + + } + } + return runCleanup(); + } + + /** + * Execute the cleanup portion of the test + * + * @param continuing If true, don't reset the counter to zero + * and just continue on with the test at the currentIndex + */ + COMPILE::SWF + private function runCleanup(continuing:Boolean = false):Boolean + { + if (!testResult.hasStatus()) + { + if (cleanup) + { + testResult.phase = TestResult.CLEANUP; + currentSteps = cleanup; + if (!continuing) + currentIndex = 0; + numSteps = cleanup.length; + // return if we need to wait for something + if (!runSteps()) + return false; + + } + } + return runComplete(); + } + + /** + * Clean up when all three phases are done. Sends an event + * to the UnitTester harness to tell it that it can run + * the next test case. + */ + COMPILE::SWF + private function runComplete():Boolean + { + var n:int = cleanupAsserts.length; + for (var i:int = 0; i < n; i++) + { + var assert:Assert = cleanupAsserts[i]; + assert.cleanup(); + } + + timer.removeEventListener("timer", timerHandler); + if (needCompleteEvent) + dispatchEvent(new Event("runComplete")); + return true; + } + + /** + * Go through the currentSteps, executing each one. + * Returns true if no test steps required waiting. + * Returns false if we have to wait for an event before + * continuing. + */ + COMPILE::SWF + private function runSteps():Boolean + { + while (currentIndex < numSteps) + { + // return if a step failed + if (testResult.hasStatus()) + return true; + + /* + The following lets you step each step but makes tests + more sensitive to which step actually sends the event. + For example, cb.mouseDown/mouseUp, the tween starts on + mousedown and fires its event before you step. Another + example is asserting with waitEvent=updateComplete. It + could have fired a long time before you step + */ + if (UnitTester.playbackControl == "pause") + { + UnitTester.callback = pauseHandler; + needCompleteEvent = true; + return false; + } + else if (UnitTester.playbackControl == "step") + UnitTester.playbackControl = "pause"; + + var step:TestStep = currentSteps[currentIndex]; + if (!(step is Assert)) + { + // look at subsequent steps for Asserts and set them up early + for (var j:int = currentIndex + 1; j < numSteps; j++) + { + // scan following asserts for AssertEvents and set them up early + var nextStep:TestStep = currentSteps[j]; + if (nextStep is Assert) + { + Assert(nextStep).preview(root, context, this, testResult); + // do a check to be sure folks are using AssertEventPropertyValue correctly + if (nextStep is AssertEventPropertyValue) + { + // AEPV must follow an AssertEvent or another AEPV + if (j == 0 || !(currentSteps[j-1] is AssertEvent || currentSteps[j-1] is AssertEventPropertyValue)) + TestOutput.logResult("WARNING: AssertEventPropertyValue may be missing preceding AssertEvent"); + } + else if (nextStep is AssertError) + { + if (step is SetProperty) + SetProperty(step).expectError = true; + } + } + else + break; + } + } + if (UnitTester.verboseMode) + { + TestOutput.logResult(step.toString()); + } + UnitTester.lastStep = step; + UnitTester.lastStepLine = currentIndex; + step.addEventListener("stepComplete", stepCompleteHandler); + if (!step.execute(root, context, this, testResult)) + { + needCompleteEvent = true; + return false; + } + step.removeEventListener("stepComplete", stepCompleteHandler); + currentIndex++; + } + return true; + } + + COMPILE::SWF + private function stepCompleteHandler(event:Event):void + { + var step:TestStep = currentSteps[currentIndex]; + step.removeEventListener("stepComplete", stepCompleteHandler); + if (UnitTester.playbackControl == "play") + UnitTester.callback = runNextSteps; + else + { + currentIndex++; + UnitTester.callback = pauseHandler; + } + } + + + /** + * Handler for timer events to see if we've waited too long + */ + COMPILE::SWF + private function timerHandler(event:Event):void + { + if (expirationTime > 0) + if (expirationTime < getTimer()) + { + var step:TestStep = currentSteps[currentIndex]; + step.timeoutCallback(); + } + } + + /** + * Determines which set of steps (setup, body, cleanup) to run next + */ + COMPILE::SWF + private function runNextSteps():void + { + currentIndex++; + if (currentSteps == setup) + runSetup(true); + else if (currentSteps == body) + runBody(true); + else if (currentSteps == cleanup) + runCleanup(true); + else + runComplete(); + } + + /** + * Determines which set of steps (setup, body, cleanup) to run next + */ + COMPILE::SWF + private function continueSteps():void + { + if (currentSteps == setup) + runSetup(true); + else if (currentSteps == body) + runBody(true); + else if (currentSteps == cleanup) + runCleanup(true); + else + runComplete(); + } + + /** + * Determines which set of steps (setup, body, cleanup) to run next + */ + COMPILE::SWF + private function pauseHandler():void + { + if (UnitTester.playbackControl == "step") + continueSteps(); + else if (UnitTester.playbackControl == "play") + continueSteps(); + else + UnitTester.callback = pauseHandler; + } +} + +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/TestOutput.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/TestOutput.as b/mustella/src/main/flex/TestOutput.as new file mode 100644 index 0000000..2fb69a6 --- /dev/null +++ b/mustella/src/main/flex/TestOutput.as @@ -0,0 +1,76 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 { + +COMPILE::SWF +{ +import flash.display.DisplayObject; +import flash.events.EventDispatcher; +import flash.events.Event; +import flash.events.IEventDispatcher; +} +COMPILE::JS +{ + import org.apache.flex.events.EventDispatcher; +} +/** + * The class that handles redirectable output + * The default here is trace, but you can + * set the output handler to something else + */ +public class TestOutput extends EventDispatcher +{ + /** + * Send the string to the output handler + */ + public static function logResult(result:String):void + { + COMPILE::SWF + { + getInstance().dispatchEvent(new MustellaLogEvent ("result", result)); + } + } + + + private static var theInst:TestOutput = null; + + COMPILE::SWF + public static function getInstance():TestOutput + { + if (theInst == null) { + theInst = new TestOutput(); + trace ("Created new test output instance"); + } + + return theInst; + + } + + + + /** + * By default, here, send the output to trace. Mixin something else, and + * with its own outputHandler function to override this. + */ + COMPILE::SWF + public static var outputHandler:Function = function(s:String):void {trace(s)}; + +} + +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/TestResult.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/TestResult.as b/mustella/src/main/flex/TestResult.as new file mode 100644 index 0000000..389abde --- /dev/null +++ b/mustella/src/main/flex/TestResult.as @@ -0,0 +1,164 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 { + +COMPILE::SWF +{ +import flash.display.DisplayObject; +import flash.utils.*; +} + +/** + * The class that collects TestResults for a TestCase + */ +public class TestResult +{ + + public static const PASS:int = 0; + public static const FAIL:int = 1; + public static const ERROR:int = 2; + + public static const SETUP:int=0; + public static const BODY:int=1; + public static const CLEANUP:int=2; + + /** + * testID + */ + public var testID:String; + + /** + * begin time + */ + public var beginTime:Number; + + /** + * end time + */ + public var endTime:Number; + + /** + * hang on to the context, ie, the script + */ + public var context:UnitTester; + + /** + * result + */ + public var result:int = -1; // "pass", "fail", "error" + + /** + * message. Failures often have messages + */ + public var message:String = ""; + + /** + * extraInfo: failures may have a file associated + */ + public var extraInfo:String = ""; + + /** + * Name of the Script associated with this result + */ + public var scriptName:String = ""; + + /** + * phase. how far the test finished. setup, body, cleanup + */ + public var phase:int = -1; + + /** + * get printable version of phase + */ + COMPILE::SWF + public static function getPhaseString(val:int):String { + if (val == CLEANUP) { + return "cleanup"; + }else if (val == BODY) { + return "body"; + }else if (val == SETUP) { + return "setup"; + + } + return "no phase set"; + } + + /** + * get printable version of result + */ + COMPILE::SWF + public static function getResultString(val:int):String { + if (val == PASS) { + return "pass"; + }else if (val == FAIL) { + return "fail"; + }else if (val == ERROR) { + return "error"; + } + return null; + } + + + /** + * default output look + */ + COMPILE::SWF + public function toString():String + { + + var className:String = getQualifiedClassName (context); + + return "RESULT: scriptName="+context.testDir + className+" id="+ testID + " result=" + getResultString(result) + " elapsed=" + (endTime-beginTime) + " phase=" + getPhaseString(phase) + " started=" + beginTime + " extraInfo=" + extraInfo + " msg=" + message ; + } + + + public function hasStatus():Boolean { + return (result != -1); + } + + public function doFail (msg:String, extraInfo:String=null):void { + COMPILE::SWF + { + // first failure is the one we keep + if (UnitTester.noFail) + { + return; + + } + if (this.result != FAIL) + { + this.result = FAIL; + // this.message = msg; + if (UnitTester.run_id == "-1") + { + var tmp:String = UnitTester.lastStep.toString().substr(0,UnitTester.lastStep.toString().indexOf (":")) + "(" +getPhaseString(phase) + ":step " + (UnitTester.lastStepLine+1) + ") "; + this.message = tmp + " " + msg; + } + else + { + this.message = msg; + } + this.extraInfo = extraInfo; + } + } + } + + +} +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/TestStep.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/TestStep.as b/mustella/src/main/flex/TestStep.as new file mode 100644 index 0000000..1733758 --- /dev/null +++ b/mustella/src/main/flex/TestStep.as @@ -0,0 +1,191 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 { + +COMPILE::SWF +{ +import flash.display.DisplayObject; +import flash.events.Event; +import flash.events.EventDispatcher; +import flash.events.IEventDispatcher; +import flash.utils.getTimer; +} +COMPILE::JS +{ + import org.apache.flex.events.Event; + import org.apache.flex.events.EventDispatcher; + import org.apache.flex.events.IEventDispatcher; +} +/** + * The abstract base class for all steps in a test case. TestStep + * cannot be used directly, instead its subclasses must be used + * such as SetProperty, RunCode, Assert, etc. + */ +public class TestStep extends EventDispatcher +{ + /** + * Called by the TestCase when it is time to start this step + * The default implementation checks for a wait event and + * returns true if there isn't one and false if there is. + */ + public function execute(root:Object, context:UnitTester, testCase:TestCase, testResult:TestResult):Boolean + { + COMPILE::SWF + { + var tryLater:Boolean = false; + + this.root = root; + this.context = context; + this.testCase = testCase; + this.testResult = testResult; + + if (waitEvent) + { + var actualTarget:IEventDispatcher = context.stringToObject(waitTarget) as IEventDispatcher; + if (!actualTarget) + { + // its ok if the target isn't here yet, it may be created during this step + tryLater = true; + } + else + { + UnitTester.waitEvent = waitEvent; + actualTarget.addEventListener(waitEvent, waitEventHandler); + testCase.setExpirationTime(getTimer() + timeout); + } + } + + if (!UnitTester.hasRTE) + doStep(); + + // if test failed, don't bother waiting, just bail + if (testResult.hasStatus() || UnitTester.hasRTE) + { + if (UnitTester.hasRTE) + { + testResult.result = 1; + testResult.message = UnitTester.RTEMsg; + dispatchEvent(new Event("runComplete")); + return true; + } + + if (waitEvent) + { + UnitTester.waitEvent = null; + actualTarget = context.stringToObject(waitTarget) as IEventDispatcher; + actualTarget.removeEventListener(waitEvent, waitEventHandler); + testCase.setExpirationTime(0); + } + return true; + } + + if (tryLater && waitEvent) + { + actualTarget = context.stringToObject(waitTarget) as IEventDispatcher; + if (!actualTarget) + { + testResult.doFail("Target " + waitTarget + " not found"); + return true; + } + UnitTester.waitEvent = waitEvent; + actualTarget.addEventListener(waitEvent, waitEventHandler); + testCase.setExpirationTime(getTimer() + timeout); + } + } + return (waitEvent == null); + } + + /** + * The name of the object to listen for an event we're waiting on + */ + public var waitTarget:String; + + /** + * The name of the event to listen for on the waitTarget + */ + public var waitEvent:String; + + /** + * The number of milliseconds to wait before giving up + */ + public var timeout:int = 3000; + + /** + * The TestResult for this TestCase + */ + protected var testResult:TestResult; + + /** + * The TestCase that this step belongs to + */ + protected var testCase:TestCase; + + /** + * The UnitTester that this step belongs to + */ + protected var context:UnitTester; + + /** + * The root for the SWF + */ + protected var root:Object; + + /** + * The method that gets called when it is time to perform the work in the step. + */ + protected function doStep():void + { + } + + /** + * The method that gets called back when the event we're waiting on fires + */ + protected function waitEventHandler(event:Event):void + { + stepComplete(); + } + + /** + * The method that gets called when it is time to clean up the step. + */ + protected function stepComplete():void + { + if (waitEvent) + { + UnitTester.waitEvent = null; + var actualTarget:IEventDispatcher = context.stringToObject(waitTarget) as IEventDispatcher; + if (actualTarget) // can be null if object killed during step + actualTarget.removeEventListener(waitEvent, waitEventHandler); + testCase.setExpirationTime(0); + } + dispatchEvent(new Event("stepComplete")); + } + + /** + * Called by the test case if you time out + */ + public function timeoutCallback():void + { + testResult.doFail("Timeout waiting for " + waitEvent + " from " + waitTarget); + stepComplete(); + } + +} + +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/TypeInfo.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/TypeInfo.as b/mustella/src/main/flex/TypeInfo.as new file mode 100644 index 0000000..064c116 --- /dev/null +++ b/mustella/src/main/flex/TypeInfo.as @@ -0,0 +1,100 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 { + +COMPILE::SWF +{ + import flash.utils.Dictionary; +} + + /** + * Helper class useful for runtime object introspection. + * This object is generally cached by the TypeInfoCache + * and reused once constructed. Right now we only care about + * base types and interfaces but the class could easily be + * extended to properties and methods if desired. + */ + public class TypeInfo + { + COMPILE::SWF + private var baseTypes:Dictionary = new Dictionary(); + COMPILE::SWF + private var interfaces:Dictionary = new Dictionary(); + public var className:String; + + /** + * Constructor + */ + public function TypeInfo(className:String):void + { + COMPILE::SWF + { + this.className = className; + describeType(className); + } + } + + /** + * Initialization method used to populate the base types and the + * implemented interfaces for our type. + */ + COMPILE::SWF + private function describeType(className:String):void + { + try + { + var definition:Object = flash.utils.getDefinitionByName(className); + } + catch(e:Error) + { + return; + } + + var typeInfo:XMLList = flash.utils.describeType(definition).child("factory"); + + var types:XMLList = typeInfo.child("extendsClass").attribute("type"); + for each (var name:String in types) + baseTypes[name] = true; + + var interfaces:XMLList = typeInfo.child("implementsInterface").attribute("type"); + for each (name in interfaces) + this.interfaces[name] = true; + } + + /** + * Returns true if our type implements the given fully qualified interface. + */ + COMPILE::SWF + public function implementsInterface(interfaceName:String):Boolean + { + return (interfaces[interfaceName] == true) ? true : false; + } + + /** + * Returns true if our type is assignable to the type provided. + */ + COMPILE::SWF + public function isAssignableTo(typeName:String):Boolean + { + return (interfaces[typeName] == true || + baseTypes[typeName] == true || + typeName == className) ? true : false; + } + } +} \ No newline at end of file