Bug 1488899 - Get TPS working. r=tcsc
authorMark Hammond <mhammond@skippinet.com.au>
Mon, 15 Oct 2018 22:42:22 +0000
changeset 489703 5622861eb245cc20b3a121ffb73f1b58c9745934
parent 489702 336f795d5b2621dd2d693649f815ad65182f5c17
child 489704 1119f9458b5d76cef62577b526c508f7908f4710
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerstcsc
bugs1488899
milestone64.0a1
Bug 1488899 - Get TPS working. r=tcsc This patch disables a couple of tests, removes old async support from TPS, and puts more effort into preventing Syncs while TPS is running, and removed an unused import. Differential Revision: https://phabricator.services.mozilla.com/D8692
services/sync/modules/browserid_identity.js
services/sync/tests/tps/.eslintrc.js
services/sync/tests/tps/all_tests.json
services/sync/tests/tps/test_addon_reconciling.js
services/sync/tests/tps/test_addon_restartless_xpi.js
services/sync/tests/tps/test_addon_webext_xpi.js
services/sync/tests/tps/test_addon_wipe.js
services/sync/tests/tps/test_addresses.js
services/sync/tests/tps/test_creditcards.js
services/sync/tests/tps/test_tabs.js
services/sync/tps/extensions/tps/api.js
services/sync/tps/extensions/tps/resource/modules/tabs.jsm
services/sync/tps/extensions/tps/resource/modules/windows.jsm
services/sync/tps/extensions/tps/resource/tps.jsm
testing/tps/create_venv.py
testing/tps/tps/testrunner.py
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -251,17 +251,19 @@ this.BrowserIDManager.prototype = {
       let isFirstSync = !Weave.Service.locked && !Svc.Prefs.get("client.syncID", null);
       if (isFirstSync) {
         this._log.info("Doing initial sync actions");
         Svc.Prefs.set("firstSync", "resetClient");
         Services.obs.notifyObservers(null, "weave:service:setup-complete");
       }
       // There's no need to wait for sync to complete and it would deadlock
       // our AsyncObserver.
-      Weave.Service.sync({why: "login"});
+      if (!Svc.Prefs.get("testing.tps", false)) {
+        Weave.Service.sync({why: "login"});
+      }
       break;
     }
 
     case fxAccountsCommon.ONLOGOUT_NOTIFICATION:
       Weave.Service.startOver().then(() => {
         this._log.trace("startOver completed");
       }).catch(err => {
         this._log.warn("Failed to reset sync", err);
--- a/services/sync/tests/tps/.eslintrc.js
+++ b/services/sync/tests/tps/.eslintrc.js
@@ -22,10 +22,20 @@ module.exports = {
     "STATE_DISABLED": false,
     "STATE_ENABLED": false,
     "Sync": false,
     "SYNC_WIPE_CLIENT": false,
     "SYNC_WIPE_REMOTE": false,
     "Tabs": false,
     "Windows": false,
     "WipeServer": false,
-  }
+  },
+  // TPS test files are also hackily parsed by python's JSON parser,
+  // so trailing commas aren't valid.
+  "overrides": [{
+    "files": [
+      "test_*.js",
+    ],
+    "rules": {
+      "comma-dangle": "off",
+    }
+  }]
 };
