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.graphics.Point; 21 import android.renderscript.Allocation; 22 import android.renderscript.RenderScript; 23 import android.test.ActivityInstrumentationTestCase2; 24 import android.uirendering.cts.bitmapcomparers.BitmapComparer; 25 import android.uirendering.cts.bitmapverifiers.BitmapVerifier; 26 import android.uirendering.cts.differencevisualizers.DifferenceVisualizer; 27 import android.uirendering.cts.differencevisualizers.PassFailVisualizer; 28 import android.uirendering.cts.util.BitmapDumper; 29 import android.util.Log; 30 31 import android.support.test.InstrumentationRegistry; 32 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.List; 36 37 /** 38 * This class contains the basis for the graphics hardware test classes. Contained within this class 39 * are several methods that help with the execution of tests, and should be extended to gain the 40 * functionality built in. 41 */ 42 public abstract class ActivityTestBase extends 43 ActivityInstrumentationTestCase2<DrawActivity> { 44 public static final String TAG = "ActivityTestBase"; 45 public static final boolean DEBUG = false; 46 public static final boolean USE_RS = false; 47 48 //The minimum height and width of a device 49 public static final int TEST_WIDTH = 90; 50 public static final int TEST_HEIGHT = 90; 51 52 public static final int MAX_SCREEN_SHOTS = 100; 53 54 private int[] mHardwareArray = new int[TEST_HEIGHT * TEST_WIDTH]; 55 private int[] mSoftwareArray = new int[TEST_HEIGHT * TEST_WIDTH]; 56 private DifferenceVisualizer mDifferenceVisualizer; 57 private RenderScript mRenderScript; 58 private TestCaseBuilder mTestCaseBuilder; 59 60 /** 61 * The default constructor creates the package name and sets the DrawActivity as the class that 62 * we would use. 63 */ ActivityTestBase()64 public ActivityTestBase() { 65 super(DrawActivity.class); 66 mDifferenceVisualizer = new PassFailVisualizer(); 67 68 // Create a location for the files to be held, if it doesn't exist already 69 BitmapDumper.createSubDirectory(this.getClass().getSimpleName()); 70 71 // If we have a test currently, let's remove the older files if they exist 72 if (getName() != null) { 73 BitmapDumper.deleteFileInClassFolder(this.getClass().getSimpleName(), getName()); 74 } 75 } 76 77 /** 78 * This method is called before each test case and should be called from the test class that 79 * extends this class. 80 */ 81 @Override setUp()82 public void setUp() { 83 // As the way to access Instrumentation is changed in the new runner, we need to inject it 84 // manually into ActivityInstrumentationTestCase2. ActivityInstrumentationTestCase2 will 85 // be marked as deprecated and replaced with ActivityTestRule. 86 injectInstrumentation(InstrumentationRegistry.getInstrumentation()); 87 mDifferenceVisualizer = new PassFailVisualizer(); 88 if (USE_RS) { 89 mRenderScript = RenderScript.create(getActivity().getApplicationContext()); 90 } 91 } 92 93 /** 94 * This method will kill the activity so that it can be reset depending on the test. 95 */ 96 @Override tearDown()97 public void tearDown() { 98 if (mTestCaseBuilder != null) { 99 List<TestCase> testCases = mTestCaseBuilder.getTestCases(); 100 101 if (testCases.size() == 0) { 102 throw new IllegalStateException("Must have at least one test case"); 103 } 104 105 106 for (TestCase testCase : testCases) { 107 if (!testCase.wasTestRan) { 108 Log.w(TAG, getName() + " not all of the tests ran"); 109 break; 110 } 111 } 112 mTestCaseBuilder = null; 113 } 114 115 Runnable finishRunnable = new Runnable() { 116 117 @Override 118 public void run() { 119 getActivity().finish(); 120 } 121 }; 122 123 getActivity().runOnUiThread(finishRunnable); 124 } 125 getBitmapPixels(Bitmap bitmap)126 static int[] getBitmapPixels(Bitmap bitmap) { 127 int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()]; 128 bitmap.getPixels(pixels, 0, bitmap.getWidth(), 129 0, 0, bitmap.getWidth(), bitmap.getHeight()); 130 return pixels; 131 } 132 takeScreenshotImpl(Point testOffset)133 private Bitmap takeScreenshotImpl(Point testOffset) { 134 Bitmap source = getInstrumentation().getUiAutomation().takeScreenshot(); 135 return Bitmap.createBitmap(source, testOffset.x, testOffset.y, TEST_WIDTH, TEST_HEIGHT); 136 } 137 takeScreenshot(Point testOffset)138 public Bitmap takeScreenshot(Point testOffset) { 139 getInstrumentation().waitForIdleSync(); 140 Bitmap bitmap1 = takeScreenshotImpl(testOffset); 141 Bitmap bitmap2; 142 int count = 0; 143 do { 144 bitmap2 = bitmap1; 145 bitmap1 = takeScreenshotImpl(testOffset); 146 count++; 147 } while (count < MAX_SCREEN_SHOTS && 148 !Arrays.equals(getBitmapPixels(bitmap2), getBitmapPixels(bitmap1))); 149 return bitmap1; 150 } 151 152 /** 153 * Sets the current DifferenceVisualizer for use in current test. 154 */ setDifferenceVisualizer(DifferenceVisualizer differenceVisualizer)155 public void setDifferenceVisualizer(DifferenceVisualizer differenceVisualizer) { 156 mDifferenceVisualizer = differenceVisualizer; 157 } 158 159 /** 160 * Used to execute a specific part of a test and get the resultant bitmap 161 */ captureRenderSpec(TestCase testCase)162 protected Bitmap captureRenderSpec(TestCase testCase) { 163 Point testOffset = getActivity().enqueueRenderSpecAndWait( 164 testCase.layoutID, testCase.canvasClient, 165 testCase.webViewUrl, testCase.viewInitializer, testCase.useHardware); 166 testCase.wasTestRan = true; 167 return takeScreenshot(testOffset); 168 } 169 170 /** 171 * Compares the two bitmaps saved using the given test. If they fail, the files are saved using 172 * the test name. 173 */ assertBitmapsAreSimilar(Bitmap bitmap1, Bitmap bitmap2, BitmapComparer comparer, String debugMessage)174 protected void assertBitmapsAreSimilar(Bitmap bitmap1, Bitmap bitmap2, 175 BitmapComparer comparer, String debugMessage) { 176 boolean success; 177 178 if (USE_RS && comparer.supportsRenderScript()) { 179 Allocation idealAllocation = Allocation.createFromBitmap(mRenderScript, bitmap1, 180 Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); 181 Allocation givenAllocation = Allocation.createFromBitmap(mRenderScript, bitmap2, 182 Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); 183 success = comparer.verifySameRS(getActivity().getResources(), idealAllocation, 184 givenAllocation, 0, TEST_WIDTH, TEST_WIDTH, TEST_HEIGHT, mRenderScript); 185 } else { 186 bitmap1.getPixels(mSoftwareArray, 0, TEST_WIDTH, 0, 0, TEST_WIDTH, TEST_HEIGHT); 187 bitmap2.getPixels(mHardwareArray, 0, TEST_WIDTH, 0, 0, TEST_WIDTH, TEST_HEIGHT); 188 success = comparer.verifySame(mSoftwareArray, mHardwareArray, 0, TEST_WIDTH, TEST_WIDTH, 189 TEST_HEIGHT); 190 } 191 192 if (!success) { 193 BitmapDumper.dumpBitmaps(bitmap1, bitmap2, getName(), this.getClass().getSimpleName(), 194 mDifferenceVisualizer); 195 } 196 197 assertTrue(debugMessage, success); 198 } 199 200 /** 201 * Tests to see if a bitmap passes a verifier's test. If it doesn't the bitmap is saved to the 202 * sdcard. 203 */ assertBitmapIsVerified(Bitmap bitmap, BitmapVerifier bitmapVerifier, String debugMessage)204 protected void assertBitmapIsVerified(Bitmap bitmap, BitmapVerifier bitmapVerifier, 205 String debugMessage) { 206 bitmap.getPixels(mSoftwareArray, 0, TEST_WIDTH, 0, 0, 207 TEST_WIDTH, TEST_HEIGHT); 208 boolean success = bitmapVerifier.verify(mSoftwareArray, 0, TEST_WIDTH, TEST_WIDTH, TEST_HEIGHT); 209 if (!success) { 210 Bitmap croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, TEST_WIDTH, TEST_HEIGHT); 211 BitmapDumper.dumpBitmap(croppedBitmap, getName(), this.getClass().getSimpleName()); 212 BitmapDumper.dumpBitmap(bitmapVerifier.getDifferenceBitmap(), getName() + "_verifier", 213 this.getClass().getSimpleName()); 214 } 215 assertTrue(debugMessage, success); 216 } 217 createTest()218 protected TestCaseBuilder createTest() { 219 mTestCaseBuilder = new TestCaseBuilder(); 220 return mTestCaseBuilder; 221 } 222 223 /** 224 * Defines a group of CanvasClients, XML layouts, and WebView html files for testing. 225 */ 226 protected class TestCaseBuilder { 227 private List<TestCase> mTestCases; 228 TestCaseBuilder()229 private TestCaseBuilder() { 230 mTestCases = new ArrayList<TestCase>(); 231 } 232 233 /** 234 * Runs a test where the first test case is considered the "ideal" image and from there, 235 * every test case is tested against it. 236 */ runWithComparer(BitmapComparer bitmapComparer)237 public void runWithComparer(BitmapComparer bitmapComparer) { 238 if (mTestCases.size() == 0) { 239 throw new IllegalStateException("Need at least one test to run"); 240 } 241 242 Bitmap idealBitmap = captureRenderSpec(mTestCases.remove(0)); 243 244 for (TestCase testCase : mTestCases) { 245 Bitmap testCaseBitmap = captureRenderSpec(testCase); 246 assertBitmapsAreSimilar(idealBitmap, testCaseBitmap, bitmapComparer, 247 testCase.getDebugString()); 248 } 249 } 250 251 /** 252 * Runs a test where each testcase is independent of the others and each is checked against 253 * the verifier given. 254 */ runWithVerifier(BitmapVerifier bitmapVerifier)255 public void runWithVerifier(BitmapVerifier bitmapVerifier) { 256 if (mTestCases.size() == 0) { 257 throw new IllegalStateException("Need at least one test to run"); 258 } 259 260 for (TestCase testCase : mTestCases) { 261 Bitmap testCaseBitmap = captureRenderSpec(testCase); 262 assertBitmapIsVerified(testCaseBitmap, bitmapVerifier, testCase.getDebugString()); 263 } 264 } 265 266 /** 267 * Runs a test where each testcase is run without verification. Should only be used 268 * where custom CanvasClients, Views, or ViewInitializers do their own internal 269 * test assertions. 270 */ runWithoutVerification()271 public void runWithoutVerification() { 272 runWithVerifier(new BitmapVerifier() { 273 @Override 274 public boolean verify(int[] bitmap, int offset, int stride, int width, int height) { 275 return true; 276 } 277 }); 278 } 279 addWebView(String webViewUrl, @Nullable ViewInitializer viewInitializer)280 public TestCaseBuilder addWebView(String webViewUrl, 281 @Nullable ViewInitializer viewInitializer) { 282 return addWebView(webViewUrl, viewInitializer, false) 283 .addWebView(webViewUrl, viewInitializer, true); 284 } 285 addLayout(int layoutId, @Nullable ViewInitializer viewInitializer)286 public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer) { 287 return addLayout(layoutId, viewInitializer, false) 288 .addLayout(layoutId, viewInitializer, true); 289 } 290 addCanvasClient(CanvasClient canvasClient)291 public TestCaseBuilder addCanvasClient(CanvasClient canvasClient) { 292 return addCanvasClient(canvasClient, false) 293 .addCanvasClient(canvasClient, true); 294 } 295 addWebView(String webViewUrl, @Nullable ViewInitializer viewInitializer, boolean useHardware)296 public TestCaseBuilder addWebView(String webViewUrl, 297 @Nullable ViewInitializer viewInitializer, boolean useHardware) { 298 mTestCases.add(new TestCase(null, 0, webViewUrl, viewInitializer, useHardware)); 299 return this; 300 } 301 addLayout(int layoutId, @Nullable ViewInitializer viewInitializer, boolean useHardware)302 public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer, 303 boolean useHardware) { 304 mTestCases.add(new TestCase(null, layoutId, null, viewInitializer, useHardware)); 305 return this; 306 } 307 addCanvasClient(CanvasClient canvasClient, boolean useHardware)308 public TestCaseBuilder addCanvasClient(CanvasClient canvasClient, boolean useHardware) { 309 mTestCases.add(new TestCase(canvasClient, 0, null, null, useHardware)); 310 return this; 311 } 312 getTestCases()313 private List<TestCase> getTestCases() { 314 return mTestCases; 315 } 316 } 317 318 private class TestCase { 319 public int layoutID; 320 public CanvasClient canvasClient; 321 public String webViewUrl; 322 public ViewInitializer viewInitializer; 323 public boolean useHardware; 324 public boolean wasTestRan; 325 TestCase(CanvasClient client, int id, String viewUrl, ViewInitializer viewInitializer, boolean useHardware)326 public TestCase(CanvasClient client, int id, String viewUrl, 327 ViewInitializer viewInitializer, boolean useHardware) { 328 int count = 0; 329 count += (client == null ? 0 : 1); 330 count += (viewUrl == null ? 0 : 1); 331 count += (id == 0 ? 0 : 1); 332 assert(count == 1); 333 assert(client == null || viewInitializer == null); 334 this.layoutID = id; 335 this.canvasClient = client; 336 this.webViewUrl = viewUrl; 337 this.viewInitializer = viewInitializer; 338 this.useHardware = useHardware; 339 this.wasTestRan = false; 340 } 341 getDebugString()342 public String getDebugString() { 343 String debug = ""; 344 if (canvasClient != null) { 345 debug += "CanvasClient : "; 346 if (canvasClient.getDebugString() != null) { 347 debug += canvasClient.getDebugString(); 348 } else { 349 debug += "no debug string given"; 350 } 351 } else if (webViewUrl != null) { 352 debug += "WebView URL : " + webViewUrl; 353 } else { 354 debug += "Layout resource : " + 355 getActivity().getResources().getResourceName(layoutID); 356 } 357 debug += "\nTest ran in " + (useHardware ? "hardware" : "software") + "\n"; 358 return debug; 359 } 360 } 361 } 362