Home | History | Annotate | Download | only in testinfrastructure
      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