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