blob: 23b729a73b4984a95c46c29b8e947222fc2d3022 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.uirendering.cts.testinfrastructure;
import com.android.compatibility.common.util.SynchronousPixelCopy;
import android.app.Instrumentation;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Point;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.uirendering.cts.bitmapcomparers.BitmapComparer;
import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
import android.uirendering.cts.util.BitmapAsserter;
import android.util.Log;
import android.view.PixelCopy;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestName;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* This class contains the basis for the graphics hardware test classes. Contained within this class
* are several methods that help with the execution of tests, and should be extended to gain the
* functionality built in.
*/
public abstract class ActivityTestBase {
public static final String TAG = "ActivityTestBase";
public static final boolean DEBUG = false;
//The minimum height and width of a device
public static final int TEST_WIDTH = 90;
public static final int TEST_HEIGHT = 90;
private TestCaseBuilder mTestCaseBuilder;
private Screenshotter mScreenshotter;
private static DrawActivity sActivity;
@Rule
public TestName name = new TestName();
private BitmapAsserter mBitmapAsserter = new BitmapAsserter(this.getClass().getSimpleName(),
name.getMethodName());
protected String getName() {
return name.getMethodName();
}
protected Instrumentation getInstrumentation() {
return InstrumentationRegistry.getInstrumentation();
}
protected DrawActivity getActivity() {
if (sActivity == null) {
Instrumentation instrumentation = getInstrumentation();
instrumentation.setInTouchMode(true);
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClass(instrumentation.getTargetContext(), DrawActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
sActivity = (DrawActivity) instrumentation.startActivitySync(intent);
}
return sActivity;
}
@AfterClass
public static void tearDownClass() {
if (sActivity != null) {
// All tests are finished, tear down the activity
sActivity.allTestsFinished();
sActivity = null;
}
}
@Before
public void setUp() {
mBitmapAsserter.setUp(getActivity());
}
@After
public void tearDown() {
if (mTestCaseBuilder != null) {
List<TestCase> testCases = mTestCaseBuilder.getTestCases();
if (testCases.size() == 0) {
throw new IllegalStateException("Must have at least one test case");
}
for (TestCase testCase : testCases) {
if (!testCase.wasTestRan) {
Log.w(TAG, getName() + " not all of the tests ran");
break;
}
}
mTestCaseBuilder = null;
}
}
public Bitmap takeScreenshot(TestPositionInfo testPositionInfo) {
if (mScreenshotter == null) {
SynchronousPixelCopy copy = new SynchronousPixelCopy();
Bitmap dest = Bitmap.createBitmap(
TEST_WIDTH, TEST_HEIGHT, Config.ARGB_8888);
Rect srcRect = new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT);
srcRect.offset(testPositionInfo.surfaceOffset.x, testPositionInfo.surfaceOffset.y);
Log.d(TAG, "capturing screenshot of " + srcRect.toShortString());
int copyResult = copy.request(getActivity().getWindow(), srcRect, dest);
Assert.assertEquals(PixelCopy.SUCCESS, copyResult);
return dest;
} else {
return mScreenshotter.takeScreenshot(testPositionInfo);
}
}
protected TestPositionInfo runRenderSpec(TestCase testCase) {
TestPositionInfo testPositionInfo = getActivity().enqueueRenderSpecAndWait(
testCase.layoutID, testCase.canvasClient,
testCase.viewInitializer, testCase.useHardware, testCase.usePicture);
testCase.wasTestRan = true;
if (testCase.readyFence != null) {
try {
testCase.readyFence.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException("readyFence didn't signal within 5 seconds");
}
}
return testPositionInfo;
}
/**
* Used to execute a specific part of a test and get the resultant bitmap
*/
protected Bitmap captureRenderSpec(TestCase testCase) {
return takeScreenshot(runRenderSpec(testCase));
}
protected TestCaseBuilder createTest() {
mTestCaseBuilder = new TestCaseBuilder();
mScreenshotter = null;
return mTestCaseBuilder;
}
public static class TestPositionInfo {
/**
* Position of capture area in surface space - use this offset for e.g.
* PixelCopy from a window's surface.
*/
public final Point surfaceOffset;
/**
* Position of capture area in screen space - use this offset for e.g.
* {@code getInstrumentation().getUiAutomation().takeScreenshot()},
* since those screenshots are captured in screen space.
*/
public final Point screenOffset;
public TestPositionInfo(Point surfaceOffset, Point screenOffset) {
this.surfaceOffset = surfaceOffset;
this.screenOffset = screenOffset;
}
}
public interface Screenshotter {
Bitmap takeScreenshot(TestPositionInfo params);
}
/**
* Defines a group of CanvasClients, XML layouts, and WebView html files for testing.
*/
protected class TestCaseBuilder {
private List<TestCase> mTestCases;
private TestCaseBuilder() {
mTestCases = new ArrayList<>();
}
/**
* Runs a test where the first test case is considered the "ideal" image and from there,
* every test case is tested against it.
*/
public void runWithComparer(BitmapComparer bitmapComparer) {
if (mTestCases.size() == 0) {
throw new IllegalStateException("Need at least one test to run");
}
Bitmap idealBitmap = captureRenderSpec(mTestCases.remove(0));
for (TestCase testCase : mTestCases) {
Bitmap testCaseBitmap = captureRenderSpec(testCase);
mBitmapAsserter.assertBitmapsAreSimilar(idealBitmap, testCaseBitmap, bitmapComparer,
getName(), testCase.getDebugString());
}
}
/**
* Runs a test where each testcase is independent of the others and each is checked against
* the verifier given.
*/
public void runWithVerifier(BitmapVerifier bitmapVerifier) {
if (mTestCases.size() == 0) {
throw new IllegalStateException("Need at least one test to run");
}
for (TestCase testCase : mTestCases) {
Bitmap testCaseBitmap = captureRenderSpec(testCase);
mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier,
getName(), testCase.getDebugString());
}
getActivity().reset();
}
private static final int VERIFY_ANIMATION_LOOP_COUNT = 20;
private static final int VERIFY_ANIMATION_SLEEP_MS = 100;
/**
* Runs a test where each testcase is independent of the others and each is checked against
* the verifier given in a loop.
*
* A screenshot is captured several times in a loop, to ensure that valid output is produced
* at many different times during the animation.
*/
public void runWithAnimationVerifier(BitmapVerifier bitmapVerifier) {
if (mTestCases.size() == 0) {
throw new IllegalStateException("Need at least one test to run");
}
for (TestCase testCase : mTestCases) {
TestPositionInfo testPositionInfo = runRenderSpec(testCase);
for (int i = 0; i < VERIFY_ANIMATION_LOOP_COUNT; i++) {
try {
Thread.sleep(VERIFY_ANIMATION_SLEEP_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
Bitmap testCaseBitmap = takeScreenshot(testPositionInfo);
mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier,
getName(), testCase.getDebugString());
}
}
}
/**
* Runs a test where each testcase is run without verification. Should only be used
* where custom CanvasClients, Views, or ViewInitializers do their own internal
* test assertions.
*/
public void runWithoutVerification() {
runWithVerifier(new BitmapVerifier() {
@Override
public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
return true;
}
});
}
public TestCaseBuilder withScreenshotter(Screenshotter screenshotter) {
Assert.assertNull("Screenshotter is already set!", mScreenshotter);
mScreenshotter = screenshotter;
return this;
}
public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer) {
return addLayout(layoutId, viewInitializer, false)
.addLayout(layoutId, viewInitializer, true);
}
public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer,
boolean useHardware) {
mTestCases.add(new TestCase(layoutId, viewInitializer, useHardware));
return this;
}
public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer,
boolean useHardware, CountDownLatch readyFence) {
TestCase test = new TestCase(layoutId, viewInitializer, useHardware);
test.readyFence = readyFence;
mTestCases.add(test);
return this;
}
public TestCaseBuilder addCanvasClient(CanvasClient canvasClient) {
return addCanvasClient(null, canvasClient);
}
public TestCaseBuilder addCanvasClient(CanvasClient canvasClient, boolean useHardware) {
return addCanvasClient(null, canvasClient, useHardware);
}
public TestCaseBuilder addCanvasClient(String debugString, CanvasClient canvasClient) {
return addCanvasClient(debugString, canvasClient, false)
.addCanvasClient(debugString, canvasClient, true);
}
public TestCaseBuilder addCanvasClient(String debugString,
CanvasClient canvasClient, boolean useHardware) {
return addCanvasClientInternal(debugString, canvasClient, useHardware, false)
.addCanvasClientInternal(debugString, canvasClient, useHardware, true);
}
public TestCaseBuilder addCanvasClientWithoutUsingPicture(CanvasClient canvasClient) {
return addCanvasClientWithoutUsingPicture(null, canvasClient);
}
public TestCaseBuilder addCanvasClientWithoutUsingPicture(String debugString,
CanvasClient canvasClient) {
return addCanvasClientInternal(debugString, canvasClient, false, false)
.addCanvasClientInternal(debugString, canvasClient, true, false);
}
public TestCaseBuilder addCanvasClientWithoutUsingPicture(CanvasClient canvasClient,
boolean useHardware) {
return addCanvasClientInternal(null, canvasClient, useHardware, false);
}
private TestCaseBuilder addCanvasClientInternal(String debugString,
CanvasClient canvasClient, boolean useHardware, boolean usePicture) {
mTestCases.add(new TestCase(canvasClient, debugString, useHardware, usePicture));
return this;
}
private List<TestCase> getTestCases() {
return mTestCases;
}
}
private class TestCase {
public int layoutID;
public ViewInitializer viewInitializer;
/** After launching the test case this fence is used to signal when
* to proceed with capture & verification. If this is null the test
* proceeds immediately to verification */
@Nullable
public CountDownLatch readyFence;
public CanvasClient canvasClient;
public String canvasClientDebugString;
public boolean useHardware;
public boolean usePicture = false;
public boolean wasTestRan = false;
public TestCase(int layoutId, ViewInitializer viewInitializer, boolean useHardware) {
this.layoutID = layoutId;
this.viewInitializer = viewInitializer;
this.useHardware = useHardware;
}
public TestCase(CanvasClient client, String debugString, boolean useHardware,
boolean usePicture) {
this.canvasClient = client;
this.canvasClientDebugString = debugString;
this.useHardware = useHardware;
this.usePicture = usePicture;
}
public String getDebugString() {
String debug = "";
if (canvasClient != null) {
debug += "CanvasClient : ";
if (canvasClientDebugString != null) {
debug += canvasClientDebugString;
} else {
debug += "no debug string given";
}
} else {
debug += "Layout resource : " +
getActivity().getResources().getResourceName(layoutID);
}
debug += "\nTest ran in " + (useHardware ? "hardware" : "software") +
(usePicture ? " with picture" : " without picture") + "\n";
return debug;
}
}
}