Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* Any copyright is dedicated to the Public Domain. |
michael@0 | 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ |
michael@0 | 3 | |
michael@0 | 4 | package org.mozilla.gecko.background.sync; |
michael@0 | 5 | |
michael@0 | 6 | import java.util.concurrent.atomic.AtomicBoolean; |
michael@0 | 7 | import java.util.concurrent.atomic.AtomicLong; |
michael@0 | 8 | |
michael@0 | 9 | import junit.framework.AssertionFailedError; |
michael@0 | 10 | |
michael@0 | 11 | import org.mozilla.gecko.background.common.log.Logger; |
michael@0 | 12 | import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; |
michael@0 | 13 | import org.mozilla.gecko.background.sync.helpers.SimpleSuccessBeginDelegate; |
michael@0 | 14 | import org.mozilla.gecko.background.sync.helpers.SimpleSuccessCreationDelegate; |
michael@0 | 15 | import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFetchDelegate; |
michael@0 | 16 | import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFinishDelegate; |
michael@0 | 17 | import org.mozilla.gecko.background.sync.helpers.SimpleSuccessStoreDelegate; |
michael@0 | 18 | import org.mozilla.gecko.background.testhelpers.WBORepository; |
michael@0 | 19 | import org.mozilla.gecko.sync.CryptoRecord; |
michael@0 | 20 | import org.mozilla.gecko.sync.ExtendedJSONObject; |
michael@0 | 21 | import org.mozilla.gecko.sync.repositories.InactiveSessionException; |
michael@0 | 22 | import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; |
michael@0 | 23 | import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; |
michael@0 | 24 | import org.mozilla.gecko.sync.repositories.RepositorySession; |
michael@0 | 25 | import org.mozilla.gecko.sync.repositories.RepositorySessionBundle; |
michael@0 | 26 | import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate; |
michael@0 | 27 | import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord; |
michael@0 | 28 | import org.mozilla.gecko.sync.repositories.domain.Record; |
michael@0 | 29 | import org.mozilla.gecko.sync.synchronizer.Synchronizer; |
michael@0 | 30 | import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate; |
michael@0 | 31 | |
michael@0 | 32 | import android.content.Context; |
michael@0 | 33 | |
michael@0 | 34 | public class TestStoreTracking extends AndroidSyncTestCase { |
michael@0 | 35 | public void assertEq(Object expected, Object actual) { |
michael@0 | 36 | try { |
michael@0 | 37 | assertEquals(expected, actual); |
michael@0 | 38 | } catch (AssertionFailedError e) { |
michael@0 | 39 | performNotify(e); |
michael@0 | 40 | } |
michael@0 | 41 | } |
michael@0 | 42 | |
michael@0 | 43 | public class TrackingWBORepository extends WBORepository { |
michael@0 | 44 | @Override |
michael@0 | 45 | public synchronized boolean shouldTrack() { |
michael@0 | 46 | return true; |
michael@0 | 47 | } |
michael@0 | 48 | } |
michael@0 | 49 | |
michael@0 | 50 | public void doTestStoreRetrieveByGUID(final WBORepository repository, |
michael@0 | 51 | final RepositorySession session, |
michael@0 | 52 | final String expectedGUID, |
michael@0 | 53 | final Record record) { |
michael@0 | 54 | |
michael@0 | 55 | final SimpleSuccessStoreDelegate storeDelegate = new SimpleSuccessStoreDelegate() { |
michael@0 | 56 | |
michael@0 | 57 | @Override |
michael@0 | 58 | public void onRecordStoreSucceeded(String guid) { |
michael@0 | 59 | Logger.debug(getName(), "Stored " + guid); |
michael@0 | 60 | assertEq(expectedGUID, guid); |
michael@0 | 61 | } |
michael@0 | 62 | |
michael@0 | 63 | @Override |
michael@0 | 64 | public void onStoreCompleted(long storeEnd) { |
michael@0 | 65 | Logger.debug(getName(), "Store completed at " + storeEnd + "."); |
michael@0 | 66 | try { |
michael@0 | 67 | session.fetch(new String[] { expectedGUID }, new SimpleSuccessFetchDelegate() { |
michael@0 | 68 | @Override |
michael@0 | 69 | public void onFetchedRecord(Record record) { |
michael@0 | 70 | Logger.debug(getName(), "Hurrah! Fetched record " + record.guid); |
michael@0 | 71 | assertEq(expectedGUID, record.guid); |
michael@0 | 72 | } |
michael@0 | 73 | |
michael@0 | 74 | @Override |
michael@0 | 75 | public void onFetchCompleted(final long fetchEnd) { |
michael@0 | 76 | Logger.debug(getName(), "Fetch completed at " + fetchEnd + "."); |
michael@0 | 77 | |
michael@0 | 78 | // But fetching by time returns nothing. |
michael@0 | 79 | session.fetchSince(0, new SimpleSuccessFetchDelegate() { |
michael@0 | 80 | private AtomicBoolean fetched = new AtomicBoolean(false); |
michael@0 | 81 | |
michael@0 | 82 | @Override |
michael@0 | 83 | public void onFetchedRecord(Record record) { |
michael@0 | 84 | Logger.debug(getName(), "Fetched record " + record.guid); |
michael@0 | 85 | fetched.set(true); |
michael@0 | 86 | performNotify(new AssertionFailedError("Should have fetched no record!")); |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | @Override |
michael@0 | 90 | public void onFetchCompleted(final long fetchEnd) { |
michael@0 | 91 | if (fetched.get()) { |
michael@0 | 92 | Logger.debug(getName(), "Not finishing session: record retrieved."); |
michael@0 | 93 | return; |
michael@0 | 94 | } |
michael@0 | 95 | try { |
michael@0 | 96 | session.finish(new SimpleSuccessFinishDelegate() { |
michael@0 | 97 | @Override |
michael@0 | 98 | public void onFinishSucceeded(RepositorySession session, |
michael@0 | 99 | RepositorySessionBundle bundle) { |
michael@0 | 100 | performNotify(); |
michael@0 | 101 | } |
michael@0 | 102 | }); |
michael@0 | 103 | } catch (InactiveSessionException e) { |
michael@0 | 104 | performNotify(e); |
michael@0 | 105 | } |
michael@0 | 106 | } |
michael@0 | 107 | }); |
michael@0 | 108 | } |
michael@0 | 109 | }); |
michael@0 | 110 | } catch (InactiveSessionException e) { |
michael@0 | 111 | performNotify(e); |
michael@0 | 112 | } |
michael@0 | 113 | } |
michael@0 | 114 | }; |
michael@0 | 115 | |
michael@0 | 116 | session.setStoreDelegate(storeDelegate); |
michael@0 | 117 | try { |
michael@0 | 118 | Logger.debug(getName(), "Storing..."); |
michael@0 | 119 | session.store(record); |
michael@0 | 120 | session.storeDone(); |
michael@0 | 121 | } catch (NoStoreDelegateException e) { |
michael@0 | 122 | // Should not happen. |
michael@0 | 123 | } |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | private void doTestNewSessionRetrieveByTime(final WBORepository repository, |
michael@0 | 127 | final String expectedGUID) { |
michael@0 | 128 | final SimpleSuccessCreationDelegate createDelegate = new SimpleSuccessCreationDelegate() { |
michael@0 | 129 | @Override |
michael@0 | 130 | public void onSessionCreated(final RepositorySession session) { |
michael@0 | 131 | Logger.debug(getName(), "Session created."); |
michael@0 | 132 | try { |
michael@0 | 133 | session.begin(new SimpleSuccessBeginDelegate() { |
michael@0 | 134 | @Override |
michael@0 | 135 | public void onBeginSucceeded(final RepositorySession session) { |
michael@0 | 136 | // Now we get a result. |
michael@0 | 137 | session.fetchSince(0, new SimpleSuccessFetchDelegate() { |
michael@0 | 138 | |
michael@0 | 139 | @Override |
michael@0 | 140 | public void onFetchedRecord(Record record) { |
michael@0 | 141 | assertEq(expectedGUID, record.guid); |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | @Override |
michael@0 | 145 | public void onFetchCompleted(long end) { |
michael@0 | 146 | try { |
michael@0 | 147 | session.finish(new SimpleSuccessFinishDelegate() { |
michael@0 | 148 | @Override |
michael@0 | 149 | public void onFinishSucceeded(RepositorySession session, |
michael@0 | 150 | RepositorySessionBundle bundle) { |
michael@0 | 151 | // Hooray! |
michael@0 | 152 | performNotify(); |
michael@0 | 153 | } |
michael@0 | 154 | }); |
michael@0 | 155 | } catch (InactiveSessionException e) { |
michael@0 | 156 | performNotify(e); |
michael@0 | 157 | } |
michael@0 | 158 | } |
michael@0 | 159 | }); |
michael@0 | 160 | } |
michael@0 | 161 | }); |
michael@0 | 162 | } catch (InvalidSessionTransitionException e) { |
michael@0 | 163 | performNotify(e); |
michael@0 | 164 | } |
michael@0 | 165 | } |
michael@0 | 166 | }; |
michael@0 | 167 | Runnable create = new Runnable() { |
michael@0 | 168 | @Override |
michael@0 | 169 | public void run() { |
michael@0 | 170 | repository.createSession(createDelegate, getApplicationContext()); |
michael@0 | 171 | } |
michael@0 | 172 | }; |
michael@0 | 173 | |
michael@0 | 174 | performWait(create); |
michael@0 | 175 | } |
michael@0 | 176 | |
michael@0 | 177 | /** |
michael@0 | 178 | * Store a record in one session. Verify that fetching by GUID returns |
michael@0 | 179 | * the record. Verify that fetching by timestamp fails to return records. |
michael@0 | 180 | * Start a new session. Verify that fetching by timestamp returns the |
michael@0 | 181 | * stored record. |
michael@0 | 182 | * |
michael@0 | 183 | * Invokes doTestStoreRetrieveByGUID, doTestNewSessionRetrieveByTime. |
michael@0 | 184 | */ |
michael@0 | 185 | public void testStoreRetrieveByGUID() { |
michael@0 | 186 | Logger.debug(getName(), "Started."); |
michael@0 | 187 | final WBORepository r = new TrackingWBORepository(); |
michael@0 | 188 | final long now = System.currentTimeMillis(); |
michael@0 | 189 | final String expectedGUID = "abcdefghijkl"; |
michael@0 | 190 | final Record record = new BookmarkRecord(expectedGUID, "bookmarks", now , false); |
michael@0 | 191 | |
michael@0 | 192 | final RepositorySessionCreationDelegate createDelegate = new SimpleSuccessCreationDelegate() { |
michael@0 | 193 | @Override |
michael@0 | 194 | public void onSessionCreated(RepositorySession session) { |
michael@0 | 195 | Logger.debug(getName(), "Session created: " + session); |
michael@0 | 196 | try { |
michael@0 | 197 | session.begin(new SimpleSuccessBeginDelegate() { |
michael@0 | 198 | @Override |
michael@0 | 199 | public void onBeginSucceeded(final RepositorySession session) { |
michael@0 | 200 | doTestStoreRetrieveByGUID(r, session, expectedGUID, record); |
michael@0 | 201 | } |
michael@0 | 202 | }); |
michael@0 | 203 | } catch (InvalidSessionTransitionException e) { |
michael@0 | 204 | performNotify(e); |
michael@0 | 205 | } |
michael@0 | 206 | } |
michael@0 | 207 | }; |
michael@0 | 208 | |
michael@0 | 209 | final Context applicationContext = getApplicationContext(); |
michael@0 | 210 | |
michael@0 | 211 | // This has to happen on a new thread so that we |
michael@0 | 212 | // can wait for it! |
michael@0 | 213 | Runnable create = onThreadRunnable(new Runnable() { |
michael@0 | 214 | @Override |
michael@0 | 215 | public void run() { |
michael@0 | 216 | r.createSession(createDelegate, applicationContext); |
michael@0 | 217 | } |
michael@0 | 218 | }); |
michael@0 | 219 | |
michael@0 | 220 | Runnable retrieve = onThreadRunnable(new Runnable() { |
michael@0 | 221 | @Override |
michael@0 | 222 | public void run() { |
michael@0 | 223 | doTestNewSessionRetrieveByTime(r, expectedGUID); |
michael@0 | 224 | performNotify(); |
michael@0 | 225 | } |
michael@0 | 226 | }); |
michael@0 | 227 | |
michael@0 | 228 | performWait(create); |
michael@0 | 229 | performWait(retrieve); |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | private Runnable onThreadRunnable(final Runnable r) { |
michael@0 | 233 | return new Runnable() { |
michael@0 | 234 | @Override |
michael@0 | 235 | public void run() { |
michael@0 | 236 | new Thread(r).start(); |
michael@0 | 237 | } |
michael@0 | 238 | }; |
michael@0 | 239 | } |
michael@0 | 240 | |
michael@0 | 241 | |
michael@0 | 242 | public class CountingWBORepository extends TrackingWBORepository { |
michael@0 | 243 | public AtomicLong counter = new AtomicLong(0L); |
michael@0 | 244 | public class CountingWBORepositorySession extends WBORepositorySession { |
michael@0 | 245 | private static final String LOG_TAG = "CountingRepoSession"; |
michael@0 | 246 | |
michael@0 | 247 | public CountingWBORepositorySession(WBORepository repository) { |
michael@0 | 248 | super(repository); |
michael@0 | 249 | } |
michael@0 | 250 | |
michael@0 | 251 | @Override |
michael@0 | 252 | public void store(final Record record) throws NoStoreDelegateException { |
michael@0 | 253 | Logger.debug(LOG_TAG, "Counter now " + counter.incrementAndGet()); |
michael@0 | 254 | super.store(record); |
michael@0 | 255 | } |
michael@0 | 256 | } |
michael@0 | 257 | |
michael@0 | 258 | @Override |
michael@0 | 259 | public void createSession(RepositorySessionCreationDelegate delegate, |
michael@0 | 260 | Context context) { |
michael@0 | 261 | delegate.deferredCreationDelegate().onSessionCreated(new CountingWBORepositorySession(this)); |
michael@0 | 262 | } |
michael@0 | 263 | } |
michael@0 | 264 | |
michael@0 | 265 | public class TestRecord extends Record { |
michael@0 | 266 | public TestRecord(String guid, String collection, long lastModified, |
michael@0 | 267 | boolean deleted) { |
michael@0 | 268 | super(guid, collection, lastModified, deleted); |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | @Override |
michael@0 | 272 | public void initFromEnvelope(CryptoRecord payload) { |
michael@0 | 273 | return; |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | @Override |
michael@0 | 277 | public CryptoRecord getEnvelope() { |
michael@0 | 278 | return null; |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | @Override |
michael@0 | 282 | protected void populatePayload(ExtendedJSONObject payload) { |
michael@0 | 283 | } |
michael@0 | 284 | |
michael@0 | 285 | @Override |
michael@0 | 286 | protected void initFromPayload(ExtendedJSONObject payload) { |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | @Override |
michael@0 | 290 | public Record copyWithIDs(String guid, long androidID) { |
michael@0 | 291 | return new TestRecord(guid, this.collection, this.lastModified, this.deleted); |
michael@0 | 292 | } |
michael@0 | 293 | } |
michael@0 | 294 | |
michael@0 | 295 | /** |
michael@0 | 296 | * Create two repositories, syncing from one to the other. Ensure |
michael@0 | 297 | * that records stored from one aren't re-uploaded. |
michael@0 | 298 | */ |
michael@0 | 299 | public void testStoreBetweenRepositories() { |
michael@0 | 300 | final CountingWBORepository repoA = new CountingWBORepository(); // "Remote". First source. |
michael@0 | 301 | final CountingWBORepository repoB = new CountingWBORepository(); // "Local". First sink. |
michael@0 | 302 | long now = System.currentTimeMillis(); |
michael@0 | 303 | |
michael@0 | 304 | TestRecord recordA1 = new TestRecord("aacdefghiaaa", "coll", now - 30, false); |
michael@0 | 305 | TestRecord recordA2 = new TestRecord("aacdefghibbb", "coll", now - 20, false); |
michael@0 | 306 | TestRecord recordB1 = new TestRecord("aacdefghiaaa", "coll", now - 10, false); |
michael@0 | 307 | TestRecord recordB2 = new TestRecord("aacdefghibbb", "coll", now - 40, false); |
michael@0 | 308 | |
michael@0 | 309 | TestRecord recordA3 = new TestRecord("nncdefghibbb", "coll", now, false); |
michael@0 | 310 | TestRecord recordB3 = new TestRecord("nncdefghiaaa", "coll", now, false); |
michael@0 | 311 | |
michael@0 | 312 | // A1 and B1 are the same, but B's version is newer. We expect A1 to be downloaded |
michael@0 | 313 | // and B1 to be uploaded. |
michael@0 | 314 | // A2 and B2 are the same, but A's version is newer. We expect A2 to be downloaded |
michael@0 | 315 | // and B2 to not be uploaded. |
michael@0 | 316 | // Both A3 and B3 are new. We expect them to go in each direction. |
michael@0 | 317 | // Expected counts, then: |
michael@0 | 318 | // Repo A: B1 + B3 |
michael@0 | 319 | // Repo B: A1 + A2 + A3 |
michael@0 | 320 | repoB.wbos.put(recordB1.guid, recordB1); |
michael@0 | 321 | repoB.wbos.put(recordB2.guid, recordB2); |
michael@0 | 322 | repoB.wbos.put(recordB3.guid, recordB3); |
michael@0 | 323 | repoA.wbos.put(recordA1.guid, recordA1); |
michael@0 | 324 | repoA.wbos.put(recordA2.guid, recordA2); |
michael@0 | 325 | repoA.wbos.put(recordA3.guid, recordA3); |
michael@0 | 326 | |
michael@0 | 327 | final Synchronizer s = new Synchronizer(); |
michael@0 | 328 | s.repositoryA = repoA; |
michael@0 | 329 | s.repositoryB = repoB; |
michael@0 | 330 | |
michael@0 | 331 | Runnable r = new Runnable() { |
michael@0 | 332 | @Override |
michael@0 | 333 | public void run() { |
michael@0 | 334 | s.synchronize(getApplicationContext(), new SynchronizerDelegate() { |
michael@0 | 335 | |
michael@0 | 336 | @Override |
michael@0 | 337 | public void onSynchronized(Synchronizer synchronizer) { |
michael@0 | 338 | long countA = repoA.counter.get(); |
michael@0 | 339 | long countB = repoB.counter.get(); |
michael@0 | 340 | Logger.debug(getName(), "Counts: " + countA + ", " + countB); |
michael@0 | 341 | assertEq(2L, countA); |
michael@0 | 342 | assertEq(3L, countB); |
michael@0 | 343 | |
michael@0 | 344 | // Testing for store timestamp 'hack'. |
michael@0 | 345 | // We fetched from A first, and so its bundle timestamp will be the last |
michael@0 | 346 | // stored time. We fetched from B second, so its bundle timestamp will be |
michael@0 | 347 | // the last fetched time. |
michael@0 | 348 | final long timestampA = synchronizer.bundleA.getTimestamp(); |
michael@0 | 349 | final long timestampB = synchronizer.bundleB.getTimestamp(); |
michael@0 | 350 | Logger.debug(getName(), "Repo A timestamp: " + timestampA); |
michael@0 | 351 | Logger.debug(getName(), "Repo B timestamp: " + timestampB); |
michael@0 | 352 | Logger.debug(getName(), "Repo A fetch done: " + repoA.stats.fetchCompleted); |
michael@0 | 353 | Logger.debug(getName(), "Repo A store done: " + repoA.stats.storeCompleted); |
michael@0 | 354 | Logger.debug(getName(), "Repo B fetch done: " + repoB.stats.fetchCompleted); |
michael@0 | 355 | Logger.debug(getName(), "Repo B store done: " + repoB.stats.storeCompleted); |
michael@0 | 356 | |
michael@0 | 357 | assertTrue(timestampB <= timestampA); |
michael@0 | 358 | assertTrue(repoA.stats.fetchCompleted <= timestampA); |
michael@0 | 359 | assertTrue(repoA.stats.storeCompleted >= repoA.stats.fetchCompleted); |
michael@0 | 360 | assertEquals(repoA.stats.storeCompleted, timestampA); |
michael@0 | 361 | assertEquals(repoB.stats.fetchCompleted, timestampB); |
michael@0 | 362 | performNotify(); |
michael@0 | 363 | } |
michael@0 | 364 | |
michael@0 | 365 | @Override |
michael@0 | 366 | public void onSynchronizeFailed(Synchronizer synchronizer, |
michael@0 | 367 | Exception lastException, String reason) { |
michael@0 | 368 | Logger.debug(getName(), "Failed."); |
michael@0 | 369 | performNotify(new AssertionFailedError("Should not fail.")); |
michael@0 | 370 | } |
michael@0 | 371 | }); |
michael@0 | 372 | } |
michael@0 | 373 | }; |
michael@0 | 374 | |
michael@0 | 375 | performWait(onThreadRunnable(r)); |
michael@0 | 376 | } |
michael@0 | 377 | } |