Bug 1274394 - Run the bookmark validator after each phase of TPS tests that touched bookmarks r?markh draft
authorThom Chiovoloni <tchiovoloni@mozilla.com>
Fri, 27 May 2016 13:31:14 -0400
changeset 373307 b39c50a107c8bc35dd1fbfbafd248591eb0ddb8d
parent 371393 b87905133eafb182539e8ea332ad19ab9548e8ee
child 522380 0bdbee0984ca7efb5ee6ce1267cf8409a5f7687f
push id19736
push userbmo:tchiovoloni@mozilla.com
push dateTue, 31 May 2016 15:42:22 +0000
reviewersmarkh
bugs1274394
milestone49.0a1
Bug 1274394 - Run the bookmark validator after each phase of TPS tests that touched bookmarks r?markh MozReview-Commit-ID: 9WH965qdhAE
services/sync/tests/tps/test_bug563989.js
services/sync/tps/extensions/tps/resource/tps.jsm
--- a/services/sync/tests/tps/test_bug563989.js
+++ b/services/sync/tests/tps/test_bug563989.js
@@ -83,17 +83,18 @@ Phase('phase1', [
 ]);
 
 // Sync to profile2 and verify that the bookmarks are present.  Delete 
 // some bookmarks, and verify that they're not present, but don't sync again.
 Phase('phase2', [
   [Sync],
   [Bookmarks.verify, bookmarks_initial],
   [Bookmarks.delete, bookmarks_to_delete],
-  [Bookmarks.verifyNot, bookmarks_to_delete]
+  [Bookmarks.verifyNot, bookmarks_to_delete],
+  [Bookmarks.skipValidation]
 ]);
 
 // Using profile1, sync again with wipe-server set to true.  Verify our
 // initial bookmarks are still all present.
 Phase('phase3', [
   [Sync, SYNC_WIPE_REMOTE],
   [Bookmarks.verify, bookmarks_initial]
 ]);
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -13,21 +13,22 @@ const {classes: Cc, interfaces: Ci, util
 
 var module = this;
 
 // Global modules
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/main.js");
 Cu.import("resource://services-sync/util.js");
-
+Cu.import("resource://services-sync/bookmark_validator.js");
 // TPS modules
 Cu.import("resource://tps/logger.jsm");
 
 // Module wrappers for tests
 Cu.import("resource://tps/modules/addons.jsm");
 Cu.import("resource://tps/modules/bookmarks.jsm");
 Cu.import("resource://tps/modules/forms.jsm");
 Cu.import("resource://tps/modules/history.jsm");
@@ -106,16 +107,17 @@ var TPS = {
   _syncErrors: 0,
   _syncWipeAction: null,
   _tabsAdded: 0,
   _tabsFinished: 0,
   _test: null,
   _triggeredSync: false,
   _usSinceEpoch: 0,
   _requestedQuit: false,
+  shouldValidateBookmarks: false,
 
   _init: function TPS__init() {
     // Check if Firefox Accounts is enabled
     let service = Cc["@mozilla.org/weave/service;1"]
                   .getService(Components.interfaces.nsISupports)
                   .wrappedJSObject;
     this.fxaccounts_enabled = service.fxAccountsEnabled;
 
@@ -487,16 +489,17 @@ var TPS = {
           throw new Error("Unknown action for add-on: " + action);
       }
     }
     Logger.logPass("executing action " + action.toUpperCase() +
                    " on addons");
   },
 
   HandleBookmarks: function (bookmarks, action) {
+    this.shouldValidateBookmarks = true;
     try {
       let items = [];
       for (let folder in bookmarks) {
         let last_item_pos = -1;
         for (let bookmark of bookmarks[folder]) {
           Logger.clearPotentialError();
           let placesItem;
           bookmark['location'] = folder;
@@ -578,20 +581,91 @@ var TPS = {
       }, 2000, this, "postmozmilltest");
     }
   },
 
   MozmillSetTestListener: function TPS__MozmillSetTestListener(obj) {
     Logger.logInfo("mozmill setTest: " + obj.name);
   },
 
+
+
+  /**
+   * Use Sync's bookmark validation code to see if we've corrupted the tree.
+   */
+  ValidateBookmarks() {
+
+    let getServerBookmarkState = () => {
+      let bookmarkEngine = Weave.Service.engineManager.get('bookmarks');
+      let collection = bookmarkEngine._itemSource();
+      let collectionKey = bookmarkEngine.service.collectionKeys.keyForCollection(bookmarkEngine.name);
+      collection.full = true;
+      let items = [];
+      collection.recordHandler = function(item) {
+        item.decrypt(collectionKey);
+        items.push(item.cleartext);
+      };
+      collection.get();
+      return items;
+    };
+    let serverRecordDumpStr;
+    try {
+      Logger.logInfo("About to perform bookmark validation");
+      let clientTree = Async.promiseSpinningly(PlacesUtils.promiseBookmarksTree("", {
+        includeItemIds: true
+      }));
+      let serverRecords = getServerBookmarkState();
+      // We can't wait until catch to stringify this, since at that point it will have cycles.
+      serverRecordDumpStr = JSON.stringify(serverRecords);
+
+      let validator = new BookmarkValidator();
+      let {problemData} = validator.compareServerWithClient(serverRecords, clientTree);
+
+      for (let {name, count} of problemData.getSummary()) {
+        // Exclude mobile showing up on the server hackily so that we don't
+        // report it every time, see bug 1273234 and 1274394 for more information.
+        if (name === "serverUnexpected" && problemData.serverUnexpected.indexOf("mobile") >= 0) {
+          --count;
+        } else if (name === "differences") {
+          // Also exclude errors in parentName/wrongParentName (bug 1276969) for
+          // the same reason.
+          let newCount = problemData.differences.filter(diffInfo =>
+            !diffInfo.differences.every(diff =>
+              diff === "parentName")).length
+          count = newCount;
+        } else if (name === "wrongParentName") {
+          continue;
+        }
+        if (count) {
+          // Log this out before we assert. This is useful in the context of TPS logs, since we
+          // can see the IDs in the test files.
+          Logger.logInfo(`Validation problem: "${name}": ${JSON.stringify(problemData[name])}`);
+        }
+        Logger.AssertEqual(count, 0, `Bookmark validation error of type ${name}`);
+      }
+    } catch (e) {
+      // Dump the client records (should always be doable)
+      DumpBookmarks();
+      // Dump the server records if gotten them already.
+      if (serverRecordDumpStr) {
+        Logger.logInfo("Server bookmark records:\n" + serverRecordDumpStr + "\n");
+      }
+      this.DumpError("Bookmark validation failed", e);
+    }
+    Logger.logInfo("Bookmark validation finished");
+  },
+
   RunNextTestAction: function() {
     try {
       if (this._currentAction >=
           this._phaselist["phase" + this._currentPhase].length) {
+        if (this.shouldValidateBookmarks) {
+          // Run bookmark validation and then finish up
+          this.ValidateBookmarks();
+        }
         // we're all done
         Logger.logInfo("test phase " + this._currentPhase + ": " +
                        (this._errors ? "FAIL" : "PASS"));
         this._phaseFinished = true;
         this.quit();
         return;
       }
 
@@ -671,16 +745,19 @@ var TPS = {
         return;
       }
 
       // Wait for Sync service to become ready.
       if (!Weave.Status.ready) {
         this.waitForEvent("weave:service:ready");
       }
 
+      // We only want to do this if we modified the bookmarks this phase.
+      this.shouldValidateBookmarks = false;
+
       // Always give Sync an extra tick to initialize. If we waited for the
       // service:ready event, this is required to ensure all handlers have
       // executed.
       Utils.nextTick(this._executeTestPhase.bind(this, file, phase, settings));
     } catch(e) {
       this.DumpError("RunTestPhase failed", e);
       return;
     }
@@ -969,16 +1046,19 @@ var Bookmarks = {
   delete: function Bookmarks__delete(bookmarks) {
     TPS.HandleBookmarks(bookmarks, ACTION_DELETE);
   },
   verify: function Bookmarks__verify(bookmarks) {
     TPS.HandleBookmarks(bookmarks, ACTION_VERIFY);
   },
   verifyNot: function Bookmarks__verifyNot(bookmarks) {
     TPS.HandleBookmarks(bookmarks, ACTION_VERIFY_NOT);
+  },
+  skipValidation() {
+    TPS.shouldValidateBookmarks = false;
   }
 };
 
 var Formdata = {
   add: function Formdata__add(formdata) {
     this.HandleForms(formdata, ACTION_ADD);
   },
   delete: function Formdata__delete(formdata) {