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