Merge fx-team to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 06 Nov 2015 15:09:39 -0800
changeset 271536 e2a910c048dc82fc3be53475f18e7f81f03e377b
parent 271526 16d93d2f06daad48b0dd70cf89ff53e40eaa83a6 (current diff)
parent 271535 bfb7ef931971963761ffc20eda51e073d5060651 (diff)
child 271542 f71204181af4a7aadd8862f154d9e32d82ce5cda
child 271566 2bd857903ba9c2f5779fcb32f24673131a405192
child 271653 6f8a9e60e1a53c5d468df04af2aac7ec5e5fb583
push id29647
push userkwierso@gmail.com
push dateFri, 06 Nov 2015 23:09:44 +0000
treeherdermozilla-central@e2a910c048dc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone45.0a1
first release with
nightly linux32
e2a910c048dc / 45.0a1 / 20151107030439 / files
nightly linux64
e2a910c048dc / 45.0a1 / 20151107030439 / files
nightly mac
e2a910c048dc / 45.0a1 / 20151107030439 / files
nightly win32
e2a910c048dc / 45.0a1 / 20151107030439 / files
nightly win64
e2a910c048dc / 45.0a1 / 20151107030439 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to central, a=merge
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1319,16 +1319,31 @@ BrowserGlue.prototype = {
 
       if (willPrompt) {
         Services.tm.mainThread.dispatch(function() {
           DefaultBrowserCheck.prompt(RecentWindow.getMostRecentBrowserWindow());
         }.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
       }
     }
 
+    if (this._mayNeedToWarnAboutTabGroups) {
+      let haveTabGroups = false;
+      let wins = Services.wm.getEnumerator("navigator:browser");
+      while (wins.hasMoreElements()) {
+        let win = wins.getNext();
+        if (win.TabView._tabBrowserHasHiddenTabs() && win.TabView.firstUseExperienced()) {
+          haveTabGroups = true;
+          break;
+        }
+      }
+      if (haveTabGroups) {
+        this._showTabGroupsDeprecationNotification();
+      }
+    }
+
 #ifdef E10S_TESTING_ONLY
     E10SUINotification.checkStatus();
 #endif
   },
 
 #ifdef MOZ_DEV_EDITION
   _createExtraDefaultProfile: function () {
     // If Developer Edition is the only installed Firefox version and no other
@@ -1357,16 +1372,37 @@ BrowserGlue.prototype = {
         }).then(null, e => {
           Cu.reportError("Could not empty profile 'default': " + e);
         });
       }
     }
   },
 #endif
 
+  _showTabGroupsDeprecationNotification() {
+    let brandShortName = gBrandBundle.GetStringFromName("brandShortName");
+    let text = gBrowserBundle.formatStringFromName("tabgroups.deprecationwarning.description",
+                                                   [brandShortName], 1);
+    let learnMore = gBrowserBundle.GetStringFromName("tabgroups.deprecationwarning.learnMore.label");
+    let learnMoreKey = gBrowserBundle.GetStringFromName("tabgroups.deprecationwarning.learnMore.accesskey");
+
+    let win = RecentWindow.getMostRecentBrowserWindow();
+    let notifyBox = win.document.getElementById("high-priority-global-notificationbox");
+    let button = {
+      label: learnMore,
+      accessKey: learnMoreKey,
+      callback: function(aNotificationBar, aButton) {
+        win.openUILinkIn("https://support.mozilla.org/kb/tab-groups-removal", "tab");
+      },
+    };
+
+    notifyBox.appendNotification(text, "tabgroups-removal-notification", null,
+                                 notifyBox.PRIORITY_WARNING_MEDIUM, [button]);
+  },
+
   _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
     // If user has already dismissed quit request, then do nothing
     if ((aCancelQuit instanceof Ci.nsISupportsPRBool) && aCancelQuit.data)
       return;
 
     // There are several cases where we won't show a dialog here:
     // 1. There is only 1 tab open in 1 window
     // 2. The session will be restored at startup, indicated by
@@ -1864,17 +1900,17 @@ BrowserGlue.prototype = {
     var notifyBox = win.gBrowser.getNotificationBox();
     var notification = notifyBox.appendNotification(text, title, null,
                                                     notifyBox.PRIORITY_CRITICAL_MEDIUM,
                                                     buttons);
     notification.persistence = -1; // Until user closes it
   },
 
   _migrateUI: function BG__migrateUI() {
-    const UI_VERSION = 32;
+    const UI_VERSION = 33;
     const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
     let currentUIVersion = 0;
     try {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } catch(ex) {}
     if (currentUIVersion >= UI_VERSION)
       return;
 
@@ -2209,16 +2245,21 @@ BrowserGlue.prototype = {
       xulStore.removeValue(BROWSER_DOCURL, "bookmarks-menu-button", "class");
       xulStore.removeValue(BROWSER_DOCURL, "home-button", "class");
     }
 
     if (currentUIVersion < 32) {
       this._notifyNotificationsUpgrade().catch(Cu.reportError);
     }
 
+    if (currentUIVersion < 33) {
+      // We'll do something once windows are open:
+      this._mayNeedToWarnAboutTabGroups = true;
+    }
+
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
 
   _hasExistingNotificationPermission: function BG__hasExistingNotificationPermission() {
     let enumerator = Services.perms.enumerator;
     while (enumerator.hasMoreElements()) {
       let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
@@ -2235,25 +2276,36 @@ BrowserGlue.prototype = {
     }
     yield this._firstWindowReady;
     function clickCallback(subject, topic, data) {
       if (topic != "alertclickcallback")
         return;
       let win = RecentWindow.getMostRecentBrowserWindow();
       win.openUILinkIn(data, "tab");
     }
-    let imageURL = "chrome://browser/skin/web-notifications-icon.svg";
+    // Show the application icon for XUL notifications. We assume system-level
+    // notifications will include their own icon.
+    let imageURL = this._hasSystemAlertsService() ? "" :
+                   "chrome://branding/content/about-logo.png";
     let title = gBrowserBundle.GetStringFromName("webNotifications.upgradeTitle");
     let text = gBrowserBundle.GetStringFromName("webNotifications.upgradeBody");
     let url = Services.urlFormatter.formatURLPref("browser.push.warning.migrationURL");
 
     AlertsService.showAlertNotification(imageURL, title, text,
                                         true, url, clickCallback);
   }),
 
+  _hasSystemAlertsService: function() {
+    try {
+      return !!Cc["@mozilla.org/system-alerts-service;1"].getService(
+        Ci.nsIAlertsService);
+    } catch (e) {}
+    return false;
+  },
+
   // ------------------------------
   // public nsIBrowserGlue members
   // ------------------------------
 
   sanitize: function BG_sanitize(aParentWindow) {
     this._sanitizer.sanitize(aParentWindow);
   },
 
--- a/browser/components/tabview/test/browser.ini
+++ b/browser/components/tabview/test/browser.ini
@@ -99,16 +99,17 @@ skip-if = e10s # Bug 1086190
 skip-if = e10s # Bug 1086190
 [browser_tabview_bug626455.js]
 skip-if = e10s # Bug 1086190
 [browser_tabview_bug626525.js]
 [browser_tabview_bug626791.js]
 skip-if = buildapp == 'mulet'
 [browser_tabview_bug627736.js]
 [browser_tabview_bug628061.js]
+skip-if = true
 [browser_tabview_bug628165.js]
 [browser_tabview_bug628270.js]
 [browser_tabview_bug628887.js]
 [browser_tabview_bug629189.js]
 [browser_tabview_bug629195.js]
 skip-if = os == 'linux'&&debug # bug 981703
 [browser_tabview_bug630102.js]
 [browser_tabview_bug630157.js]
--- a/browser/components/tabview/test/browser_tabview_bug626791.js
+++ b/browser/components/tabview/test/browser_tabview_bug626791.js
@@ -153,16 +153,18 @@ function test() {
     let onLoad = function (newWin) {
       win = newWin;
       removeToolbarButton();
     };
 
     let onShow = function () {
       cw = win.TabView.getContentWindow();
 
+      cw.document.querySelector('.banner').remove();
+
       let groupItem = cw.GroupItems.groupItems[0];
       groupItem.setSize(200, 200, true);
       groupItem.setUserSize();
 
       SimpleTest.waitForFocus(function () {
         assertToolbarButtonNotExists();
         test();
       }, cw);
--- a/browser/components/tabview/test/browser_tabview_bug656778.js
+++ b/browser/components/tabview/test/browser_tabview_bug656778.js
@@ -27,22 +27,22 @@ function test() {
   let assertPreferences = function (startupPage, firstRun, enabledOnce) {
     assertIntPref(TabView.PREF_STARTUP_PAGE, startupPage);
     assertBoolPref(TabView.PREF_FIRST_RUN, firstRun);
     assertBoolPref(TabView.PREF_RESTORE_ENABLED_ONCE, enabledOnce);
   };
 
   let assertNotificationBannerVisible = function (win) {
     let cw = win.TabView.getContentWindow();
-    is(cw.iQ(".banner").length, 1, "notification banner is visible");
+    is(cw.iQ(".banner").length, 2, "notification banner is visible");
   };
 
   let assertNotificationBannerNotVisible = function (win) {
     let cw = win.TabView.getContentWindow();
-    is(cw.iQ(".banner").length, 0, "notification banner is not visible");
+    is(cw.iQ(".banner").length, 1, "notification banner is not visible");
   };
 
   let next = function () {
     if (tests.length == 0) {
       waitForFocus(finish);
       return;
     }
 
--- a/browser/components/tabview/ui.js
+++ b/browser/components/tabview/ui.js
@@ -506,27 +506,31 @@ var UI = {
       dispatchEvent(event);
 
       // Flush pending updates
       GroupItems.flushAppTabUpdates();
 
       TabItems.resumePainting();
     }
 
+    this.notifyDeprecation();
+
     if (gTabView.firstUseExperienced)
       gTabView.enableSessionRestore();
   },
 
   // ----------
   // Function: hideTabView
   // Hides TabView and shows the main browser UI.
   hideTabView: function UI_hideTabView() {
     if (!this.isTabViewVisible() || this._isChangingVisibility)
       return;
 
+    iQ(".banner").remove();
+
     // another tab might be select if user decides to stay on a page when
     // a onclose confirmation prompts.
     GroupItems.removeHiddenGroups();
 
     // We need to set this after removing the hidden groups because doing so
     // might show prompts which will cause us to be called again, and we'd get
     // stuck if we prevent re-entrancy before doing that.
     this._isChangingVisibility = true;
@@ -1536,13 +1540,43 @@ var UI = {
 
     let onFadeIn = function () {
       setTimeout(function () {
         banner.animate({opacity: 0}, {duration: 1500, complete: onFadeOut});
       }, 5000);
     };
 
     banner.animate({opacity: 0.7}, {duration: 1500, complete: onFadeIn});
-  }
+  },
+
+  // Function: notifyDeprecation
+  // Notify the user that tab groups will be deprecated soon.
+  notifyDeprecation() {
+    let brandBundle = gWindow.document.getElementById("bundle_brand");
+    let brandShortName = brandBundle.getString("brandShortName");
+    let browserBundle = gWindow.document.getElementById("bundle_browser");
+    let notificationText = browserBundle.getFormattedString(
+      "tabgroups.deprecationwarning.description", [brandShortName]);
+
+    let learnMoreText = browserBundle.getString("tabgroups.deprecationwarning.learnMore.label");
+
+    let onButtonClick = () => {
+      this.hideTabView();
+      gWindow.openUILinkIn("https://support.mozilla.org/kb/tab-groups-removal", "tab");
+    };
+
+    let button = iQ("<button>")
+      .text(learnMoreText)
+      .css('-moz-margin-start', '10px')
+      .one('click', onButtonClick);
+
+    let banner = iQ("<div>")
+      .text(notificationText)
+      .addClass("banner")
+      .append(button)
+      .appendTo("body");
+
+    banner.animate({opacity: 0.7}, {duration: 1500});
+  },
 };
 
 // ----------
 UI.init();
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -792,8 +792,14 @@ usercontext.personal.label = Personal
 usercontext.work.label = Work
 usercontext.shopping.label = Shopping
 usercontext.banking.label = Banking
 
 muteTab.label = Mute Tab
 muteTab.accesskey = M
 unmuteTab.label = Unmute Tab
 unmuteTab.accesskey = M
+
+# LOCALIZATION NOTE (tabgroups.deprecationwarning.description):
+# %S is brandShortName
+tabgroups.deprecationwarning.description         = Heads up! Tab Groups will be removed from %S soon.
+tabgroups.deprecationwarning.learnMore.label     = Learn More
+tabgroups.deprecationwarning.learnMore.accesskey = L
--- a/browser/themes/linux/tabview/tabview.css
+++ b/browser/themes/linux/tabview/tabview.css
@@ -559,17 +559,17 @@ html[dir=rtl] .iq-resizable-se {
 #searchshade{
   background-color: rgba(0,0,0,.42);
   width: 100%;
   height: 100%;
 }
 
 #search{
   width: 100%;
-  height: 100%;
+  height: calc(100% - 1.7em - 20px); /* leave room for banner */
 }
 
 #searchbox{
   width: 270px;
   max-width: -moz-available;
   -moz-margin-start: 20px;
   height: 30px;
   box-shadow: 0px 1px 0px rgba(255,255,255,.5), 0px -1px 0px rgba(0,0,0,1), 0px 0px 9px rgba(0,0,0,.8);
--- a/browser/themes/osx/tabview/tabview.css
+++ b/browser/themes/osx/tabview/tabview.css
@@ -547,17 +547,17 @@ html[dir=rtl] .iq-resizable-se {
 }
 
 #searchshade:-moz-window-inactive {
   background: linear-gradient(rgba(237,237,237,0.42), rgba(216,216,216,0.42));
 }
 
 #search{
   width: 100%;
-  height: 100%;
+  height: calc(100% - 1.7em - 20px); /* leave room for banner */
 }
 
 #searchbox {
   width: 270px;
   max-width: -moz-available;
   -moz-margin-start: 20px;
   height: 30px;
   box-shadow: 0px 1px 0px rgba(255,255,255,.5), 0px -1px 0px rgba(0,0,0,1), 0px 0px 13px rgba(0,0,0,.8);
