Skip to content

Instantly share code, notes, and snippets.

@dbachelder
Created April 29, 2015 23:44
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dbachelder/4d0588ab6adf0aa6e69a to your computer and use it in GitHub Desktop.
Save dbachelder/4d0588ab6adf0aa6e69a to your computer and use it in GitHub Desktop.
Android test runner that doesn't finish the activity under test

The NoFinishAndroidJUnitRunner class is taken directly from the android testing project:

https://android.googlesource.com/platform/frameworks/testing/+/7a552ffc0bce492a7b87755490f3df7490dc357c/support/src/android/support/test/runner/AndroidJUnitRunner.java

all I did was prevent the adding of ActivityFinisher and prevent the call to super.finish(resultCode, results); by default.

This will cause the test to run but to leave the app under test in the same state it was in when the test completed... this is intended for development only!

It must be used with the NoFinishActivityTestRule to prevent that class from finished your activity as well.

package android.support.test.rule;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
/**
* this is a rule that does not finish the activity under test at the end of the test.
*
* useful for debugging and development. use in the same way as ActivityTestRule.
*/
public class NoFinishActivityTestRule<T extends Activity> extends ActivityTestRule<T> {
private static final String TAG = "NoFinishActivityRule";
public NoFinishActivityTestRule(Class<T> activityClass) {
super(activityClass);
}
public NoFinishActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
super(activityClass, initialTouchMode);
}
public NoFinishActivityTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) {
super(activityClass, initialTouchMode, launchActivity);
}
void finishActivity() {
// do nothing on purpose.
Log.d(TAG, "Rule attempted to finish activity and we didn't!");
}
}
/*
* Copyright (C) 2012 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.support.test.runner;
import android.app.Activity;
import android.app.Instrumentation;
import android.os.Build;
import android.os.Bundle;
import android.support.test.internal.runner.RunnerArgs;
import android.support.test.internal.runner.TestExecutor;
import android.support.test.internal.runner.TestRequest;
import android.support.test.internal.runner.TestRequestBuilder;
import android.support.test.internal.runner.listener.ActivityFinisherRunListener;
import android.support.test.internal.runner.listener.CoverageListener;
import android.support.test.internal.runner.listener.DelayInjector;
import android.support.test.internal.runner.listener.InstrumentationResultPrinter;
import android.support.test.internal.runner.listener.LogRunListener;
import android.support.test.internal.runner.listener.SuiteAssignmentPrinter;
import android.support.test.internal.runner.tracker.AnalyticsBasedUsageTracker;
import android.support.test.internal.runner.tracker.UsageTracker;
import android.support.test.internal.runner.tracker.UsageTrackerRegistry;
import android.support.test.runner.lifecycle.ApplicationLifecycleCallback;
import android.support.test.runner.lifecycle.ApplicationLifecycleMonitorRegistry;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import org.junit.runner.notification.RunListener;
/**
* An {@link Instrumentation} that runs JUnit3 and JUnit4 tests against
* an Android package (application).
* <p/>
* Based on and replacement for {@link android.test.InstrumentationTestRunner}. Supports a superset
* of {@link android.test.InstrumentationTestRunner} features,
* while maintaining command/output format compatibility with that class.
*
* <h3>Typical Usage</h3>
* <p/>
* Write JUnit3 style {@link junit.framework.TestCase}s and/or JUnit4 style
* <a href="http://junit.org/javadoc/latest/org/junit/Test.html">
* <code>Test</code></a>s that perform tests against the classes in your package.
* Make use of the {@link android.support.test.InstrumentationRegistry} if needed.
* <p/>
* In an appropriate AndroidManifest.xml, define an instrumentation with android:name set to
* {@link android.support.test.runner.AndroidJUnitRunner} and the appropriate android:targetPackage
* set.
* <p/>
* Execution options:
* <p/>
* <b>Running all tests:</b> adb shell am instrument -w
* com.android.foo/android.support.test.runner.AndroidJUnitRunner
* <p/>
* <b>Running all tests in a class:</b> adb shell am instrument -w
* -e class com.android.foo.FooTest
* com.android.foo/android.support.test.runner.AndroidJUnitRunner
* <p/>
* <b>Running a single test:</b> adb shell am instrument -w
* -e class com.android.foo.FooTest#testFoo
* com.android.foo/android.support.test.runner.AndroidJUnitRunner
* <p/>
* <b>Running all tests in multiple classes:</b> adb shell am instrument -w
* -e class com.android.foo.FooTest,com.android.foo.TooTest
* com.android.foo/android.support.test.runner.AndroidJUnitRunner
* <p/>
* <b>Running all tests listed in a file:</b> adb shell am instrument -w
* -e testFile /sdcard/tmp/testFile.txt com.android.foo/com.android.test.runner.AndroidJUnitRunner
* The file should contain a list of line separated test classes and optionally methods (expected
* format: com.android.foo.FooClassName#testMethodName).
* <p/>
* <b>Running all tests in a java package:</b> adb shell am instrument -w
* -e package com.android.foo.bar
* com.android.foo/android.support.test.runner.AndroidJUnitRunner
* <b>To debug your tests, set a break point in your code and pass:</b>
* -e debug true
* <p/>
* <b>Running a specific test size i.e. annotated with
* {@link android.test.suitebuilder.annotation.SmallTest} or
* {@link android.test.suitebuilder.annotation.MediumTest} or
* {@link android.test.suitebuilder.annotation.LargeTest}:</b>
* adb shell am instrument -w -e size [small|medium|large]
* com.android.foo/android.support.test.runner.AndroidJUnitRunner
* <p/>
* <b>Filter test run to tests with given annotation:</b> adb shell am instrument -w
* -e annotation com.android.foo.MyAnnotation
* com.android.foo/android.support.test.runner.AndroidJUnitRunner
* <p/>
* If used with other options, the resulting test run will contain the intersection of the two
* options.
* e.g. "-e size large -e annotation com.android.foo.MyAnnotation" will run only tests with both
* the {@link LargeTest} and "com.android.foo.MyAnnotation" annotations.
* <p/>
* <b>Filter test run to tests <i>without</i> given annotation:</b> adb shell am instrument -w
* -e notAnnotation com.android.foo.MyAnnotation
* com.android.foo/android.support.test.runner.AndroidJUnitRunner
* <p/>
* As above, if used with other options, the resulting test run will contain the intersection of
* the two options.
* e.g. "-e size large -e notAnnotation com.android.foo.MyAnnotation" will run tests with
* the {@link LargeTest} annotation that do NOT have the "com.android.foo.MyAnnotation" annotations.
* <p/>
* <b>Filter test run to tests <i>without any</i> of a list of annotations:</b> adb shell am
* instrument -w -e notAnnotation com.android.foo.MyAnnotation,com.android.foo.AnotherAnnotation
* com.android.foo/android.support.test.runner.AndroidJUnitRunner
* <p/>
* <b>Filter test run to a shard of all tests, where numShards is an integer greater than 0 and
* shardIndex is an integer between 0 (inclusive) and numShards (exclusive):</b> adb shell am
* instrument -w -e numShards 4 -e shardIndex 1
* com.android.foo/android.support.test.runner.AndroidJUnitRunner
* <p/>
* <b>To run in 'log only' mode</b>
* -e log true
* This option will load and iterate through all test classes and methods, but will bypass actual
* test execution. Useful for quickly obtaining info on the tests to be executed by an
* instrumentation command.
* <p/>
* <b>To generate EMMA code coverage:</b>
* -e coverage true
* Note: this requires an emma instrumented build. By default, the code coverage results file
* will be saved in a /data/<app>/coverage.ec file, unless overridden by coverageFile flag (see
* below)
* <p/>
* <b> To specify EMMA code coverage results file path:</b>
* -e coverageFile /sdcard/myFile.ec
* <p/>
* <b> To specify one or more
* <a href="http://junit.org/javadoc/latest/org/junit/runner/notification/RunListener.html">
* <code>RunListener</code></a>s to observe the test run:</b>
* -e listener com.foo.Listener,com.foo.Listener2
* <p/>
* <b>Set timeout (in milliseconds) that will be applied to each test:</b>
* -e timeout_msec 5000
* <p/>
* Supported for both JUnit3 and JUnit4 style tests. For JUnit3 tests, this flag is the only way
* to specify timeouts. For JUnit4 tests, this flag overrides timeouts specified via
* <a href="http://junit.org/javadoc/latest/org/junit/rules/Timeout.html">
* <code>org.junit.rules.Timeout</code></a>. Please note that in JUnit4
* <a href="http://junit.org/javadoc/latest/org/junit/Test.html#timeout()">
* <code>org.junit.Test#timeout()</code></a>
* annotation take precedence over both, this flag and
* <a href="http://junit.org/javadoc/latest/org/junit/Test.html#timeout()">
* <code>org.junit.Test#timeout()</code></a>
* annotation.
* <p/>
* <b>To disable Google Analytics:</b>
* -e disableAnalytics true
* <p/>
* In order to make sure we are on the right track with each new release,
* the test runner collects analytics. More specifically, it uploads a hash of the package name
* of the application under test for each invocation. This allows us to measure both the count of
* unique packages using this library as well as the volume of usage.
* <p/>
* <b/>All arguments can also be specified in the in the AndroidManifest via a meta-data tag:</b>
* eg. using listeners:
* instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" ...
* meta-data android:name="listener"
* android:value="com.foo.Listener,com.foo.Listener2"
* Arguments specified via shell will take override manifest specified arguments.
*/
public class NoFinishAndroidJUnitRunner extends MonitoringInstrumentation {
private static final String LOG_TAG = "AndroidJUnitRunner";
private Bundle mArguments;
private InstrumentationResultPrinter mInstrumentationResultPrinter = null;
private RunnerArgs mRunnerArgs;
private boolean finishActivities;
@Override
public void onCreate(Bundle arguments) {
super.onCreate(arguments);
finishActivities = arguments.getBoolean("finish_activities", false);
mArguments = arguments;
// build the arguments. Read from manifest first so manifest-provided args can be overridden
// with command line arguments
mRunnerArgs = new RunnerArgs.Builder()
.fromManifest(this)
.fromBundle(getArguments())
.build();
for (ApplicationLifecycleCallback listener : mRunnerArgs.appListeners) {
ApplicationLifecycleMonitorRegistry.getInstance().addLifecycleCallback(listener);
}
start();
}
/**
* Get the Bundle object that contains the arguments passed to the instrumentation
*
* @return the Bundle object
*/
private Bundle getArguments(){
return mArguments;
}
// Visible for testing
InstrumentationResultPrinter getInstrumentationResultPrinter() {
return mInstrumentationResultPrinter;
}
@Override
public void onStart() {
super.onStart();
Bundle results = new Bundle();
try {
TestExecutor.Builder executorBuilder = new TestExecutor.Builder(this);
if (mRunnerArgs.debug) {
executorBuilder.setWaitForDebugger(true);
}
addListeners(mRunnerArgs, executorBuilder);
TestRequest testRequest = buildRequest(mRunnerArgs, getArguments());
results = executorBuilder.build().execute(testRequest);
} catch (RuntimeException e) {
final String msg = "Fatal exception when running tests";
Log.e(LOG_TAG, msg, e);
// report the exception to instrumentation out
results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
msg + "\n" + Log.getStackTraceString(e));
}
finish(Activity.RESULT_OK, results);
}
@Override
public void finish(int resultCode, Bundle results) {
try {
UsageTrackerRegistry.getInstance().trackUsage("AndroidJUnitRunner");
UsageTrackerRegistry.getInstance().sendUsages();
} catch (RuntimeException re) {
Log.w(LOG_TAG, "Failed to send analytics.", re);
}
if (finishActivities) {
super.finish(resultCode, results);
}
}
private void addListeners(RunnerArgs args, TestExecutor.Builder builder) {
if (args.suiteAssignment) {
builder.addRunListener(new SuiteAssignmentPrinter());
} else {
builder.addRunListener(new LogRunListener());
mInstrumentationResultPrinter = new InstrumentationResultPrinter();
builder.addRunListener(mInstrumentationResultPrinter);
if (finishActivities) {
builder.addRunListener(new ActivityFinisherRunListener(this,
new ActivityFinisher()));
}
addDelayListener(args, builder);
addCoverageListener(args, builder);
}
addListenersFromArg(args, builder);
}
private void addCoverageListener(RunnerArgs args, TestExecutor.Builder builder) {
if (args.codeCoverage) {
builder.addRunListener(new CoverageListener(args.codeCoveragePath));
}
}
/**
* Sets up listener to inject a delay between each test, if specified.
*/
private void addDelayListener(RunnerArgs args, TestExecutor.Builder builder) {
if (args.delayMsec > 0) {
builder.addRunListener(new DelayInjector(args.delayMsec));
} else if (args.logOnly && Build.VERSION.SDK_INT < 16) {
// On older platforms, collecting tests can fail for large volume of tests.
// Insert a small delay between each test to prevent this
builder.addRunListener(new DelayInjector(15 /* msec */));
}
}
private void addListenersFromArg(RunnerArgs args, TestExecutor.Builder builder) {
for (RunListener listener : args.listeners) {
builder.addRunListener(listener);
}
}
@Override
public boolean onException(Object obj, Throwable e) {
InstrumentationResultPrinter instResultPrinter = getInstrumentationResultPrinter();
if (instResultPrinter != null) {
// report better error message back to Instrumentation results.
instResultPrinter.reportProcessCrash(e);
}
return super.onException(obj, e);
}
/**
* Builds a {@link TestRequest} based on given input arguments.
* <p/>
*/
// Visible for testing
TestRequest buildRequest(RunnerArgs runnerArgs, Bundle bundleArgs) {
TestRequestBuilder builder = createTestRequestBuilder(this, bundleArgs);
// only scan for tests for current apk aka testContext
// Note that this represents a change from InstrumentationTestRunner where
// getTargetContext().getPackageCodePath() aka app under test was also scanned
builder.addApkToScan(getContext().getPackageCodePath());
builder.addFromRunnerArgs(runnerArgs);
if (!runnerArgs.disableAnalytics) {
if (null != getTargetContext()) {
UsageTracker tracker = new AnalyticsBasedUsageTracker.Builder(
getTargetContext()).buildIfPossible();
if (null != tracker) {
UsageTrackerRegistry.registerInstance(tracker);
}
}
}
return builder.build();
}
/**
* Factory method for {@link TestRequestBuilder}.
*/
// Visible for testing
TestRequestBuilder createTestRequestBuilder(Instrumentation instr, Bundle arguments) {
return new TestRequestBuilder(instr, arguments);
}
}
@prabintim
Copy link

Can't get it to work. App is still terminated. I also tried with latest MonitoringInstrumentation code. Also, void finishActivity() is unused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment