public class

JankTestBase

extends InstrumentationTestCase

 java.lang.Object

↳InstrumentationTestCase

↳androidx.test.jank.JankTestBase

Gradle dependencies

compile group: 'androidx.test.janktesthelper', name: 'janktesthelper', version: '1.0.1'

  • groupId: androidx.test.janktesthelper
  • artifactId: janktesthelper
  • version: 1.0.1

Artifact androidx.test.janktesthelper:janktesthelper:1.0.1 it located at Google repository (https://maven.google.com/)

Androidx class mapping:

androidx.test.jank.JankTestBase android.support.test.jank.JankTestBase

Overview

Base test class for measuring Jank. This test class automatically monitors jank while executing each test method. Each test method is executed several times in a loop, according to the 'iterations' command line parameter. To perform additional setup / tear down steps for each iteration, subclasses can optionally override JankTestBase.beforeLoop() and JankTestBase.afterLoop() methods. Test methods must be configured with the JankTest annotation. At minimum, the type of jank to measure and the number of expected frames must be specified.

Summary

Constructors
publicJankTestBase()

Methods
public voidafterLoop()

Called after each iteration of the test method.

public voidafterTest(Bundle metrics)

Called once after all iterations have completed.

public voidbeforeLoop()

Called before each iteration of the test method.

public voidbeforeTest()

Called once before executing a test method.

protected IMonitorFactorycreateMonitorFactory()

Creates an IMonitorFactory instance that should be used to construct IMonitor(s) for the test methods in this class.

protected final BundlegetArguments()

Returns a containing the command line parameters.

public final intgetCurrentIteration()

Return the index of the currently executing iteration.

protected IMonitorFactorygetMonitorFactory()

Returns the IMonitorFactory instance that should be used to construct applicable IMonitor(s) for a given test method.

protected java.util.List<IMonitor>getMonitors(java.lang.reflect.Method method)

Returns the list of IMonitors that should be applied to the given method.

protected voidrunTest()

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public JankTestBase()

Methods

public void beforeTest()

Called once before executing a test method.

public void beforeLoop()

Called before each iteration of the test method.

public void afterLoop()

Called after each iteration of the test method.

public void afterTest(Bundle metrics)

Called once after all iterations have completed.

Note: default implementation reports the aggregated jank metrics via

Parameters:

metrics: the aggregated jank metrics after looped execution

public final int getCurrentIteration()

Return the index of the currently executing iteration.

protected void runTest()

protected IMonitorFactory createMonitorFactory()

Creates an IMonitorFactory instance that should be used to construct IMonitor(s) for the test methods in this class.

protected java.util.List<IMonitor> getMonitors(java.lang.reflect.Method method)

Returns the list of IMonitors that should be applied to the given method.

protected IMonitorFactory getMonitorFactory()

Returns the IMonitorFactory instance that should be used to construct applicable IMonitor(s) for a given test method.

protected final Bundle getArguments()

Returns a containing the command line parameters.

Source

/*
 * 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 androidx.test.jank;

import android.app.Activity;
import android.app.Instrumentation;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import androidx.test.InstrumentationRegistry;
import androidx.test.jank.annotations.UseMonitorFactory;
import androidx.test.jank.internal.JankMonitorFactory;
import androidx.test.runner.AndroidJUnitRunner;
import androidx.test.rule.logging.AtraceLogger;
import android.test.InstrumentationTestCase;
import android.test.InstrumentationTestRunner;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Base test class for measuring Jank.
 *
 * This test class automatically monitors jank while executing each test method. Each test method is
 * executed several times in a loop, according to the 'iterations' command line parameter.
 *
 * To perform additional setup / tear down steps for each iteration, subclasses can optionally
 * override {@link JankTestBase#beforeLoop()} and {@link JankTestBase#afterLoop()} methods.
 *
 * Test methods must be configured with the {@link JankTest} annotation. At minimum, the type of
 * jank to measure and the number of expected frames must be specified.
 */
public class JankTestBase extends InstrumentationTestCase {

    private static final String TRACE_CATEGORIES = "sched,gfx,view,dalvik,webview,input,disk,am,"
            + "wm,freq";
    private static final String TRACE_BUFFER_SIZE = "20000";
    private static final String TRACE_DUMP_INTERVAL = "120";
    private static final String TRACE_ALL = "all";
    private static final String DELIMITER = ",";

    private IMonitorFactory mMonitorFactory;

    private Bundle mArguments = null;
    private int mCurrentIteration = 0;

    /** Called once before executing a test method. */
    public void beforeTest() throws Exception {
        // Default implementation. Do nothing.
    }

    /** Called before each iteration of the test method. */
    public void beforeLoop() throws Exception {
        // Default implementation. Do nothing.
    }

    /** Called after each iteration of the test method. */
    public void afterLoop() throws Exception {
        // Default implementation. Do nothing.
    }

    /**
     * Called once after all iterations have completed.
     * <p>Note: default implementation reports the aggregated jank metrics via
     * {@link Instrumentation#sendStatus(int, Bundle)}
     * @param metrics the aggregated jank metrics after looped execution
     */
    public void afterTest(Bundle metrics) {
        getInstrumentation().sendStatus(Activity.RESULT_OK, metrics);
    }

    /** Return the index of the currently executing iteration. */
    public final int getCurrentIteration() {
        return mCurrentIteration;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void runTest() throws Throwable {

        // Resolve test methods
        Method testMethod = resolveMethod(getName());
        JankTest annotation = testMethod.getAnnotation(JankTest.class);
        Method beforeTest = resolveMethod(annotation.beforeTest());
        Method beforeLoop = resolveMethod(annotation.beforeLoop());
        Method afterLoop  = resolveMethod(annotation.afterLoop());
        Method afterTest  = resolveAfterTest(annotation.afterTest());

        // Test setup
        beforeTest.invoke(this, (Object[])null);

        // Get the appropriate JankMonitors for the test type
        List<IMonitor> monitors = getMonitors(testMethod);
        assertTrue("No monitors configured for this test", monitors.size() > 0);

        // Execute the test several times according to the "iteration" parameter
        int iterations = Integer.valueOf(getArguments().getString("iterations",
                Integer.toString(annotation.defaultIterationCount())));

        /*
         * traceIterations "" captures nothing
         * traceIterations "all" captures atrace for all the iterations
         * traceIterations "0,1,2..(n-1)" captures atrace for the iterations mentioned
         */
        AtraceLogger atraceLogger = null;
        String traceIterationsStr = getArguments().getString("traceIterations", "");
        String traceCategoriesStr = getArguments().getString("traceCategories", TRACE_CATEGORIES);
        String traceDirectoryStr = getArguments().getString("destDirectory", "");
        int traceBufferSize = Integer.valueOf(getArguments().getString("traceBufferSize",
                TRACE_BUFFER_SIZE));
        int traceDumpInterval = Integer.valueOf(getArguments().getString("traceDumpInterval",
                TRACE_DUMP_INTERVAL));
        File testDestDirectory = null;
        if (traceDirectoryStr != null && !traceDirectoryStr.isEmpty()){
            testDestDirectory = new File(String.format("%s/%s", traceDirectoryStr, getName()));
        }
        Set<Integer> traceIterationsSet = new HashSet<Integer>();
        Set<String> traceCategoriesSet = new HashSet<String>();
        if (!traceIterationsStr.isEmpty() && !traceIterationsStr.equalsIgnoreCase(TRACE_ALL)) {
            String[] traceIterationsSplit = traceIterationsStr.split(DELIMITER);
            for (int i = 0; i < traceIterationsSplit.length; i++) {
                traceIterationsSet.add(Integer.valueOf(traceIterationsSplit[i]));
            }
        }
        if (!traceCategoriesStr.isEmpty()) {
            String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER);
            for (int i = 0; i < traceCategoriesSplit.length; i++) {
                traceCategoriesSet.add(traceCategoriesSplit[i]);
            }
        }

        for (; mCurrentIteration < iterations; mCurrentIteration++) {
            // Loop setup
            beforeLoop.invoke(this, (Object[])null);

            // Start trace if set to capture for the current iteration
            if ((traceIterationsStr.equalsIgnoreCase(TRACE_ALL) || traceIterationsSet
                    .contains(mCurrentIteration)) && !traceCategoriesSet.isEmpty() &&
                    testDestDirectory != null) {
                atraceLogger = AtraceLogger.getAtraceLoggerInstance(getInstrumentation());
                atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize, traceDumpInterval,
                        testDestDirectory, getName() + "-" + (mCurrentIteration));
            }

            try {
                // Start monitoring jank
                for (IMonitor monitor : monitors) {
                    monitor.startIteration();
                }

                // Run the test method
                testMethod.invoke(this, (Object[])null);

                // Stop monitoring
                for (IMonitor monitor : monitors) {
                    Bundle results = monitor.stopIteration();
                    int numFrames = results.getInt("num-frames", 0);

                    // Fail the test if we didn't get enough frames
                    assertTrue(String.format(
                            "Too few frames received. Monitor: %s, Expected: %d, Received: %d.",
                            monitor.getClass().getSimpleName(), annotation.expectedFrames(),
                            numFrames),
                            numFrames >= annotation.expectedFrames());
                }
            } finally {
                // Stop the trace if started before test
                if (atraceLogger != null) {
                    atraceLogger.atraceStop();
                    atraceLogger = null;
                }
            }

            // Loop tear down
            afterLoop.invoke(this, (Object[])null);
        }

        // Report aggregated results
        Bundle metrics = new Bundle();
        for (IMonitor monitor : monitors) {
            metrics.putAll(monitor.getMetrics());
        }
        afterTest.invoke(this, metrics);
    }

    /**
     * Creates an {@link IMonitorFactory} instance that should be used to construct
     * {@link IMonitor}(s) for the test methods in this class.
     */
    @VisibleForTesting
    protected IMonitorFactory createMonitorFactory() {
        return createMonitorFactoryForClass(getClass(), getInstrumentation());
    }

    /**
     * Creates an {@link IMonitorFactory} instance that should be used to construct
     * {@link IMonitor}(s) for the test methods in the given {@code clazz}.
     */
    @VisibleForTesting
    static IMonitorFactory createMonitorFactoryForClass(Class<?> clazz,
            Instrumentation instrumentation) {

        // If the class has a {@link UseMonitorFactory} annotation, use the specified factory class
        UseMonitorFactory annotation = clazz.getAnnotation(UseMonitorFactory.class);
        if (annotation != null) {
            Class<? extends IMonitorFactory> monitorFactoryClass = annotation.value();
            // Try to construct a new instance
            try {
                Constructor<? extends IMonitorFactory> ctor =
                        monitorFactoryClass.getConstructor(Instrumentation.class);
                return ctor.newInstance(instrumentation);
            } catch (NoSuchMethodException e) {
                String msg = String.format("Could not instantiate factory '%s'",
                        monitorFactoryClass.getName());
                throw new AssertionError(msg, e);
            } catch (IllegalAccessException e) {
                // Shouldn't happen. If the constructor is private, getConstructor(..) will throw
                // NoSuchMethodException first. Catching this just to keep the compiler happy.
                String msg = String.format("Could not instantiate factory '%s'",
                        monitorFactoryClass.getName());
                throw new AssertionError(msg, e);
            } catch (InstantiationException e) {
                String msg = String.format("Could not instantiate factory '%s'",
                        monitorFactoryClass.getName());
                throw new AssertionError(msg, e);
            } catch (InvocationTargetException e) {
                String msg = String.format("Exception occured while trying to instantiate '%s'",
                        monitorFactoryClass.getName());
                throw new AssertionError(msg, e.getCause() != null ? e.getCause() : e);
            }
        }

        // If no monitor factory is specified, use the default
        return new JankMonitorFactory(instrumentation);
    }

    /**
     * Returns the list of {@link IMonitor}s that should be applied to the given {@code method}.
     */
    @VisibleForTesting
    protected List<IMonitor> getMonitors(Method method) {
        return getMonitorFactory().getMonitors(method, this);
    }

    /**
     * Returns the {@link IMonitorFactory} instance that should be used to construct applicable
     * {@link IMonitor}(s) for a given test method.
     */
    @VisibleForTesting
    protected IMonitorFactory getMonitorFactory() {
        if (mMonitorFactory == null) {
            mMonitorFactory = createMonitorFactory();
        }
        return mMonitorFactory;
    }

    /** Returns a {@link Method}} object representing the method with the given {@code name}. */
    private Method resolveMethod(String name) {
        assertNotNull(name);

        Method method = null;
        try {
            method = getClass().getMethod(name, (Class[]) null);
        } catch (NoSuchMethodException e) {
            fail(String.format("Method \"%s\" not found", name));
        }

        if (!Modifier.isPublic(method.getModifiers())) {
            fail(String.format("Method \"%s\" should be public", name));
        }

        return method;
    }

    /**
     * Returns a {@link Method}} object representing the method annotated with
     * {@link JankTest#afterTest()}.
     */
    private Method resolveAfterTest(String name) {
        assertNotNull(name);

        Method method = null;
        try {
            method = getClass().getMethod(name, Bundle.class);
        } catch (NoSuchMethodException e) {
            fail("method annotated with JankTest#afterTest has wrong signature");
        }

        if (!Modifier.isPublic(method.getModifiers())) {
            fail(String.format("Method \"%s\" should be public", name));
        }

        return method;
    }

    /** Returns a {@link Bundle} containing the command line parameters. */
    protected final Bundle getArguments() {
        if (mArguments == null) {
            Instrumentation instrumentation = getInstrumentation();
            // Attempt to obtain the command line arguments bundle
            if (instrumentation instanceof InstrumentationTestRunner) {
                // The InstrumentationTestRunner has its own method for obtaining arguments
                mArguments = ((InstrumentationTestRunner) instrumentation).getArguments();
            } else {
                // Any other type of TestRunner must register its arguments with the
                // InstrumentationRegistry.
                try {
                    // Make sure the InstrumentationRegistry is on the runtime classpath
                    Class.forName("androidx.test.InstrumentationRegistry");
                    // Try to get the command line arguments from the registry
                    mArguments = InstrumentationRegistry.getArguments();
                } catch (ClassNotFoundException | IllegalStateException e) {
                    throw new RuntimeException("Unable to get command line arguments. Custom test "
                            + "runners must register arguments with the InstrumentationRegistry.",
                            e);
                }

            }
        }
        return mArguments;
    }
}