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