aboutsummaryrefslogtreecommitdiff
path: root/src/io/appium/droiddriver/runner/TestRunner.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/io/appium/droiddriver/runner/TestRunner.java')
-rw-r--r--src/io/appium/droiddriver/runner/TestRunner.java218
1 files changed, 218 insertions, 0 deletions
diff --git a/src/io/appium/droiddriver/runner/TestRunner.java b/src/io/appium/droiddriver/runner/TestRunner.java
new file mode 100644
index 0000000..ec97f9c
--- /dev/null
+++ b/src/io/appium/droiddriver/runner/TestRunner.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2013 DroidDriver committers
+ *
+ * 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 io.appium.droiddriver.runner;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.os.Build;
+import android.os.Bundle;
+import android.test.AndroidTestRunner;
+import android.test.InstrumentationTestRunner;
+import android.test.suitebuilder.TestMethod;
+import android.util.Log;
+
+import com.android.internal.util.Predicate;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestListener;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+import io.appium.droiddriver.helpers.DroidDrivers;
+import io.appium.droiddriver.util.ActivityUtils;
+import io.appium.droiddriver.util.ActivityUtils.Supplier;
+import io.appium.droiddriver.util.Logs;
+
+/**
+ * Adds activity watcher to InstrumentationTestRunner.
+ */
+public class TestRunner extends InstrumentationTestRunner {
+ private final Set<Activity> activities = new HashSet<Activity>();
+ private final AndroidTestRunner androidTestRunner = new AndroidTestRunner();
+ private volatile Activity runningActivity;
+
+ /**
+ * Returns an {@link AndroidTestRunner} that is shared by this and super, such
+ * that we can add custom {@link TestListener}s.
+ */
+ @Override
+ protected AndroidTestRunner getAndroidTestRunner() {
+ return androidTestRunner;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Adds a {@link TestListener} that finishes all created activities.
+ */
+ @Override
+ @TargetApi(18)
+ public void onStart() {
+ if (Build.VERSION.SDK_INT >= 18) {
+ DroidDrivers.initInstrumentation(this, getArguments());
+ }
+
+ getAndroidTestRunner().addTestListener(new TestListener() {
+ @Override
+ public void endTest(Test test) {
+ // Try to finish activity on best-effort basis - TestListener should
+ // not throw.
+ final Activity[] activitiesCopy;
+ synchronized (activities) {
+ if (activities.isEmpty()) {
+ return;
+ }
+ activitiesCopy = activities.toArray(new Activity[activities.size()]);
+ }
+
+ runOnMainSyncWithTimeLimit(new Runnable() {
+ @Override
+ public void run() {
+ for (Activity activity : activitiesCopy) {
+ if (!activity.isFinishing()) {
+ try {
+ Logs.log(Log.INFO, "Stopping activity: " + activity);
+ activity.finish();
+ } catch (Throwable e) {
+ Logs.log(Log.ERROR, e, "Failed to stop activity");
+ }
+ }
+ }
+ }
+ });
+
+ // We've done what we can. Clear activities if any are left.
+ synchronized (activities) {
+ activities.clear();
+ runningActivity = null;
+ }
+ }
+
+ @Override
+ public void addError(Test arg0, Throwable arg1) {}
+
+ @Override
+ public void addFailure(Test arg0, AssertionFailedError arg1) {}
+
+ @Override
+ public void startTest(Test arg0) {}
+ });
+
+ ActivityUtils.setRunningActivitySupplier(new Supplier<Activity>() {
+ @Override
+ public Activity get() {
+ return runningActivity;
+ }
+ });
+
+ super.onStart();
+ }
+
+ // Overrides InstrumentationTestRunner
+ List<Predicate<TestMethod>> getBuilderRequirements() {
+ List<Predicate<TestMethod>> requirements = new ArrayList<Predicate<TestMethod>>();
+ requirements.add(new Predicate<TestMethod>() {
+ @Override
+ public boolean apply(TestMethod arg0) {
+ MinSdkVersion minSdkVersion = getAnnotation(arg0, MinSdkVersion.class);
+ if (minSdkVersion != null && minSdkVersion.value() > Build.VERSION.SDK_INT) {
+ Logs.logfmt(Log.INFO, "filtered %s#%s: MinSdkVersion=%d", arg0.getEnclosingClassname(),
+ arg0.getName(), minSdkVersion.value());
+ return false;
+ }
+
+ UseUiAutomation useUiAutomation = getAnnotation(arg0, UseUiAutomation.class);
+ if (useUiAutomation != null && !DroidDrivers.hasUiAutomation()) {
+ Logs.logfmt(Log.INFO,
+ "filtered %s#%s: Has @UseUiAutomation, but ro.build.version.sdk=%d",
+ arg0.getEnclosingClassname(), arg0.getName(), Build.VERSION.SDK_INT);
+ return false;
+ }
+ return true;
+ }
+
+ private <T extends Annotation> T getAnnotation(TestMethod testMethod, Class<T> clazz) {
+ T annotation = testMethod.getAnnotation(clazz);
+ if (annotation == null) {
+ annotation = testMethod.getEnclosingClass().getAnnotation(clazz);
+ }
+ return annotation;
+ }
+ });
+ return requirements;
+ }
+
+ @Override
+ public void callActivityOnDestroy(Activity activity) {
+ super.callActivityOnDestroy(activity);
+ synchronized (activities) {
+ activities.remove(activity);
+ }
+ }
+
+ @Override
+ public void callActivityOnCreate(Activity activity, Bundle bundle) {
+ super.callActivityOnCreate(activity, bundle);
+ synchronized (activities) {
+ activities.add(activity);
+ }
+ }
+
+ @Override
+ public void callActivityOnResume(Activity activity) {
+ super.callActivityOnResume(activity);
+ runningActivity = activity;
+ }
+
+ @Override
+ public void callActivityOnPause(Activity activity) {
+ super.callActivityOnPause(activity);
+ if (activity == runningActivity) {
+ runningActivity = null;
+ }
+ }
+
+ private boolean runOnMainSyncWithTimeLimit(Runnable runnable) {
+ // Do we need it configurable? Now only used in endTest.
+ long timeoutMillis = 10000L;
+ final FutureTask<?> futureTask = new FutureTask<Void>(runnable, null);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ runOnMainSync(futureTask);
+ }
+ }).start();
+
+ try {
+ futureTask.get(timeoutMillis, TimeUnit.MILLISECONDS);
+ return true;
+ } catch (Throwable e) {
+ Logs.log(Log.WARN, e, String.format(
+ "Timed out after %d milliseconds waiting for Instrumentation.runOnMainSync",
+ timeoutMillis));
+ futureTask.cancel(false);
+ return false;
+ }
+ }
+}