--- a/browser/themes/windows/tabview/tabview.css
+++ b/browser/themes/windows/tabview/tabview.css
@@ -564,17 +564,17 @@ html[dir=rtl] .iq-resizable-se {
 #searchshade{
   background-color: rgba(0,0,0,.42);
   width: 100%;
   height: 100%;
 }
 
 #search{
   width: 100%;
-  height: 100%;
+  height: calc(100% - 1.7em - 20px); /* leave room for banner */
 }
 
 #searchbox{
   width: 270px;
   max-width: -moz-available;
   -moz-margin-start: 20px;
   height: 30px;
   box-shadow: 0px 1px 0px rgba(255,255,255,.5), 0px -1px 0px rgba(0,0,0,1), 0px 0px 9px rgba(0,0,0,.8);
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sun Oct 18 17:00:46 PDT 2015
+#Mon Nov 02 13:44:39 GMT 2015
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-all.zip
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -548,16 +548,22 @@ public class BrowserApp extends GeckoApp
         if (AndroidGamepadManager.handleKeyEvent(event)) {
             return true;
         }
         return super.onKeyUp(keyCode, event);
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
+        if (!isSupportedSDK()) {
+            // This build does not support the Android version of the device; Exit early.
+            super.onCreate(savedInstanceState);
+            return;
+        }
+
         final Intent intent = getIntent();
 
         // Note that we're calling GeckoProfile.get *before GeckoApp.onCreate*.
         // This means we're reliant on the logic in GeckoProfile to correctly
         // look up our launch intent (via BrowserApp's Activity-ness) and pull
         // out the arguments. Be careful if you change that!
         final GeckoProfile p = GeckoProfile.get(this);
 
@@ -1212,16 +1218,22 @@ public class BrowserApp extends GeckoApp
 
     @Override
     public void setAccessibilityEnabled(boolean enabled) {
         mDynamicToolbar.setAccessibilityEnabled(enabled);
     }
 
     @Override
     public void onDestroy() {
+        if (!isSupportedSDK()) {
+            // This build does not support the Android version of the device; Exit early.
+            super.onDestroy();
+            return;
+        }
+
         mDynamicToolbar.destroy();
 
         if (mBrowserToolbar != null)
             mBrowserToolbar.onDestroy();
 
         if (mFindInPageBar != null) {
             mFindInPageBar.onDestroy();
             mFindInPageBar = null;
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -59,16 +59,17 @@ import android.graphics.BitmapFactory;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.location.Location;
 import android.location.LocationListener;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.StrictMode;
 import android.provider.ContactsContract;
 import android.provider.MediaStore.Images.Media;
@@ -1215,16 +1216,24 @@ public abstract class GeckoApp
     public void onCreate(Bundle savedInstanceState) {
         GeckoAppShell.ensureCrashHandling();
 
         // Enable Android Strict Mode for developers' local builds (the "default" channel).
         if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
             enableStrictMode();
         }
 
+        if (!isSupportedSDK()) {
+            // This build does not support the Android version of the device: Show an error and finish the app.
+            super.onCreate(savedInstanceState);
+            showSDKVersionError();
+            finish();
+            return;
+        }
+
         // The clock starts...now. Better hurry!
         mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
         mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY");
 
         final SafeIntent intent = new SafeIntent(getIntent());
         final String action = intent.getAction();
         final String args = intent.getStringExtra("args");
 
@@ -2128,16 +2137,23 @@ public abstract class GeckoApp
             StrictMode.setThreadPolicy(savedPolicy);
         }
 
         super.onRestart();
     }
 
     @Override
     public void onDestroy() {
+        if (!isSupportedSDK()) {
+            // This build does not support the Android version of the device:
+            // We did not initialize anything, so skip cleaning up.
+            super.onDestroy();
+            return;
+        }
+
         EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
             "Gecko:Ready",
             "Gecko:DelayedStartup",
             "Gecko:Exited",
             "Accessibility:Event",
             "NativeApp:IsDebuggable");
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener)this,
@@ -2230,16 +2246,26 @@ public abstract class GeckoApp
                   .putExtra(Intent.EXTRA_INTENT, mRestartIntent);
             startService(intent);
         } else {
             // Exiting, so kill our own process.
             Process.killProcess(Process.myPid());
         }
     }
 
