| /* |
| * 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 android.annotation.Nullable; |
| import android.graphics.Bitmap; |
| import android.renderscript.Allocation; |
| import android.renderscript.RenderScript; |
| import android.test.ActivityInstrumentationTestCase2; |
| import android.uirendering.cts.bitmapcomparers.BitmapComparer; |
| import android.uirendering.cts.bitmapverifiers.BitmapVerifier; |
| import android.uirendering.cts.differencevisualizers.DifferenceVisualizer; |
| import android.uirendering.cts.differencevisualizers.PassFailVisualizer; |
| import android.uirendering.cts.util.BitmapDumper; |
| import android.util.Log; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * 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 extends |
| ActivityInstrumentationTestCase2<DrawActivity> { |
| public static final String TAG = "ActivityTestBase"; |
| public static final boolean DEBUG = false; |
| public static final boolean USE_RS = false; |
| public static final int TEST_WIDTH = 180; |
| public static final int TEST_HEIGHT = 180; //The minimum height and width of a device |
| public static final int MAX_SCREEN_SHOTS = 100; |
| |
| private int[] mHardwareArray = new int[TEST_HEIGHT * TEST_WIDTH]; |
| private int[] mSoftwareArray = new int[TEST_HEIGHT * TEST_WIDTH]; |
| private DifferenceVisualizer mDifferenceVisualizer; |
| private Allocation mIdealAllocation; |
| private Allocation mGivenAllocation; |
| private RenderScript mRenderScript; |
| private TestCaseBuilder mTestCaseBuilder; |
| |
| /** |
| * The default constructor creates the package name and sets the DrawActivity as the class that |
| * we would use. |
| */ |
| public ActivityTestBase() { |
| super(DrawActivity.class); |
| mDifferenceVisualizer = new PassFailVisualizer(); |
| |
| // Create a location for the files to be held, if it doesn't exist already |
| BitmapDumper.createSubDirectory(this.getClass().getSimpleName()); |
| |
| // If we have a test currently, let's remove the older files if they exist |
| if (getName() != null) { |
| BitmapDumper.deleteFileInClassFolder(this.getClass().getSimpleName(), getName()); |
| } |
| } |
| |
| /** |
| * This method is called before each test case and should be called from the test class that |
| * extends this class. |
| */ |
| @Override |
| public void setUp() { |
| mDifferenceVisualizer = new PassFailVisualizer(); |
| if (USE_RS) { |
| mRenderScript = RenderScript.create(getActivity().getApplicationContext()); |
| } |
| } |
| |
| /** |
| * This method will kill the activity so that it can be reset depending on the test. |
| */ |
| @Override |
| 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; |
| } |
| |
| Runnable finishRunnable = new Runnable() { |
| |
| @Override |
| public void run() { |
| getActivity().finish(); |
| } |
| }; |
| |
| getActivity().runOnUiThread(finishRunnable); |
| } |
| |
| public Bitmap takeScreenshot() { |
| getInstrumentation().waitForIdleSync(); |
| Bitmap bitmap1 = getInstrumentation().getUiAutomation().takeScreenshot(); |
| Bitmap bitmap2; |
| int count = 0; |
| do { |
| bitmap2 = bitmap1; |
| bitmap1 = getInstrumentation().getUiAutomation().takeScreenshot(); |
| count++; |
| } while (count < MAX_SCREEN_SHOTS && !Arrays.equals(bitmap2.mBuffer, bitmap1.mBuffer)); |
| return bitmap1; |
| } |
| |
| /** |
| * Sets the current DifferenceVisualizer for use in current test. |
| */ |
| public void setDifferenceVisualizer(DifferenceVisualizer differenceVisualizer) { |
| mDifferenceVisualizer = differenceVisualizer; |
| } |
| |
| /** |
| * Used to execute a specific part of a test and get the resultant bitmap |
| */ |
| protected Bitmap captureRenderSpec(TestCase testCase) { |
| getActivity().enqueueRenderSpecAndWait(testCase.layoutID, testCase.canvasClient, |
| testCase.webViewUrl, testCase.viewInitializer, testCase.useHardware); |
| testCase.wasTestRan = true; |
| return takeScreenshot(); |
| } |
| |
| /** |
| * Compares the two bitmaps saved using the given test. If they fail, the files are saved using |
| * the test name. |
| */ |
| protected void assertBitmapsAreSimilar(Bitmap bitmap1, Bitmap bitmap2, |
| BitmapComparer comparer, String debugMessage) { |
| boolean success; |
| |
| if (USE_RS && comparer.supportsRenderScript()) { |
| mIdealAllocation = Allocation.createFromBitmap(mRenderScript, bitmap1, |
| Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); |
| mGivenAllocation = Allocation.createFromBitmap(mRenderScript, bitmap2, |
| Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); |
| success = comparer.verifySameRS(getActivity().getResources(), mIdealAllocation, |
| mGivenAllocation, 0, TEST_WIDTH, TEST_WIDTH, TEST_HEIGHT, mRenderScript); |
| } else { |
| bitmap1.getPixels(mSoftwareArray, 0, TEST_WIDTH, 0, 0, TEST_WIDTH, TEST_HEIGHT); |
| bitmap2.getPixels(mHardwareArray, 0, TEST_WIDTH, 0, 0, TEST_WIDTH, TEST_HEIGHT); |
| success = comparer.verifySame(mSoftwareArray, mHardwareArray, 0, TEST_WIDTH, TEST_WIDTH, |
| TEST_HEIGHT); |
| } |
| |
| if (!success) { |
| BitmapDumper.dumpBitmaps(bitmap1, bitmap2, getName(), this.getClass().getSimpleName(), |
| mDifferenceVisualizer); |
| } |
| |
| assertTrue(debugMessage, success); |
| } |
| |
| /** |
| * Tests to see if a bitmap passes a verifier's test. If it doesn't the bitmap is saved to the |
| * sdcard. |
| */ |
| protected void assertBitmapIsVerified(Bitmap bitmap, BitmapVerifier bitmapVerifier, |
| String debugMessage) { |
| bitmap.getPixels(mSoftwareArray, 0, TEST_WIDTH, 0, 0, |
| TEST_WIDTH, TEST_HEIGHT); |
| boolean success = bitmapVerifier.verify(mSoftwareArray, 0, TEST_WIDTH, TEST_WIDTH, TEST_HEIGHT); |
| if (!success) { |
| Bitmap croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, TEST_WIDTH, TEST_HEIGHT); |
| BitmapDumper.dumpBitmap(croppedBitmap, getName(), this.getClass().getSimpleName()); |
| BitmapDumper.dumpBitmap(bitmapVerifier.getDifferenceBitmap(), getName() + "_verifier", |
| this.getClass().getSimpleName()); |
| } |
| assertTrue(debugMessage, success); |
| } |
| |
| protected TestCaseBuilder createTest() { |
| mTestCaseBuilder = new TestCaseBuilder(); |
| return mTestCaseBuilder; |
| } |
| |
| /** |
| * 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<TestCase>(); |
| } |
| |
| /** |
| * 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 (getActivity().getOnWatch()) { |
| Log.d(TAG, getName() + "skipped"); |
| return; |
| } |
| |
| 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); |
| assertBitmapsAreSimilar(idealBitmap, testCaseBitmap, bitmapComparer, |
| 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 (getActivity().getOnWatch()) { |
| Log.d(TAG, getName() + "skipped"); |
| return; |
| } |
| |
| if (mTestCases.size() == 0) { |
| throw new IllegalStateException("Need at least one test to run"); |
| } |
| |
| for (TestCase testCase : mTestCases) { |
| Bitmap testCaseBitmap = captureRenderSpec(testCase); |
| assertBitmapIsVerified(testCaseBitmap, bitmapVerifier, testCase.getDebugString()); |
| } |
| } |
| |
| public TestCaseBuilder addWebView(String webViewUrl, |
| @Nullable ViewInitializer viewInitializer) { |
| return addWebView(webViewUrl, viewInitializer, false) |
| .addWebView(webViewUrl, viewInitializer, true); |
| } |
| |
| public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer) { |
| return addLayout(layoutId, viewInitializer, false) |
| .addLayout(layoutId, viewInitializer, true); |
| } |
| |
| public TestCaseBuilder addCanvasClient(CanvasClient canvasClient) { |
| return addCanvasClient(canvasClient, false) |
| .addCanvasClient(canvasClient, true); |
| } |
| |
| public TestCaseBuilder addWebView(String webViewUrl, |
| @Nullable ViewInitializer viewInitializer, boolean useHardware) { |
| mTestCases.add(new TestCase(null, 0, webViewUrl, viewInitializer, useHardware)); |
| return this; |
| } |
| |
| public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer, |
| boolean useHardware) { |
| mTestCases.add(new TestCase(null, layoutId, null, viewInitializer, useHardware)); |
| return this; |
| } |
| |
| public TestCaseBuilder addCanvasClient(CanvasClient canvasClient, boolean useHardware) { |
| mTestCases.add(new TestCase(canvasClient, 0, null, null, useHardware)); |
| return this; |
| } |
| |
| private List<TestCase> getTestCases() { |
| return mTestCases; |
| } |
| } |
| |
| private class TestCase { |
| public int layoutID; |
| public CanvasClient canvasClient; |
| public String webViewUrl; |
| public ViewInitializer viewInitializer; |
| public boolean useHardware; |
| public boolean wasTestRan; |
| |
| public TestCase(CanvasClient client, int id, String viewUrl, |
| ViewInitializer viewInitializer, boolean useHardware) { |
| int count = 0; |
| count += (client == null ? 0 : 1); |
| count += (viewUrl == null ? 0 : 1); |
| count += (id == 0 ? 0 : 1); |
| assert(count == 1); |
| assert(client == null || viewInitializer == null); |
| this.layoutID = id; |
| this.canvasClient = client; |
| this.webViewUrl = viewUrl; |
| this.viewInitializer = viewInitializer; |
| this.useHardware = useHardware; |
| this.wasTestRan = false; |
| } |
| |
| public String getDebugString() { |
| String debug = ""; |
| if (canvasClient != null) { |
| debug += "CanvasClient : "; |
| if (canvasClient.getDebugString() != null) { |
| debug += canvasClient.getDebugString(); |
| } else { |
| debug += "no debug string given"; |
| } |
| } else if (webViewUrl != null) { |
| debug += "WebView URL : " + webViewUrl; |
| } else { |
| debug += "Layout resource : " + |
| getActivity().getResources().getResourceName(layoutID); |
| } |
| debug += "\nTest ran in " + (useHardware ? "hardware" : "software") + "\n"; |
| return debug; |
| } |
| } |
| } |