diff options
Diffstat (limited to 'services/sync/tests/unit/test_interval_triggers.js')
-rw-r--r-- | services/sync/tests/unit/test_interval_triggers.js | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/services/sync/tests/unit/test_interval_triggers.js b/services/sync/tests/unit/test_interval_triggers.js new file mode 100644 index 0000000000..6f2821ec45 --- /dev/null +++ b/services/sync/tests/unit/test_interval_triggers.js @@ -0,0 +1,472 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Svc.PrefBranch.setStringPref("registerEngines", ""); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +let scheduler; +let clientsEngine; + +async function sync_httpd_setup() { + let clientsSyncID = await clientsEngine.resetLocalSyncID(); + let global = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: { + clients: { version: clientsEngine.version, syncID: clientsSyncID }, + }, + }); + let clientsColl = new ServerCollection({}, true); + + // Tracking info/collections. + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + + return httpd_setup({ + "/1.1/johndoe/storage/meta/global": upd("meta", global.handler()), + "/1.1/johndoe/info/collections": collectionsHelper.handler, + "/1.1/johndoe/storage/crypto/keys": upd( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()), + }); +} + +async function setUp(server) { + syncTestLogging(); + await configureIdentity({ username: "johndoe" }, server); + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + await serverKeys.encrypt(Service.identity.syncKeyBundle); + await serverKeys.upload(Service.resource(Service.cryptoKeysURL)); +} + +add_task(async function setup() { + scheduler = Service.scheduler; + clientsEngine = Service.clientsEngine; + + // Don't remove stale clients when syncing. This is a test-only workaround + // that lets us add clients directly to the store, without losing them on + // the next sync. + clientsEngine._removeRemoteClient = async id => {}; +}); + +add_task(async function test_successful_sync_adjustSyncInterval() { + enableValidationPrefs(); + + _("Test successful sync calling adjustSyncInterval"); + let syncSuccesses = 0; + function onSyncFinish() { + _("Sync success."); + syncSuccesses++; + } + Svc.Obs.add("weave:service:sync:finish", onSyncFinish); + + let server = await sync_httpd_setup(); + await setUp(server); + + // Confirm defaults + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.ok(!scheduler.hasIncomingItems); + + _("Test as long as numClients <= 1 our sync interval is SINGLE_USER."); + // idle == true && numClients <= 1 && hasIncomingItems == false + scheduler.idle = true; + await Service.sync(); + Assert.equal(syncSuccesses, 1); + Assert.ok(scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == false && numClients <= 1 && hasIncomingItems == false + scheduler.idle = false; + await Service.sync(); + Assert.equal(syncSuccesses, 2); + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == false && numClients <= 1 && hasIncomingItems == true + scheduler.hasIncomingItems = true; + await Service.sync(); + Assert.equal(syncSuccesses, 3); + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == true && numClients <= 1 && hasIncomingItems == true + scheduler.idle = true; + await Service.sync(); + Assert.equal(syncSuccesses, 4); + Assert.ok(scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + _( + "Test as long as idle && numClients > 1 our sync interval is idleInterval." + ); + // idle == true && numClients > 1 && hasIncomingItems == true + await Service.clientsEngine._store.create({ + id: "foo", + cleartext: { name: "bar", type: "mobile" }, + }); + await Service.sync(); + Assert.equal(syncSuccesses, 5); + Assert.ok(scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.idleInterval); + + // idle == true && numClients > 1 && hasIncomingItems == false + scheduler.hasIncomingItems = false; + await Service.sync(); + Assert.equal(syncSuccesses, 6); + Assert.ok(scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.idleInterval); + + _("Test non-idle, numClients > 1, no incoming items => activeInterval."); + // idle == false && numClients > 1 && hasIncomingItems == false + scheduler.idle = false; + await Service.sync(); + Assert.equal(syncSuccesses, 7); + Assert.ok(!scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + _("Test non-idle, numClients > 1, incoming items => immediateInterval."); + // idle == false && numClients > 1 && hasIncomingItems == true + scheduler.hasIncomingItems = true; + await Service.sync(); + Assert.equal(syncSuccesses, 8); + Assert.ok(!scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); // gets reset to false + Assert.equal(scheduler.syncInterval, scheduler.immediateInterval); + + Svc.Obs.remove("weave:service:sync:finish", onSyncFinish); + await Service.startOver(); + await promiseStopServer(server); +}); + +add_task(async function test_unsuccessful_sync_adjustSyncInterval() { + enableValidationPrefs(); + + _("Test unsuccessful sync calling adjustSyncInterval"); + + let syncFailures = 0; + function onSyncError() { + _("Sync error."); + syncFailures++; + } + Svc.Obs.add("weave:service:sync:error", onSyncError); + + _("Test unsuccessful sync calls adjustSyncInterval"); + // Force sync to fail. + Svc.PrefBranch.setStringPref("firstSync", "notReady"); + + let server = await sync_httpd_setup(); + await setUp(server); + + // Confirm defaults + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.ok(!scheduler.hasIncomingItems); + + _("Test as long as numClients <= 1 our sync interval is SINGLE_USER."); + // idle == true && numClients <= 1 && hasIncomingItems == false + scheduler.idle = true; + await Service.sync(); + Assert.equal(syncFailures, 1); + Assert.ok(scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == false && numClients <= 1 && hasIncomingItems == false + scheduler.idle = false; + await Service.sync(); + Assert.equal(syncFailures, 2); + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == false && numClients <= 1 && hasIncomingItems == true + scheduler.hasIncomingItems = true; + await Service.sync(); + Assert.equal(syncFailures, 3); + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == true && numClients <= 1 && hasIncomingItems == true + scheduler.idle = true; + await Service.sync(); + Assert.equal(syncFailures, 4); + Assert.ok(scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + _( + "Test as long as idle && numClients > 1 our sync interval is idleInterval." + ); + // idle == true && numClients > 1 && hasIncomingItems == true + Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); + scheduler.updateClientMode(); + + await Service.sync(); + Assert.equal(syncFailures, 5); + Assert.ok(scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.idleInterval); + + // idle == true && numClients > 1 && hasIncomingItems == false + scheduler.hasIncomingItems = false; + await Service.sync(); + Assert.equal(syncFailures, 6); + Assert.ok(scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.idleInterval); + + _("Test non-idle, numClients > 1, no incoming items => activeInterval."); + // idle == false && numClients > 1 && hasIncomingItems == false + scheduler.idle = false; + await Service.sync(); + Assert.equal(syncFailures, 7); + Assert.ok(!scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + _("Test non-idle, numClients > 1, incoming items => immediateInterval."); + // idle == false && numClients > 1 && hasIncomingItems == true + scheduler.hasIncomingItems = true; + await Service.sync(); + Assert.equal(syncFailures, 8); + Assert.ok(!scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); // gets reset to false + Assert.equal(scheduler.syncInterval, scheduler.immediateInterval); + + await Service.startOver(); + Svc.Obs.remove("weave:service:sync:error", onSyncError); + await promiseStopServer(server); +}); + +add_task(async function test_back_triggers_sync() { + enableValidationPrefs(); + + let server = await sync_httpd_setup(); + await setUp(server); + + // Single device: no sync triggered. + scheduler.idle = true; + scheduler.observe( + null, + "active", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + Assert.ok(!scheduler.idle); + + // Multiple devices: sync is triggered. + Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); + scheduler.updateClientMode(); + + let promiseDone = promiseOneObserver("weave:service:sync:finish"); + + scheduler.idle = true; + scheduler.observe( + null, + "active", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + Assert.ok(!scheduler.idle); + await promiseDone; + + Service.recordManager.clearCache(); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + scheduler.setDefaults(); + await clientsEngine.resetClient(); + + await Service.startOver(); + await promiseStopServer(server); +}); + +add_task(async function test_adjust_interval_on_sync_error() { + enableValidationPrefs(); + + let server = await sync_httpd_setup(); + await setUp(server); + + let syncFailures = 0; + function onSyncError() { + _("Sync error."); + syncFailures++; + } + Svc.Obs.add("weave:service:sync:error", onSyncError); + + _("Test unsuccessful sync updates client mode & sync intervals"); + // Force a sync fail. + Svc.PrefBranch.setStringPref("firstSync", "notReady"); + + Assert.equal(syncFailures, 0); + Assert.equal(false, scheduler.numClients > 1); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); + await Service.sync(); + + Assert.equal(syncFailures, 1); + Assert.ok(scheduler.numClients > 1); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + Svc.Obs.remove("weave:service:sync:error", onSyncError); + await Service.startOver(); + await promiseStopServer(server); +}); + +add_task(async function test_bug671378_scenario() { + enableValidationPrefs(); + + // Test scenario similar to bug 671378. This bug appeared when a score + // update occurred that wasn't large enough to trigger a sync so + // scheduleNextSync() was called without a time interval parameter, + // setting nextSync to a non-zero value and preventing the timer from + // being adjusted in the next call to scheduleNextSync(). + let server = await sync_httpd_setup(); + await setUp(server); + + let syncSuccesses = 0; + function onSyncFinish() { + _("Sync success."); + syncSuccesses++; + } + Svc.Obs.add("weave:service:sync:finish", onSyncFinish); + + // After first sync call, syncInterval & syncTimer are singleDeviceInterval. + await Service.sync(); + Assert.equal(syncSuccesses, 1); + Assert.equal(false, scheduler.numClients > 1); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); + + let promiseDone = new Promise(resolve => { + // Wrap scheduleNextSync so we are notified when it is finished. + scheduler._scheduleNextSync = scheduler.scheduleNextSync; + scheduler.scheduleNextSync = function () { + scheduler._scheduleNextSync(); + + // Check on sync:finish scheduleNextSync sets the appropriate + // syncInterval and syncTimer values. + if (syncSuccesses == 2) { + Assert.notEqual(scheduler.nextSync, 0); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval); + + scheduler.scheduleNextSync = scheduler._scheduleNextSync; + Svc.Obs.remove("weave:service:sync:finish", onSyncFinish); + Service.startOver().then(() => { + server.stop(resolve); + }); + } + }; + }); + + // Set nextSync != 0 + // syncInterval still hasn't been set by call to updateClientMode. + // Explicitly trying to invoke scheduleNextSync during a sync + // (to immitate a score update that isn't big enough to trigger a sync). + Svc.Obs.add("weave:service:sync:start", function onSyncStart() { + // Wait for other sync:start observers to be called so that + // nextSync is set to 0. + CommonUtils.nextTick(function () { + Svc.Obs.remove("weave:service:sync:start", onSyncStart); + + scheduler.scheduleNextSync(); + Assert.notEqual(scheduler.nextSync, 0); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); + }); + }); + + await Service.clientsEngine._store.create({ + id: "foo", + cleartext: { name: "bar", type: "mobile" }, + }); + await Service.sync(); + await promiseDone; +}); + +add_task(async function test_adjust_timer_larger_syncInterval() { + _( + "Test syncInterval > current timout period && nextSync != 0, syncInterval is NOT used." + ); + Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); + scheduler.updateClientMode(); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + scheduler.scheduleNextSync(); + + // Ensure we have a small interval. + Assert.notEqual(scheduler.nextSync, 0); + Assert.equal(scheduler.syncTimer.delay, scheduler.activeInterval); + + // Make interval large again + await clientsEngine._wipeClient(); + Svc.PrefBranch.clearUserPref("clients.devices.mobile"); + scheduler.updateClientMode(); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + scheduler.scheduleNextSync(); + + // Ensure timer delay remains as the small interval. + Assert.notEqual(scheduler.nextSync, 0); + Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval); + + // SyncSchedule. + await Service.startOver(); +}); + +add_task(async function test_adjust_timer_smaller_syncInterval() { + _( + "Test current timout > syncInterval period && nextSync != 0, syncInterval is used." + ); + scheduler.scheduleNextSync(); + + // Ensure we have a large interval. + Assert.notEqual(scheduler.nextSync, 0); + Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); + + // Make interval smaller + Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); + scheduler.updateClientMode(); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + scheduler.scheduleNextSync(); + + // Ensure smaller timer delay is used. + Assert.notEqual(scheduler.nextSync, 0); + Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval); + + // SyncSchedule. + await Service.startOver(); +}); |