1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.uirendering.cts.testinfrastructure; 17 18 import android.annotation.Nullable; 19 import android.graphics.Bitmap; 20 import android.renderscript.Allocation; 21 import android.renderscript.RenderScript; 22 import android.test.ActivityInstrumentationTestCase2; 23 import android.uirendering.cts.bitmapcomparers.BitmapComparer; 24 import android.uirendering.cts.bitmapverifiers.BitmapVerifier; 25 import android.uirendering.cts.differencevisualizers.DifferenceVisualizer; 26 import android.uirendering.cts.differencevisualizers.PassFailVisualizer; 27 import android.uirendering.cts.util.BitmapDumper; 28 import android.util.Log; 29 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.List; 33 34 /** 35 * This class contains the basis for the graphics hardware test classes. Contained within this class 36 * are several methods that help with the execution of tests, and should be extended to gain the 37 * functionality built in. 38 */ 39 public abstract class ActivityTestBase extends 40 ActivityInstrumentationTestCase2<DrawActivity> { 41 public static final String TAG_NAME = "ActivityTestBase"; 42 public static final boolean DEBUG = false; 43 public static final boolean USE_RS = false; 44 public static final int TEST_WIDTH = 180; 45 public static final int TEST_HEIGHT = 180; //The minimum height and width of a device 46 public static final int MAX_SCREEN_SHOTS = 100; 47 48 private int[] mHardwareArray = new int[TEST_HEIGHT * TEST_WIDTH]; 49 private int[] mSoftwareArray = new int[TEST_HEIGHT * TEST_WIDTH]; 50 private DifferenceVisualizer mDifferenceVisualizer; 51 private Allocation mIdealAllocation; 52 private Allocation mGivenAllocation; 53 private RenderScript mRenderScript; 54 private TestCaseBuilder mTestCaseBuilder; 55 56 /** 57 * The default constructor creates the package name and sets the DrawActivity as the class that 58 * we would use. 59 */ 60 public ActivityTestBase() { 61 super(DrawActivity.class); 62 mDifferenceVisualizer = new PassFailVisualizer(); 63 64 // Create a location for the files to be held, if it doesn't exist already 65 BitmapDumper.createSubDirectory(this.getClass().getSimpleName()); 66 67 // If we have a test currently, let's remove the older files if they exist 68 if (getName() != null) { 69 BitmapDumper.deleteFileInClassFolder(this.getClass().getSimpleName(), getName()); 70 } 71 } 72 73 /** 74 * This method is called before each test case and should be called from the test class that 75 * extends this class. 76 */ 77 @Override 78 public void setUp() { 79 mDifferenceVisualizer = new PassFailVisualizer(); 80 if (USE_RS) { 81 mRenderScript = RenderScript.create(getActivity().getApplicationContext()); 82 } 83 } 84 85 /** 86 * This method will kill the activity so that it can be reset depending on the test. 87 */ 88 @Override 89 public void tearDown() { 90 if (mTestCaseBuilder != null) { 91 List<TestCase> testCases = mTestCaseBuilder.getTestCases(); 92 93 if (testCases.size() == 0) { 94 throw new IllegalStateException("Must have at least one test case"); 95 } 96 97 98 for (TestCase testCase : testCases) { 99 if (!testCase.wasTestRan) { 100 Log.w(TAG_NAME, getName() + " not all of the tests were ran"); 101 break; 102 } 103 } 104 mTestCaseBuilder = null; 105 } 106 107 Runnable finishRunnable = new Runnable() { 108 109 @Override 110 public void run() { 111 getActivity().finish(); 112 } 113 }; 114 115 getActivity().runOnUiThread(finishRunnable); 116 } 117 118 public Bitmap takeScreenshot() { 119 getInstrumentation().waitForIdleSync(); 120 Bitmap bitmap1 = getInstrumentation().getUiAutomation().takeScreenshot(); 121 Bitmap bitmap2; 122 int count = 0; 123 do { 124 bitmap2 = bitmap1; 125 bitmap1 = getInstrumentation().getUiAutomation().takeScreenshot(); 126 count++; 127 } while (count < MAX_SCREEN_SHOTS && !Arrays.equals(bitmap2.mBuffer, bitmap1.mBuffer)); 128 return bitmap1; 129 } 130 131 /** 132 * Sets the current DifferenceVisualizer for use in current test. 133 */ 134 public void setDifferenceVisualizer(DifferenceVisualizer differenceVisualizer) { 135 mDifferenceVisualizer = differenceVisualizer; 136 } 137 138 /** 139 * Used to execute a specific part of a test and get the resultant bitmap 140 */ 141 protected Bitmap captureRenderSpec(TestCase testCase) { 142 getActivity().enqueueRenderSpecAndWait(testCase.layoutID, testCase.canvasClient, 143 testCase.webViewUrl, testCase.viewInitializer, testCase.useHardware); 144 testCase.wasTestRan = true; 145 return takeScreenshot(); 146 } 147 148 /** 149 * Compares the two bitmaps saved using the given test. If they fail, the files are saved using 150 * the test name. 151 */ 152 protected void assertBitmapsAreSimilar(Bitmap bitmap1, Bitmap bitmap2, 153 BitmapComparer comparer, String debugMessage) { 154 boolean success; 155 156 if (USE_RS && comparer.supportsRenderScript()) { 157 mIdealAllocation = Allocation.createFromBitmap(mRenderScript, bitmap1, 158 Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); 159 mGivenAllocation = Allocation.createFromBitmap(mRenderScript, bitmap2, 160 Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); 161 success = comparer.verifySameRS(getActivity().getResources(), mIdealAllocation, 162 mGivenAllocation, 0, TEST_WIDTH, TEST_WIDTH, TEST_HEIGHT, mRenderScript); 163 } else { 164 bitmap1.getPixels(mSoftwareArray, 0, TEST_WIDTH, 0, 0, TEST_WIDTH, TEST_HEIGHT); 165 bitmap2.getPixels(mHardwareArray, 0, TEST_WIDTH, 0, 0, TEST_WIDTH, TEST_HEIGHT); 166 success = comparer.verifySame(mSoftwareArray, mHardwareArray, 0, TEST_WIDTH, TEST_WIDTH, 167 TEST_HEIGHT); 168 } 169 170 if (!success) { 171 BitmapDumper.dumpBitmaps(bitmap1, bitmap2, getName(), this.getClass().getSimpleName(), 172 mDifferenceVisualizer); 173 } 174 175 assertTrue(debugMessage, success); 176 } 177 178 /** 179 * Tests to see if a bitmap passes a verifier's test. If it doesn't the bitmap is saved to the 180 * sdcard. 181 */ 182 protected void assertBitmapIsVerified(Bitmap bitmap, BitmapVerifier bitmapVerifier, 183 String debugMessage) { 184 bitmap.getPixels(mSoftwareArray, 0, TEST_WIDTH, 0, 0, 185 TEST_WIDTH, TEST_HEIGHT); 186 boolean success = bitmapVerifier.verify(mSoftwareArray, 0, TEST_WIDTH, TEST_WIDTH, TEST_HEIGHT); 187 if (!success) { 188 Bitmap croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, TEST_WIDTH, TEST_HEIGHT); 189 BitmapDumper.dumpBitmap(croppedBitmap, getName(), this.getClass().getSimpleName()); 190 BitmapDumper.dumpBitmap(bitmapVerifier.getDifferenceBitmap(), getName() + "_verifier", 191 this.getClass().getSimpleName()); 192 } 193 assertTrue(debugMessage, success); 194 } 195 196 protected TestCaseBuilder createTest() { 197 mTestCaseBuilder = new TestCaseBuilder(); 198 return mTestCaseBuilder; 199 } 200 201 /** 202 * Defines a group of CanvasClients, XML layouts, and WebView html files for testing. 203 */ 204 protected class TestCaseBuilder { 205 private List<TestCase> mTestCases; 206 207 private TestCaseBuilder() { 208 mTestCases = new ArrayList<TestCase>(); 209 } 210 211 /** 212 * Runs a test where the first test case is considered the "ideal" image and from there, 213 * every test case is tested against it. 214 */ 215 public void runWithComparer(BitmapComparer bitmapComparer) { 216 if (mTestCases.size() == 0) { 217 throw new IllegalStateException("Need at least one test to run"); 218 } 219 220 Bitmap idealBitmap = captureRenderSpec(mTestCases.remove(0)); 221 222 for (TestCase testCase : mTestCases) { 223 Bitmap testCaseBitmap = captureRenderSpec(testCase); 224 assertBitmapsAreSimilar(idealBitmap, testCaseBitmap, bitmapComparer, 225 testCase.getDebugString()); 226 } 227 } 228 229 /** 230 * Runs a test where each testcase is independent of the others and each is checked against 231 * the verifier given. 232 */ 233 public void runWithVerifier(BitmapVerifier bitmapVerifier) { 234 if (mTestCases.size() == 0) { 235 throw new IllegalStateException("Need at least one test to run"); 236 } 237 238 for (TestCase testCase : mTestCases) { 239 Bitmap testCaseBitmap = captureRenderSpec(testCase); 240 assertBitmapIsVerified(testCaseBitmap, bitmapVerifier, testCase.getDebugString()); 241 } 242 } 243 244 public TestCaseBuilder addWebView(String webViewUrl, 245 @Nullable ViewInitializer viewInitializer) { 246 return addWebView(webViewUrl, viewInitializer, false) 247 .addWebView(webViewUrl, viewInitializer, true); 248 } 249 250 public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer) { 251 return addLayout(layoutId, viewInitializer, false) 252 .addLayout(layoutId, viewInitializer, true); 253 } 254 255 public TestCaseBuilder addCanvasClient(CanvasClient canvasClient) { 256 return addCanvasClient(canvasClient, false) 257 .addCanvasClient(canvasClient, true); 258 } 259 260 public TestCaseBuilder addWebView(String webViewUrl, 261 @Nullable ViewInitializer viewInitializer, boolean useHardware) { 262 mTestCases.add(new TestCase(null, 0, webViewUrl, viewInitializer, useHardware)); 263 return this; 264 } 265 266 public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer, 267 boolean useHardware) { 268 mTestCases.add(new TestCase(null, layoutId, null, viewInitializer, useHardware)); 269 return this; 270 } 271 272 public TestCaseBuilder addCanvasClient(CanvasClient canvasClient, boolean useHardware) { 273 mTestCases.add(new TestCase(canvasClient, 0, null, null, useHardware)); 274 return this; 275 } 276 277 private List<TestCase> getTestCases() { 278 return mTestCases; 279 } 280 } 281 282 private class TestCase { 283 public int layoutID; 284 public CanvasClient canvasClient; 285 public String webViewUrl; 286 public ViewInitializer viewInitializer; 287 public boolean useHardware; 288 public boolean wasTestRan; 289 290 public TestCase(CanvasClient client, int id, String viewUrl, 291 ViewInitializer viewInitializer, boolean useHardware) { 292 int count = 0; 293 count += (client == null ? 0 : 1); 294 count += (viewUrl == null ? 0 : 1); 295 count += (id == 0 ? 0 : 1); 296 assert(count == 1); 297 assert(client == null || viewInitializer == null); 298 this.layoutID = id; 299 this.canvasClient = client; 300 this.webViewUrl = viewUrl; 301 this.viewInitializer = viewInitializer; 302 this.useHardware = useHardware; 303 this.wasTestRan = false; 304 } 305 306 public String getDebugString() { 307 String debug = ""; 308 if (canvasClient != null) { 309 debug += "CanvasClient : "; 310 if (canvasClient.getDebugString() != null) { 311 debug += canvasClient.getDebugString(); 312 } else { 313 debug += "no debug string given"; 314 } 315 } else if (webViewUrl != null) { 316 debug += "WebView URL : " + webViewUrl; 317 } else { 318 debug += "Layout resource : " + 319 getActivity().getResources().getResourceName(layoutID); 320 } 321 debug += "\nTest ran in " + (useHardware ? "hardware" : "software") + "\n"; 322 return debug; 323 } 324 } 325 } 326