--- a/services/sync/tests/tps/all_tests.json
+++ b/services/sync/tests/tps/all_tests.json
@@ -1,34 +1,34 @@
-{ "tests": [
-    "test_bookmark_conflict.js",
-    "test_sync.js",
-    "test_prefs.js",
-    "test_tabs.js",
-    "test_passwords.js",
-    "test_history.js",
-    "test_formdata.js",
-    "test_bug530717.js",
-    "test_bug531489.js",
-    "test_bug538298.js",
-    "test_bug556509.js",
-    "test_bug562515.js",
-    "test_bug535326.js",
-    "test_bug501528.js",
-    "test_bug575423.js",
-    "test_bug546807.js",
-    "test_history_collision.js",
-    "test_privbrw_passwords.js",
-    "test_privbrw_tabs.js",
-    "test_bookmarks_in_same_named_folder.js",
-    "test_client_wipe.js",
-    "test_special_tabs.js",
-    "test_addon_restartless_xpi.js",
-    "test_addon_webext_xpi.js",
-    "test_addon_reconciling.js",
-    "test_addon_wipe.js",
-    "test_existing_bookmarks.js",
-    "test_addresses.js",
-    "test_creditcards.js"
-  ]
+{ "tests": {
+    "test_bookmark_conflict.js": {},
+    "test_sync.js": {},
+    "test_prefs.js": {},
+    "test_tabs.js": {},
+    "test_passwords.js": {},
+    "test_history.js": {},
+    "test_formdata.js": {},
+    "test_bug530717.js": {},
+    "test_bug531489.js": {},
+    "test_bug538298.js": {},
+    "test_bug556509.js": {},
+    "test_bug562515.js": {},
+    "test_bug535326.js": {},
+    "test_bug501528.js": {},
+    "test_bug575423.js": {},
+    "test_bug546807.js": {},
+    "test_history_collision.js": {},
+    "test_privbrw_passwords.js": {},
+    "test_privbrw_tabs.js": {},
+    "test_bookmarks_in_same_named_folder.js": {},
+    "test_client_wipe.js": {},
+    "test_special_tabs.js": {},
+    "test_addon_restartless_xpi.js": {"disabled": "Bug 1498974"},
+    "test_addon_webext_xpi.js": {"disabled": "Bug 1498974"},
+    "test_addon_reconciling.js": {"disabled": "Bug 1498974"},
+    "test_addon_wipe.js": {},
+    "test_existing_bookmarks.js": {},
+    "test_addresses.js": {},
+    "test_creditcards.js": {}
+  }
 }
 
 
--- a/services/sync/tests/tps/test_addon_reconciling.js
+++ b/services/sync/tests/tps/test_addon_reconciling.js
@@ -7,17 +7,17 @@
 EnableEngines(["addons"]);
 
 var phases = {
   "phase01": "profile1",
   "phase02": "profile2",
   "phase03": "profile1",
   "phase04": "profile2",
   "phase05": "profile1",
-  "phase06": "profile2",
+  "phase06": "profile2"
 };
 
 const id = "restartless-xpi@tests.mozilla.org";
 
 // Install the add-on in 2 profiles.
 Phase("phase01", [
   [Addons.verifyNot, [id]],
   [Addons.install, [id]],
--- a/services/sync/tests/tps/test_addon_restartless_xpi.js
+++ b/services/sync/tests/tps/test_addon_restartless_xpi.js
@@ -8,17 +8,17 @@ EnableEngines(["addons"]);
 var phases = {
   "phase01": "profile1",
   "phase02": "profile2",
   "phase03": "profile1",
   "phase04": "profile2",
   "phase05": "profile1",
   "phase06": "profile2",
   "phase07": "profile1",
-  "phase08": "profile2",
+  "phase08": "profile2"
 };
 
 const id = "restartless-xpi@tests.mozilla.org";
 
 // Verify install is synced
 Phase("phase01", [
   [Addons.verifyNot, [id]],
   [Addons.install, [id]],
--- a/services/sync/tests/tps/test_addon_webext_xpi.js
+++ b/services/sync/tests/tps/test_addon_webext_xpi.js
@@ -9,17 +9,17 @@ EnableEngines(["addons"]);
 var phases = {
   "phase01": "profile1",
   "phase02": "profile2",
   "phase03": "profile1",
   "phase04": "profile2",
   "phase05": "profile1",
   "phase06": "profile2",
   "phase07": "profile1",
-  "phase08": "profile2",
+  "phase08": "profile2"
 };
 
 const id = "test-webext@quality.mozilla.org";
 
 // Verify install is synced
 Phase("phase01", [
   [Addons.verifyNot, [id]],
   [Addons.install, [id]],
--- a/services/sync/tests/tps/test_addon_wipe.js
+++ b/services/sync/tests/tps/test_addon_wipe.js
@@ -6,17 +6,17 @@
 // specifically around AddonsReconciler. This test is in response to bug
 // 792990.
 
 EnableEngines(["addons"]);
 
 var phases = {
   "phase01": "profile1",
   "phase02": "profile1",
-  "phase03": "profile1",
+  "phase03": "profile1"
 };
 
 const id1 = "restartless-xpi@tests.mozilla.org";
 const id2 = "test-webext@quality.mozilla.org";
 
 Phase("phase01", [
   [Addons.install, [id1]],
   [Addons.install, [id2]],
--- a/services/sync/tests/tps/test_addresses.js
+++ b/services/sync/tests/tps/test_addresses.js
@@ -4,17 +4,17 @@
 /* global Services */
 Services.prefs.setBoolPref("services.sync.engine.addresses", true);
 
 EnableEngines(["addresses"]);
 
 var phases = {
   "phase1": "profile1",
   "phase2": "profile2",
-  "phase3": "profile1",
+  "phase3": "profile1"
 };
 
 const address1 = [{
   "given-name": "Timothy",
   "additional-name": "John",
   "family-name": "Berners-Lee",
   "organization": "World Wide Web Consortium",
   "street-address": "32 Vassar Street\nMIT Room 32-G524",
--- a/services/sync/tests/tps/test_creditcards.js
+++ b/services/sync/tests/tps/test_creditcards.js
@@ -4,17 +4,17 @@
 /* global Services */
 Services.prefs.setBoolPref("services.sync.engine.creditcards", true);
 
 EnableEngines(["creditcards"]);
 
 var phases = {
   "phase1": "profile1",
   "phase2": "profile2",
-  "phase3": "profile1",
+  "phase3": "profile1"
 };
 
 const cc1 = [{
   "cc-name": "John Doe",
   "cc-number": "4716179744040592",
   "cc-exp-month": 4,
   "cc-exp-year": 2050,
   "changes": {
--- a/services/sync/tests/tps/test_tabs.js
+++ b/services/sync/tests/tps/test_tabs.js
@@ -13,28 +13,26 @@ var phases = { "phase1": "profile1",
                "phase3": "profile1"};
 
 /*
  * Tab lists.
  */
 
 var tabs1 = [
   { uri: "https://www.mozilla.org/en-US/firefox/",
-    title: "Firefox",
     profile: "profile1",
   },
   { uri: "data:text/html,<html><head><title>Hello</title></head><body>Hello</body></html>",
     title: "Hello",
     profile: "profile1",
   },
 ];
 
 var tabs2 = [
   { uri: "https://www.mozilla.org/en-US/contribute/",
-    title: "Get Involved",
     profile: "profile2",
   },
   { uri: "data:text/html,<html><head><title>Bye</title></head><body>Bye</body></html>",
     profile: "profile2",
   },
 ];
 
 /*
--- a/services/sync/tps/extensions/tps/api.js
+++ b/services/sync/tps/extensions/tps/api.js
@@ -37,16 +37,17 @@ async function tpsStartup() {
     } catch (err) {
       TPS.DumpError("TestPhase failed", err);
     }
   } catch (e) {
     if (typeof TPS != "undefined") {
       // Note: This calls quit() under the hood
       TPS.DumpError("Test initialization failed", e);
     }
+    dump(`TPS test initialization failed: ${e} - ${e.stack}\n`);
     // Try and quit right away, no reason to wait around for python
     // to kill us if initialization failed.
     Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
   }
 }
 
 function onStartupFinished() {
   return new Promise(resolve => {
--- a/services/sync/tps/extensions/tps/resource/modules/tabs.jsm
+++ b/services/sync/tps/extensions/tps/resource/modules/tabs.jsm
@@ -35,17 +35,17 @@ var BrowserTabs = {
    * given uri. Rejects on error.
    *
    * @param uri The uri to load in the new tab
    * @return Promise
    */
   async Add(uri) {
     let mainWindow = Services.wm.getMostRecentWindow("navigator:browser");
     let browser = mainWindow.getBrowser();
-    let newtab = browser.addTab(uri);
+    let newtab = browser.addTrustedTab(uri);
 
     // Wait for the tab to load.
     await new Promise(resolve => {
       let mm = browser.ownerGlobal.messageManager;
       mm.addMessageListener("tps:loadEvent", function onLoad(msg) {
         mm.removeMessageListener("tps:loadEvent", onLoad);
         resolve();
       });
@@ -79,14 +79,15 @@ var BrowserTabs = {
       for (let key in tabClient.tabs) {
         let tab = tabClient.tabs[key];
         let weaveTabUrl = tab.urlHistory[0];
         if (uri == weaveTabUrl && profile == client.name) {
           if (title == undefined || title == tab.title) {
             return true;
           }
         }
-        }
-        Logger.logInfo(`Dumping tabs for ${client.clientName}...\n` + JSON.stringify(client.tabs));
       }
+      Logger.logInfo(`Dumping tabs for ${client.name}...\n` +
+                     JSON.stringify(tabClient.tabs, null, 2));
+    }
     return false;
   },
 };
--- a/services/sync/tps/extensions/tps/resource/modules/windows.jsm
+++ b/services/sync/tps/extensions/tps/resource/modules/windows.jsm
@@ -17,15 +17,17 @@ var BrowserWindows = {
    * Add
    *
    * Opens a new window. Throws on error.
    *
    * @param aPrivate The private option.
    * @return nothing
    */
   Add(aPrivate, fn) {
-    let mainWindow = Services.wm.getMostRecentWindow("navigator:browser");
-    let win = mainWindow.OpenBrowserWindow({private: aPrivate});
-    win.addEventListener("load", function() {
-      fn.call(win);
-    }, {once: true});
+    return new Promise(resolve => {
+      let mainWindow = Services.wm.getMostRecentWindow("navigator:browser");
+      let win = mainWindow.OpenBrowserWindow({private: aPrivate});
+      win.addEventListener("load", function() {
+        resolve(win);
+      }, {once: true});
+    });
   },
 };
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -11,26 +11,26 @@ var EXPORTED_SYMBOLS = [
   "ACTIONS", "Addons", "Addresses", "Bookmarks", "CreditCards",
   "Formdata", "History", "Passwords", "Prefs",
   "Tabs", "TPS", "Windows",
 ];
 
 var module = this;
 
 // Global modules
-ChromeUtils.import("resource://formautofill/FormAutofillSync.jsm");
 ChromeUtils.import("resource://gre/modules/Log.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/PlacesUtils.jsm");
 ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
 ChromeUtils.import("resource://gre/modules/osfile.jsm");
+ChromeUtils.import("resource:///modules/sessionstore/SessionStore.jsm");
 ChromeUtils.import("resource://services-common/async.js");
 ChromeUtils.import("resource://services-common/utils.js");
 ChromeUtils.import("resource://services-sync/constants.js");
 ChromeUtils.import("resource://services-sync/main.js");
 ChromeUtils.import("resource://services-sync/util.js");
 ChromeUtils.import("resource://services-sync/telemetry.js");
 ChromeUtils.import("resource://services-sync/bookmark_validator.js");
 ChromeUtils.import("resource://services-sync/engines/passwords.js");
@@ -90,17 +90,16 @@ const ACTIONS = [
   ACTION_SYNC_WIPE_REMOTE,
   ACTION_VERIFY,
   ACTION_VERIFY_NOT,
 ];
 
 const OBSERVER_TOPICS = ["fxaccounts:onlogin",
                          "fxaccounts:onlogout",
                          "profile-before-change",
-                         "sessionstore-windows-restored",
                          "weave:service:tracking-started",
                          "weave:service:tracking-stopped",
                          "weave:service:login:error",
                          "weave:service:setup-complete",
                          "weave:service:sync:finish",
                          "weave:service:sync:delayed",
                          "weave:service:sync:error",
                          "weave:service:sync:start",
@@ -109,17 +108,16 @@ const OBSERVER_TOPICS = ["fxaccounts:onl
                         ];
 
 var TPS = {
   _currentAction: -1,
   _currentPhase: -1,
   _enabledEngines: null,
   _errors: 0,
   _isTracking: false,
-  _operations_pending: 0,
   _phaseFinished: false,
   _phaselist: {},
   _setupComplete: false,
   _syncActive: false,
   _syncCount: 0,
   _syncsReportedViaTelemetry: 0,
   _syncErrors: 0,
   _syncWipeAction: null,
@@ -128,28 +126,37 @@ var TPS = {
   _test: null,
   _triggeredSync: false,
   _msSinceEpoch: 0,
   _requestedQuit: false,
   shouldValidateAddons: false,
   shouldValidateBookmarks: false,
   shouldValidatePasswords: false,
   shouldValidateForms: false,
-  _windowsUpDeferred: PromiseUtils.defer(),
   _placesInitDeferred: PromiseUtils.defer(),
 
   _init: function TPS__init() {
     this.delayAutoSync();
 
     OBSERVER_TOPICS.forEach(function(aTopic) {
       Services.obs.addObserver(this, aTopic, true);
     }, this);
 
     /* global Authentication */
     ChromeUtils.import("resource://tps/auth/fxaccounts.jsm", module);
+
+    // Some engines bump their score during their sync, which then causes
+    // another sync immediately (notably, prefs and addons). We don't want
+    // this to happen, and there's no obvious preference to kill it - so
+    // we do this nasty hack to ensure the global score is always zero.
+    Services.prefs.addObserver("services.sync.globalScore", () => {
+      if (Weave.Service.scheduler.globalScore != 0) {
+        Weave.Service.scheduler.globalScore = 0;
+      }
+    });
   },
 
   DumpError(msg, exc = null) {
     this._errors++;
     let errInfo;
     if (exc) {
       errInfo = Log.exceptionStr(exc); // includes details and stack-trace.
     } else {
@@ -172,20 +179,16 @@ var TPS = {
           OBSERVER_TOPICS.forEach(function(topic) {
             Services.obs.removeObserver(this, topic);
           }, this);
 
           Logger.close();
 
           break;
 
-        case "sessionstore-windows-restored":
-          this._windowsUpDeferred.resolve();
-          break;
-
         case "places-browser-init-complete":
           this._placesInitDeferred.resolve();
           break;
 
         case "weave:service:setup-complete":
           this._setupComplete = true;
 
           if (this._syncWipeAction) {
@@ -218,23 +221,16 @@ var TPS = {
           break;
 
         case "weave:service:resyncs-finished":
           this._syncActive = false;
           this._syncErrors = 0;
           this._triggeredSync = false;
 
           this.delayAutoSync();
-
-          // Wait a second before continuing, otherwise we can get
-          // 'sync not complete' errors.
-          CommonUtils.namedTimer(function() {
-            this.FinishAsyncOperation();
-          }, 1000, this, "postsync");
-
           break;
 
         case "weave:service:sync:start":
           // Ensure that the sync operation has been started by TPS
           if (!this._triggeredSync) {
             this.DumpError("Automatic sync got triggered, which is not allowed.");
           }
 
@@ -251,65 +247,39 @@ var TPS = {
       }
     } catch (e) {
       this.DumpError("Observer failed", e);
 
     }
   },
 
   /**
-   * Given that we cannot complely disable the automatic sync operations, we
+   * Given that we cannot completely disable the automatic sync operations, we
    * massively delay the next sync. Sync operations have to only happen when
    * directly called via TPS.Sync()!
    */
   delayAutoSync: function TPS_delayAutoSync() {
     Weave.Svc.Prefs.set("scheduler.immediateInterval", 7200);
     Weave.Svc.Prefs.set("scheduler.idleInterval", 7200);
     Weave.Svc.Prefs.set("scheduler.activeInterval", 7200);
     Weave.Svc.Prefs.set("syncThreshold", 10000000);
   },
 
-  StartAsyncOperation: function TPS__StartAsyncOperation() {
-    this._operations_pending++;
-  },
-
-  FinishAsyncOperation: function TPS__FinishAsyncOperation() {
-    // We fire a FinishAsyncOperation at the end of a sync finish (somewhat
-    // dubious, but we're consistent about it), but a sync may or may not be
-    // triggered during login, and it's hard for us to know (without e.g.
-    // auth/fxaccounts.jsm calling back into this module). So we just assume
-    // the FinishAsyncOperation without a StartAsyncOperation is fine, and works
-    // as if a StartAsyncOperation had been called.
-    if (this._operations_pending) {
-      this._operations_pending--;
-    }
-    if (!this.operations_pending) {
-      this._currentAction++;
-      CommonUtils.nextTick(function() {
-        this.RunNextTestAction().catch(err => {
-          this.DumpError("RunNextTestActionFailed", err);
-        });
-      }, this);
-    }
-  },
-
   quit: function TPS__quit() {
+    Logger.logInfo("quitting");
     this._requestedQuit = true;
     this.goQuitApplication();
   },
 
-  HandleWindows(aWindow, action) {
+  async HandleWindows(aWindow, action) {
     Logger.logInfo("executing action " + action.toUpperCase() +
                    " on window " + JSON.stringify(aWindow));
     switch (action) {
       case ACTION_ADD:
-        BrowserWindows.Add(aWindow.private, win => {
-          Logger.logInfo("window finished loading");
-          this.FinishAsyncOperation();
-        });
+        await BrowserWindows.Add(aWindow.private);
         break;
     }
     Logger.logPass("executing action " + action.toUpperCase() + " on windows");
   },
 
   async HandleTabs(tabs, action) {
     for (let tab of tabs) {
       Logger.logInfo("executing action " + action.toUpperCase() +
@@ -779,19 +749,21 @@ var TPS = {
     return this.ValidateCollection("forms", FormValidator);
   },
 
   ValidateAddons() {
     return this.ValidateCollection("addons", AddonValidator);
   },
 
   async RunNextTestAction() {
+    Logger.logInfo("Running next test action");
     try {
       if (this._currentAction >= this._phaselist[this._currentPhase].length) {
         // Run necessary validations and then finish up
+        Logger.logInfo("No more actions - running validations...");
         if (this.shouldValidateBookmarks) {
           await this.ValidateBookmarks();
         }
         if (this.shouldValidatePasswords) {
           await this.ValidatePasswords();
         }
         if (this.shouldValidateForms) {
           await this.ValidateForms();
@@ -821,21 +793,16 @@ var TPS = {
         return;
       }
 
       let phase = this._phaselist[this._currentPhase];
       let action = phase[this._currentAction];
       Logger.logInfo("starting action: " + action[0].name);
       await action[0].apply(this, action.slice(1));
 
-      // if we're in an async operation, don't continue on to the next action
-      if (this._operations_pending) {
-        return;
-      }
-
       this._currentAction++;
     } catch (e) {
       if (Async.isShutdownException(e)) {
         if (this._requestedQuit) {
           Logger.logInfo("Sync aborted due to requested shutdown");
         } else {
           this.DumpError("Sync aborted due to shutdown, but we didn't request it");
         }
@@ -1012,17 +979,17 @@ var TPS = {
 
       Logger.logInfo("setting client.name to " + this.phases[this._currentPhase]);
       Weave.Svc.Prefs.set("client.name", this.phases[this._currentPhase]);
 
       this._interceptSyncTelemetry();
 
       // start processing the test actions
       this._currentAction = 0;
-      await this._windowsUpDeferred.promise;
+      await SessionStore.promiseAllWindowsRestored;
       await this.RunNextTestAction();
     } catch (e) {
       this.DumpError("_executeTestPhase failed", e);
     }
   },
 
   /**
    * Override sync telemetry functions so that we can detect errors generating
@@ -1074,18 +1041,19 @@ var TPS = {
    *
    * @param  phasename
    *         String name of the phase being loaded.
    * @param  fnlist
    *         Array of functions/actions to perform.
    */
   Phase: function Test__Phase(phasename, fnlist) {
     if (Object.keys(this._phaselist).length === 0) {
-      // This is the first phase, add that we need to login.
-      fnlist.unshift([this.Login]);
+      // This is the first phase we should wipe the server - this has the
+      // side-effect of forcing a login, which we also need.
+      fnlist.unshift([this.WipeServer]);
     }
     this._phaselist[phasename] = fnlist;
   },
 
   /**
    * Restrict enabled Sync engines to a specified set.
    *
    * This can be called by a test to limit what engines are enabled. It is
@@ -1162,97 +1130,98 @@ var TPS = {
       await this.waitForEvent("weave:service:setup-complete");
     }
   },
 
   /**
    * Waits for Sync to be finished before returning
    */
   async waitForSyncFinished() {
-    if (this._syncActive) {
+    if (Weave.Service.locked) {
       await this.waitForEvent("weave:service:resyncs-finished");
     }
   },
 
   /**
    * Waits for Sync to start tracking before returning.
    */
   async waitForTracking() {
     if (!this._isTracking) {
       await this.waitForEvent("weave:service:tracking-started");
     }
   },
 
   /**
    * Login on the server
    */
-  async Login(force) {
-    if ((await Authentication.isReady()) && !force) {
+  async Login() {
+    if ((await Authentication.isReady())) {
       return;
     }
 
-    // This might come during Authentication.signIn
-    this._triggeredSync = true;
     Logger.logInfo("Setting client credentials and login.");
     await Authentication.signIn(this.config.fx_account);
     await this.waitForSetupComplete();
     Logger.AssertEqual(Weave.Status.service, Weave.STATUS_OK, "Weave status OK");
     await this.waitForTracking();
-    // We might get an initial sync at login time - let that complete.
-    await this.waitForSyncFinished();
   },
 
   /**
    * Triggers a sync operation
    *
    * @param {String} [wipeAction]
    *        Type of wipe to perform (resetClient, wipeClient, wipeRemote)
    *
    */
   async Sync(wipeAction) {
     if (this._syncActive) {
-      Logger.logInfo("WARNING: Sync currently active! Waiting, before triggering another");
-      await this.waitForSyncFinished();
+      this.DumpError("Sync currently active which should be impossible");
+      return;
     }
     Logger.logInfo("Executing Sync" + (wipeAction ? ": " + wipeAction : ""));
 
     // Force a wipe action if requested. In case of an initial sync the pref
     // will be overwritten by Sync itself (see bug 992198), so ensure that we
     // also handle it via the "weave:service:setup-complete" notification.
     if (wipeAction) {
       this._syncWipeAction = wipeAction;
       Weave.Svc.Prefs.set("firstSync", wipeAction);
     } else {
       Weave.Svc.Prefs.reset("firstSync");
     }
     if (!await Weave.Service.login()) {
       // We need to complete verification.
-      await this.Login(false);
+      Logger.logInfo("Logging in before performing sync");
+      await this.Login();
     }
     ++this._syncCount;
 
+    Logger.logInfo("Executing Sync" + (wipeAction ? ": " + wipeAction : ""));
     this._triggeredSync = true;
-    this.StartAsyncOperation();
     await Weave.Service.sync();
     Logger.logInfo("Sync is complete");
+    // wait a second for things to settle...
+    await new Promise(resolve => {
+      CommonUtils.namedTimer(resolve, 1000, this, "postsync");
+    });
   },
 
   async WipeServer() {
     Logger.logInfo("Wiping data from server.");
 
-    await this.Login(false);
+    await this.Login();
     await Weave.Service.login();
     await Weave.Service.wipeServer();
   },
 
   /**
    * Action which ensures changes are being tracked before returning.
    */
   async EnsureTracking() {
-    await this.Login(false);
+    await this.Login();
     await this.waitForTracking();
   },
 };
 
 var Addons = {
   async install(addons) {
     await TPS.HandleAddons(addons, ACTION_ADD);
   },
@@ -1398,16 +1367,15 @@ var Tabs = {
     await TPS.HandleTabs(tabs, ACTION_VERIFY);
   },
   async verifyNot(tabs) {
     await TPS.HandleTabs(tabs, ACTION_VERIFY_NOT);
   },
 };
 
 var Windows = {
-  add: function Window__add(aWindow) {
-    TPS.StartAsyncOperation();
-    TPS.HandleWindows(aWindow, ACTION_ADD);
+  async add(aWindow) {
+    await TPS.HandleWindows(aWindow, ACTION_ADD);
   },
 };
 
 // Initialize TPS
 TPS._init();
--- a/testing/tps/create_venv.py
+++ b/testing/tps/create_venv.py
@@ -100,16 +100,20 @@ def update_configfile(source, target, re
 
     with open(target, 'w') as config:
         for line in lines:
             config.write(line)
 
 
 def main():
     parser = optparse.OptionParser('Usage: %prog [options] path_to_venv')
+    parser.add_option('--keep-config',
+                      dest='keep_config',
+                      action='store_true',
+                      help='Keep the existing config file.')
     parser.add_option('--password',
                       type='string',
                       dest='password',
                       metavar='FX_ACCOUNT_PASSWORD',
                       default=None,
                       help='The Firefox Account password.')
     parser.add_option('-p', '--python',
                       type='string',
@@ -165,30 +169,31 @@ def main():
                                             'sync'))
     if os.path.exists(sync_dir):
         testdir = os.path.join(sync_dir, 'tests', 'tps')
         extdir = os.path.join(sync_dir, 'tps', 'extensions')
     else:
         testdir = os.path.join(here, 'tests')
         extdir = os.path.join(here, 'extensions')
 
-    update_configfile(os.path.join(here, 'config', 'config.json.in'),
-                      os.path.join(target, 'config.json'),
-                      replacements={
-                      '__TESTDIR__': testdir.replace('\\','/'),
-                      '__EXTENSIONDIR__': extdir.replace('\\','/'),
-                      '__FX_ACCOUNT_USERNAME__': options.username,
-                      '__FX_ACCOUNT_PASSWORD__': options.password,
-                      '__SYNC_ACCOUNT_USERNAME__': options.sync_username,
-                      '__SYNC_ACCOUNT_PASSWORD__': options.sync_password,
-                      '__SYNC_ACCOUNT_PASSPHRASE__': options.sync_passphrase})
+    if not options.keep_config:
+        update_configfile(os.path.join(here, 'config', 'config.json.in'),
+                          os.path.join(target, 'config.json'),
+                          replacements={
+                          '__TESTDIR__': testdir.replace('\\','/'),
+                          '__EXTENSIONDIR__': extdir.replace('\\','/'),
+                          '__FX_ACCOUNT_USERNAME__': options.username,
+                          '__FX_ACCOUNT_PASSWORD__': options.password,
+                          '__SYNC_ACCOUNT_USERNAME__': options.sync_username,
+                          '__SYNC_ACCOUNT_PASSWORD__': options.sync_password,
+                          '__SYNC_ACCOUNT_PASSPHRASE__': options.sync_passphrase})
 
-    if not (options.username and options.password):
-        print '\nFirefox Account credentials not specified.'
-    if not (options.sync_username and options.sync_password and options.passphrase):
-        print '\nFirefox Sync account credentials not specified.'
+        if not (options.username and options.password):
+            print '\nFirefox Account credentials not specified.'
+        if not (options.sync_username and options.sync_password and options.passphrase):
+            print '\nFirefox Sync account credentials not specified.'
 
     # Print the user instructions
     print usage_message.format(TARGET=target,
                                BIN_NAME=bin_name)
 
 if __name__ == "__main__":
     main()
--- a/testing/tps/tps/testrunner.py
+++ b/testing/tps/tps/testrunner.py
@@ -69,16 +69,17 @@ class TPSTestRunner(object):
         # Our pretend addons server doesn't support metadata...
         'extensions.getAddons.cache.enabled': False,
         'extensions.install.requireSecureOrigin': False,
         'extensions.update.enabled': False,
         # Don't open a dialog to show available add-on updates
         'extensions.update.notifyUser': False,
         'services.sync.firstSync': 'notReady',
         'services.sync.lastversion': '1.0',
+        'services.sync.autoconnectDelay': 60 * 60 * 10,
         'toolkit.startup.max_resumed_crashes': -1,
         # hrm - not sure what the release/beta channels will do?
         'xpinstall.signatures.required': False,
         'services.sync.testing.tps': True,
         'engine.bookmarks.repair.enabled': False,
         'extensions.legacy.enabled': True,
     }
 
@@ -432,17 +433,23 @@ class TPSTestRunner(object):
         self.extensions.append(os.path.join(self.extensionDir, 'tps'))
 
         # build the test list
         try:
             f = open(self.testfile)
             jsondata = f.read()
             f.close()
             testfiles = json.loads(jsondata)
-            testlist = testfiles['tests']
+            testlist = []
+            for (filename, meta) in testfiles['tests'].items():
+                skip_reason = meta.get("disabled")
+                if skip_reason:
+                    print 'Skipping test %s - %s' % (filename, skip_reason)
+                else:
+                    testlist.append(filename)
         except ValueError:
             testlist = [os.path.basename(self.testfile)]
         testdir = os.path.dirname(self.testfile)
 
         self.mozhttpd = MozHttpd(port=4567, docroot=testdir)
         self.mozhttpd.start()
 
         # run each test, and save the results