Bug 720144 - Expose methods in robocop to grab the painted surface and to compare pixels. r=jmaher

This commit is contained in:
Kartikaya Gupta 2012-01-30 22:46:13 -05:00
parent de6b967c0a
commit f34c6b083c
8 changed files with 158 additions and 4 deletions

View File

@ -60,6 +60,12 @@ public interface Actions {
* @param geckoEvent The geckoEvent JSONObject's type
*/
EventExpecter expectGeckoEvent(String geckoEvent);
/**
* Listens for a paint event.
*/
EventExpecter expectPaint();
// Send the string kewsToSend to the application
void sendKeys(String keysToSend);
//Send any of the above keys to the element

View File

@ -52,4 +52,7 @@ public interface Assert {
void todo_is(Object a, Object b, String name);
void todo_isnot(Object a, Object b, String name);
void info(String name, String message);
// robocop-specific asserts
void ispixel(int actual, int r, int g, int b, String name);
}

View File

@ -68,4 +68,11 @@ public interface Driver {
void startFrameRecording();
int stopFrameRecording();
/**
* Get a copy of the painted content region.
* @return A 2-D array of pixels (indexed by y, then x). The pixels
* are in ARGB-8888 format.
*/
int[][] getPaintedSurface();
}

View File

@ -73,20 +73,24 @@ public class FennecNativeActions implements Actions {
// Map of IDs to element names.
private Solo solo;
private Instrumentation instr;
private Activity geckoApp;
// Objects for reflexive access of fennec classes.
private ClassLoader classLoader;
private Class gel;
private Class ge;
private Class gas;
private Class drawListener;
private Method registerGEL;
private Method unregisterGEL;
private Method sendGE;
private Method getLayerClient;
private Method setDrawListener;
public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation){
this.solo = robocop;
this.instr = instrumentation;
this.geckoApp = activity;
// Set up reflexive access of java classes and methods.
try {
classLoader = activity.getClassLoader();
@ -101,6 +105,11 @@ public class FennecNativeActions implements Actions {
parameters = new Class[1];
parameters[0] = ge;
sendGE = gas.getMethod("sendEventToGecko", parameters);
getLayerClient = activity.getClass().getMethod("getSoftwareLayerClient");
Class gslc = classLoader.loadClass("org.mozilla.gecko.gfx.GeckoSoftwareLayerClient");
drawListener = classLoader.loadClass("org.mozilla.gecko.gfx.GeckoSoftwareLayerClient$DrawListener");
setDrawListener = gslc.getDeclaredMethod("setDrawListener", drawListener);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
@ -205,6 +214,75 @@ public class FennecNativeActions implements Actions {
return null;
}
class DrawListenerProxy implements InvocationHandler {
private final PaintExpecter mPaintExpecter;
DrawListenerProxy(PaintExpecter paintExpecter) {
mPaintExpecter = paintExpecter;
}
public Object invoke(Object proxy, Method method, Object[] args) {
String methodName = method.getName();
if ("drawFinished".equals(methodName)) {
Log.i("Robocop", "Received drawFinished notification");
mPaintExpecter.notifyOfEvent();
} else if ("toString".equals(methodName)) {
return "DrawListenerProxy";
} else if ("equals".equals(methodName)) {
return false;
} else if ("hashCode".equals(methodName)) {
return 0;
}
return null;
}
}
class PaintExpecter implements EventExpecter {
private Object mLayerClient;
private boolean mPaintDone;
PaintExpecter() throws IllegalAccessException, InvocationTargetException {
mLayerClient = getLayerClient.invoke(geckoApp);
setDrawListener.invoke(mLayerClient, Proxy.newProxyInstance(classLoader, new Class[] { drawListener }, new DrawListenerProxy(this)));
}
void notifyOfEvent() {
try {
setDrawListener.invoke(mLayerClient, (Object)null);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (this) {
mPaintDone = true;
this.notifyAll();
}
}
public synchronized void blockForEvent() {
while (! mPaintDone) {
try {
this.wait();
} catch (InterruptedException ie) {
ie.printStackTrace();
break;
}
}
}
public synchronized boolean eventReceived() {
return mPaintDone;
}
}
public EventExpecter expectPaint() {
try {
return new PaintExpecter();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void sendSpecialKey(SpecialKey button) {
switch( button) {
case DOWN:

View File

@ -243,6 +243,23 @@ public class FennecNativeAssert implements Assert {
ok(pass, name, diag);
}
public void ispixel(int actual, int r, int g, int b, String name) {
// When we read GL pixels the GPU has already processed them and they
// are usually off by a little bit. For example a CSS-color pixel of color #64FFF5
// was turned into #63FFF7 when it came out of glReadPixels. So in order to compare
// against the expected value, we use a little fuzz factor. For the alpha we just
// make sure it is always 0xFF.
int aAlpha = ((actual >> 24) & 0xFF);
int aR = ((actual >> 16) & 0xFF);
int aG = ((actual >> 8) & 0xFF);
int aB = (actual & 0xFF);
boolean pass = (aAlpha == 0xFF) /* alpha */
&& (Math.abs(aR - r) < 8) /* red */
&& (Math.abs(aG - g) < 8) /* green */
&& (Math.abs(aB - b) < 8); /* blue */
ok(pass, name, "Color rgba(" + aR + "," + aG + "," + aB + "," + aAlpha + ")" + (pass ? " " : " not") + " close enough to expected rgb(" + r + "," + g + "," + b + ")");
}
public void todo(boolean condition, String name, String diag) {
testInfo test = new testInfo(condition, name, diag, true);
_logMochitestResult(test, "TEST-UNEXPECTED-PASS", "TEST-KNOWN-FAIL");

View File

@ -45,6 +45,7 @@ import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.HashMap;
@ -58,6 +59,7 @@ import java.lang.reflect.InvocationHandler;
import java.lang.Long;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.view.View;
@ -81,6 +83,7 @@ public class FennecNativeDriver implements Driver {
private Method sendGE;
private Method _startFrameRecording;
private Method _stopFrameRecording;
private Method _getPixels;
public FennecNativeDriver(Activity activity, Solo robocop){
this.activity = activity;
@ -107,6 +110,9 @@ public class FennecNativeDriver implements Driver {
Class gfx = classLoader.loadClass("org.mozilla.gecko.gfx.PanningPerfAPI");
_startFrameRecording = gfx.getDeclaredMethod("startFrameTimeRecording");
_stopFrameRecording = gfx.getDeclaredMethod("stopFrameTimeRecording");
Class layerView = classLoader.loadClass("org.mozilla.gecko.gfx.LayerView");
_getPixels = layerView.getDeclaredMethod("getPixels");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
@ -212,6 +218,43 @@ public class FennecNativeDriver implements Driver {
return 0;
}
private GLSurfaceView getSurfaceView() {
for (View v : solo.getCurrentViews()) {
if (v instanceof GLSurfaceView) {
return (GLSurfaceView)v;
}
}
return null;
}
public int[][] getPaintedSurface() {
GLSurfaceView view = getSurfaceView();
if (view == null) {
return null;
}
IntBuffer pixelBuffer;
try {
pixelBuffer = (IntBuffer)_getPixels.invoke(view);
} catch (Exception e) {
e.printStackTrace();
return null;
}
// now we need to (1) flip the image, because GL likes to do things up-side-down,
// and (2) rearrange the bits from AGBR-8888 to ARGB-8888.
int w = view.getWidth();
int h = view.getHeight();
pixelBuffer.position(0);
int[][] pixels = new int[h][w];
for (int y = h - 1; y >= 0; y--) {
for (int x = 0; x < w; x++) {
int agbr = pixelBuffer.get();
pixels[y][x] = (agbr & 0xFF00FF00) | ((agbr >> 16) & 0x000000FF) | ((agbr << 16) & 0x00FF0000);
}
}
return pixels;
}
class scrollHandler implements InvocationHandler {
public scrollHandler(){};
public Object invoke(Object proxy, Method method, Object[] args) {

View File

@ -545,12 +545,12 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
return Color.rgb(r, g, b);
}
/** Used by robocop for testing purposes. Not for production use! */
/** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
public void setDrawListener(DrawListener listener) {
mDrawListener = listener;
}
/** Used by robocop for testing purposes. Not for production use! */
/** Used by robocop for testing purposes. Not for production use! This is used via reflection by robocop. */
public interface DrawListener {
public void drawFinished(int x, int y, int width, int height);
}

View File

@ -239,7 +239,7 @@ public class LayerView extends GLSurfaceView
return mRenderer.getMaxTextureSize();
}
/** Used by robocop for testing purposes. Not for production use! */
/** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
public IntBuffer getPixels() {
return mRenderer.getPixels();
}