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