blob: e96da6f7566fe7de2c2e6eb81460e41a018abfa6 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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 AIR {
import flash.display.*;
import flash.events.*;
import flash.filesystem.*;
import flash.net.*;
import flash.text.*;
import flash.text.engine.*;
import flash.geom.ColorTransform;
import flash.utils.*;
import flash.filesystem.*;
import flash.geom.*;
import mx.core.IChildList;
import mx.core.IRawChildrenContainer;
import mx.core.mx_internal;
use namespace mx_internal;
/**
* Vector of conditionalValue objects.
**/
[DefaultProperty("conditionalValues")]
/**
* The test step that compares a bitmap against a reference bitmap
* MXML attributes:
* target
* url
* timeout (optional);
* stageText (optional)
* maxColorVariance
* numColorVariances
* waitTarget Do Not Use
* waitEvent Do Not Use
*
* Do not set waitEvent or waitTarget on this step. They are set internally
* to manage the loading of the reference bitmap. The step prior to this
* step must wait for the system to synch up.
*
* CompareBitmap will parse the url attribute for $testID, replacing with the current testID
*/
public class CompareBitmap extends Assert
{
public static var useRemoteDiffer:Boolean = false;
private static var identityMatrix:String = new Matrix().toString();
private static var identityColorTransform:String = new ColorTransform().toString();
public static var DEFAULT_MAX_COLOR_VARIANCE:int = 0;
public static var DEFAULT_NUM_COLOR_VARIANCES:int = 0;
// This is the default property.
public var conditionalValues:Vector.<ConditionalValue> = null;
/**
* The url of the file to read. If UnitTester.createBitmapReferences = true,
* the url to store the bitmap
*/
public var url:String;
/**
* If the user tells us it's a stageText, we'll try to grab the bitmapdata
* via mx_internal call
*/
public var stageText:Boolean
private var _maxColorVariance:int = 0;
/**
* The maximum color variation allowed in a bitmap compare.
* Some machines render slightly differently and thus we have
* to allow the the compare to not be exact.
*/
public function get maxColorVariance():int
{
if (_maxColorVariance)
return _maxColorVariance;
return DEFAULT_MAX_COLOR_VARIANCE;
}
public function set maxColorVariance(value:int):void
{
_maxColorVariance = value;
}
private var _ignoreMaxColorVariance:Boolean = false;
/**
* Sometimes you have numColorVariance defined and you don't really care by how much the pixel really differ.
* as long as the number of mismatching pixels is &lt;= numColorVariance
* Setting this to true will skip the maxColorVariance check (and take the guess work out of picture). default is false
*/
public function get ignoreMaxColorVariance():Boolean
{
return _ignoreMaxColorVariance;
}
public function set ignoreMaxColorVariance(value:Boolean):void
{
_ignoreMaxColorVariance = value;
}
private var _numColorVariances:int = 0;
/**
* The number of color variation allowed in a bitmap compare.
* Some machines render slightly differently and thus we have
* to allow the the compare to not be exact.
*/
public function get numColorVariances():int
{
if (_numColorVariances)
return _numColorVariances;
return DEFAULT_NUM_COLOR_VARIANCES;
}
public function set numColorVariances(value:int):void
{
_numColorVariances = value;
}
/**
* Suffix to add to the file being written out (the case of a compare failure)
*/
public static var fileSuffix:String = "";
private var reader:Loader;
private var xmlreader:URLLoader;
private var writer:URLLoader;
private static var connection:LocalConnection;
private static var commandconnection:LocalConnection;
private function statusHandler(event:Event):void
{
}
/**
* Constructor
*/
public function CompareBitmap()
{
if (!connection)
{
connection = new LocalConnection();
connection.allowDomain("*");
connection.addEventListener(StatusEvent.STATUS, statusHandler);
commandconnection = new LocalConnection();
commandconnection.allowDomain("*");
try
{
commandconnection.connect("_ImageDifferCommands");
}
catch (e:Error)
{
trace("connection failed");
}
}
}
// We override execute here. In other TestSteps, we override doStep().
override public function execute(root:DisplayObject, context:UnitTester, testCase:TestCase, testResult:TestResult):Boolean
{
var cv:ConditionalValue = null;
var configID:String = null;
if( conditionalValues ){
// Use MultiResult to determine the proper URL.
cv = new MultiResult().chooseCV(conditionalValues);
if(cv){
// Use thet cv's url if it is set; otherwise, stick with the CompareBitmap's url (directory).
if( cv.url != null ){
url = cv.url;
}
}
}else{
// We do not have ConditionalValues. If the current config is unknown, it is probably
// a desktop AIR run, and we should just let things take the course they always have.
// If a config is known, then we want to use the new config ID suffix mechanism later.
configID = TargetConfigurations.getTargetConfigID( UnitTester.cv );
if( configID ){
trace( "CompareBitmap: No ConditionalValues found. configID is " + configID.toString() );
}else{
trace( "CompareBitmap: No ConditionalValues found. configID is " + configID );
}
}
if( url == null ){
if( cv == null ){
throw new Error("Found no url on the CompareBitmap for test case " + testCase.testID);
}else{
throw new Error("Found no url on the ConditionalValue for test case " + testCase.testID + ", ConditionalValue: " + cv.toString());
}
}
// See if url ends with .png. If not, create a file name.
if( url.lastIndexOf( ".png" ) != url.length - 4 ){
// Add a path separator if necessary.
if( url.lastIndexOf( "/" ) != url.length - 1 ){
url += "/";
}
// Decide on a file name.
if( conditionalValues ){
// If we ended up with a matching CV, ask it to create a file name.
// Otherwise, go with the test ID.
// Keep this path alive until (if ever) ConditionalValues in CompareBitmaps have all been removed.
if(cv){
trace( "CompareBitmap: Asking the ConditionalValue to create the file name." );
url += cv.createFilename( testCase.testID );
} else {
trace( "CompareBitmap: Creating the file name from the testID." );
url += testCase.testID + ".png";
}
}else if( configID ){
// We have no ConditionalValues and we're running a known config,
// so use the config id in the suffix.
trace( "CompareBitmap: Creating the file name from the configID." );
url += testCase.testID + "@" + configID + ".png";
}else{
trace( "There is no file name, there are no Conditional Values, and there is no configID. There's not much we can do now, is there?" );
}
}
if (url != null && url.indexOf ("$testID") != -1) {
trace ("Replacing $testID with " + UnitTester.currentTestID);
url = url.replace ("$testID", UnitTester.currentTestID);
trace ("result 2: " + url);
}
if (url == null)
trace ("URL was null at execute time");
if (commandconnection)
commandconnection.client = this;
var actualTarget:DisplayObject = DisplayObject(context.stringToObject(target));
if (!actualTarget)
{
testResult.doFail("Target " + target + " not found");
return true;
}
this.root = root;
this.context = context;
this.testResult = testResult;
if (UnitTester.createBitmapReferences)
{
if (UnitTester.checkEmbeddedFonts)
{
if (!checkEmbeddedFonts(actualTarget))
{
testResult.doFail ("Target " + actualTarget + " is using non-embedded or advanced anti-aliased fonts");
return true;
}
}
writeBaselines(actualTarget);
return false;
}
else
{
readPNG();
return false;
}
}
private function getTargetSize(target:DisplayObject):Point
{
var width:Number;
var height:Number;
try
{
width = target["getUnscaledWidth"]() * Math.abs(target.scaleX) * target.root.scaleX;
height = target["getUnscaledHeight"]() * Math.abs(target.scaleY) * target.root.scaleY;
}
catch(e:ReferenceError)
{
width = target.width * target.root.scaleX;
height = target.height * target.root.scaleY;
}
return new Point(width, height);
}
// Given a displayObject, sets up the screenBits.
private function getScreenBits(target:DisplayObject):void{
try
{
if (!stageText) {
var targetSize:Point = getTargetSize(target);
var stagePt:Point = target.localToGlobal(new Point(0, 0));
var altPt:Point = target.localToGlobal(targetSize);
stagePt.x = Math.min(stagePt.x, altPt.x);
stagePt.y = Math.min(stagePt.y, altPt.y);
screenBits = new BitmapData(targetSize.x, targetSize.y);
screenBits.draw(target.stage, new Matrix(1, 0, 0, 1, -stagePt.x, -stagePt.y));
} else {
trace ("Using stagetext");
try {
var tmpbm:BitmapData = target["textDisplay"]["captureBitmapData"]();
screenBits = new BitmapData(tmpbm.rect.width, tmpbm.rect.height, true, 0xFFFFFFFF);
screenBits.draw (tmpbm, new Matrix());
} catch (e:Error) {
trace ("Tried for StageText bitmap data, but it failed");
trace (e.getStackTrace());
}
}
}
catch (se:SecurityError)
{
UnitTester.hideSandboxes();
try
{
screenBits.draw(target.stage, new Matrix(1, 0, 0, 1, -stagePt.x, -stagePt.y));
}
catch (se2:Error)
{
try
{
// if we got a security error and ended up here, assume we're in the
// genericLoader loads us scenario
screenBits.draw(target.root, new Matrix(1, 0, 0, 1, -stagePt.x, -stagePt.y));
}
catch (se3:Error)
{
}
}
UnitTester.showSandboxes();
var sb:Array = UnitTester.getSandboxBitmaps();
var n:int = sb.length;
for (var i:int = 0; i < n; i++)
{
mergeSandboxBitmap(target, stagePt, screenBits, sb[i]);
}
}
catch (e:Error)
{
testResult.doFail (e.getStackTrace());
}
}
private var MAX_LC:int = 12000;
private var screenBits:BitmapData;
private var baselineBits:BitmapData;
private var compareVal:Object;
public function comparePNG(target:DisplayObject):Boolean
{
if (UnitTester.checkEmbeddedFonts)
{
if (!checkEmbeddedFonts(target))
{
testResult.doFail ("Target " + target + " is using non-embedded or advanced anti-aliased fonts");
return true;
}
}
if (!reader.content)
{
testResult.doFail ("baseline image not available");
return true;
}
getScreenBits(target);
try
{
baselineBits = new BitmapData(reader.content.width, reader.content.height);
baselineBits.draw(reader.content, new Matrix());
compareVal = baselineBits.compare (screenBits);
if (compareVal is BitmapData && numColorVariances) {
compareVal = compareWithVariances(compareVal as BitmapData)
}
if (compareVal != 0)
{
trace ("compare returned" + compareVal);
var req:URLRequest = new URLRequest();
if (UnitTester.isApollo)
{
req.url = encodeURI2(CompareBitmap.adjustPath (url));
}
else
{
req.url = url;
var base:String = normalizeURL(context.application.url);
base = base.substring(0, base.lastIndexOf("/"));
while (req.url.indexOf("../") == 0)
{
base = base.substring(0, base.lastIndexOf("/"));
req.url = req.url.substring(3);
}
req.url = encodeURI2(base + "/" + req.url);
}
req.url += ".xml";
xmlreader = new URLLoader();
xmlreader.addEventListener(Event.COMPLETE, readXMLCompleteHandler);
xmlreader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, readErrorHandler);
xmlreader.addEventListener(IOErrorEvent.IO_ERROR, readXMLIOErrorHandler);
xmlreader.load (req);
return false;
}
}
catch (e:Error)
{
testResult.doFail (e.getStackTrace());
}
return true;
}
private function readXMLCompleteHandler(event:Event):void
{
var actualTarget:DisplayObject = DisplayObject(context.stringToObject(target));
var s:String = getDisplayListXML(actualTarget).toXMLString();
if (s !== xmlreader.data)
{
testResult.doFail ("compare returned" + compareVal, absolutePathResult(url) + ".bad.png");
if (useRemoteDiffer)
{
sendImagesToDiffer();
}
else if (fileSuffix != "")
{
writeBaselines (actualTarget);
}
else
stepComplete();
}
private function readXMLIOErrorHandler(event:Event):void
{
if (useRemoteDiffer)
{
sendImagesToDiffer();
}
else if (fileSuffix != "")
{
testResult.doFail ("compare returned" + compareVal, absolutePathResult(url) + ".bad.png");
var actualTarget:DisplayObject = DisplayObject(context.stringToObject(target));
writePNG (actualTarget);
}
}
private function mergeSandboxBitmap(target:DisplayObject, pt:Point, bm:BitmapData, obj:Object):void
{
var targetSize:Point = getTargetSize(target);
var sbm:BitmapData = new BitmapData(obj.width, obj.height);
var srcRect:Rectangle = new Rectangle(0, 0, obj.width, obj.height);
sbm.setPixels(srcRect, obj.bits);
var targetRect:Rectangle = new Rectangle(pt.x, pt.y, targetSize.x, targetSize.y);
var sbRect:Rectangle = new Rectangle(obj.x, obj.y, obj.width, obj.height);
var area:Rectangle = targetRect.intersection(sbRect);
if (area)
bm.copyPixels(sbm, srcRect, target.globalToLocal(area.topLeft));
}
private function sendImagesToDiffer():void
{
UnitTester.callback = stringifyScreen;
}
private var ba:ByteArray;
private function stringifyScreen():void
{
ba = screenBits.getPixels(screenBits.rect);
ba.position = 0;
connection.send("_ImageDiffer", "startScreenData", screenBits.width, screenBits.height, ba.length, UnitTester.currentTestID, UnitTester.currentScript);
UnitTester.callback = sendScreen;
}
private function sendScreen():void
{
if (ba.position + MAX_LC < ba.length)
{
connection.send("_ImageDiffer", "addScreenData", stringify(ba));
UnitTester.callback = sendScreen;
}
else
{
connection.send("_ImageDiffer", "addScreenData", stringify(ba));
UnitTester.callback = stringifyBase;
}
}
private function stringifyBase():void
{
ba = baselineBits.getPixels(baselineBits.rect);
ba.position = 0;
connection.send("_ImageDiffer", "startBaseData", baselineBits.width, baselineBits.height, ba.length);
UnitTester.callback = sendBase;
}
private function sendBase():void
{
if (ba.position + MAX_LC < ba.length)
{
connection.send("_ImageDiffer", "addBaseData", stringify(ba));
UnitTester.callback = sendBase;
}
else
{
connection.send("_ImageDiffer", "addBaseData", stringify(ba));
connection.send("_ImageDiffer", "compareBitmaps");
}
}
private function stringify(ba:ByteArray):String
{
var n:int = Math.min(ba.length - ba.position, MAX_LC);
var arr:Array = [];
for (var i:int = 0; i < n; i++)
{
var b:int = ba.readUnsignedByte();
arr.push(b.toString(16))
}
return arr.toString();
}
private function readCompleteHandler(event:Event):void
{
var actualTarget:DisplayObject = DisplayObject(context.stringToObject(target));
if (comparePNG(actualTarget))
stepComplete();
}
private function readErrorHandler(event:Event = null):void
{
var failString:String = "baseline image could not be read.";
failString += " Creating image file as a .bad.png.";
var actualTarget:DisplayObject = DisplayObject(context.stringToObject(target));
getScreenBits(actualTarget);
writePNG(actualTarget);
testResult.doFail ( failString );
stepComplete();
}
/**
* Read a file and return a ByteArray that we can feed into reader (a Loader).
**/
public function loadFileBytes( file:File ):ByteArray{
var fileStream:FileStream = new FileStream();
var file:File;
var ba:ByteArray = new ByteArray();
var bytesRead:int = 0;
var bytesAvail:int = 0;
fileStream.open(file, FileMode.READ);
bytesAvail = fileStream.bytesAvailable;
while( bytesAvail > 0 ){
fileStream.readBytes(ba, bytesRead, bytesAvail);
bytesAvail = fileStream.bytesAvailable;
}
fileStream.close();
return ba;
}
public function readPNG():void
{
var req:URLRequest = new URLRequest();
var ba:ByteArray = null;
var file:File;
// If iOS and AIR, let's try using file I/O instead of loader stuff. AIR on devices can be a pain with the url stuff.
if( UnitTester.isApollo && (UnitTester.cv.os.toLowerCase() == DeviceNames.IOS.toLowerCase()) ){
// Trim the leading ../ if we have it.
if ( url.indexOf ("../") == 0 ){
url = url.substring (3);
}
file = File.documentsDirectory.resolvePath( url );
if( !file.exists ){
readErrorHandler();
return;
}
ba = loadFileBytes( file );
reader.loadBytes( ba );
}else{
if (UnitTester.isApollo)
{
req.url = encodeURI2(CompareBitmap.adjustPath (url));
}
else
{
req.url = url;
var base:String = normalizeURL(context.application.url);
base = base.substring(0, base.lastIndexOf("/"));
while (req.url.indexOf("../") == 0)
{
base = base.substring(0, base.lastIndexOf("/"));
req.url = req.url.substring(3);
}
req.url = encodeURI2(base + "/" + req.url);
}
}
reader = new Loader();
reader.contentLoaderInfo.addEventListener(Event.COMPLETE, readCompleteHandler);
reader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, readErrorHandler);
reader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, readErrorHandler);
// req.url = encodeURI2(url);
// }
trace ("readPNG:requesting url: " + req.url);
reader.load (req);
}
public static var adjustPath:Function = function(url:String):String { return url; };
public function getPngByteArray(target:DisplayObject, bitmapData:BitmapData):ByteArray
{
// add png headers
if (UnitTester.createBitmapReferences)
{
if (stageText == false)
{
var targetSize:Point = getTargetSize(target);
var stagePt:Point = target.localToGlobal(new Point(0, 0));
var altPt:Point = target.localToGlobal(targetSize);
stagePt.x = Math.min(stagePt.x, altPt.x);
stagePt.y = Math.min(stagePt.y, altPt.y);
bitmapData = new BitmapData(targetSize.x, targetSize.y);
bitmapData.draw(target.stage, new Matrix(1, 0, 0, 1, -stagePt.x, -stagePt.y));
} else {
trace ("stageText value: " + stageText);
try {
bitmapData = target["textDisplay"]["captureBitmapData"]();
trace ("success asking StageText for bitmap data");
} catch (e:Error) {
trace ("Tried for StageText bitmap data, but it failed");
trace (e.getStackTrace());
}
}
}
var png:MustellaPNGEncoder = new MustellaPNGEncoder();
var ba:ByteArray = png.encode (bitmapData);
return ba;
}
public function writeBaselines(target:DisplayObject, writeDisplayList:Boolean = true):void
{
var req:URLRequest = new URLRequest();
writer = new URLLoader();
req.method = "POST";
/**
* either we got called here to write new baselines
* or to save a .bad.png for investigation
* in addition, with failures, we upload baseline and failure to a server
*/
if (UnitTester.createBitmapReferences)
{
fileSuffix = "";
}
if (writeDisplayList)
{
var s:String = getDisplayListXML(target).toXMLString();
// request data goes on the URL Request
req.data = s;
req.contentType = "text/xml";
if (UnitTester.isApollo)
{
req.url = encodeURI2(UnitTester.bitmapServerPrefix + adjustWriteURI(adjustPath(url))) + fileSuffix + ".xml";
} else
{
req.url = encodeURI2(UnitTester.bitmapServerPrefix + absolutePath(url)) + fileSuffix + ".xml";
}
trace ("writing url: " + req.url);
writer.addEventListener(Event.COMPLETE, writeXMLCompleteHandler);
writer.addEventListener(SecurityErrorEvent.SECURITY_ERROR, writeErrorHandler);
writer.addEventListener(IOErrorEvent.IO_ERROR, writeErrorHandler);
writer.load (req);
}
}
private function writeXMLCompleteHandler(event:Event):void
{
var actualTarget:DisplayObject = DisplayObject(context.stringToObject(target));
writePNG(actualTarget);
}
private function writePNG(target:DisplayObject):void
{
var req:URLRequest = new URLRequest();
writer = new URLLoader();
req.method = "POST";
var ba:ByteArray = getPngByteArray(target, screenBits);
trace ("image size: " + ba.length);
if( UnitTester.createBitmapReferences ){
fileSuffix = "";
}
if( UnitTester.writeBaselinesToDisk ){
// context.testDir: mobile/components/Button/properties/
// url: ../properties/baselines/button_android_test1_CustomName.png
var writePath:String = createWritePath( context.testDir, url + fileSuffix );
var deviceWritePath:String = UnitTester.mustellaWriteLocation + "/" + writePath;
// trace("*******deviceWritePath = " + deviceWritePath);
var hostWritePath:String = writePath;
var file:File = new File ( deviceWritePath );
var fileStream:FileStream = new FileStream();
var hostCommand:String;
// open() opens synchronously, so we don't have to (and can't) use listeners.
fileStream.open(file, FileMode.WRITE);
fileStream.writeBytes(ba);
fileStream.close();
// Tell the host machine to copy the baseline image.
// Also, give it a URL with test info. to use when sending it to flexqa01's baseline server.
if( UnitTester.cv.os.toLowerCase() == DeviceNames.ANDROID.toLowerCase() ||
UnitTester.cv.os.toLowerCase() == DeviceNames.IOS.toLowerCase() ||
UnitTester.cv.os.toLowerCase() == DeviceNames.QNX.toLowerCase() ){
hostCommand = "CopyDeviceFile: FROM=" + deviceWritePath + " TO=" + hostWritePath;
// If this is about creating bitmaps, skip the upload, we're done
if ( !UnitTester.createBitmapReferences ){
hostCommand += " SCREENURL=" + (UnitTester.urlAssemble ("screen", context.testDir, context.scriptName, this.testResult.testID, UnitTester.run_id));
hostCommand += " BASELINEURL=" + (UnitTester.urlAssemble ("baseline", context.testDir, context.scriptName, this.testResult.testID, UnitTester.run_id));
}
// Trace statements are parsed by the host.
trace( hostCommand );
}
if (UnitTester.createBitmapReferences){
stepComplete();
}
return;
}else{
trace("AIR CompareBitmap is called, but we're not writing to disk. This is wrong.");
}
}
private function adjustWriteURI(url:String):String
{
var base:String = null;
// For iOS, do something different.
if( UnitTester.isApollo && (UnitTester.cv.os != null) && (UnitTester.cv.os.toLowerCase() == DeviceNames.IOS.toLowerCase()) ){
base = url;
// Trim the leading ../ if we have it.
if ( base.indexOf ("../") == 0 ){
base = base.substring (3);
}
base = File.documentsDirectory.resolvePath(base).nativePath;
return base;
}else{
var pos:int = url.indexOf("file:///");
if (pos != 0)
{
return url;
}
url = url.substring(8);
pos = url.indexOf("|");
if (pos != 1)
{
return url;
}
var drive:String = url.substring(0, 1);
drive = drive.toLowerCase();
return drive + ":" + url.substring(2);
}
}
/**
* Called by writePNG when, for example, we're testing on Android.
* firstPart: mobile/components/Button/properties/
* secondPart: ../properties/baselines/button_android_test1_CustomName.png
**/
private function createWritePath( firstPart:String, secondPart:String ):String{
var ret:String = "tests/" + firstPart.substring( 0, firstPart.lastIndexOf( "/" ) );
var removeMe:String = ret.substring( ret.lastIndexOf( "/" ), ret.length ); // "properties"
ret += secondPart.substring( secondPart.indexOf( removeMe ) + removeMe.length, secondPart.length );
trace("createWritePath returning " + ret);
return ret;
}
private var screenDone:Boolean = false;
private var baselineDone:Boolean = false;
private function writeCompleteHandler(event:Event):void
{
trace("baseline write successful " + event);
stepComplete();
}
private function uploadCompleteHandler(event:Event):void
{
trace("screen image upload successful " + event);
screenDone = true;
checkForStepComplete();
}
private function upload2CompleteHandler(event:Event):void
{
trace("baseline image upload successful " + event);
baselineDone = true;
checkForStepComplete();
}
private function writeErrorHandler(event:Event):void
{
testResult.doFail ("error on baseline write: " + event);
trace("Image baseline write failed " + event);
if (UnitTester.createBitmapReferences)
stepComplete();
}
private function uploadErrorHandler(event:Event):void
{
testResult.doFail ("error on baseline write: " + event);
trace("Image screen upload failed " + event);
screenDone = true;
checkForStepComplete();
}
private function upload2ErrorHandler(event:Event):void
{
testResult.doFail ("error on baseline write: " + event);
trace("Image baseline upload failed " + event);
baselineDone = true;
checkForStepComplete();
}
private function checkForStepComplete():void
{
if (baselineDone && screenDone)
stepComplete();
}
/**
* customize string representation
*/
override public function toString():String
{
var s:String = (UnitTester.createBitmapReferences) ? "CreateBitmap: " : "CompareBitmap";
if (target)
s += ": target = " + target;
if (url)
s += ", url = " + url;
return s;
}
private function absolutePathResult(url:String):String
{
var base:String = null;
if( UnitTester.isApollo && (UnitTester.cv.os != null) && (UnitTester.cv.os.toLowerCase() == DeviceNames.IOS.toLowerCase()) ){
base = url;
// Trim the leading ../ if we have it.
if ( base.indexOf ("../") == 0 ){
base = base.substring (3);
}
base = File.documentsDirectory.resolvePath(base).nativePath;
} else if( UnitTester.isApollo && (UnitTester.cv.os != null) && (UnitTester.cv.os.toLowerCase() == DeviceNames.ANDROID.toLowerCase()) ){
/// code doing what it does now:
var fPath:String = createWritePath( context.testDir, url + fileSuffix );
/// clean up
if (fPath.indexOf ("tests/") == 0)
fPath = fPath.substring (5);
/// clean up
/// it probably has the .bad.png suffix
if (fPath.indexOf (".bad.png") > 0)
fPath = fPath.substring (0, fPath.length-".bad.png".length);
base = fPath;
}else{
if (UnitTester.isApollo) {
base = adjustWriteURI(adjustPath (url));
}else{
base = context.application.url;
}
base = normalizeURL(base);
base = base.substring (base.indexOf ("mustella/tests")+14);
if (!UnitTester.isApollo) {
base = base.substring(0, base.lastIndexOf("/"));
var tmp:String = url;
while (tmp.indexOf("../") == 0){
base = base.substring(0, base.lastIndexOf("/"));
tmp = tmp.substring(3);
}
base += "/" + tmp;
}
}
return base;
}
private function absolutePath(url:String):String
{
var swf:String = normalizeURL(root.loaderInfo.url);
var pos:int = swf.indexOf("file:///");
if (pos != 0)
{
trace("WARNING: unexpected swf url format, no file:/// at offset 0");
return url;
}
swf = swf.substring(8);
pos = swf.indexOf("|");
if (pos != 1)
{
trace("WARNING: unexpected swf url format, no | at offset 1 in: " + swf);
// assume we're on a mac or other unix box, it will do no harm
return "/" + swf.substring(0, swf.lastIndexOf ("/")+1) + url;
}
var drive:String = swf.substring(0, 1);
drive = drive.toLowerCase();
return drive + ":" + swf.substring(2, swf.lastIndexOf("/") + 1) + url;
}
public static function normalizeURL(url:String):String
{
var results:Array = url.split("/[[DYNAMIC]]/");
return results[0];
}
public function keepGoing():void
{
trace("keepgoing", url, hasEventListener("stepComplete"));
stepComplete();
}
private function encodeURI2(s:String):String
{
var pos:int = s.lastIndexOf("/");
if (pos != -1)
{
var fragment:String = s.substring(pos + 1);
s = s.substring(0, pos + 1);
fragment= encodeURIComponent(fragment);
s = s + fragment;
}
return s;
}
private function compareWithVariances(bm:BitmapData):Object
{
var allowed:int = numColorVariances * UnitTester.pixelToleranceMultiplier;
var n:int = bm.height;
var m:int = bm.width;
for (var i:int = 0; i < n; i++)
{
for (var j:int = 0; j < m; j++)
{
var pix:int = bm.getPixel(j, i);
if (pix)
{
if(!ignoreMaxColorVariance)
{
var red:int = pix >> 16 & 0xff;
var green:int = pix >> 8 & 0xff;
var blue:int = pix & 0xff;
if (red & 0x80)
red = 256 - red;
if (blue & 0x80)
blue = 256 - blue;
if (green & 0x80)
green = 256 - green;
if (red > maxColorVariance ||
blue > maxColorVariance ||
green > maxColorVariance)
{
return bm;
}
}
allowed--;
if (allowed < 0)
{
return bm;
}
}
}
}
return 0;
}
private function checkEmbeddedFonts(target:Object):Boolean
{
if ("rawChildren" in target)
target = target.rawChildren;
if (target is TextField)
{
if (target.embedFonts == false)
return false;
if (target.antiAliasType == "advanced")
return false;
return true;
}
else if ("numChildren" in target)
{
var n:int = target.numChildren;
for (var i:int = 0; i < n; i++)
{
if (!checkEmbeddedFonts(target.getChildAt(i)))
return false;
}
}
return true;
}
override protected function stepComplete():void
{
if (baselineBits != null)
baselineBits.dispose();
if (screenBits != null)
screenBits.dispose();
reader=null;
writer=null;
super.stepComplete();
}
/****** DisplayList Comparision ******/
protected function getDisplayListProperties(d:DisplayObject, noMask:Boolean = false):XML
{
var xml:XML;
var n:int;
var i:int;
var childXML:XML;
var s:String = getQualifiedClassName(d);
s = s.replace("::", ".");
xml = new XML("<" + s + "/>");
s = d.transform.concatenatedColorTransform.toString();
if (s != identityColorTransform)
xml.@concatenatedColorTransform = s;
if (d.transform.matrix)
{
s = d.transform.matrix.toString();
if (s != identityMatrix)
{
if (s.indexOf("(a=1, b=0, c=0, d=1, ") == -1)
xml.@matrix = s;
}
}
else
{
s = d.transform.matrix3D.rawData.toString();
xml.@matrix3D = s;
}
if (d.x != 0)
xml.@x = d.x;
if (d.y != 0)
xml.@y = d.y;
xml.@width = d.width;
xml.@height = d.height;
if (xml.visible == false)
xml.@visible = "false";
if (d.mask && !noMask)
{
xml.mask = <mask/>;
childXML = getDisplayListProperties(d.mask, true);
xml.mask.appendChild = childXML;
}
if (d.scrollRect)
{
s = d.scrollRect.toString();
xml.@scrollRect = s;
}
if (d.blendMode && d.blendMode != "normal")
xml.@blendMode = d.blendMode;
if (d.cacheAsBitmap)
xml.@cacheAsBitmap = "true";
if (d.filters && d.filters.length > 0)
{
s = d.filters.toString();
xml.@filters = s;
}
if (d.opaqueBackground)
xml.@opaqueBackground = "true";
if (d.scale9Grid)
{
s = d.scale9Grid.toString();
xml.@scale9Grid = s;
}
if (d is TextField)
{
xml.htmlText = TextField(d).htmlText;
}
if (d is Loader && Loader(d).contentLoaderInfo.contentType.indexOf("image") != -1)
{
s = Loader(d).contentLoaderInfo.url;
s = s.substring(s.lastIndexOf("/") + 1);
xml.@loaderbitmap = s;
}
if (d is TextLine)
{
var tl:TextLine = TextLine(d);
xml.@ascent = tl.ascent;
xml.@descent = tl.descent;
xml.@atomCount = tl.atomCount;
xml.@hasGraphicElement = tl.hasGraphicElement;
if (tl.textBlock)
{
var tb:TextBlock = TextLine(d).textBlock;
var ce:ContentElement = tb.content;
s = ce.rawText.substr(tl.textBlockBeginIndex, tl.rawTextLength);
xml.@text = s;
}
}
if (d is IRawChildrenContainer)
{
var rawChildren:IChildList = IRawChildrenContainer(d).rawChildren;
n = rawChildren.numChildren;
for (i = 0; i < n; i++)
{
childXML = getDisplayListProperties(rawChildren.getChildAt(i));
xml.appendChild(childXML);
}
}
else if (d is DisplayObjectContainer)
{
var doc:DisplayObjectContainer = d as DisplayObjectContainer;
n = doc.numChildren;
for (i = 0; i < n; i++)
{
childXML = getDisplayListProperties(doc.getChildAt(i));
xml.appendChild(childXML);
}
}
return xml;
}
// scan entire display list, but only dump objects intersecting target
protected function getDisplayListXML(target:DisplayObject):XML
{
var n:int;
var i:int;
var child:DisplayObject;
var childXML:XML;
var doc:DisplayObjectContainer = DisplayObjectContainer(target.root);
var xml:XML = <DisplayList />;
if (doc is IRawChildrenContainer)
{
var rawChildren:IChildList = IRawChildrenContainer(doc).rawChildren;
n = rawChildren.numChildren;
for (i = 0; i < n; i++)
{
child = rawChildren.getChildAt(i);
if (target.hitTestObject(child))
{
childXML = getDisplayListProperties(child);
xml.appendChild(childXML);
}
}
}
else
{
n = doc.numChildren;
for (i = 0; i < n; i++)
{
child = doc.getChildAt(i);
if (target.hitTestObject(child))
{
childXML = getDisplayListProperties(child);
xml.appendChild(childXML);
}
}
}
return xml;
}
}
}