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