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