+    protected boolean isSupportedSDK() {
+        return Build.VERSION.SDK_INT >= Versions.MIN_SDK_VERSION &&
+               Build.VERSION.SDK_INT <= Versions.MAX_SDK_VERSION;
+    }
+
+    public void showSDKVersionError() {
+        final String message = getString(R.string.unsupported_sdk_version, Build.CPU_ABI, Build.VERSION.SDK_INT);
+        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+    }
+
     // Get a temporary directory, may return null
     public static File getTempDirectory() {
         File dir = GeckoApplication.get().getExternalFilesDir("temp");
         return dir;
     }
 
     // Delete any files in our temporary directory
     public static void deleteTempFiles() {
--- a/mobile/android/base/build.gradle
+++ b/mobile/android/base/build.gradle
@@ -4,16 +4,18 @@ apply plugin: 'com.android.library'
 
 android {
     compileSdkVersion 23
     buildToolsVersion "23.0.1"
 
     defaultConfig {
         targetSdkVersion 22
         minSdkVersion 9
+        // Used by Robolectric based tests; see TestRunner.
+        buildConfigField 'String', 'BUILD_DIR', "\"${project.buildDir}\""
     }
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_7
         targetCompatibility JavaVersion.VERSION_1_7
     }
 
     lintOptions {
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -743,8 +743,13 @@ just addresses the organization to follo
 <!-- LOCALIZATION NOTE (bookmarks_addons): link title for https://addons.mozilla.org/en-US/mobile -->
 <!ENTITY bookmarks_addons "Firefox: Customize with add-ons">
 <!-- LOCALIZATION NOTE (bookmarks_support): link title for https://support.mozilla.org/ -->
 <!ENTITY bookmarks_support "Firefox: Support">
 <!-- LOCALIZATION NOTE (bookmarks_restricted_support): link title for https://support.mozilla.org/kb/controlledaccess -->
 <!ENTITY bookmarks_restricted_support2 "Firefox Help and Support for restricted profiles on Android tablets">
 <!-- LOCALIZATION NOTE (bookmarks_restricted_webmaker):link title for https://webmaker.org -->
 <!ENTITY bookmarks_restricted_webmaker "Learn the Web: Mozilla Webmaker">
+
+<!-- LOCALIZATION NOTE (unsupported_sdk_version): The user installed a build of this app that does not support
+     the Android version of this device. the formatS1 is replaced by the CPU ABI (e.g., ARMv7); the formatS2 is
+     replaced by the Android OS version (e.g., 14)-->
+<!ENTITY unsupported_sdk_version "Sorry! This &brandShortName; won\'t work on this device (&formatS1;, &formatS2;). Please download the correct version.">
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -590,9 +590,11 @@
 
   <string name="remote_tabs_last_synced">&remote_tabs_last_synced;</string>
 
   <string name="intent_uri_cannot_open">&intent_uri_cannot_open;</string>
   <string name="intent_uri_private_browsing_prompt">&intent_uri_private_browsing_prompt;</string>
   <string name="intent_uri_private_browsing_multiple_match_title">&intent_uri_private_browsing_multiple_match_title;</string>
 
   <string name="devtools_auth_scan_header">&devtools_auth_scan_header;</string>
+
+  <string name="unsupported_sdk_version">&unsupported_sdk_version;</string>
 </resources>
--- a/mobile/android/gradle/base/build.gradle
+++ b/mobile/android/gradle/base/build.gradle
@@ -2,16 +2,18 @@ apply plugin: 'com.android.library'
 
 android {
     compileSdkVersion 23
     buildToolsVersion "23.0.1"
 
     defaultConfig {
         targetSdkVersion 22
         minSdkVersion 9
+        // Used by Robolectric based tests; see TestRunner.
+        buildConfigField 'String', 'BUILD_DIR', "\"${project.buildDir}\""
     }
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_7
         targetCompatibility JavaVersion.VERSION_1_7
     }
 
     lintOptions {
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -170,16 +170,18 @@ class ArtifactSubCommand(SubCommand):
 
     def __call__(self, func):
         after = SubCommand.__call__(self, func)
         args = [
             CommandArgument('--tree', metavar='TREE', type=str,
                 help='Firefox tree.'),
             CommandArgument('--job', metavar='JOB', choices=['android-api-11', 'android-x86'],
                 help='Build job.'),
+            CommandArgument('--verbose', '-v', action='store_true',
+                help='Print verbose output.'),
         ]
         for arg in args:
             after = arg(after)
         return after
 
 
 @CommandProvider
 class PackageFrontend(MachCommandBase):
@@ -202,19 +204,20 @@ class PackageFrontend(MachCommandBase):
         to download, cache, and install binary artifacts from Mozilla automation,
         replacing whatever may be in your object directory.  Use |mach artifact last|
         to see what binary artifacts were last used.
 
         Never build libxul again!
         '''
         pass
 
+    def _set_log_level(self, verbose):
+        self.log_manager.terminal_handler.setLevel(logging.INFO if not verbose else logging.DEBUG)
+
     def _make_artifacts(self, tree=None, job=None):
-        self.log_manager.terminal_handler.setLevel(logging.INFO)
-
         self._activate_virtualenv()
         self.virtualenv_manager.install_pip_package('pylru==1.0.9')
         self.virtualenv_manager.install_pip_package('taskcluster==0.0.16')
         self.virtualenv_manager.install_pip_package('mozregression==1.0.2')
 
         state_dir = self._mach_context.state_dir
         cache_dir = os.path.join(state_dir, 'package-frontend')
 
@@ -237,40 +240,44 @@ class PackageFrontend(MachCommandBase):
 
     @ArtifactSubCommand('artifact', 'install',
         'Install a good pre-built artifact.')
     @CommandArgument('source', metavar='SRC', nargs='?', type=str,
         help='Where to fetch and install artifacts from.  Can be omitted, in '
             'which case the current hg repository is inspected; an hg revision; '
             'a remote URL; or a local file.',
         default=None)
-    def artifact_install(self, source=None, tree=None, job=None):
+    def artifact_install(self, source=None, tree=None, job=None, verbose=False):
+        self._set_log_level(verbose)
         tree, job = self._compute_defaults(tree, job)
         artifacts = self._make_artifacts(tree=tree, job=job)
         return artifacts.install_from(source, self.distdir)
 
     @ArtifactSubCommand('artifact', 'last',
         'Print the last pre-built artifact installed.')
-    def artifact_print_last(self, tree=None, job=None):
+    def artifact_print_last(self, tree=None, job=None, verbose=False):
+        self._set_log_level(verbose)
         tree, job = self._compute_defaults(tree, job)
         artifacts = self._make_artifacts(tree=tree, job=job)
         artifacts.print_last()
         return 0
 
     @ArtifactSubCommand('artifact', 'print-cache',
         'Print local artifact cache for debugging.')
-    def artifact_print_cache(self, tree=None, job=None):
+    def artifact_print_cache(self, tree=None, job=None, verbose=False):
+        self._set_log_level(verbose)
         tree, job = self._compute_defaults(tree, job)
         artifacts = self._make_artifacts(tree=tree, job=job)
         artifacts.print_cache()
         return 0
 
     @ArtifactSubCommand('artifact', 'clear-cache',
         'Delete local artifacts and reset local artifact cache.')
-    def artifact_clear_cache(self, tree=None, job=None):
+    def artifact_clear_cache(self, tree=None, job=None, verbose=False):
+        self._set_log_level(verbose)
         tree, job = self._compute_defaults(tree, job)
         artifacts = self._make_artifacts(tree=tree, job=job)
         artifacts.clear_cache()
         return 0
 
 @CommandProvider
 class AndroidEmulatorCommands(MachCommandBase):
     """
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestAccountAuthenticatorStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestAccountAuthenticatorStage.java
@@ -1,43 +1,42 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.net.URISyntaxException;
-
+import ch.boye.httpclientandroidlib.HttpResponse;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockServer;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.setup.auth.AuthenticateAccountStage;
 import org.mozilla.gecko.sync.setup.auth.AuthenticateAccountStage.AuthenticateAccountStageDelegate;
-import org.robolectric.RobolectricGradleTestRunner;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
-import ch.boye.httpclientandroidlib.HttpResponse;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.URISyntaxException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * Tests the authentication request stage of manual Account setup.
  * @author liuche
  *
  */
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestAccountAuthenticatorStage {
   private static final int TEST_PORT      = HTTPServerTestHelper.getTestPort();
   private static final String TEST_SERVER = "http://localhost:" + TEST_PORT;
 
   private static final String USERNAME  = "john-hashed";
   private static final String PASSWORD  = "password";
 
   private MockServer authServer;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestBackoff.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestBackoff.java
@@ -1,34 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.ProtocolVersion;
+import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
+import ch.boye.httpclientandroidlib.message.BasicStatusLine;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
 import org.mozilla.gecko.background.testhelpers.MockGlobalSession;
 import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.ProtocolVersion;
-import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
-import ch.boye.httpclientandroidlib.message.BasicStatusLine;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
-import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestBackoff {
   private final String TEST_USERNAME            = "johndoe";
   private final String TEST_PASSWORD            = "password";
   private final String TEST_SYNC_KEY            = "abcdeabcdeabcdeabcdeabcdea";
   private final long   TEST_BACKOFF_IN_SECONDS  = 1201;
 
   /**
    * Test that interpretHTTPFailure calls requestBackoff if
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestBrowserIDAuthHeaderProvider.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestBrowserIDAuthHeaderProvider.java
@@ -1,23 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
-
+import ch.boye.httpclientandroidlib.Header;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.net.BrowserIDAuthHeaderProvider;
 
-import ch.boye.httpclientandroidlib.Header;
-import org.robolectric.RobolectricGradleTestRunner;
+import static org.junit.Assert.assertEquals;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestBrowserIDAuthHeaderProvider {
   @Test
   public void testHeader() {
     Header header = new BrowserIDAuthHeaderProvider("assertion").getAuthHeader(null, null, null);
 
     assertEquals("authorization", header.getName().toLowerCase());
     assertEquals("BrowserID assertion", header.getValue());
   }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestClientsEngineStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestClientsEngineStage.java
@@ -1,42 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.math.BigDecimal;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-
+import ch.boye.httpclientandroidlib.HttpStatus;
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.json.simple.parser.ParseException;
 import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
 import org.mozilla.android.sync.test.helpers.MockServer;
 import org.mozilla.android.sync.test.helpers.MockSyncClientsEngineStage;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.testhelpers.CommandHelpers;
 import org.mozilla.gecko.background.testhelpers.MockClientsDataDelegate;
 import org.mozilla.gecko.background.testhelpers.MockClientsDatabaseAccessor;
 import org.mozilla.gecko.background.testhelpers.MockGlobalSession;
 import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.CollectionKeys;
 import org.mozilla.gecko.sync.CommandProcessor.Command;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SyncConfiguration;
@@ -47,23 +36,33 @@ import org.mozilla.gecko.sync.crypto.Key
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-import org.robolectric.RobolectricGradleTestRunner;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
-import ch.boye.httpclientandroidlib.HttpStatus;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.math.BigDecimal;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(TestRunner.class)
 public class TestClientsEngineStage extends MockSyncClientsEngineStage {
   public final static String LOG_TAG = "TestClientsEngSta";
 
   public TestClientsEngineStage() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException, URISyntaxException {
     super();
     session = initializeSession();
   }
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestCredentialsEndToEnd.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestCredentialsEndToEnd.java
@@ -1,34 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
+import ch.boye.httpclientandroidlib.Header;
 import org.json.simple.parser.ParseException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 
-import ch.boye.httpclientandroidlib.Header;
-import org.robolectric.RobolectricGradleTestRunner;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import static org.junit.Assert.assertEquals;
 
 /**
  * Test the transfer of a UTF-8 string from desktop, and ensure that it results in the
  * correct hashed Basic Auth header.
  */
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestCredentialsEndToEnd {
 
   public static final String REAL_PASSWORD         = "pïgéons1";
   public static final String USERNAME              = "utvm3mk6hnngiir2sp4jsxf2uvoycrv6";
   public static final String DESKTOP_PASSWORD_JSON = "{\"password\":\"pïgéons1\"}";
   public static final String BTOA_PASSWORD         = "cMOvZ8Opb25zMQ==";
   public static final int    DESKTOP_ASSERTED_SIZE = 10;
   public static final String DESKTOP_BASIC_AUTH    = "Basic dXR2bTNtazZobm5naWlyMnNwNGpzeGYydXZveWNydjY6cMOvZ8Opb25zMQ==";
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestGlobalSession.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestGlobalSession.java
@@ -1,42 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.ProtocolVersion;
+import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
+import ch.boye.httpclientandroidlib.message.BasicStatusLine;
 import junit.framework.AssertionFailedError;
-
 import org.json.simple.parser.ParseException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
 import org.mozilla.android.sync.test.helpers.MockResourceDelegate;
 import org.mozilla.android.sync.test.helpers.MockServer;
 import org.mozilla.gecko.background.testhelpers.MockAbstractNonRepositorySyncStage;
 import org.mozilla.gecko.background.testhelpers.MockGlobalSession;
 import org.mozilla.gecko.background.testhelpers.MockPrefsGlobalSession;
 import org.mozilla.gecko.background.testhelpers.MockServerSyncStage;
 import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.EngineSettings;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.MetaGlobal;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConfigurationException;
@@ -46,26 +36,34 @@ import org.mozilla.gecko.sync.crypto.Key
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
 import org.mozilla.gecko.sync.stage.AndroidBrowserBookmarksServerSyncStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 import org.mozilla.gecko.sync.stage.NoSuchStageException;
-import org.robolectric.RobolectricGradleTestRunner;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.ProtocolVersion;
-import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
-import ch.boye.httpclientandroidlib.message.BasicStatusLine;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(TestRunner.class)
 public class TestGlobalSession {
   private int          TEST_PORT                = HTTPServerTestHelper.getTestPort();
   private final String TEST_CLUSTER_URL         = "http://localhost:" + TEST_PORT;
   private final String TEST_USERNAME            = "johndoe";
   private final String TEST_PASSWORD            = "password";
   private final String TEST_SYNC_KEY            = "abcdeabcdeabcdeabcdeabcdea";
   private final long   TEST_BACKOFF_IN_SECONDS  = 2401;
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestHeaderParsing.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestHeaderParsing.java
@@ -1,21 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.Utils;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+
+@RunWith(TestRunner.class)
 public class TestHeaderParsing {
 
   @SuppressWarnings("static-method")
   @Test
   public void testDecimalSecondsToMilliseconds() {
     assertEquals(Utils.decimalSecondsToMilliseconds(""),         -1);
     assertEquals(Utils.decimalSecondsToMilliseconds("1234.1.1"), -1);
     assertEquals(Utils.decimalSecondsToMilliseconds("1234"),     1234000);
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestLineByLineHandling.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestLineByLineHandling.java
@@ -1,37 +1,38 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
+import org.mozilla.android.sync.test.helpers.MockServer;
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.net.AuthHeaderProvider;
+import org.mozilla.gecko.sync.net.BaseResource;
+import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest;
+import org.mozilla.gecko.sync.net.SyncStorageCollectionRequestDelegate;
+import org.mozilla.gecko.sync.net.SyncStorageResponse;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
 
 import java.io.IOException;
 import java.io.PrintStream;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
-import org.mozilla.android.sync.test.helpers.MockServer;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest;
-import org.mozilla.gecko.sync.net.SyncStorageCollectionRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.robolectric.RobolectricGradleTestRunner;
-import org.simpleframework.http.Request;
-import org.simpleframework.http.Response;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestLineByLineHandling {
   private static final int     TEST_PORT   = HTTPServerTestHelper.getTestPort();
   private static final String  TEST_SERVER = "http://localhost:" + TEST_PORT;
   private static final String  LOG_TAG     = "TestLineByLineHandling";
   static String                STORAGE_URL = TEST_SERVER + "/1.1/c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd/storage/lines";
   private HTTPServerTestHelper data        = new HTTPServerTestHelper();
 
   public ArrayList<String>     lines       = new ArrayList<String>();
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestMetaGlobal.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestMetaGlobal.java
@@ -1,42 +1,42 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 import org.json.simple.parser.ParseException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockServer;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.MetaGlobal;
 import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.robolectric.RobolectricGradleTestRunner;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestMetaGlobal {
   public static Object monitor = new Object();
 
   private static final int    TEST_PORT    = HTTPServerTestHelper.getTestPort();
   private static final String TEST_SERVER  = "http://localhost:" + TEST_PORT;
   private static final String TEST_SYNC_ID = "foobar";
 
   public static final String USER_PASS = "c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd:password";
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestResource.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestResource.java
@@ -1,35 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
+import org.mozilla.android.sync.test.helpers.MockResourceDelegate;
+import org.mozilla.android.sync.test.helpers.MockServer;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.background.testhelpers.WaitHelper;
+import org.mozilla.gecko.sync.net.BaseResource;
+import org.mozilla.gecko.sync.net.HttpResponseObserver;
+
+import java.net.URISyntaxException;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import java.net.URISyntaxException;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
-import org.mozilla.android.sync.test.helpers.MockResourceDelegate;
-import org.mozilla.android.sync.test.helpers.MockServer;
-import org.mozilla.gecko.background.testhelpers.WaitHelper;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.HttpResponseObserver;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestResource {
   private static final int    TEST_PORT   = HTTPServerTestHelper.getTestPort();
   private static final String TEST_SERVER = "http://localhost:" + TEST_PORT;
 
   private HTTPServerTestHelper data     = new HTTPServerTestHelper();
 
   @SuppressWarnings("static-method")
   @Before
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestRetryAfter.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestRetryAfter.java
@@ -1,27 +1,26 @@
 package org.mozilla.android.sync.net.test;
 
-import java.util.Date;
-
-import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.net.SyncResponse;
-
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.ProtocolVersion;
 import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
 import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
 import ch.boye.httpclientandroidlib.message.BasicStatusLine;
-import org.robolectric.RobolectricGradleTestRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.net.SyncResponse;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.util.Date;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestRetryAfter {
   private int TEST_SECONDS = 120;
 
   @Test
   public void testRetryAfterParsesSeconds() {
     final HttpResponse response = new BasicHttpResponse(
         new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol"));
     response.addHeader("Retry-After", Long.toString(TEST_SECONDS)); // Retry-After given in seconds.
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestServer11Repository.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestServer11Repository.java
@@ -1,24 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
-import java.net.URI;
-import java.net.URISyntaxException;
-
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.InfoCollections;
 import org.mozilla.gecko.sync.repositories.Server11Repository;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.net.URI;
+import java.net.URISyntaxException;
+
+@RunWith(TestRunner.class)
 public class TestServer11Repository {
 
   private static final String COLLECTION = "bookmarks";
   private static final String COLLECTION_URL = "http://foo.com/1.1/n6ec3u5bee3tixzp2asys7bs6fve4jfw/storage";
 
   protected final InfoCollections infoCollections = new InfoCollections();
 
   public static void assertQueryEquals(String expected, URI u) {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestSyncStorageRequest.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestSyncStorageRequest.java
@@ -1,37 +1,37 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-
 import org.json.simple.JSONObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.helpers.BaseTestStorageRequestDelegate;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockServer;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.robolectric.RobolectricGradleTestRunner;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(TestRunner.class)
 public class TestSyncStorageRequest {
   private static final int    TEST_PORT   = HTTPServerTestHelper.getTestPort();
   private static final String TEST_SERVER = "http://localhost:" + TEST_PORT;
 
   private static final String LOCAL_META_URL  = TEST_SERVER + "/1.1/c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd/storage/meta/global";
   private static final String LOCAL_BAD_REQUEST_URL  = TEST_SERVER + "/1.1/c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd/storage/bad";
 
   private static final String EXPECTED_ERROR_CODE = "12";
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/SynchronizerHelpers.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/SynchronizerHelpers.java
@@ -1,30 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
-
+import android.content.Context;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
 import org.mozilla.gecko.sync.repositories.FetchFailedException;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.StoreFailedException;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
-import android.content.Context;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
 
 public class SynchronizerHelpers {
   public static final String FAIL_SENTINEL = "Fail";
 
   /**
    * Store one at a time, failing if the guid contains FAIL_SENTINEL.
    */
   public static class FailFetchWBORepository extends WBORepository {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestCollectionKeys.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestCollectionKeys.java
@@ -1,38 +1,38 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
+import org.json.simple.JSONArray;
+import org.json.simple.parser.ParseException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.apache.commons.codec.binary.Base64;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.CollectionKeys;
+import org.mozilla.gecko.sync.CryptoRecord;
+import org.mozilla.gecko.sync.NoCollectionKeysSetException;
+import org.mozilla.gecko.sync.NonObjectJSONException;
+import org.mozilla.gecko.sync.crypto.CryptoException;
+import org.mozilla.gecko.sync.crypto.KeyBundle;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Set;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Set;
-
-import org.json.simple.JSONArray;
-import org.json.simple.parser.ParseException;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.CollectionKeys;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.NoCollectionKeysSetException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestCollectionKeys {
 
   @Test
   public void testDefaultKeys() throws CryptoException, NoCollectionKeysSetException {
     CollectionKeys ck = new CollectionKeys();
     try {
       ck.defaultKeyBundle();
       fail("defaultKeys should throw.");
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestCommandProcessor.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestCommandProcessor.java
@@ -1,34 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
+import org.json.simple.parser.ParseException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.CommandProcessor;
+import org.mozilla.gecko.sync.CommandRunner;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.sync.GlobalSession;
+import org.mozilla.gecko.sync.NonObjectJSONException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.json.simple.parser.ParseException;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.CommandProcessor;
-import org.mozilla.gecko.sync.CommandRunner;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.GlobalSession;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestCommandProcessor extends CommandProcessor {
 
   public static final String commandType = "displayURI";
   public static final String commandWithNoArgs = "{\"command\":\"displayURI\"}";
   public static final String commandWithNoType = "{\"args\":[\"https://bugzilla.mozilla.org/show_bug.cgi?id=731341\",\"PKsljsuqYbGg\"]}";
   public static final String wellFormedCommand = "{\"args\":[\"https://bugzilla.mozilla.org/show_bug.cgi?id=731341\",\"PKsljsuqYbGg\"],\"command\":\"displayURI\"}";
   public static final String wellFormedCommandWithNullArgs = "{\"args\":[\"https://bugzilla.mozilla.org/show_bug.cgi?id=731341\",null,\"PKsljsuqYbGg\",null],\"command\":\"displayURI\"}";
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestCryptoRecord.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestCryptoRecord.java
@@ -1,40 +1,40 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
-
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.json.simple.parser.ParseException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.apache.commons.codec.binary.Base64;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
 import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
-import org.robolectric.RobolectricGradleTestRunner;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestCryptoRecord {
   String base64EncryptionKey = "9K/wLdXdw+nrTtXo4ZpECyHFNr4d7aYHqeg3KW9+m6Q=";
   String base64HmacKey = "MMntEfutgLTc8FlTLQFms8/xMPmCldqPlq/QQXEjx70=";
 
   @Test
   public void testBaseCryptoRecordEncrypt() throws IOException, ParseException, NonObjectJSONException, CryptoException {
     ExtendedJSONObject clearPayload = ExtendedJSONObject.parseJSONObject("{\"id\":\"5qRsgXWRJZXr\",\"title\":\"Index of file:///Users/jason/Library/Application Support/Firefox/Profiles/ksgd7wpk.LocalSyncServer/weave/logs/\",\"histUri\":\"file:///Users/jason/Library/Application%20Support/Firefox/Profiles/ksgd7wpk.LocalSyncServer/weave/logs/\",\"visits\":[{\"type\":1,\"date\":1319149012372425}]}");
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestJPakeSetup.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestJPakeSetup.java
@@ -1,42 +1,42 @@
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
 import org.json.simple.parser.ParseException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.apache.commons.codec.binary.Base64;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.jpake.Gx3OrGx4IsZeroOrOneException;
 import org.mozilla.gecko.sync.jpake.IncorrectZkpException;
 import org.mozilla.gecko.sync.jpake.JPakeClient;
 import org.mozilla.gecko.sync.jpake.JPakeCrypto;
 import org.mozilla.gecko.sync.jpake.JPakeNumGenerator;
 import org.mozilla.gecko.sync.jpake.JPakeNumGeneratorRandom;
 import org.mozilla.gecko.sync.jpake.JPakeParty;
 import org.mozilla.gecko.sync.jpake.stage.ComputeKeyVerificationStage;
 import org.mozilla.gecko.sync.jpake.stage.VerifyPairingStage;
 import org.mozilla.gecko.sync.setup.Constants;
-import org.robolectric.RobolectricGradleTestRunner;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(TestRunner.class)
 public class TestJPakeSetup {
   // Note: will throw NullPointerException if aborts. Only use stateless public
   // methods.
 
   @Test
   public void testGx3OrGx4ZeroOrOneThrowsException()
       throws UnsupportedEncodingException
   {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestRecord.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestRecord.java
@@ -1,39 +1,39 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.json.simple.parser.ParseException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.db.Tab;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
 import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 import org.mozilla.gecko.sync.repositories.domain.RecordParseException;
 import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
-import org.robolectric.RobolectricGradleTestRunner;
+
+import java.io.IOException;
+import java.util.ArrayList;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(TestRunner.class)
 public class TestRecord {
 
   @SuppressWarnings("static-method")
   @Test
   public void testQueryRecord() throws NonObjectJSONException, IOException, ParseException {
     final String expectedGUID = "Bl3n3gpKag3s";
     final String testRecord =
         "{\"id\":\"" + expectedGUID + "\"," +
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestRecordsChannel.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestRecordsChannel.java
@@ -1,38 +1,38 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.SynchronizerHelpers.FailFetchWBORepository;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionCreationDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionFinishDelegate;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.synchronizer.RecordsChannel;
 import org.mozilla.gecko.sync.synchronizer.RecordsChannelDelegate;
-import org.robolectric.RobolectricGradleTestRunner;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestRecordsChannel {
 
   protected WBORepository remote;
   protected WBORepository local;
 
   protected RepositorySession source;
   protected RepositorySession sink;
   protected RecordsChannelDelegate rcDelegate;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestResetCommands.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestResetCommands.java
@@ -1,50 +1,49 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.util.HashMap;
-
+import android.content.SharedPreferences;
 import org.json.simple.parser.ParseException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.DefaultGlobalSessionCallback;
 import org.mozilla.gecko.background.testhelpers.MockPrefsGlobalSession;
 import org.mozilla.gecko.background.testhelpers.MockServerSyncStage;
 import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.CommandProcessor;
 import org.mozilla.gecko.sync.EngineSettings;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.MetaGlobalException;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConfigurationException;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 
-import android.content.SharedPreferences;
-import org.robolectric.RobolectricGradleTestRunner;
+import java.io.IOException;
+import java.util.HashMap;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Test that reset commands properly invoke the reset methods on the correct stage.
  */
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestResetCommands {
   private static final String TEST_USERNAME    = "johndoe";
   private static final String TEST_PASSWORD    = "password";
   private static final String TEST_SYNC_KEY    = "abcdeabcdeabcdeabcdeabcdea";
 
   public static void performNotify() {
     WaitHelper.getTestWaiter().performNotify();
   }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestServer11RepositorySession.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestServer11RepositorySession.java
@@ -1,29 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
+import ch.boye.httpclientandroidlib.HttpEntity;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.SynchronizerHelpers.TrackingWBORepository;
 import org.mozilla.android.sync.test.helpers.BaseTestStorageRequestDelegate;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockServer;
 import org.mozilla.gecko.background.testhelpers.MockRecord;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.InfoCollections;
 import org.mozilla.gecko.sync.JSONRecordFetcher;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
@@ -38,24 +31,30 @@ import org.mozilla.gecko.sync.repositori
 import org.mozilla.gecko.sync.repositories.StoreFailedException;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecordFactory;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 import org.mozilla.gecko.sync.stage.SafeConstrainedServer11Repository;
 import org.mozilla.gecko.sync.synchronizer.ServerLocalSynchronizer;
 import org.mozilla.gecko.sync.synchronizer.Synchronizer;
-import org.robolectric.RobolectricGradleTestRunner;
 import org.simpleframework.http.ContentType;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
-import ch.boye.httpclientandroidlib.HttpEntity;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.atomic.AtomicBoolean;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestServer11RepositorySession {
 
   public class POSTMockServer extends MockServer {
     @Override
     public void handle(Request request, Response response) {
       try {
         String content = request.getContent();
         System.out.println("Content:" + content);
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestServerLocalSynchronizer.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestServerLocalSynchronizer.java
@@ -1,41 +1,41 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import java.util.ArrayList;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.SynchronizerHelpers.BatchFailStoreWBORepository;
 import org.mozilla.android.sync.test.SynchronizerHelpers.BeginErrorWBORepository;
 import org.mozilla.android.sync.test.SynchronizerHelpers.BeginFailedException;
 import org.mozilla.android.sync.test.SynchronizerHelpers.FailFetchWBORepository;
 import org.mozilla.android.sync.test.SynchronizerHelpers.FinishErrorWBORepository;
 import org.mozilla.android.sync.test.SynchronizerHelpers.FinishFailedException;
 import org.mozilla.android.sync.test.SynchronizerHelpers.SerialFailStoreWBORepository;
 import org.mozilla.android.sync.test.SynchronizerHelpers.TrackingWBORepository;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.repositories.FetchFailedException;
 import org.mozilla.gecko.sync.repositories.StoreFailedException;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.synchronizer.ServerLocalSynchronizer;
 import org.mozilla.gecko.sync.synchronizer.Synchronizer;
 import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate;
-import org.robolectric.RobolectricGradleTestRunner;
+
+import java.util.ArrayList;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+@RunWith(TestRunner.class)
 public class TestServerLocalSynchronizer {
   public static final String LOG_TAG = "TestServLocSync";
 
   protected Synchronizer getSynchronizer(WBORepository remote, WBORepository local) {
     BookmarkRecord[] inbounds = new BookmarkRecord[] {
         new BookmarkRecord("inboundSucc1", "bookmarks", 1, false),
         new BookmarkRecord("inboundSucc2", "bookmarks", 1, false),
         new BookmarkRecord("inboundFail1", "bookmarks", 1, false),
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestSyncConfiguration.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestSyncConfiguration.java
@@ -1,24 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import java.net.URI;
-
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.Sync11Configuration;
 import org.mozilla.gecko.sync.SyncConfiguration;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.net.URI;
+
+@RunWith(TestRunner.class)
 public class TestSyncConfiguration {
   @Test
   public void testURLs() throws Exception {
     final MockSharedPreferences prefs = new MockSharedPreferences();
 
     // N.B., the username isn't used in the cluster path.
     SyncConfiguration fxaConfig = new SyncConfiguration("username", null, prefs);
     fxaConfig.clusterURL = new URI("http://db1.oldsync.dev.lcip.org/1.1/174");
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestSyncKeyVerification.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestSyncKeyVerification.java
@@ -1,23 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.fail;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.setup.InvalidSyncKeyException;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.fail;
+
+@RunWith(TestRunner.class)
 public class TestSyncKeyVerification {
 
   private int[] mutateIndices;
   private final String validBasicKey = "abcdefghijkmnpqrstuvwxyz23"; // 26 char, valid characters.
   char[] invalidChars = new char[] { '1', 'l', 'o', '0' };
 
   @Before
   public void setUp() {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestSynchronizer.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestSynchronizer.java
@@ -1,41 +1,40 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.util.Date;
-
+import android.content.Context;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.SynchronizerHelpers.TrackingWBORepository;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.synchronizer.Synchronizer;
 import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate;
 import org.mozilla.gecko.sync.synchronizer.SynchronizerSession;
 import org.mozilla.gecko.sync.synchronizer.SynchronizerSessionDelegate;
 
-import android.content.Context;
-import org.robolectric.RobolectricGradleTestRunner;
+import java.util.Date;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(TestRunner.class)
 public class TestSynchronizer {
   public static final String LOG_TAG = "TestSynchronizer";
 
   public static void assertInRangeInclusive(long earliest, long value, long latest) {
     assertTrue(earliest <= value);
     assertTrue(latest   >= value);
   }
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestSynchronizerSession.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestSynchronizerSession.java
@@ -1,44 +1,43 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
+import android.content.Context;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.SynchronizerHelpers.DataAvailableWBORepository;
 import org.mozilla.android.sync.test.SynchronizerHelpers.ShouldSkipWBORepository;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.SynchronizerConfiguration;
 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 import org.mozilla.gecko.sync.synchronizer.Synchronizer;
 import org.mozilla.gecko.sync.synchronizer.SynchronizerSession;
 import org.mozilla.gecko.sync.synchronizer.SynchronizerSessionDelegate;
 
-import android.content.Context;
-import org.robolectric.RobolectricGradleTestRunner;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(TestRunner.class)
 public class TestSynchronizerSession {
   public static final String LOG_TAG = TestSynchronizerSession.class.getSimpleName();
 
   protected static void assertFirstContainsSecond(Map<String, Record> first, Map<String, Record> second) {
     for (Entry<String, Record> entry : second.entrySet()) {
       assertTrue("Expected key " + entry.getKey(), first.containsKey(entry.getKey()));
       Record record = first.get(entry.getKey());
       assertEquals(entry.getValue(), record);
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestUtils.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestUtils.java
@@ -1,29 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.SyncConstants;
+import org.mozilla.gecko.sync.Utils;
 
 import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Arrays;
 
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.SyncConstants;
-import org.mozilla.gecko.sync.Utils;
-import org.robolectric.RobolectricGradleTestRunner;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestUtils extends Utils {
 
   @Test
   public void testGenerateGUID() {
     for (int i = 0; i < 1000; ++i) {
       assertEquals(12, Utils.generateGuid().length());
     }
   }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/BaseTestStorageRequestDelegate.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/BaseTestStorageRequestDelegate.java
@@ -1,23 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 
+import java.io.IOException;
+
+import static org.junit.Assert.fail;
+
 public class BaseTestStorageRequestDelegate implements
     SyncStorageRequestDelegate {
 
   protected final AuthHeaderProvider authHeaderProvider;
 
   public BaseTestStorageRequestDelegate(AuthHeaderProvider authHeaderProvider) {
     this.authHeaderProvider = authHeaderProvider;
   }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionBeginDelegate.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionBeginDelegate.java
@@ -1,21 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import java.util.concurrent.ExecutorService;
-
 import junit.framework.AssertionFailedError;
-
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
 
+import java.util.concurrent.ExecutorService;
+
 public class ExpectSuccessRepositorySessionBeginDelegate
 extends ExpectSuccessDelegate
 implements RepositorySessionBeginDelegate {
 
   public ExpectSuccessRepositorySessionBeginDelegate(WaitHelper waitHelper) {
     super(waitHelper);
   }
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionCreationDelegate.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionCreationDelegate.java
@@ -1,15 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
 import junit.framework.AssertionFailedError;
-
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 
 public class ExpectSuccessRepositorySessionCreationDelegate extends
     ExpectSuccessDelegate implements RepositorySessionCreationDelegate {
 
   public ExpectSuccessRepositorySessionCreationDelegate(WaitHelper waitHelper) {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionFetchRecordsDelegate.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionFetchRecordsDelegate.java
@@ -1,22 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
-
 import junit.framework.AssertionFailedError;
-
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+
 public class ExpectSuccessRepositorySessionFetchRecordsDelegate extends
     ExpectSuccessDelegate implements RepositorySessionFetchRecordsDelegate {
   public ArrayList<Record> fetchedRecords = new ArrayList<Record>();
 
   public ExpectSuccessRepositorySessionFetchRecordsDelegate(WaitHelper waitHelper) {
     super(waitHelper);
   }
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionFinishDelegate.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionFinishDelegate.java
@@ -1,22 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import java.util.concurrent.ExecutorService;
-
 import junit.framework.AssertionFailedError;
-
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 
+import java.util.concurrent.ExecutorService;
+
 public class ExpectSuccessRepositorySessionFinishDelegate extends
     ExpectSuccessDelegate implements RepositorySessionFinishDelegate {
 
   public ExpectSuccessRepositorySessionFinishDelegate(WaitHelper waitHelper) {
     super(waitHelper);
   }
 
   @Override
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionStoreDelegate.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionStoreDelegate.java
@@ -1,20 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import java.util.concurrent.ExecutorService;
-
 import junit.framework.AssertionFailedError;
-
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
 
+import java.util.concurrent.ExecutorService;
+
 public class ExpectSuccessRepositorySessionStoreDelegate extends
     ExpectSuccessDelegate implements RepositorySessionStoreDelegate {
 
   public ExpectSuccessRepositorySessionStoreDelegate(WaitHelper waitHelper) {
     super(waitHelper);
   }
 
   @Override
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositoryWipeDelegate.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositoryWipeDelegate.java
@@ -1,20 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import java.util.concurrent.ExecutorService;
-
 import junit.framework.AssertionFailedError;
-
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 
+import java.util.concurrent.ExecutorService;
+
 public class ExpectSuccessRepositoryWipeDelegate extends ExpectSuccessDelegate
     implements RepositorySessionWipeDelegate {
 
   public ExpectSuccessRepositoryWipeDelegate(WaitHelper waitHelper) {
     super(waitHelper);
   }
 
   @Override
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/HTTPServerTestHelper.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/HTTPServerTestHelper.java
@@ -1,26 +1,26 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import static org.junit.Assert.fail;
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.sync.net.BaseResource;
+import org.mozilla.gecko.sync.net.BaseResourceDelegate;
+import org.simpleframework.transport.connect.Connection;
+import org.simpleframework.transport.connect.SocketConnection;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.BaseResourceDelegate;
-import org.simpleframework.transport.connect.Connection;
-import org.simpleframework.transport.connect.SocketConnection;
+import static org.junit.Assert.fail;
 
 /**
  * Test helper code to bind <code>MockServer</code> instances to ports.
  * <p>
  * Maintains a collection of running servers and (by default) throws helpful
  * errors if two servers are started "on top" of each other. The
  * <b>unchecked</b> exception thrown contains a stack trace pointing to where
  * the new server is being created and where the pre-existing server was
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockGlobalSessionCallback.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockGlobalSessionCallback.java
@@ -1,22 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import static org.junit.Assert.assertEquals;
-
-import java.net.URI;
-
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+
 /**
  * A callback for use with a GlobalSession that records what happens for later
  * inspection.
  *
  * This callback is expected to be used from within the friendly confines of a
  * WaitHelper performWait.
  */
 public class MockGlobalSessionCallback implements GlobalSessionCallback {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockResourceDelegate.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockResourceDelegate.java
@@ -1,28 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.client.ClientProtocolException;
+import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
+import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.ResourceDelegate;
 
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import static org.junit.Assert.assertEquals;
 
 public class MockResourceDelegate implements ResourceDelegate {
   public WaitHelper waitHelper = null;
   public static String USER_PASS    = "john:password";
   public static String EXPECT_BASIC = "Basic am9objpwYXNzd29yZA==";
 
   public boolean handledHttpResponse = false;
   public HttpResponse httpResponse = null;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockServer.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockServer.java
@@ -1,24 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.io.PrintStream;
-
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.Utils;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 import org.simpleframework.http.core.Container;
 
+import java.io.IOException;
+import java.io.PrintStream;
+
+import static org.junit.Assert.assertEquals;
+
 public class MockServer implements Container {
   public static final String LOG_TAG = "MockServer";
 
   public int statusCode = 200;
   public String body = "Hello World";
 
   public MockServer() {
   }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockSyncClientsEngineStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockSyncClientsEngineStage.java
@@ -1,19 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers;
 
-import static org.junit.Assert.assertTrue;
-
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
 
+import static org.junit.Assert.assertTrue;
+
 public class MockSyncClientsEngineStage extends SyncClientsEngineStage {
   public class MockClientUploadDelegate extends ClientUploadDelegate {
     HTTPServerTestHelper data;
 
     public MockClientUploadDelegate(HTTPServerTestHelper data) {
       this.data = data;
     }
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockWBOServer.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockWBOServer.java
@@ -1,16 +1,16 @@
 package org.mozilla.android.sync.test.helpers;
 
-import java.util.HashMap;
-
 import org.simpleframework.http.Path;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
+import java.util.HashMap;
+
 /**
  * A trivial server that collects and returns WBOs.
  *
  * @author rnewman
  *
  */
 public class MockWBOServer extends MockServer {
   public HashMap<String, HashMap<String, String> > collections;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/test/TestHTTPServerTestHelper.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/test/TestHTTPServerTestHelper.java
@@ -1,30 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test.helpers.test;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
+import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper.HTTPServerAlreadyRunningError;
+import org.mozilla.android.sync.test.helpers.MockServer;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
-import org.mozilla.android.sync.test.helpers.MockServer;
-import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper.HTTPServerAlreadyRunningError;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestHTTPServerTestHelper {
   public static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
 
   protected MockServer mockServer = new MockServer();
 
   @Test
   public void testStartStop() {
     // Need to be able to start and stop multiple times.
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/common/log/writers/test/TestLogWriters.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/common/log/writers/test/TestLogWriters.java
@@ -1,37 +1,36 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.common.log.writers.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
+import android.util.Log;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.common.log.writers.LevelFilteringLogWriter;
 import org.mozilla.gecko.background.common.log.writers.LogWriter;
 import org.mozilla.gecko.background.common.log.writers.PrintLogWriter;
 import org.mozilla.gecko.background.common.log.writers.SimpleTagLogWriter;
 import org.mozilla.gecko.background.common.log.writers.StringLogWriter;
 import org.mozilla.gecko.background.common.log.writers.ThreadLocalTagLogWriter;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 
-import android.util.Log;
-import org.robolectric.RobolectricGradleTestRunner;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestLogWriters {
 
   public static final String TEST_LOG_TAG_1 = "TestLogTag1";
   public static final String TEST_LOG_TAG_2 = "TestLogTag2";
 
   public static final String TEST_MESSAGE_1  = "LOG TEST MESSAGE one";
   public static final String TEST_MESSAGE_2  = "LOG TEST MESSAGE two";
   public static final String TEST_MESSAGE_3  = "LOG TEST MESSAGE three";
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/fxa/test/TestFxAccountAgeLockoutHelper.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/fxa/test/TestFxAccountAgeLockoutHelper.java
@@ -1,23 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.fxa.test;
 
-import java.util.Calendar;
-
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.util.Calendar;
+
+@RunWith(TestRunner.class)
 public class TestFxAccountAgeLockoutHelper {
   @Test
   public void testPassesAgeCheck() {
     Calendar today = Calendar.getInstance();
     int birthMonthIndex = today.get(Calendar.MONTH);
     int birthDate = today.get(Calendar.DATE);
     int birthYear = today.get(Calendar.YEAR) - FxAccountConstants.MINIMUM_AGE_TO_CREATE_AN_ACCOUNT;
     Assert.assertTrue("Minimum age as of today",
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/fxa/test/TestFxAccountClient20.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/fxa/test/TestFxAccountClient20.java
@@ -1,27 +1,26 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.fxa.test;
 
+import junit.framework.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.fxa.FxAccountClient20;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.net.BaseResource;
+
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
-import junit.framework.Assert;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.fxa.FxAccountClient20;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestFxAccountClient20 {
   protected static class MockFxAccountClient20 extends FxAccountClient20 {
     public MockFxAccountClient20(String serverURI, Executor executor) {
       super(serverURI, executor);
     }
 
     // Public for testing.
     @Override
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/fxa/test/TestFxAccountUtils.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/fxa/test/TestFxAccountUtils.java
@@ -1,32 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.fxa.test;
 
-import java.math.BigInteger;
-
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.net.SRPConstants;
-import org.robolectric.RobolectricGradleTestRunner;
+
+import java.math.BigInteger;
 
 /**
  * Test vectors from
  * <a href="https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF">https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF</a>
  * and
  * <a href="https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol/5a9bc81e499306d769ca19b40b50fa60123df15d">https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol/5a9bc81e499306d769ca19b40b50fa60123df15d</a>.
  */
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestFxAccountUtils {
   protected static void assertEncoding(String base16String, String utf8String) throws Exception {
     Assert.assertEquals(base16String, FxAccountUtils.bytes(utf8String));
   }
 
   @Test
   public void testUTF8Encoding() throws Exception {
     assertEncoding("616e6472c3a9406578616d706c652e6f7267", "andré@example.org");
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/prune/test/TestPrunePolicy.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/prune/test/TestPrunePolicy.java
@@ -1,29 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.healthreport.prune.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
+import android.content.SharedPreferences;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.healthreport.HealthReportConstants;
 import org.mozilla.gecko.background.healthreport.prune.PrunePolicy;
 import org.mozilla.gecko.background.healthreport.prune.PrunePolicyStorage;
 import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 
-import android.content.SharedPreferences;
-import org.robolectric.RobolectricGradleTestRunner;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestPrunePolicy {
   public static class MockPrunePolicy extends PrunePolicy {
     public MockPrunePolicy(final PrunePolicyStorage storage, final SharedPreferences sharedPrefs) {
       super(storage, sharedPrefs);
     }
 
     @Override
     public boolean attemptPruneBySize(final long time) {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/test/HealthReportStorageStub.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/test/HealthReportStorageStub.java
@@ -1,18 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.healthreport.test;
 
-import org.json.JSONObject;
-
 import android.database.Cursor;
 import android.util.SparseArray;
-
+import org.json.JSONObject;
 import org.mozilla.gecko.background.healthreport.Environment;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage;
 
 public class HealthReportStorageStub implements HealthReportStorage {
   public void close() { throw new UnsupportedOperationException(); }
 
   public int getDay(long time) { throw new UnsupportedOperationException(); }
   public int getDay() { throw new UnsupportedOperationException(); }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/upload/test/MockAndroidSubmissionClient.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/upload/test/MockAndroidSubmissionClient.java
@@ -1,16 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.healthreport.upload.test;
 
 import android.content.Context;
 import android.content.SharedPreferences;
-
 import org.mozilla.gecko.background.healthreport.Environment;
 import org.mozilla.gecko.background.healthreport.Environment.UIType;
 import org.mozilla.gecko.background.healthreport.EnvironmentBuilder.ConfigurationProvider;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage;
 import org.mozilla.gecko.background.healthreport.ProfileInformationCache;
 import org.mozilla.gecko.background.healthreport.test.HealthReportStorageStub;
 import org.mozilla.gecko.background.healthreport.upload.AndroidSubmissionClient;
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/upload/test/TestObsoleteDocumentTracker.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/upload/test/TestObsoleteDocumentTracker.java
@@ -1,37 +1,36 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.healthreport.upload.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import android.content.SharedPreferences;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.healthreport.HealthReportConstants;
+import org.mozilla.gecko.background.healthreport.upload.ObsoleteDocumentTracker;
+import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
 
 import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.healthreport.HealthReportConstants;
-import org.mozilla.gecko.background.healthreport.upload.ObsoleteDocumentTracker;
-import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-import android.content.SharedPreferences;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestObsoleteDocumentTracker {
   public static class MockObsoleteDocumentTracker extends ObsoleteDocumentTracker {
     public MockObsoleteDocumentTracker(SharedPreferences sharedPrefs) {
       super(sharedPrefs);
     }
 
     @Override
     public ExtendedJSONObject getObsoleteIds() {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/upload/test/TestSubmissionPolicy.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/upload/test/TestSubmissionPolicy.java
@@ -1,37 +1,36 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.healthreport.upload.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.net.UnknownHostException;
-import java.util.Collection;
-import java.util.HashSet;
-
+import android.content.SharedPreferences;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.healthreport.HealthReportConstants;
 import org.mozilla.gecko.background.healthreport.upload.SubmissionClient;
 import org.mozilla.gecko.background.healthreport.upload.SubmissionPolicy;
 import org.mozilla.gecko.background.healthreport.upload.test.TestObsoleteDocumentTracker.MockObsoleteDocumentTracker;
 import org.mozilla.gecko.background.healthreport.upload.test.TestSubmissionPolicy.MockSubmissionClient.Response;
 import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 
-import android.content.SharedPreferences;
-import org.robolectric.RobolectricGradleTestRunner;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.HashSet;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestSubmissionPolicy {
   public static class MockSubmissionClient implements SubmissionClient {
     public String lastId = null;
     public Collection<String> lastOldIds = null;
 
     public enum Response { SUCCESS, SOFT_FAILURE, HARD_FAILURE };
     public Response upload = Response.SUCCESS;
     public Response delete = Response.SUCCESS;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/upload/test/TestSubmissionsTracker.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/upload/test/TestSubmissionsTracker.java
@@ -1,30 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.healthreport.upload.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.healthreport.upload.AndroidSubmissionClient.SubmissionsFieldName;
+import org.mozilla.gecko.background.healthreport.upload.test.MockAndroidSubmissionClient.MockHealthReportStorage;
+import org.mozilla.gecko.background.healthreport.upload.test.MockAndroidSubmissionClient.MockSubmissionsTracker;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 
 import java.util.Arrays;
 import java.util.HashSet;
 
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.healthreport.upload.AndroidSubmissionClient.SubmissionsFieldName;
-import org.mozilla.gecko.background.healthreport.upload.test.MockAndroidSubmissionClient;
-import org.mozilla.gecko.background.healthreport.upload.test.MockAndroidSubmissionClient.MockHealthReportStorage;
-import org.mozilla.gecko.background.healthreport.upload.test.MockAndroidSubmissionClient.MockSubmissionsTracker;
-import org.robolectric.RobolectricGradleTestRunner;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestSubmissionsTracker {
   protected static class MockHealthReportStorage2 extends MockHealthReportStorage {
     public final int FIRST_ATTEMPT_ID = SubmissionsFieldName.FIRST_ATTEMPT.getID(this);
     public final int CONTINUATION_ATTEMPT_ID = SubmissionsFieldName.CONTINUATION_ATTEMPT.getID(this);
 
     public final int SUCCESS_ID = SubmissionsFieldName.SUCCESS.getID(this);
     public final int CLIENT_FAILURE_ID = SubmissionsFieldName.CLIENT_FAILURE.getID(this);
     public final int TRANSPORT_FAILURE_ID = SubmissionsFieldName.TRANSPORT_FAILURE.getID(this);
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/upload/test/TestTrackingRequestDelegate.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/healthreport/upload/test/TestTrackingRequestDelegate.java
@@ -1,32 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.healthreport.upload.test;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.healthreport.HealthReportStorage;
+import org.mozilla.gecko.background.healthreport.upload.AndroidSubmissionClient.SubmissionsTracker.TrackingRequestDelegate;
+import org.mozilla.gecko.background.healthreport.upload.test.MockAndroidSubmissionClient.MockHealthReportStorage;
+import org.mozilla.gecko.background.testhelpers.StubDelegate;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.util.HashSet;
 
-import org.junit.Before;
-import org.junit.Test;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.healthreport.HealthReportStorage;
-import org.mozilla.gecko.background.healthreport.upload.AndroidSubmissionClient.SubmissionsTracker.TrackingRequestDelegate;
-import org.mozilla.gecko.background.healthreport.upload.test.MockAndroidSubmissionClient;
-import org.mozilla.gecko.background.healthreport.upload.test.MockAndroidSubmissionClient.MockHealthReportStorage;
-import org.mozilla.gecko.background.testhelpers.StubDelegate;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestTrackingRequestDelegate {
   public static class MockAndroidSubmissionClient2 extends MockAndroidSubmissionClient {
     public MockAndroidSubmissionClient2() {
       super(null, null, null);
     }
 
     @Override
     public void setLastUploadLocalTimeAndDocumentId(long localTime, String id) { /* Do nothing. */ }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/test/EntityTestHelper.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/test/EntityTestHelper.java
@@ -1,20 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.test;
 
+import ch.boye.httpclientandroidlib.HttpEntity;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
-import ch.boye.httpclientandroidlib.HttpEntity;
-
 public class EntityTestHelper {
   private static final int DEFAULT_SIZE = 1024;
 
   public static byte[] bytesFromEntity(final HttpEntity entity) throws IOException {
     final InputStream is = entity.getContent();
 
     if (is instanceof ByteArrayInputStream) {
       final int size = is.available();
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/test/TestBoundedByteArrayEntity.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/test/TestBoundedByteArrayEntity.java
@@ -1,25 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.test;
 
-import java.io.IOException;
-import java.util.Arrays;
-
+import ch.boye.httpclientandroidlib.HttpEntity;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.bagheera.BoundedByteArrayEntity;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 
-import ch.boye.httpclientandroidlib.HttpEntity;
-import org.robolectric.RobolectricGradleTestRunner;
+import java.io.IOException;
+import java.util.Arrays;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestBoundedByteArrayEntity {
   private static void expectFail(byte[] input, int start, int end) {
     try {
       new BoundedByteArrayEntity(input, start, end);
       Assert.fail("Should have thrown.");
     } catch (Exception ex) {
       return;
     }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/test/TestDeflation.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/test/TestDeflation.java
@@ -1,28 +1,26 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.test;
 
+import ch.boye.httpclientandroidlib.HttpEntity;
+import junit.framework.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.bagheera.DeflateHelper;
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+
 import java.util.Arrays;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
 
-import junit.framework.Assert;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.bagheera.DeflateHelper;
-import org.mozilla.gecko.background.common.log.Logger;
-
-import ch.boye.httpclientandroidlib.HttpEntity;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestDeflation {
   public static final String TEST_BODY_A = "";
   public static final String TEST_BODY_B = "éíôü}ABCDEFGHaaQRSTUVWXYZá{Zá{";
   public static final String TEST_BODY_C = "{}\n";
   public static final String TEST_BODY_D =
       "{éíôü}ABCDEFGHaaQRSTUVWXYZá{Zá{éíôü}ABCDEFGHaaQRSTUVWXYZá{Zá{éíôü}A" +
       "BCDEFGHaaQRSTUVWXYZá{Zá{éíôü}ABCDEFGHaaQRSTUVWXYZá{Zá{éíôü}ABCDEFGH" +
       "aQRSTUVWXYZá{Zá{éíôü}ABCDEFGHaaQRSTUVWXYZá{Zá{}\n";
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/BaseMockServerSyncStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/BaseMockServerSyncStage.java
@@ -1,24 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.testhelpers;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
-
 import org.json.simple.parser.ParseException;
 import org.mozilla.gecko.sync.NoCollectionKeysSetException;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SynchronizerConfiguration;
 import org.mozilla.gecko.sync.repositories.RecordFactory;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.stage.ServerSyncStage;
 
+import java.io.IOException;
+import java.net.URISyntaxException;
+
 /**
  * A stage that joins two Repositories with no wrapping.
  */
 public abstract class BaseMockServerSyncStage extends ServerSyncStage {
 
   public Repository local;
   public Repository remote;
   public String name;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/DefaultGlobalSessionCallback.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/DefaultGlobalSessionCallback.java
@@ -1,19 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.testhelpers;
 
-import java.net.URI;
-
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 
+import java.net.URI;
+
 public class DefaultGlobalSessionCallback implements GlobalSessionCallback {
 
   @Override
   public void requestBackoff(long backoff) {
   }
 
   @Override
   public boolean wantNodeAssignment() {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/JPakeNumGeneratorFixed.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/JPakeNumGeneratorFixed.java
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.testhelpers;
 
-import java.math.BigInteger;
+import org.mozilla.gecko.sync.jpake.JPakeNumGenerator;
 
-import org.mozilla.gecko.sync.jpake.JPakeNumGenerator;
+import java.math.BigInteger;
 
 public class JPakeNumGeneratorFixed implements JPakeNumGenerator {
   private String[] values;
   private int index = 0;
 
   public JPakeNumGeneratorFixed(String[] values) {
     this.values = values;
   }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockClientsDatabaseAccessor.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockClientsDatabaseAccessor.java
@@ -1,22 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.testhelpers;
 
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
 import org.mozilla.gecko.sync.CommandProcessor.Command;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
 public class MockClientsDatabaseAccessor extends ClientsDatabaseAccessor {
   public boolean storedRecord = false;
   public boolean dbWiped = false;
   public boolean clientsTableWiped = false;
   public boolean closed = false;
   public boolean storedArrayList = false;
   public boolean storedCommand;
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java
@@ -1,28 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.testhelpers;
 
-import java.io.IOException;
-import java.util.HashMap;
-
 import org.json.simple.parser.ParseException;
 import org.mozilla.gecko.sync.EngineSettings;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConfigurationException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.stage.CompletedStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 
+import java.io.IOException;
+import java.util.HashMap;
+
 
 public class MockGlobalSession extends MockPrefsGlobalSession {
 
   public MockGlobalSession(String username, String password, KeyBundle keyBundle, GlobalSessionCallback callback) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException {
     this(new SyncConfiguration(username, new BasicAuthHeaderProvider(username, password), new MockSharedPreferences(), keyBundle), callback);
   }
 
   public MockGlobalSession(SyncConfiguration config, GlobalSessionCallback callback)
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java
@@ -1,28 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.testhelpers;
 
-import java.io.IOException;
-
+import android.content.Context;
+import android.content.SharedPreferences;
 import org.json.simple.parser.ParseException;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConfigurationException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 
-import android.content.Context;
-import android.content.SharedPreferences;
+import java.io.IOException;
 
 /**
  * GlobalSession touches the Android prefs system. Stub that out.
  */
 public class MockPrefsGlobalSession extends GlobalSession {
 
   public MockSharedPreferences prefs;
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockServerSyncStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockServerSyncStage.java
@@ -1,12 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.testhelpers;
 
-
 public class MockServerSyncStage extends BaseMockServerSyncStage {
   @Override
   public void execute() {
     session.advance();
   }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/TestRunner.java
@@ -0,0 +1,138 @@
+/**
+ * The MIT License
+ * 
+ * Copyright (c) 2010 Xtreme Labs and Pivotal Labs
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.mozilla.gecko.background.testhelpers;
+
+import org.junit.runners.model.InitializationError;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.manifest.AndroidManifest;
+import org.robolectric.res.FileFsFile;
+import org.robolectric.res.FsFile;
+import org.robolectric.util.Logger;
+import org.robolectric.util.ReflectionHelpers;
+
+/**
+ * Test runner customized for running unit tests either through the Gradle CLI or
+ * Android Studio. The runner uses the build type and build flavor to compute the
+ * resource, asset, and AndroidManifest paths.
+ *
+ * This test runner requires that you set the 'constants' field on the @Config
+ * annotation (or the org.robolectric.Config.properties file) for your tests.
+ *
+ * This is a modified version of
+ * https://github.com/robolectric/robolectric/blob/8676da2daa4c140679fb5903696b8191415cec8f/robolectric/src/main/java/org/robolectric/RobolectricGradleTestRunner.java
+ * that uses a Gradle `buildConfigField` to find build outputs.
+ * See https://github.com/robolectric/robolectric/issues/1648#issuecomment-113731011.
+ */
+public class TestRunner extends RobolectricGradleTestRunner {
+    private FsFile buildFolder;
+
+    public TestRunner(Class<?> klass) throws InitializationError {
+        super(klass);
+    }
+
+    @Override
+    protected AndroidManifest getAppManifest(Config config) {
+        if (config.constants() == Void.class) {
+            Logger.error("Field 'constants' not specified in @Config annotation");
+            Logger.error("This is required when using RobolectricGradleTestRunner!");
+            throw new RuntimeException("No 'constants' field in @Config annotation!");
+        }
+
+        buildFolder = FileFsFile.from(getBuildDir(config)).join("intermediates");
+
+        final String type = getType(config);
+        final String flavor = getFlavor(config);
+        final String packageName = getPackageName(config);
+
+        final FsFile res;
+        final FsFile assets;
+        final FsFile manifest;
+
+        if (areResourcesFromLibrary()) {
+            FsFile bundlesFolder = buildFolder.join("bundles", flavor, type);
+            res = bundlesFolder.join("res");
+            assets = bundlesFolder.join("assets");
+            manifest = bundlesFolder.join("AndroidManifest.xml");
+        } else {
+            if (buildFolder.join("res", "merged").exists()) {
+                res = buildFolder.join("res", "merged", flavor, type);
+            } else if(buildFolder.join("res").exists()) {
+                res = buildFolder.join("res", flavor, type);
+            } else {
+                throw new IllegalStateException("No resource folder found");
+            }
+            assets = buildFolder.join("assets", flavor, type);
+            manifest = buildFolder.join("manifests", "full", flavor, type, "AndroidManifest.xml");
+        }
+
+        Logger.debug("Robolectric assets directory: " + assets.getPath());
+        Logger.debug("   Robolectric res directory: " + res.getPath());
+        Logger.debug("   Robolectric manifest path: " + manifest.getPath());
+        Logger.debug("    Robolectric package name: " + packageName);
+        return new AndroidManifest(manifest, res, assets, packageName);
+    }
+
+    private boolean areResourcesFromLibrary() {
+        return buildFolder.join("bundles").exists();
+    }
+
+    private static String getType(Config config) {
+        try {
+            return ReflectionHelpers.getStaticField(config.constants(), "BUILD_TYPE");
+        } catch (Throwable e) {
+            return null;
+        }
+    }
+
+    private static String getFlavor(Config config) {
+        try {
+            return ReflectionHelpers.getStaticField(config.constants(), "FLAVOR");
+        } catch (Throwable e) {
+            return null;
+        }
+    }
+
+    private static String getPackageName(Config config) {
+        try {
+            final String packageName = config.packageName();
+            if (packageName != null && !packageName.isEmpty()) {
+                return packageName;
+            } else {
+                return ReflectionHelpers.getStaticField(config.constants(), "APPLICATION_ID");
+            }
+        } catch (Throwable e) {
+            return null;
+        }
+    }
+
+    private String getBuildDir(Config config) {
+        try {
+            return ReflectionHelpers.getStaticField(config.constants(), "BUILD_DIR");
+        } catch (Throwable e) {
+            return null;
+        }
+    }
+}
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/WBORepository.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/WBORepository.java
@@ -1,34 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.testhelpers;
 
-import java.util.Map.Entry;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
+import android.content.Context;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.RecordFilter;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
-import android.content.Context;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 public class WBORepository extends Repository {
 
   public class WBORepositoryStats {
     public long created         = -1;
     public long begun           = -1;
     public long fetchBegan      = -1;
     public long fetchCompleted  = -1;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/WaitHelper.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/WaitHelper.java
@@ -1,19 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.testhelpers;
 
+import org.mozilla.gecko.background.common.log.Logger;
+
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import org.mozilla.gecko.background.common.log.Logger;
-
 /**
  * Implements waiting for asynchronous test events.
  *
  * Call WaitHelper.getTestWaiter() to get the unique instance.
  *
  * Call performWait(runnable) to execute runnable synchronously.
  * runnable *must* call performNotify() on all exit paths to signal to
  * the TestWaiter that the runnable has completed.
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestASNUtils.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestASNUtils.java
@@ -1,23 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.browserid.test;
 
-import java.math.BigInteger;
-
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.browserid.ASNUtils;
 import org.mozilla.gecko.sync.Utils;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.math.BigInteger;
+
+@RunWith(TestRunner.class)
 public class TestASNUtils {
   public void doTestEncodeDecodeArrays(int length1, int length2) {
     if (4 + length1 + length2 > 127) {
       throw new IllegalArgumentException("Total length must be < 128 - 4.");
     }
     byte[] first = Utils.generateRandomBytes(length1);
     byte[] second = Utils.generateRandomBytes(length2);
     byte[] encoded = ASNUtils.encodeTwoArraysToASN1(first, second);
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestDSACryptoImplementation.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestDSACryptoImplementation.java
@@ -1,25 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.browserid.test;
 
-import java.math.BigInteger;
-
 import junit.framework.Assert;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
 import org.mozilla.gecko.browserid.DSACryptoImplementation;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.math.BigInteger;
+
+@RunWith(TestRunner.class)
 public class TestDSACryptoImplementation {
   @Test
   public void testToJSONObject() throws Exception {
     BigInteger p = new BigInteger("fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e17", 16);
     BigInteger q = new BigInteger("962eddcc369cba8ebb260ee6b6a126d9346e38c5", 16);
     BigInteger g = new BigInteger("678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4", 16);
     BigInteger x = new BigInteger("9516d860392003db5a4f168444903265467614db", 16);
     BigInteger y = new BigInteger("455152a0e499f5c9d11f9f1868c8b868b1443ca853843226a5a9552dd909b4bdba879acc504acb690df0348d60e63ea37e8c7f075302e0df5bcdc76a383888a0", 16);
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestJSONWebTokenUtils.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestJSONWebTokenUtils.java
@@ -1,29 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.browserid.test;
 
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
 import org.mozilla.gecko.browserid.DSACryptoImplementation;
 import org.mozilla.gecko.browserid.JSONWebTokenUtils;
 import org.mozilla.gecko.browserid.RSACryptoImplementation;
 import org.mozilla.gecko.browserid.SigningPrivateKey;
 import org.mozilla.gecko.browserid.VerifyingPublicKey;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+
+@RunWith(TestRunner.class)
 public class TestJSONWebTokenUtils {
   public void doTestEncodeDecode(BrowserIDKeyPair keyPair) throws Exception {
     SigningPrivateKey privateKey = keyPair.getPrivate();
     VerifyingPublicKey publicKey = keyPair.getPublic();
 
     ExtendedJSONObject o = new ExtendedJSONObject();
     o.put("key", "value");
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestRSACryptoImplementation.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestRSACryptoImplementation.java
@@ -1,25 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.browserid.test;
 
-import java.math.BigInteger;
-
 import junit.framework.Assert;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
 import org.mozilla.gecko.browserid.RSACryptoImplementation;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.math.BigInteger;
+
+@RunWith(TestRunner.class)
 public class TestRSACryptoImplementation {
   @Test
   public void testToJSONObject() throws Exception {
     BigInteger n = new BigInteger("7042170764319402120473546823641395184140303948430445023576085129538272863656735924617881022040465877164076593767104512065359975488480629290310209335113577");
     BigInteger e = new BigInteger("65537");
     BigInteger d = new BigInteger("2050102629239206449128199335463237235732683202345308155771672920433658970744825199440426256856862541525088288448769859770132714705204296375901885294992205");
 
     BrowserIDKeyPair keyPair = new BrowserIDKeyPair(
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/TestSkewHandler.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/TestSkewHandler.java
@@ -1,24 +1,25 @@
 /* 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.gecko.fxa;
 
-import org.mozilla.gecko.background.fxa.SkewHandler;
-import org.mozilla.gecko.sync.net.BaseResource;
 import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricGradleTestRunner;
+import org.mozilla.gecko.background.fxa.SkewHandler;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.net.BaseResource;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestSkewHandler {
   public TestSkewHandler() {
   }
 
   @Test
   public void testSkewUpdating() throws Throwable {
     SkewHandler h = new SkewHandler("foo.com");
     assertEquals(0L, h.getSkewInSeconds());
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/MockFxAccountClient.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/MockFxAccountClient.java
@@ -1,36 +1,35 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.fxa.login;
 
-import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
+import ch.boye.httpclientandroidlib.HttpStatus;
+import ch.boye.httpclientandroidlib.ProtocolVersion;
+import ch.boye.httpclientandroidlib.entity.StringEntity;
+import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.StatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.TwoKeys;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.background.fxa.PasswordStretcher;
 import org.mozilla.gecko.browserid.MockMyIDTokenFactory;
 import org.mozilla.gecko.browserid.RSACryptoImplementation;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
 
-import ch.boye.httpclientandroidlib.HttpStatus;
-import ch.boye.httpclientandroidlib.ProtocolVersion;
-import ch.boye.httpclientandroidlib.entity.StringEntity;
-import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 public class MockFxAccountClient implements FxAccountClient {
   protected static MockMyIDTokenFactory mockMyIdTokenFactory = new MockMyIDTokenFactory();
 
   public final String serverURI = "http://testServer.com";
 
   public final Map<String, User> users = new HashMap<String, User>();
   public final Map<String, String> sessionTokens = new HashMap<String, String>();
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/TestFxAccountLoginStateMachine.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/TestFxAccountLoginStateMachine.java
@@ -1,34 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.fxa.login;
 
-import java.security.NoSuchAlgorithmException;
-import java.util.LinkedList;
-
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mozilla.gecko.BuildConfig;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
 import org.mozilla.gecko.browserid.RSACryptoImplementation;
 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
 import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 import org.mozilla.gecko.sync.Utils;
-import org.robolectric.RobolectricGradleTestRunner;
-import org.robolectric.annotation.Config;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.security.NoSuchAlgorithmException;
+import java.util.LinkedList;
+
+@RunWith(TestRunner.class)
 public class TestFxAccountLoginStateMachine {
   // private static final String TEST_AUDIENCE = "http://testAudience.com";
   private static final String TEST_EMAIL = "test@test.com";
   private static byte[] TEST_EMAIL_UTF8;
   private static final String TEST_PASSWORD = "testtest";
   private static byte[] TEST_PASSWORD_UTF8;
   private static byte[] TEST_QUICK_STRETCHED_PW;
   private static byte[] TEST_UNWRAPKB;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/TestStateFactory.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/TestStateFactory.java
@@ -1,27 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.fxa.login;
 
 import junit.framework.Assert;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mozilla.gecko.BuildConfig;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
 import org.mozilla.gecko.browserid.DSACryptoImplementation;
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
-import org.robolectric.RobolectricGradleTestRunner;
-import org.robolectric.annotation.Config;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestStateFactory {
   @Test
   public void testGetStateV3() throws Exception {
     MigratedFromSync11 migrated = new MigratedFromSync11("email", "uid", true, "password");
 
     // For the current version, we expect to read back what we wrote.
     ExtendedJSONObject o;
     State state;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestBase32.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestBase32.java
@@ -1,26 +1,26 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.crypto.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.apache.commons.codec.binary.Base32;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.Utils;
 
 import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.apache.commons.codec.binary.Base32;
-import org.mozilla.gecko.sync.Utils;
-import org.robolectric.RobolectricGradleTestRunner;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestBase32 {
 
   public static void assertSame(byte[] arrayOne, byte[] arrayTwo) {
     assertTrue(Arrays.equals(arrayOne, arrayTwo));
   }
 
   @Test
   public void testBase32() throws UnsupportedEncodingException {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestCryptoInfo.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestCryptoInfo.java
@@ -1,31 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.crypto.test;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.apache.commons.codec.binary.Base64;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.sync.crypto.CryptoException;
+import org.mozilla.gecko.sync.crypto.CryptoInfo;
+import org.mozilla.gecko.sync.crypto.KeyBundle;
+
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.CryptoInfo;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestCryptoInfo {
 
   @Test
   public void testEncryptedHMACIsSet() throws CryptoException, UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
     KeyBundle kb = KeyBundle.withRandomKeys();
     CryptoInfo encrypted = CryptoInfo.encrypt("plaintext".getBytes("UTF-8"), kb);
     assertSame(kb, encrypted.getKeys());
     assertTrue(encrypted.generatedHMACIsHMAC());
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestHKDF.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestHKDF.java
@@ -1,32 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.crypto.test;
 
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.apache.commons.codec.binary.Base64;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.sync.crypto.HKDF;
+import org.mozilla.gecko.sync.crypto.KeyBundle;
 
 import java.util.Arrays;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.HKDF;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.robolectric.RobolectricGradleTestRunner;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /*
  * This class tests the HKDF.java class.
  * The tests are the 3 HMAC-based test cases
  * from the RFC 5869 specification.
  */
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestHKDF {
   @Test
   public void testCase1() {
     String IKM  = "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b";
     String salt = "000102030405060708090a0b0c";
     String info = "f0f1f2f3f4f5f6f7f8f9";
     int L       = 42;
     String PRK  = "077709362c2e32df0ddc3f0dc47bba63" +
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestKeyBundle.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestKeyBundle.java
@@ -1,29 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.crypto.test;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.apache.commons.codec.binary.Base64;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.crypto.CryptoException;
+import org.mozilla.gecko.sync.crypto.KeyBundle;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestKeyBundle {
   @Test
   public void testCreateKeyBundle() throws UnsupportedEncodingException, CryptoException {
     String username              = "smqvooxj664hmrkrv6bw4r4vkegjhkns";
     String friendlyBase32SyncKey = "gbh7teqqcgyzd65svjgibd7tqy";
     String base64EncryptionKey   = "069EnS3EtDK4y1tZ1AyKX+U7WEsWRp9b" +
                                    "RIKLdW/7aoE=";
     String base64HmacKey         = "LF2YCS1QCgSNCf0BCQvQ06SGH8jqJDi9" +
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestPBKDF2.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestPBKDF2.java
@@ -1,34 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.crypto.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.sync.crypto.PBKDF2;
 
 import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.PBKDF2;
-import org.robolectric.RobolectricGradleTestRunner;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 /**
  * Test PBKDF2 implementations against vectors from
  * <dl>
  * <dt>SHA-256</dt>
  * <dd><a href="https://github.com/ircmaxell/PHP-PasswordLib/blob/master/test/Data/Vectors/pbkdf2-draft-josefsson-sha256.test-vectors">https://github.com/ircmaxell/PHP-PasswordLib/blob/master/test/Data/Vectors/pbkdf2-draft-josefsson-sha256.test-vectors</a></dd>
  * <dd><a href="https://gitorious.org/scrypt/nettle-scrypt/blobs/37c0d5288e991604fe33dba2f1724986a8dddf56/testsuite/pbkdf2-test.c">https://gitorious.org/scrypt/nettle-scrypt/blobs/37c0d5288e991604fe33dba2f1724986a8dddf56/testsuite/pbkdf2-test.c</a></dd>
  * </dl>
  */
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestPBKDF2 {
 
   @Test
   public final void testPBKDF2SHA256A() throws UnsupportedEncodingException, GeneralSecurityException {
     String  p = "password";
     String  s = "salt";
     int dkLen = 32;
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestPersistedCrypto5Keys.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestPersistedCrypto5Keys.java
@@ -1,30 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.crypto.test;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.CollectionKeys;
+import org.mozilla.gecko.sync.NoCollectionKeysSetException;
+import org.mozilla.gecko.sync.crypto.CryptoException;
+import org.mozilla.gecko.sync.crypto.KeyBundle;
+import org.mozilla.gecko.sync.crypto.PersistedCrypto5Keys;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
-import org.mozilla.gecko.sync.CollectionKeys;
-import org.mozilla.gecko.sync.NoCollectionKeysSetException;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.crypto.PersistedCrypto5Keys;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestPersistedCrypto5Keys {
   MockSharedPreferences prefs = null;
 
   @Before
   public void setUp() {
     prefs = new MockSharedPreferences();
   }
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestSRPConstants.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestSRPConstants.java
@@ -1,22 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.crypto.test;
 
-import java.math.BigInteger;
-
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.net.SRPConstants;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.math.BigInteger;
+
+@RunWith(TestRunner.class)
 public class TestSRPConstants extends SRPConstants {
   public void assertSRPConstants(SRPConstants.Parameters params, int bitLength) {
     Assert.assertNotNull(params.g);
     Assert.assertNotNull(params.N);
     Assert.assertEquals(bitLength, bitLength);
     Assert.assertEquals(bitLength / 8, params.byteLength);
     Assert.assertEquals(bitLength / 4, params.hexLength);
     BigInteger N = params.N;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/middleware/test/TestCrypto5MiddlewareRepositorySession.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/middleware/test/TestCrypto5MiddlewareRepositorySession.java
@@ -1,51 +1,50 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.middleware.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
 import junit.framework.AssertionFailedError;
-
 import org.json.simple.parser.ParseException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionBeginDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionCreationDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionFetchRecordsDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionFinishDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionStoreDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositoryWipeDelegate;
 import org.mozilla.gecko.background.testhelpers.MockRecord;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
 import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepositorySession;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
-import org.robolectric.RobolectricGradleTestRunner;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestCrypto5MiddlewareRepositorySession {
   public static WaitHelper getTestWaiter() {
     return WaitHelper.getTestWaiter();
   }
 
   public static void performWait(Runnable runnable) {
     getTestWaiter().performWait(runnable);
   }
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestHMACAuthHeaderProvider.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestHMACAuthHeaderProvider.java
@@ -1,31 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
+import ch.boye.httpclientandroidlib.client.methods.HttpGet;
+import ch.boye.httpclientandroidlib.client.methods.HttpPost;
+import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
+import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.net.HMACAuthHeaderProvider;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.net.HMACAuthHeaderProvider;
+import static org.junit.Assert.assertEquals;
 
-import ch.boye.httpclientandroidlib.client.methods.HttpGet;
-import ch.boye.httpclientandroidlib.client.methods.HttpPost;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestHMACAuthHeaderProvider {
   // Expose a few protected static member functions as public for testing.
   protected static class LeakyHMACAuthHeaderProvider extends HMACAuthHeaderProvider {
     public LeakyHMACAuthHeaderProvider(String identifier, String key) {
       super(identifier, key);
     }
 
     public static String getRequestString(HttpUriRequest request, long timestampInSeconds, String nonce, String extra) {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestHawkAuthHeaderProvider.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestHawkAuthHeaderProvider.java
@@ -1,41 +1,40 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
-
 import ch.boye.httpclientandroidlib.Header;
 import ch.boye.httpclientandroidlib.client.methods.HttpGet;
 import ch.boye.httpclientandroidlib.client.methods.HttpPost;
 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
 import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
 import ch.boye.httpclientandroidlib.entity.StringEntity;
 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
 import ch.boye.httpclientandroidlib.message.BasicHeader;
 import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-import org.robolectric.RobolectricGradleTestRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import static org.junit.Assert.assertEquals;
 
 /**
  * These test vectors were taken from
  * <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/README.md">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/README.md</a>.
  */
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestHawkAuthHeaderProvider {
   // Expose a few protected static member functions as public for testing.
   protected static class LeakyHawkAuthHeaderProvider extends HawkAuthHeaderProvider {
     public LeakyHawkAuthHeaderProvider(String tokenId, byte[] reqHMACKey) {
       // getAuthHeader takes includePayloadHash as a parameter.
       super(tokenId, reqHMACKey, false, 0L);
     }
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestLiveHawkAuth.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestLiveHawkAuth.java
@@ -1,38 +1,37 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.net.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.GeneralSecurityException;
-
+import ch.boye.httpclientandroidlib.Header;
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.client.ClientProtocolException;
+import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
+import ch.boye.httpclientandroidlib.entity.StringEntity;
+import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
+import ch.boye.httpclientandroidlib.message.BasicHeader;
+import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
 import org.junit.Assert;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.Resource;
 import org.mozilla.gecko.sync.net.SyncResponse;
 
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.entity.StringEntity;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.message.BasicHeader;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 
 public class TestLiveHawkAuth {
   /**
    * Hawk comes with an example/usage.js server. Modify it to serve indefinitely,
    * un-comment the following line, and verify that the port and credentials
    * have not changed; then the following test should pass.
    */
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestUserAgentHeaders.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestUserAgentHeaders.java
@@ -1,43 +1,41 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.net.test;
 
-import java.util.concurrent.Executors;
-
+import ch.boye.httpclientandroidlib.protocol.HTTP;
 import junit.framework.Assert;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockServer;
 import org.mozilla.gecko.background.fxa.FxAccountClient10;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.StatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
 import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.robolectric.RobolectricGradleTestRunner;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
-import ch.boye.httpclientandroidlib.protocol.HTTP;
+import java.util.concurrent.Executors;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestUserAgentHeaders {
   private static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
   private static final String TEST_SERVER = "http://localhost:" + TEST_PORT;
 
   protected final HTTPServerTestHelper data = new HTTPServerTestHelper();
 
   protected class UserAgentServer extends MockServer {
     public String lastUserAgent = null;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/android/test/TestBookmarksInsertionManager.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/android/test/TestBookmarksInsertionManager.java
@@ -1,32 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.repositories.android.test;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.sync.repositories.android.BookmarksInsertionManager;
+import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.BookmarksInsertionManager;
-import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
-import org.robolectric.RobolectricGradleTestRunner;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestBookmarksInsertionManager {
   public BookmarksInsertionManager manager;
   public ArrayList<String[]> insertions;
 
   @Before
   public void setUp() {
     insertions = new ArrayList<String[]>();
     Set<String> writtenFolders = new HashSet<String>();
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/domain/TestClientRecord.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/domain/TestClientRecord.java
@@ -1,24 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.repositories.domain;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.CryptoRecord;
+import org.mozilla.gecko.sync.Utils;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.Utils;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestClientRecord {
 
   @Test
   public void testEnsureDefaults() {
     // Ensure defaults.
     ClientRecord record = new ClientRecord();
     assertEquals(ClientRecord.COLLECTION_NAME, record.collection);
     assertEquals(0, record.lastModified);
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/domain/test/TestFormHistoryRecord.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/domain/test/TestFormHistoryRecord.java
@@ -1,25 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.repositories.domain.test;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.CryptoRecord;
+import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.sync.repositories.domain.FormHistoryRecord;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.domain.FormHistoryRecord;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestFormHistoryRecord {
   public static FormHistoryRecord withIdFieldNameAndValue(long id, String fieldName, String value) {
     FormHistoryRecord fr = new FormHistoryRecord();
     fr.androidID = id;
     fr.fieldName = fieldName;
     fr.fieldValue = value;
 
     return fr;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/test/TestRepositorySessionBundle.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/test/TestRepositorySessionBundle.java
@@ -1,21 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.repositories.test;
 
-import static org.junit.Assert.*;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-import org.robolectric.RobolectricGradleTestRunner;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+@RunWith(TestRunner.class)
 public class TestRepositorySessionBundle {
   @Test
   public void testSetGetTimestamp() {
     RepositorySessionBundle bundle = new RepositorySessionBundle(-1);
     assertEquals(-1, bundle.getTimestamp());
 
     bundle.setTimestamp(10);
     assertEquals(10, bundle.getTimestamp());
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/test/TestSafeConstrainedServer11Repository.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/test/TestSafeConstrainedServer11Repository.java
@@ -1,34 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.repositories.test;
 
-import java.net.URISyntaxException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockServer;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.InfoCollections;
 import org.mozilla.gecko.sync.JSONRecordFetcher;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.stage.SafeConstrainedServer11Repository;
-import org.robolectric.RobolectricGradleTestRunner;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.net.URISyntaxException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@RunWith(TestRunner.class)
 public class TestSafeConstrainedServer11Repository {
   private static final int     TEST_PORT      = HTTPServerTestHelper.getTestPort();
   private static final String  TEST_SERVER    = "http://localhost:" + TEST_PORT + "/";
   private static final String  TEST_USERNAME  = "c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd";
   private static final String  TEST_BASE_PATH = "/1.1/" + TEST_USERNAME + "/";
 
   public AuthHeaderProvider getAuthHeaderProvider() {
     return null;
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestEnsureClusterURLStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestEnsureClusterURLStage.java
@@ -1,48 +1,47 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.stage.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.ProtocolVersion;
+import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
+import ch.boye.httpclientandroidlib.message.BasicStatusLine;
 import org.json.simple.parser.ParseException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
 import org.mozilla.android.sync.test.helpers.MockServer;
 import org.mozilla.gecko.background.testhelpers.MockGlobalSession;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.AlreadySyncingException;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.NodeAuthenticationException;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SyncConfigurationException;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.stage.EnsureClusterURLStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.ProtocolVersion;
-import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
-import ch.boye.httpclientandroidlib.message.BasicStatusLine;
-import org.robolectric.RobolectricGradleTestRunner;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestEnsureClusterURLStage {
   private static final int     TEST_PORT        = HTTPServerTestHelper.getTestPort();
   private static final String  TEST_SERVER      = "http://localhost:" + TEST_PORT + "/";
   private static final String  TEST_OLD_CLUSTER_URL = TEST_SERVER + "cluster/old/";
   private static final String  TEST_NEW_CLUSTER_URL = TEST_SERVER + "cluster/new/";
   static String                TEST_NW_URL      = TEST_SERVER + "/1.0/c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd/node/weave"; // GET https://server/pathname/version/username/node/weave
   private HTTPServerTestHelper data             = new HTTPServerTestHelper();
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestEnsureCrypto5KeysStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestEnsureCrypto5KeysStage.java
@@ -1,44 +1,44 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.stage.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collection;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
 import org.mozilla.android.sync.test.helpers.MockServer;
 import org.mozilla.gecko.background.testhelpers.MockGlobalSession;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.AlreadySyncingException;
 import org.mozilla.gecko.sync.CollectionKeys;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.InfoCollections;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.stage.EnsureCrypto5KeysStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-import org.robolectric.RobolectricGradleTestRunner;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestEnsureCrypto5KeysStage {
   private int          TEST_PORT                = HTTPServerTestHelper.getTestPort();
   private final String TEST_CLUSTER_URL         = "http://localhost:" + TEST_PORT;
   private final String TEST_USERNAME            = "johndoe";
   private final String TEST_PASSWORD            = "password";
   private final String TEST_SYNC_KEY            = "abcdeabcdeabcdeabcdeabcdea";
 
   private final String TEST_JSON_NO_CRYPTO =
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestFetchMetaGlobalStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestFetchMetaGlobalStage.java
@@ -1,33 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.stage.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 import org.json.simple.JSONArray;
 import org.json.simple.parser.ParseException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.net.test.TestMetaGlobal;
 import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
 import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
 import org.mozilla.android.sync.test.helpers.MockServer;
 import org.mozilla.gecko.background.testhelpers.MockGlobalSession;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.AlreadySyncingException;
 import org.mozilla.gecko.sync.CollectionKeys;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.InfoCollections;
 import org.mozilla.gecko.sync.MetaGlobal;
@@ -36,21 +27,30 @@ import org.mozilla.gecko.sync.SyncConfig
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.delegates.FreshStartDelegate;
 import org.mozilla.gecko.sync.delegates.KeyUploadDelegate;
 import org.mozilla.gecko.sync.delegates.WipeServerDelegate;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.stage.FetchMetaGlobalStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-import org.robolectric.RobolectricGradleTestRunner;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestFetchMetaGlobalStage {
   @SuppressWarnings("unused")
   private static final String  LOG_TAG          = "TestMetaGlobalStage";
 
   private static final int     TEST_PORT        = HTTPServerTestHelper.getTestPort();
   private static final String  TEST_SERVER      = "http://localhost:" + TEST_PORT + "/";
   private static final String  TEST_CLUSTER_URL = TEST_SERVER + "cluster/";
   static String                TEST_NW_URL      = TEST_SERVER + "/1.0/c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd/node/weave"; // GET https://server/pathname/version/username/node/weave
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestStageLookup.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestStageLookup.java
@@ -1,24 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.stage.test;
 
-import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 
 import java.util.HashSet;
 import java.util.Set;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-import org.robolectric.RobolectricGradleTestRunner;
+import static org.junit.Assert.assertEquals;
 
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestStageLookup {
 
   @Test
   public void testStageLookupByName() {
     Set<Stage> namedStages = new HashSet<Stage>(Stage.getNamedStages());
     Set<Stage> expected = new HashSet<Stage>();
     expected.add(Stage.syncClientsEngine);
     expected.add(Stage.syncBookmarks);
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/test/TestExtendedJSONObject.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/test/TestExtendedJSONObject.java
@@ -1,38 +1,38 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.test;
 
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.ParseException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.sync.NonArrayJSONException;
+import org.mozilla.gecko.sync.NonObjectJSONException;
+import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException;
+
+import java.io.IOException;
+
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import java.io.IOException;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.json.simple.parser.ParseException;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException;
-import org.robolectric.RobolectricGradleTestRunner;
-
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestExtendedJSONObject {
   public static String exampleJSON = "{\"modified\":1233702554.25,\"success\":[\"{GXS58IDC}12\",\"{GXS58IDC}13\",\"{GXS58IDC}15\",\"{GXS58IDC}16\",\"{GXS58IDC}18\",\"{GXS58IDC}19\"],\"failed\":{\"{GXS58IDC}11\":[\"invalid parentid\"],\"{GXS58IDC}14\":[\"invalid parentid\"],\"{GXS58IDC}17\":[\"invalid parentid\"],\"{GXS58IDC}20\":[\"invalid parentid\"]}}";
   public static String exampleIntegral = "{\"modified\":1233702554,}";
 
   @Test
   public void testDeepCopy() throws NonObjectJSONException, IOException, ParseException, NonArrayJSONException {
     ExtendedJSONObject a = new ExtendedJSONObject(exampleJSON);
     ExtendedJSONObject c = a.deepCopy();
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/test/TestInfoCollections.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/test/TestInfoCollections.java
@@ -1,30 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.test;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.sync.InfoCollections;
+import org.mozilla.gecko.sync.InfoCounts;
+import org.mozilla.gecko.sync.Utils;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.InfoCounts;
-import org.mozilla.gecko.sync.Utils;
-import org.robolectric.RobolectricGradleTestRunner;
-
 /**
  * Test both info/collections and info/collection_counts.
  */
-@RunWith(RobolectricGradleTestRunner.class)
+@RunWith(TestRunner.class)
 public class TestInfoCollections {
   public static final String TEST_COLLECTIONS_JSON =
       "{\"history\":1.3319567131E9, "    +
       " \"bookmarks\":1.33195669592E9, " +
       " \"prefs\":1.33115408641E9, "     +
       " \"crypto\":1.32046063664E9, "    +
       " \"meta\":1.321E9, "              +
       " \"forms\":1.33136685374E9, "     +
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/test/TestPersistedMetaGlobal.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/test/TestPersistedMetaGlobal.java
@@ -1,35 +1,35 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Set;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.MetaGlobal;
 import org.mozilla.gecko.sync.NoCollectionKeysSetException;
 import org.mozilla.gecko.sync.PersistedMetaGlobal;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
-import org.robolectric.RobolectricGradleTestRunner;
+
+import java.util.Set;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(TestRunner.class)
 public class TestPersistedMetaGlobal {
   MockSharedPreferences prefs = null;
   private final String TEST_META_URL = "metaURL";
   private final String TEST_CREDENTIALS = "credentials";
 
   @Before
   public void setUp() {
     prefs = new MockSharedPreferences();
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/tokenserver/test/TestTokenServerClient.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/tokenserver/test/TestTokenServerClient.java
@@ -1,58 +1,56 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.tokenserver.test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
+import ch.boye.httpclientandroidlib.Header;
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.ProtocolVersion;
+import ch.boye.httpclientandroidlib.client.methods.HttpGet;
+import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
+import ch.boye.httpclientandroidlib.entity.StringEntity;
+import ch.boye.httpclientandroidlib.message.BasicHeader;
+import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
+import ch.boye.httpclientandroidlib.message.BasicStatusLine;
 import junit.framework.Assert;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.common.log.writers.StringLogWriter;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.SyncResponse;
 import org.mozilla.gecko.tokenserver.TokenServerClient;
 import org.mozilla.gecko.tokenserver.TokenServerClient.TokenFetchResourceDelegate;
 import org.mozilla.gecko.tokenserver.TokenServerClientDelegate;
 import org.mozilla.gecko.tokenserver.TokenServerException;
 import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerConditionsRequiredException;
 import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerInvalidCredentialsException;
 import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerMalformedRequestException;
 import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerMalformedResponseException;
 import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerUnknownServiceException;
 import org.mozilla.gecko.tokenserver.TokenServerToken;
 
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.ProtocolVersion;
-import ch.boye.httpclientandroidlib.client.methods.HttpGet;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.entity.StringEntity;
-import ch.boye.httpclientandroidlib.message.BasicHeader;
-import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
-import ch.boye.httpclientandroidlib.message.BasicStatusLine;
-import org.robolectric.RobolectricGradleTestRunner;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 
-@RunWith(RobolectricGradleTestRunner.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(TestRunner.class)
 public class TestTokenServerClient {
   public static final String JSON = "application/json";
   public static final String TEXT = "text/plain";
 
   public static final String TEST_TOKEN_RESPONSE = "{\"api_endpoint\": \"https://stage-aitc1.services.mozilla.com/1.0/1659259\"," +
       "\"duration\": 300," +
       "\"id\": \"eySHORTENED\"," +
       "\"key\": \"-plSHORTENED\"," +
--- a/toolkit/components/search/tests/xpcshell/test_geodefaults.js
+++ b/toolkit/components/search/tests/xpcshell/test_geodefaults.js
@@ -62,27 +62,21 @@ function checkRequest(cohort = "") {
   do_check_eq(req._queryString, cohort ? "/" + cohort : "");
 }
 
 add_task(function* no_request_if_prefed_off() {
   // Disable geoSpecificDefaults and check no HTTP request is made.
   Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
   yield asyncInit();
   checkNoRequest();
+  yield promiseAfterCache();
 
   // The default engine should be set based on the prefs.
   do_check_eq(Services.search.currentEngine.name, getDefaultEngineName(false));
 
-  // Change the default engine and then revert to default to ensure
-  // the metadata file exists.
-  let commitPromise = promiseAfterCache();
-  Services.search.currentEngine = Services.search.getEngineByName(kTestEngineName);
-  Services.search.resetToOriginalDefaultEngine();
-  yield commitPromise;
-
   // Ensure nothing related to geoSpecificDefaults has been written in the metadata.
   let metadata = yield promiseGlobalMetadata();
   do_check_eq(typeof metadata.searchDefaultExpir, "undefined");
   do_check_eq(typeof metadata.searchDefault, "undefined");
   do_check_eq(typeof metadata.searchDefaultHash, "undefined");
 
   Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", true);
 });