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