summaryrefslogtreecommitdiffstats
path: root/mobile/android/tests/browser/junit3/src
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /mobile/android/tests/browser/junit3/src
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'mobile/android/tests/browser/junit3/src')
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestDistribution.java37
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoBackgroundThread.java56
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoMenu.java72
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoProfilesProvider.java50
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoSharedPrefs.java153
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestImageDownloader.java205
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestJarReader.java124
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestRawResource.java66
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestSuggestedSites.java473
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/harness/BrowserInstrumentationTestRunner.java33
-rw-r--r--mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/harness/BrowserTestListener.java42
11 files changed, 1311 insertions, 0 deletions
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestDistribution.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestDistribution.java
new file mode 100644
index 000000000..9cabb346c
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestDistribution.java
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.tests.browser.junit3;
+
+import android.test.InstrumentationTestCase;
+import org.mozilla.gecko.distribution.ReferrerDescriptor;
+
+public class TestDistribution extends InstrumentationTestCase {
+ private static final String TEST_REFERRER_STRING = "utm_source=campsource&utm_medium=campmed&utm_term=term%2Bhere&utm_content=content&utm_campaign=name";
+ private static final String TEST_MALFORMED_REFERRER_STRING = "utm_source=campsource&utm_medium=campmed&utm_term=term%2";
+
+ public void testReferrerParsing() {
+ ReferrerDescriptor good = new ReferrerDescriptor(TEST_REFERRER_STRING);
+ assertEquals("campsource", good.source);
+ assertEquals("campmed", good.medium);
+ assertEquals("term+here", good.term);
+ assertEquals("content", good.content);
+ assertEquals("name", good.campaign);
+
+ // Uri.Builder is permissive.
+ ReferrerDescriptor bad = new ReferrerDescriptor(TEST_MALFORMED_REFERRER_STRING);
+ assertEquals("campsource", bad.source);
+ assertEquals("campmed", bad.medium);
+ assertFalse("term+here".equals(bad.term));
+ assertNull(bad.content);
+ assertNull(bad.campaign);
+
+ ReferrerDescriptor ugly = new ReferrerDescriptor(null);
+ assertNull(ugly.source);
+ assertNull(ugly.medium);
+ assertNull(ugly.term);
+ assertNull(ugly.content);
+ assertNull(ugly.campaign);
+ }
+}
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoBackgroundThread.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoBackgroundThread.java
new file mode 100644
index 000000000..cbf9dffe3
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoBackgroundThread.java
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.tests.browser.junit3;
+
+import android.test.InstrumentationTestCase;
+import org.mozilla.gecko.util.ThreadUtils;
+
+public class TestGeckoBackgroundThread extends InstrumentationTestCase {
+
+ private boolean finishedTest;
+ private boolean ranFirstRunnable;
+
+ public void testGeckoBackgroundThread() throws InterruptedException {
+
+ final Thread testThread = Thread.currentThread();
+
+ ThreadUtils.postToBackgroundThread(new Runnable() {
+ @Override
+ public void run() {
+ // Must *not* be on thread that posted the Runnable.
+ assertFalse(ThreadUtils.isOnThread(testThread));
+
+ // Must be on background thread.
+ assertTrue(ThreadUtils.isOnBackgroundThread());
+
+ ranFirstRunnable = true;
+ }
+ });
+
+ // Post a second Runnable to make sure it still runs on the background thread,
+ // and it only runs after the first Runnable has run.
+ ThreadUtils.postToBackgroundThread(new Runnable() {
+ @Override
+ public void run() {
+ // Must still be on background thread.
+ assertTrue(ThreadUtils.isOnBackgroundThread());
+
+ // This Runnable must be run after the first Runnable had finished.
+ assertTrue(ranFirstRunnable);
+
+ synchronized (TestGeckoBackgroundThread.this) {
+ finishedTest = true;
+ TestGeckoBackgroundThread.this.notify();
+ }
+ }
+ });
+
+ synchronized (this) {
+ while (!finishedTest) {
+ wait();
+ }
+ }
+ }
+}
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoMenu.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoMenu.java
new file mode 100644
index 000000000..15be26004
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoMenu.java
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.tests.browser.junit3;
+
+import android.test.InstrumentationTestCase;
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.menu.GeckoMenu;
+import org.mozilla.gecko.util.ThreadUtils;
+
+public class TestGeckoMenu extends InstrumentationTestCase {
+
+ private volatile Exception exception;
+ private void setException(Exception e) {
+ this.exception = e;
+ }
+
+ public void testMenuThreading() throws InterruptedException {
+ final GeckoMenu menu = new GeckoMenu(getInstrumentation().getTargetContext());
+ final Object semaphore = new Object();
+
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ menu.add("test1");
+ } catch (Exception e) {
+ setException(e);
+ }
+
+ synchronized (semaphore) {
+ semaphore.notify();
+ }
+ }
+ });
+ synchronized (semaphore) {
+ semaphore.wait();
+ }
+
+ // No exception thrown if called on UI thread.
+ assertNull(exception);
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ menu.add("test2");
+ } catch (Exception e) {
+ setException(e);
+ }
+
+ synchronized (semaphore) {
+ semaphore.notify();
+ }
+ }
+ }).start();
+
+ synchronized (semaphore) {
+ semaphore.wait();
+ }
+
+ if (AppConstants.RELEASE_OR_BETA) {
+ // No exception thrown: release build.
+ assertNull(exception);
+ return;
+ }
+
+ assertNotNull(exception);
+ assertEquals(exception.getClass(), IllegalThreadStateException.class);
+ }
+}
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoProfilesProvider.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoProfilesProvider.java
new file mode 100644
index 000000000..2b1da3295
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoProfilesProvider.java
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.tests.browser.junit3;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.test.InstrumentationTestCase;
+import org.mozilla.gecko.db.BrowserContract;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TestGeckoProfilesProvider extends InstrumentationTestCase {
+ private static final String[] NAME_AND_PATH = new String[] { BrowserContract.Profiles.NAME, BrowserContract.Profiles.PATH };
+
+ /**
+ * Ensure that the default profile is found in the results from the provider.
+ */
+ public void testQueryDefault() throws RemoteException {
+ final ContentResolver contentResolver = getInstrumentation().getContext().getContentResolver();
+ final Uri uri = BrowserContract.PROFILES_AUTHORITY_URI.buildUpon().appendPath("profiles").build();
+ final Cursor c = contentResolver.query(uri, NAME_AND_PATH, null, null, null);
+ assertNotNull(c);
+ try {
+ assertTrue(c.moveToFirst());
+ assertTrue(c.getCount() > 0);
+ Map<String, String> profiles = new HashMap<String, String>();
+ while (!c.isAfterLast()) {
+ final String name = c.getString(0);
+ final String path = c.getString(1);
+ profiles.put(name, path);
+ c.moveToNext();
+ }
+
+ assertTrue(profiles.containsKey("default"));
+ final String path = profiles.get("default");
+ assertTrue(path.endsWith(".default")); // It's the right profile...
+ assertTrue(path.startsWith("/data/")); // ... in the 'data' dir...
+ assertTrue(path.contains("/mozilla/")); // ... in the 'mozilla' dir.
+ assertTrue(new File(path).exists());
+ } finally {
+ c.close();
+ }
+ }
+}
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoSharedPrefs.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoSharedPrefs.java
new file mode 100644
index 000000000..9e35cab35
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoSharedPrefs.java
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.tests.browser.junit3;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.preference.PreferenceManager;
+import android.test.InstrumentationTestCase;
+import android.test.RenamingDelegatingContext;
+import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.GeckoSharedPrefs.Flags;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test GeckoSharedPrefs migrations.
+ */
+public class TestGeckoSharedPrefs extends InstrumentationTestCase {
+
+ private static class TestContext extends RenamingDelegatingContext {
+ private static final String PREFIX = "TestGeckoSharedPrefs-";
+
+ private final Set<String> usedPrefs;
+
+ public TestContext(Context context) {
+ super(context, PREFIX);
+ usedPrefs = Collections.synchronizedSet(new HashSet<String>());
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ usedPrefs.add(name);
+ return super.getSharedPreferences(PREFIX + name, mode);
+ }
+
+ public void clearUsedPrefs() {
+ for (String prefsName : usedPrefs) {
+ getSharedPreferences(prefsName, 0).edit().clear().commit();
+ }
+
+ usedPrefs.clear();
+ }
+ }
+
+ private static final EnumSet<Flags> disableMigrations = EnumSet.of(Flags.DISABLE_MIGRATIONS);
+
+ private TestContext context;
+
+ protected void setUp() {
+ context = new TestContext(getInstrumentation().getTargetContext());
+ }
+
+ protected void tearDown() {
+ context.clearUsedPrefs();
+ GeckoSharedPrefs.reset();
+ }
+
+ public void testDisableMigrations() {
+ // Version is 0 before any migration
+ assertEquals(0, GeckoSharedPrefs.getVersion(context));
+
+ // Get prefs with migrations disabled
+ GeckoSharedPrefs.forApp(context, disableMigrations);
+ GeckoSharedPrefs.forProfile(context, disableMigrations);
+ GeckoSharedPrefs.forProfileName(context, "someProfile", disableMigrations);
+
+ // Version should still be 0
+ assertEquals(0, GeckoSharedPrefs.getVersion(context));
+ }
+
+ public void testPrefsVersion() {
+ // Version is 0 before any migration
+ assertEquals(0, GeckoSharedPrefs.getVersion(context));
+
+ // Trigger migration by getting a SharedPreferences instance
+ GeckoSharedPrefs.forApp(context);
+
+ // Version should be current after migration
+ assertEquals(GeckoSharedPrefs.PREFS_VERSION, GeckoSharedPrefs.getVersion(context));
+ }
+
+ public void testMigrateFromPreferenceManager() {
+ SharedPreferences appPrefs = GeckoSharedPrefs.forApp(context, disableMigrations);
+ assertTrue(appPrefs.getAll().isEmpty());
+ final Editor appEditor = appPrefs.edit();
+
+ SharedPreferences profilePrefs = GeckoSharedPrefs.forProfileName(context, GeckoProfile.DEFAULT_PROFILE, disableMigrations);
+ assertTrue(profilePrefs.getAll().isEmpty());
+ final Editor profileEditor = profilePrefs.edit();
+
+ final SharedPreferences pmPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+ assertTrue(pmPrefs.getAll().isEmpty());
+ Editor pmEditor = pmPrefs.edit();
+
+ // Insert a key for each type to exercise the
+ // migration path a bit more thoroughly.
+ pmEditor.putInt("int_key", 23);
+ pmEditor.putLong("long_key", 23L);
+ pmEditor.putString("string_key", "23");
+ pmEditor.putFloat("float_key", 23.3f);
+
+ final String[] profileKeys = {
+ "string_profile",
+ "int_profile"
+ };
+
+ // Insert keys that are expected to be moved to the
+ // PROFILE scope.
+ pmEditor.putString(profileKeys[0], "24");
+ pmEditor.putInt(profileKeys[1], 24);
+
+ // Commit changes to PreferenceManager
+ pmEditor.commit();
+ assertEquals(6, pmPrefs.getAll().size());
+
+ // Perform actual migration with the given editors
+ pmEditor = GeckoSharedPrefs.migrateFromPreferenceManager(context, appEditor,
+ profileEditor, Arrays.asList(profileKeys));
+
+ // Commit changes applied during the migration
+ appEditor.commit();
+ profileEditor.commit();
+ pmEditor.commit();
+
+ // PreferenceManager should have no keys
+ assertTrue(pmPrefs.getAll().isEmpty());
+
+ // App should have all keys except the profile ones
+ assertEquals(4, appPrefs.getAll().size());
+
+ // Ensure app scope doesn't have any of the profile keys
+ for (int i = 0; i < profileKeys.length; i++) {
+ assertFalse(appPrefs.contains(profileKeys[i]));
+ }
+
+ // Check app keys
+ assertEquals(23, appPrefs.getInt("int_key", 0));
+ assertEquals(23L, appPrefs.getLong("long_key", 0L));
+ assertEquals("23", appPrefs.getString("string_key", ""));
+ assertEquals(23.3f, appPrefs.getFloat("float_key", 0));
+
+ assertEquals(2, profilePrefs.getAll().size());
+ assertEquals("24", profilePrefs.getString(profileKeys[0], ""));
+ assertEquals(24, profilePrefs.getInt(profileKeys[1], 0));
+ }
+}
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestImageDownloader.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestImageDownloader.java
new file mode 100644
index 000000000..e0cf9bc63
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestImageDownloader.java
@@ -0,0 +1,205 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.tests.browser.junit3;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.test.InstrumentationTestCase;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockResources;
+import android.util.DisplayMetrics;
+import org.mozilla.gecko.distribution.Distribution;
+import org.mozilla.gecko.home.ImageLoader.ImageDownloader;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class TestImageDownloader extends InstrumentationTestCase {
+ private static class TestContext extends RenamingDelegatingContext {
+ private static final String PREFIX = "TestImageDownloader-";
+
+ private final Resources resources;
+ private final Set<String> usedPrefs;
+
+ public TestContext(Context context) {
+ super(context, PREFIX);
+ resources = new TestResources();
+ usedPrefs = Collections.synchronizedSet(new HashSet<String>());
+ }
+
+ @Override
+ public Resources getResources() {
+ return resources;
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ usedPrefs.add(name);
+ return super.getSharedPreferences(PREFIX + name, mode);
+ }
+
+ public void clearUsedPrefs() {
+ for (String prefsName : usedPrefs) {
+ getSharedPreferences(prefsName, 0).edit().clear().commit();
+ }
+
+ usedPrefs.clear();
+ }
+ }
+
+ private static class TestResources extends MockResources {
+ private final DisplayMetrics metrics;
+
+ public TestResources() {
+ metrics = new DisplayMetrics();
+ }
+
+ @Override
+ public DisplayMetrics getDisplayMetrics() {
+ return metrics;
+ }
+
+ public void setDensityDpi(int densityDpi) {
+ metrics.densityDpi = densityDpi;
+ }
+ }
+
+ private static class TestDistribution extends Distribution {
+ final List<String> accessedFiles;
+
+ public TestDistribution(Context context) {
+ super(context);
+ accessedFiles = new ArrayList<String>();
+ }
+
+ @Override
+ public File getDistributionFile(String name) {
+ accessedFiles.add(name);
+
+ // Return null to ensure the ImageDownloader will go
+ // through a complete density lookup for each filename.
+ return null;
+ }
+
+ public List<String> getAccessedFiles() {
+ return Collections.unmodifiableList(accessedFiles);
+ }
+
+ public void resetAccessedFiles() {
+ accessedFiles.clear();
+ }
+ }
+
+ private TestContext context;
+ private TestResources resources;
+ private TestDistribution distribution;
+ private ImageDownloader downloader;
+
+ protected void setUp() {
+ context = new TestContext(getInstrumentation().getTargetContext());
+ resources = (TestResources) context.getResources();
+ distribution = new TestDistribution(context);
+ downloader = new ImageDownloader(context, distribution);
+ }
+
+ protected void tearDown() {
+ context.clearUsedPrefs();
+ }
+
+ private void triggerLoad(Uri uri) {
+ try {
+ downloader.load(uri, false);
+ } catch (IOException e) {
+ // Ignore any IO exceptions.
+ }
+ }
+
+ private void checkAccessedFiles(String[] filenames) {
+ List<String> accessedFiles = distribution.getAccessedFiles();
+
+ for (int i = 0; i < filenames.length; i++) {
+ assertEquals(filenames[i], accessedFiles.get(i));
+ }
+ }
+
+ private void checkAccessedFilesForUri(Uri uri, int densityDpi, String[] filenames) {
+ resources.setDensityDpi(densityDpi);
+ triggerLoad(uri);
+ checkAccessedFiles(filenames);
+ distribution.resetAccessedFiles();
+ }
+
+ public void testAccessedFiles() {
+ // Filename only.
+ checkAccessedFilesForUri(Uri.parse("gecko.distribution://file"),
+ DisplayMetrics.DENSITY_MEDIUM,
+ new String[] {
+ "mdpi/file.png",
+ "xhdpi/file.png",
+ "hdpi/file.png"
+ });
+
+ // Directory and filename.
+ checkAccessedFilesForUri(Uri.parse("gecko.distribution://dir/file"),
+ DisplayMetrics.DENSITY_MEDIUM,
+ new String[] {
+ "dir/mdpi/file.png",
+ "dir/xhdpi/file.png",
+ "dir/hdpi/file.png"
+ });
+
+ // Sub-directories and filename.
+ checkAccessedFilesForUri(Uri.parse("gecko.distribution://dir/subdir/file"),
+ DisplayMetrics.DENSITY_MEDIUM,
+ new String[] {
+ "dir/subdir/mdpi/file.png",
+ "dir/subdir/xhdpi/file.png",
+ "dir/subdir/hdpi/file.png"
+ });
+ }
+
+ public void testDensityLookup() {
+ Uri uri = Uri.parse("gecko.distribution://file");
+
+ // Medium density
+ checkAccessedFilesForUri(uri,
+ DisplayMetrics.DENSITY_MEDIUM,
+ new String[] {
+ "mdpi/file.png",
+ "xhdpi/file.png",
+ "hdpi/file.png"
+ });
+
+ checkAccessedFilesForUri(uri,
+ DisplayMetrics.DENSITY_HIGH,
+ new String[] {
+ "hdpi/file.png",
+ "xxhdpi/file.png",
+ "xhdpi/file.png"
+ });
+
+ checkAccessedFilesForUri(uri,
+ DisplayMetrics.DENSITY_XHIGH,
+ new String[] {
+ "xhdpi/file.png",
+ "xxhdpi/file.png",
+ "mdpi/file.png"
+ });
+
+
+ checkAccessedFilesForUri(uri,
+ DisplayMetrics.DENSITY_XXHIGH,
+ new String[] {
+ "xxhdpi/file.png",
+ "hdpi/file.png"
+ });
+ }
+}
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestJarReader.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestJarReader.java
new file mode 100644
index 000000000..125ffe933
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestJarReader.java
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.tests.browser.junit3;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Stack;
+
+import android.test.InstrumentationTestCase;
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.util.FileUtils;
+import org.mozilla.gecko.util.GeckoJarReader;
+
+import android.content.Context;
+
+/**
+ * A basic jar reader test. Tests reading a png from fennec's apk, as well as
+ * loading some invalid jar urls.
+ */
+public class TestJarReader extends InstrumentationTestCase {
+ public void testJarReader() {
+ final Context context = getInstrumentation().getTargetContext().getApplicationContext();
+ String appPath = getInstrumentation().getTargetContext().getPackageResourcePath();
+ assertNotNull(appPath);
+
+ // Test reading a file from a jar url that looks correct.
+ String url = "jar:file://" + appPath + "!/" + AppConstants.OMNIJAR_NAME;
+ InputStream stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
+ assertNotNull(stream);
+
+ // Test looking for an non-existent file in a jar.
+ url = "jar:file://" + appPath + "!/" + AppConstants.OMNIJAR_NAME;
+ stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/nonexistent_file.png");
+ assertNull(stream);
+
+ // Test looking for a file that doesn't exist in the APK.
+ url = "jar:file://" + appPath + "!/" + "BAD" + AppConstants.OMNIJAR_NAME;
+ stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
+ assertNull(stream);
+
+ // Test looking for a file that doesn't exist in the APK.
+ // Bug 1174922, prefixed string / length error.
+ url = "jar:file://" + appPath + "!/" + AppConstants.OMNIJAR_NAME + "BAD";
+ stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
+ assertNull(stream);
+
+ // Test looking for an jar with an invalid url.
+ url = "jar:file://" + appPath + "!" + "!/" + AppConstants.OMNIJAR_NAME;
+ stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/nonexistent_file.png");
+ assertNull(stream);
+
+ // Test looking for a file that doesn't exist on disk.
+ url = "jar:file://" + appPath + "BAD" + "!/" + AppConstants.OMNIJAR_NAME;
+ stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
+ assertNull(stream);
+ }
+
+ protected void assertExtractStream(String url) throws IOException {
+ final File file = GeckoJarReader.extractStream(getInstrumentation().getTargetContext(), url, getInstrumentation().getContext().getCacheDir(), ".test");
+ assertNotNull(file);
+ try {
+ assertTrue(file.getName().endsWith("test"));
+ final String contents = FileUtils.readStringFromFile(file);
+ assertNotNull(contents);
+ assertTrue(contents.length() > 0);
+ } finally {
+ file.delete();
+ }
+ }
+
+ public void testExtractStream() throws IOException {
+ String appPath = getInstrumentation().getTargetContext().getPackageResourcePath();
+ assertNotNull(appPath);
+
+ // We don't have a lot of good files to choose from. package-name.txt isn't included in Gradle APKs.
+ assertExtractStream("jar:file://" + appPath + "!/resources.arsc");
+
+ final String url = GeckoJarReader.getJarURL(getInstrumentation().getTargetContext(), "chrome.manifest");
+ assertExtractStream(url);
+
+ // Now use an extracted copy of chrome.manifest to test further.
+ final File file = GeckoJarReader.extractStream(getInstrumentation().getTargetContext(), url, getInstrumentation().getContext().getCacheDir(), ".test");
+ assertNotNull(file);
+ try {
+ assertExtractStream("file://" + file.getAbsolutePath()); // file:// URI.
+ assertExtractStream(file.getAbsolutePath()); // Vanilla path.
+ } finally {
+ file.delete();
+ }
+ }
+
+ protected void assertExtractStreamFails(String url) throws IOException {
+ final File file = GeckoJarReader.extractStream(getInstrumentation().getTargetContext(), url, getInstrumentation().getContext().getCacheDir(), ".test");
+ assertNull(file);
+ }
+
+ public void testExtractStreamFailureCases() throws IOException {
+ String appPath = getInstrumentation().getTargetContext().getPackageResourcePath();
+ assertNotNull(appPath);
+
+ // First, a bad APK.
+ assertExtractStreamFails("jar:file://" + appPath + "BAD!/resources.arsc");
+
+ // Second, a bad file in the APK.
+ assertExtractStreamFails("jar:file://" + appPath + "!/BADresources.arsc");
+
+ // Now a bad file in the omnijar.
+ final String badUrl = GeckoJarReader.getJarURL(getInstrumentation().getTargetContext(), "BADchrome.manifest");
+ assertExtractStreamFails(badUrl);
+
+ // Now use an extracted copy of chrome.manifest to test further.
+ final String goodUrl = GeckoJarReader.getJarURL(getInstrumentation().getTargetContext(), "chrome.manifest");
+ final File file = GeckoJarReader.extractStream(getInstrumentation().getTargetContext(), goodUrl, getInstrumentation().getContext().getCacheDir(), ".test");
+ assertNotNull(file);
+ try {
+ assertExtractStreamFails("file://" + file.getAbsolutePath() + "BAD"); // Bad file:// URI.
+ assertExtractStreamFails(file.getAbsolutePath() + "BAD"); //Bad vanilla path.
+ } finally {
+ file.delete();
+ }
+ }
+}
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestRawResource.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestRawResource.java
new file mode 100644
index 000000000..8e27cbe9c
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestRawResource.java
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.tests.browser.junit3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.test.InstrumentationTestCase;
+import android.test.mock.MockContext;
+import android.test.mock.MockResources;
+import org.mozilla.gecko.util.RawResource;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Tests whether RawResource.getAsString() produces the right String
+ * result after reading the returned raw resource's InputStream.
+ */
+public class TestRawResource extends InstrumentationTestCase {
+ private static final int RAW_RESOURCE_ID = 1;
+ private static final String RAW_CONTENTS = "RAW";
+
+ private static class TestContext extends MockContext {
+ private final Resources resources;
+
+ public TestContext() {
+ resources = new TestResources();
+ }
+
+ @Override
+ public Resources getResources() {
+ return resources;
+ }
+ }
+
+ /**
+ * Browser instrumentation tests can't have access to test-only
+ * resources (bug 994135) yet so we mock the access to resources
+ * for now.
+ */
+ private static class TestResources extends MockResources {
+ @Override
+ public InputStream openRawResource(int id) {
+ if (id == RAW_RESOURCE_ID) {
+ return new ByteArrayInputStream(RAW_CONTENTS.getBytes());
+ }
+
+ return null;
+ }
+ }
+
+ public void testGet() {
+ Context context = new TestContext();
+ String result;
+
+ try {
+ result = RawResource.getAsString(context, RAW_RESOURCE_ID);
+ } catch (IOException e) {
+ result = null;
+ }
+
+ assertEquals(RAW_CONTENTS, result);
+ }
+}
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestSuggestedSites.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestSuggestedSites.java
new file mode 100644
index 000000000..c29c369d6
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestSuggestedSites.java
@@ -0,0 +1,473 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.tests.browser.junit3;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.test.InstrumentationTestCase;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockResources;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.Locales;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.SuggestedSites;
+import org.mozilla.gecko.distribution.Distribution;
+import org.mozilla.gecko.preferences.GeckoPreferences;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestSuggestedSites extends InstrumentationTestCase {
+ private static class TestContext extends RenamingDelegatingContext {
+ private static final String PREFIX = "TestSuggestedSites-";
+
+ private final Resources resources;
+ private final Set<String> usedPrefs;
+
+ public TestContext(Context context) {
+ super(context, PREFIX);
+ resources = new TestResources();
+ usedPrefs = Collections.synchronizedSet(new HashSet<String>());
+ }
+
+ @Override
+ public Resources getResources() {
+ return resources;
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ usedPrefs.add(name);
+ return super.getSharedPreferences(PREFIX + name, mode);
+ }
+
+ public void clearUsedPrefs() {
+ for (String prefsName : usedPrefs) {
+ getSharedPreferences(prefsName, 0).edit().clear().commit();
+ }
+
+ usedPrefs.clear();
+ }
+ }
+
+ private static class TestResources extends MockResources {
+ private String suggestedSites;
+
+ @Override
+ public InputStream openRawResource(int id) {
+ if (id == R.raw.suggestedsites && suggestedSites != null) {
+ return new ByteArrayInputStream(suggestedSites.getBytes());
+ }
+
+ return null;
+ }
+
+ public void setSuggestedSitesResource(String suggestedSites) {
+ this.suggestedSites = suggestedSites;
+ }
+ }
+
+ private static class TestDistribution extends Distribution {
+ private final Map<Locale, File> filesPerLocale;
+
+ public TestDistribution(Context context) {
+ super(context);
+ this.filesPerLocale = new HashMap<Locale, File>();
+ }
+
+ @Override
+ public File getDistributionFile(String name) {
+ for (Locale locale : filesPerLocale.keySet()) {
+ if (name.startsWith("suggestedsites/locales/" + Locales.getLanguageTag(locale))) {
+ return filesPerLocale.get(locale);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean exists() {
+ return true;
+ }
+
+ public void setFileForLocale(Locale locale, File file) {
+ filesPerLocale.put(locale, file);
+ }
+
+ public void start() {
+ doInit();
+ }
+ }
+
+ class TestObserver extends ContentObserver {
+ private final CountDownLatch changeLatch;
+
+ public TestObserver(CountDownLatch changeLatch) {
+ super(null);
+ this.changeLatch = changeLatch;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ changeLatch.countDown();
+ }
+ }
+
+ private static final int DEFAULT_LIMIT = 6;
+
+ private static final String DIST_PREFIX = "dist";
+
+ private TestContext context;
+ private TestResources resources;
+ private List<File> tempFiles;
+
+ private String generateSites(int n) {
+ return generateSites(n, "");
+ }
+
+ private String generateSites(int n, String prefix) {
+ JSONArray sites = new JSONArray();
+
+ try {
+ for (int i = 0; i < n; i++) {
+ JSONObject site = new JSONObject();
+ site.put("url", prefix + "url" + i);
+ site.put("title", prefix + "title" + i);
+ site.put("imageurl", prefix + "imageUrl" + i);
+ site.put("bgcolor", prefix + "bgColor" + i);
+
+ sites.put(site);
+ }
+ } catch (Exception e) {
+ return "";
+ }
+
+ return sites.toString();
+ }
+
+ private File createDistSuggestedSitesFile(int n) {
+ FileOutputStream fos = null;
+
+ try {
+ File distFile = File.createTempFile("distrosites", ".json",
+ context.getCacheDir());
+
+ fos = new FileOutputStream(distFile);
+ fos.write(generateSites(n, DIST_PREFIX).getBytes());
+
+ return distFile;
+ } catch (IOException e) {
+ fail("Failed to create temp suggested sites file");
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ // Ignore.
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void checkCursorCount(String content, int expectedCount) {
+ checkCursorCount(content, expectedCount, DEFAULT_LIMIT);
+ }
+
+ private void checkCursorCount(String content, int expectedCount, int limit) {
+ resources.setSuggestedSitesResource(content);
+ Cursor c = new SuggestedSites(context).get(limit);
+ assertEquals(expectedCount, c.getCount());
+ c.close();
+ }
+
+ protected void setUp() {
+ context = new TestContext(getInstrumentation().getTargetContext());
+ resources = (TestResources) context.getResources();
+ tempFiles = new ArrayList<File>();
+ }
+
+ protected void tearDown() {
+ context.clearUsedPrefs();
+ for (File f : tempFiles) {
+ f.delete();
+ }
+ }
+
+ public void testCount() {
+ // Empty array = empty cursor
+ checkCursorCount(generateSites(0), 0);
+
+ // 2 items = cursor with 2 rows
+ checkCursorCount(generateSites(2), 2);
+
+ // 10 items with lower limit = cursor respects limit
+ checkCursorCount(generateSites(10), 3, 3);
+ }
+
+ public void testEmptyCursor() {
+ // Null resource = empty cursor
+ checkCursorCount(null, 0);
+
+ // Empty string = empty cursor
+ checkCursorCount("", 0);
+
+ // Invalid json string = empty cursor
+ checkCursorCount("{ broken: }", 0);
+ }
+
+ public void testCursorContent() {
+ resources.setSuggestedSitesResource(generateSites(3));
+
+ Cursor c = new SuggestedSites(context).get(DEFAULT_LIMIT);
+ assertEquals(3, c.getCount());
+
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ int position = c.getPosition();
+
+ String url = c.getString(c.getColumnIndexOrThrow(BrowserContract.SuggestedSites.URL));
+ assertEquals("url" + position, url);
+
+ String title = c.getString(c.getColumnIndexOrThrow(BrowserContract.SuggestedSites.TITLE));
+ assertEquals("title" + position, title);
+ }
+
+ c.close();
+ }
+
+ public void testExcludeUrls() {
+ resources.setSuggestedSitesResource(generateSites(6));
+
+ List<String> excludedUrls = new ArrayList<String>(3);
+ excludedUrls.add("url1");
+ excludedUrls.add("url3");
+ excludedUrls.add("url5");
+
+ List<String> includedUrls = new ArrayList<String>(3);
+ includedUrls.add("url0");
+ includedUrls.add("url2");
+ includedUrls.add("url4");
+
+ Cursor c = new SuggestedSites(context).get(DEFAULT_LIMIT, excludedUrls);
+
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ String url = c.getString(c.getColumnIndexOrThrow(BrowserContract.SuggestedSites.URL));
+ assertFalse(excludedUrls.contains(url));
+ assertTrue(includedUrls.contains(url));
+ }
+
+ c.close();
+ }
+
+ public void testHiddenSites() {
+ resources.setSuggestedSitesResource(generateSites(6));
+
+ List<String> visibleUrls = new ArrayList<String>(3);
+ visibleUrls.add("url3");
+ visibleUrls.add("url4");
+ visibleUrls.add("url5");
+
+ List<String> hiddenUrls = new ArrayList<String>(3);
+ hiddenUrls.add("url0");
+ hiddenUrls.add("url1");
+ hiddenUrls.add("url2");
+
+ // Add mocked hidden sites to SharedPreferences.
+ StringBuilder hiddenUrlBuilder = new StringBuilder();
+ for (String s : hiddenUrls) {
+ hiddenUrlBuilder.append(" ");
+ hiddenUrlBuilder.append(Uri.encode(s));
+ }
+
+ final String hiddenPref = hiddenUrlBuilder.toString();
+ GeckoSharedPrefs.forProfile(context).edit()
+ .putString(SuggestedSites.PREF_SUGGESTED_SITES_HIDDEN, hiddenPref)
+ .commit();
+
+ Cursor c = new SuggestedSites(context).get(DEFAULT_LIMIT);
+ assertEquals(Math.min(3, DEFAULT_LIMIT), c.getCount());
+
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ String url = c.getString(c.getColumnIndexOrThrow(BrowserContract.SuggestedSites.URL));
+ assertFalse(hiddenUrls.contains(url));
+ assertTrue(visibleUrls.contains(url));
+ }
+
+ c.close();
+ }
+
+ public void testImageUrlAndBgColor() {
+ final int count = 3;
+ resources.setSuggestedSitesResource(generateSites(count));
+
+ SuggestedSites suggestedSites = new SuggestedSites(context);
+
+ // Suggested sites hasn't been loaded yet.
+ for (int i = 0; i < count; i++) {
+ String url = "url" + i;
+ assertFalse(suggestedSites.contains(url));
+ assertNull(suggestedSites.getImageUrlForUrl(url));
+ assertNull(suggestedSites.getBackgroundColorForUrl(url));
+ }
+
+ Cursor c = suggestedSites.get(DEFAULT_LIMIT);
+ c.moveToPosition(-1);
+
+ // We should have cached results after the get() call.
+ while (c.moveToNext()) {
+ String url = c.getString(c.getColumnIndexOrThrow(BrowserContract.SuggestedSites.URL));
+ assertTrue(suggestedSites.contains(url));
+ assertEquals("imageUrl" + c.getPosition(),
+ suggestedSites.getImageUrlForUrl(url));
+ assertEquals("bgColor" + c.getPosition(),
+ suggestedSites.getBackgroundColorForUrl(url));
+ }
+ c.close();
+
+ // No valid values for unknown URLs.
+ assertFalse(suggestedSites.contains("foo"));
+ assertNull(suggestedSites.getImageUrlForUrl("foo"));
+ assertNull(suggestedSites.getBackgroundColorForUrl("foo"));
+ }
+
+ public void testLocaleChanges() {
+ resources.setSuggestedSitesResource(generateSites(3));
+
+ SuggestedSites suggestedSites = new SuggestedSites(context);
+
+ // Initial load with predefined locale
+ Cursor c = suggestedSites.get(DEFAULT_LIMIT, Locale.UK);
+ assertEquals(3, c.getCount());
+ c.close();
+
+ resources.setSuggestedSitesResource(generateSites(5));
+
+ // Second load with same locale should return same results
+ // even though the contents of the resource have changed.
+ c = suggestedSites.get(DEFAULT_LIMIT, Locale.UK);
+ assertEquals(3, c.getCount());
+ c.close();
+
+ // Changing the locale forces the cached list to be refreshed.
+ c = suggestedSites.get(DEFAULT_LIMIT, Locale.US);
+ assertEquals(5, c.getCount());
+ c.close();
+ }
+
+ public void testDistribution() {
+ final int DIST_COUNT = 2;
+ final int DEFAULT_COUNT = 3;
+
+ File sitesFile = new File(context.getCacheDir(),
+ "suggestedsites-" + SystemClock.uptimeMillis() + ".json");
+ tempFiles.add(sitesFile);
+ assertFalse(sitesFile.exists());
+
+ File distFile = createDistSuggestedSitesFile(DIST_COUNT);
+ tempFiles.add(distFile);
+ assertTrue(distFile.exists());
+
+ final CountDownLatch changeLatch = new CountDownLatch(1);
+
+ // Watch for change notifications on suggested sites.
+ ContentResolver cr = context.getContentResolver();
+ ContentObserver observer = new TestObserver(changeLatch);
+ cr.registerContentObserver(BrowserContract.SuggestedSites.CONTENT_URI,
+ false, observer);
+
+ // Init distribution with the mock file.
+ TestDistribution distribution = new TestDistribution(context);
+ distribution.setFileForLocale(Locale.getDefault(), distFile);
+ distribution.start();
+
+ // Init suggested sites with default values.
+ resources.setSuggestedSitesResource(generateSites(DEFAULT_COUNT));
+ SuggestedSites suggestedSites =
+ new SuggestedSites(context, distribution, sitesFile);
+
+ // The initial query will not contain the distribution sites
+ // yet. This will happen asynchronously once the distribution
+ // is installed.
+ Cursor c1 = null;
+ try {
+ c1 = suggestedSites.get(DEFAULT_LIMIT);
+ assertEquals(DEFAULT_COUNT, c1.getCount());
+ } finally {
+ if (c1 != null) {
+ c1.close();
+ }
+ }
+
+ try {
+ assertTrue(changeLatch.await(5, TimeUnit.SECONDS));
+ } catch (InterruptedException ie) {
+ fail("No change notification after fetching distribution file");
+ }
+
+ // Target file should exist after distribution is deployed.
+ assertTrue(sitesFile.exists());
+ cr.unregisterContentObserver(observer);
+
+ Cursor c2 = null;
+ try {
+ c2 = suggestedSites.get(DEFAULT_LIMIT);
+
+ // The next query should contain the distribution contents.
+ assertEquals(DIST_COUNT + DEFAULT_COUNT, c2.getCount());
+
+ // The first items should be from the distribution
+ for (int i = 0; i < DIST_COUNT; i++) {
+ c2.moveToPosition(i);
+
+ String url = c2.getString(c2.getColumnIndexOrThrow(BrowserContract.SuggestedSites.URL));
+ assertEquals(DIST_PREFIX + "url" + i, url);
+
+ String title = c2.getString(c2.getColumnIndexOrThrow(BrowserContract.SuggestedSites.TITLE));
+ assertEquals(DIST_PREFIX + "title" + i, title);
+ }
+
+ // The remaining items should be the default ones
+ for (int i = 0; i < c2.getCount() - DIST_COUNT; i++) {
+ c2.moveToPosition(i + DIST_COUNT);
+
+ String url = c2.getString(c2.getColumnIndexOrThrow(BrowserContract.SuggestedSites.URL));
+ assertEquals("url" + i, url);
+
+ String title = c2.getString(c2.getColumnIndexOrThrow(BrowserContract.SuggestedSites.TITLE));
+ assertEquals("title" + i, title);
+ }
+ } finally {
+ if (c2 != null) {
+ c2.close();
+ }
+ }
+ }
+}
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/harness/BrowserInstrumentationTestRunner.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/harness/BrowserInstrumentationTestRunner.java
new file mode 100644
index 000000000..36c60d92e
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/harness/BrowserInstrumentationTestRunner.java
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.tests.browser.junit3.harness;
+
+import android.os.Bundle;
+import android.test.AndroidTestRunner;
+import android.test.InstrumentationTestRunner;
+import android.util.Log;
+
+/**
+ * A test runner that installs a special test listener.
+ * <p>
+ * In future, this listener will turn JUnit 3 test events into log messages in
+ * the format that Mochitest parsers understand.
+ */
+public class BrowserInstrumentationTestRunner extends InstrumentationTestRunner {
+ private static final String LOG_TAG = "BInstTestRunner";
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ Log.d(LOG_TAG, "onCreate");
+ super.onCreate(arguments);
+ }
+
+ @Override
+ protected AndroidTestRunner getAndroidTestRunner() {
+ Log.d(LOG_TAG, "getAndroidTestRunner");
+ AndroidTestRunner testRunner = super.getAndroidTestRunner();
+ testRunner.addTestListener(new BrowserTestListener());
+ return testRunner;
+ }
+}
diff --git a/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/harness/BrowserTestListener.java b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/harness/BrowserTestListener.java
new file mode 100644
index 000000000..026d5065f
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/harness/BrowserTestListener.java
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.tests.browser.junit3.harness;
+
+import android.util.Log;
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestListener;
+
+/**
+ * BrowserTestListener turns JUnit 3 test events into log messages in the format
+ * that Mochitest parsers understand.
+ * <p>
+ * The idea is that, on infrastructure, we'll be able to use the same test
+ * parsing code for Browser JUnit 3 tests as we do for Robocop tests.
+ * <p>
+ * In future, that is!
+ */
+public class BrowserTestListener implements TestListener {
+ public static final String LOG_TAG = "BTestListener";
+
+ @Override
+ public void startTest(Test test) {
+ Log.d(LOG_TAG, "startTest: " + test);
+ }
+
+ @Override
+ public void endTest(Test test) {
+ Log.d(LOG_TAG, "endTest: " + test);
+ }
+
+ @Override
+ public void addFailure(Test test, AssertionFailedError t) {
+ Log.d(LOG_TAG, "addFailure: " + test);
+ }
+
+ @Override
+ public void addError(Test test, Throwable t) {
+ Log.d(LOG_TAG, "addError: " + test);
+ }
+}