1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/tests/background/junit3/src/db/AndroidBrowserRepositoryTestCase.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,818 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +package org.mozilla.gecko.background.db; 1.8 + 1.9 +import java.util.concurrent.ExecutorService; 1.10 + 1.11 +import org.mozilla.gecko.background.common.log.Logger; 1.12 +import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; 1.13 +import org.mozilla.gecko.background.sync.helpers.DefaultBeginDelegate; 1.14 +import org.mozilla.gecko.background.sync.helpers.DefaultCleanDelegate; 1.15 +import org.mozilla.gecko.background.sync.helpers.DefaultFetchDelegate; 1.16 +import org.mozilla.gecko.background.sync.helpers.DefaultFinishDelegate; 1.17 +import org.mozilla.gecko.background.sync.helpers.DefaultSessionCreationDelegate; 1.18 +import org.mozilla.gecko.background.sync.helpers.DefaultStoreDelegate; 1.19 +import org.mozilla.gecko.background.sync.helpers.ExpectBeginDelegate; 1.20 +import org.mozilla.gecko.background.sync.helpers.ExpectBeginFailDelegate; 1.21 +import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate; 1.22 +import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate; 1.23 +import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate; 1.24 +import org.mozilla.gecko.background.sync.helpers.ExpectFinishFailDelegate; 1.25 +import org.mozilla.gecko.background.sync.helpers.ExpectGuidsSinceDelegate; 1.26 +import org.mozilla.gecko.background.sync.helpers.ExpectInvalidRequestFetchDelegate; 1.27 +import org.mozilla.gecko.background.sync.helpers.ExpectManyStoredDelegate; 1.28 +import org.mozilla.gecko.background.sync.helpers.ExpectStoreCompletedDelegate; 1.29 +import org.mozilla.gecko.background.sync.helpers.ExpectStoredDelegate; 1.30 +import org.mozilla.gecko.background.sync.helpers.SessionTestHelper; 1.31 +import org.mozilla.gecko.background.testhelpers.WaitHelper; 1.32 +import org.mozilla.gecko.db.BrowserContract; 1.33 +import org.mozilla.gecko.sync.Utils; 1.34 +import org.mozilla.gecko.sync.repositories.InactiveSessionException; 1.35 +import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; 1.36 +import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; 1.37 +import org.mozilla.gecko.sync.repositories.Repository; 1.38 +import org.mozilla.gecko.sync.repositories.RepositorySession; 1.39 +import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositoryDataAccessor; 1.40 +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate; 1.41 +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate; 1.42 +import org.mozilla.gecko.sync.repositories.domain.Record; 1.43 + 1.44 +import android.content.ContentValues; 1.45 +import android.content.Context; 1.46 + 1.47 +public abstract class AndroidBrowserRepositoryTestCase extends AndroidSyncTestCase { 1.48 + protected static String LOG_TAG = "BrowserRepositoryTest"; 1.49 + 1.50 + protected static void wipe(AndroidBrowserRepositoryDataAccessor helper) { 1.51 + Logger.debug(LOG_TAG, "Wiping."); 1.52 + try { 1.53 + helper.wipe(); 1.54 + } catch (NullPointerException e) { 1.55 + // This will be handled in begin, here we can just ignore 1.56 + // the error if it actually occurs since this is just test 1.57 + // code. We will throw a ProfileDatabaseException. This 1.58 + // error shouldn't occur in the future, but results from 1.59 + // trying to access content providers before Fennec has 1.60 + // been run at least once. 1.61 + Logger.error(LOG_TAG, "ProfileDatabaseException seen in wipe. Begin should fail"); 1.62 + fail("NullPointerException in wipe."); 1.63 + } 1.64 + } 1.65 + 1.66 + @Override 1.67 + public void setUp() { 1.68 + AndroidBrowserRepositoryDataAccessor helper = getDataAccessor(); 1.69 + wipe(helper); 1.70 + assertTrue(WaitHelper.getTestWaiter().isIdle()); 1.71 + closeDataAccessor(helper); 1.72 + } 1.73 + 1.74 + public void tearDown() { 1.75 + assertTrue(WaitHelper.getTestWaiter().isIdle()); 1.76 + } 1.77 + 1.78 + protected RepositorySession createSession() { 1.79 + return SessionTestHelper.createSession( 1.80 + getApplicationContext(), 1.81 + getRepository()); 1.82 + } 1.83 + 1.84 + protected RepositorySession createAndBeginSession() { 1.85 + return SessionTestHelper.createAndBeginSession( 1.86 + getApplicationContext(), 1.87 + getRepository()); 1.88 + } 1.89 + 1.90 + protected static void dispose(RepositorySession session) { 1.91 + if (session != null) { 1.92 + session.abort(); 1.93 + } 1.94 + } 1.95 + 1.96 + /** 1.97 + * Hook to return an ExpectFetchDelegate, possibly with special GUIDs ignored. 1.98 + */ 1.99 + public ExpectFetchDelegate preparedExpectFetchDelegate(Record[] expected) { 1.100 + return new ExpectFetchDelegate(expected); 1.101 + } 1.102 + 1.103 + /** 1.104 + * Hook to return an ExpectGuidsSinceDelegate, possibly with special GUIDs ignored. 1.105 + */ 1.106 + public ExpectGuidsSinceDelegate preparedExpectGuidsSinceDelegate(String[] expected) { 1.107 + return new ExpectGuidsSinceDelegate(expected); 1.108 + } 1.109 + 1.110 + /** 1.111 + * Hook to return an ExpectGuidsSinceDelegate expecting only special GUIDs (if there are any). 1.112 + */ 1.113 + public ExpectGuidsSinceDelegate preparedExpectOnlySpecialGuidsSinceDelegate() { 1.114 + return new ExpectGuidsSinceDelegate(new String[] {}); 1.115 + } 1.116 + 1.117 + /** 1.118 + * Hook to return an ExpectFetchSinceDelegate, possibly with special GUIDs ignored. 1.119 + */ 1.120 + public ExpectFetchSinceDelegate preparedExpectFetchSinceDelegate(long timestamp, String[] expected) { 1.121 + return new ExpectFetchSinceDelegate(timestamp, expected); 1.122 + } 1.123 + 1.124 + public static Runnable storeRunnable(final RepositorySession session, final Record record, final DefaultStoreDelegate delegate) { 1.125 + return new Runnable() { 1.126 + @Override 1.127 + public void run() { 1.128 + session.setStoreDelegate(delegate); 1.129 + try { 1.130 + session.store(record); 1.131 + session.storeDone(); 1.132 + } catch (NoStoreDelegateException e) { 1.133 + fail("NoStoreDelegateException should not occur."); 1.134 + } 1.135 + } 1.136 + }; 1.137 + } 1.138 + 1.139 + public static Runnable storeRunnable(final RepositorySession session, final Record record) { 1.140 + return storeRunnable(session, record, new ExpectStoredDelegate(record.guid)); 1.141 + } 1.142 + 1.143 + public static Runnable storeManyRunnable(final RepositorySession session, final Record[] records, final DefaultStoreDelegate delegate) { 1.144 + return new Runnable() { 1.145 + @Override 1.146 + public void run() { 1.147 + session.setStoreDelegate(delegate); 1.148 + try { 1.149 + for (Record record : records) { 1.150 + session.store(record); 1.151 + } 1.152 + session.storeDone(); 1.153 + } catch (NoStoreDelegateException e) { 1.154 + fail("NoStoreDelegateException should not occur."); 1.155 + } 1.156 + } 1.157 + }; 1.158 + } 1.159 + 1.160 + public static Runnable storeManyRunnable(final RepositorySession session, final Record[] records) { 1.161 + return storeManyRunnable(session, records, new ExpectManyStoredDelegate(records)); 1.162 + } 1.163 + 1.164 + /** 1.165 + * Store a record and don't expect a store callback until we're done. 1.166 + * 1.167 + * @param session 1.168 + * @param record 1.169 + * @return Runnable. 1.170 + */ 1.171 + public static Runnable quietStoreRunnable(final RepositorySession session, final Record record) { 1.172 + return storeRunnable(session, record, new ExpectStoreCompletedDelegate()); 1.173 + } 1.174 + 1.175 + public static Runnable beginRunnable(final RepositorySession session, final DefaultBeginDelegate delegate) { 1.176 + return new Runnable() { 1.177 + @Override 1.178 + public void run() { 1.179 + try { 1.180 + session.begin(delegate); 1.181 + } catch (InvalidSessionTransitionException e) { 1.182 + performNotify(e); 1.183 + } 1.184 + } 1.185 + }; 1.186 + } 1.187 + 1.188 + public static Runnable finishRunnable(final RepositorySession session, final DefaultFinishDelegate delegate) { 1.189 + return new Runnable() { 1.190 + @Override 1.191 + public void run() { 1.192 + try { 1.193 + session.finish(delegate); 1.194 + } catch (InactiveSessionException e) { 1.195 + performNotify(e); 1.196 + } 1.197 + } 1.198 + }; 1.199 + } 1.200 + 1.201 + public static Runnable fetchAllRunnable(final RepositorySession session, final ExpectFetchDelegate delegate) { 1.202 + return new Runnable() { 1.203 + @Override 1.204 + public void run() { 1.205 + session.fetchAll(delegate); 1.206 + } 1.207 + }; 1.208 + } 1.209 + 1.210 + public Runnable fetchAllRunnable(final RepositorySession session, final Record[] expectedRecords) { 1.211 + return fetchAllRunnable(session, preparedExpectFetchDelegate(expectedRecords)); 1.212 + } 1.213 + 1.214 + public Runnable guidsSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) { 1.215 + return new Runnable() { 1.216 + @Override 1.217 + public void run() { 1.218 + session.guidsSince(timestamp, preparedExpectGuidsSinceDelegate(expected)); 1.219 + } 1.220 + }; 1.221 + } 1.222 + 1.223 + public Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) { 1.224 + return new Runnable() { 1.225 + @Override 1.226 + public void run() { 1.227 + session.fetchSince(timestamp, preparedExpectFetchSinceDelegate(timestamp, expected)); 1.228 + } 1.229 + }; 1.230 + } 1.231 + 1.232 + public static Runnable fetchRunnable(final RepositorySession session, final String[] guids, final DefaultFetchDelegate delegate) { 1.233 + return new Runnable() { 1.234 + @Override 1.235 + public void run() { 1.236 + try { 1.237 + session.fetch(guids, delegate); 1.238 + } catch (InactiveSessionException e) { 1.239 + performNotify(e); 1.240 + } 1.241 + } 1.242 + }; 1.243 + } 1.244 + public Runnable fetchRunnable(final RepositorySession session, final String[] guids, final Record[] expected) { 1.245 + return fetchRunnable(session, guids, preparedExpectFetchDelegate(expected)); 1.246 + } 1.247 + 1.248 + public static Runnable cleanRunnable(final Repository repository, final boolean success, final Context context, final DefaultCleanDelegate delegate) { 1.249 + return new Runnable() { 1.250 + @Override 1.251 + public void run() { 1.252 + repository.clean(success, delegate, context); 1.253 + } 1.254 + }; 1.255 + } 1.256 + 1.257 + protected abstract Repository getRepository(); 1.258 + protected abstract AndroidBrowserRepositoryDataAccessor getDataAccessor(); 1.259 + 1.260 + protected static void doStore(RepositorySession session, Record[] records) { 1.261 + performWait(storeManyRunnable(session, records)); 1.262 + } 1.263 + 1.264 + // Tests to implement 1.265 + public abstract void testFetchAll(); 1.266 + public abstract void testGuidsSinceReturnMultipleRecords(); 1.267 + public abstract void testGuidsSinceReturnNoRecords(); 1.268 + public abstract void testFetchSinceOneRecord(); 1.269 + public abstract void testFetchSinceReturnNoRecords(); 1.270 + public abstract void testFetchOneRecordByGuid(); 1.271 + public abstract void testFetchMultipleRecordsByGuids(); 1.272 + public abstract void testFetchNoRecordByGuid(); 1.273 + public abstract void testWipe(); 1.274 + public abstract void testStore(); 1.275 + public abstract void testRemoteNewerTimeStamp(); 1.276 + public abstract void testLocalNewerTimeStamp(); 1.277 + public abstract void testDeleteRemoteNewer(); 1.278 + public abstract void testDeleteLocalNewer(); 1.279 + public abstract void testDeleteRemoteLocalNonexistent(); 1.280 + public abstract void testStoreIdenticalExceptGuid(); 1.281 + public abstract void testCleanMultipleRecords(); 1.282 + 1.283 + 1.284 + /* 1.285 + * Test abstractions 1.286 + */ 1.287 + protected void basicStoreTest(Record record) { 1.288 + final RepositorySession session = createAndBeginSession(); 1.289 + performWait(storeRunnable(session, record)); 1.290 + } 1.291 + 1.292 + protected void basicFetchAllTest(Record[] expected) { 1.293 + Logger.debug("rnewman", "Starting testFetchAll."); 1.294 + RepositorySession session = createAndBeginSession(); 1.295 + Logger.debug("rnewman", "Prepared."); 1.296 + 1.297 + AndroidBrowserRepositoryDataAccessor helper = getDataAccessor(); 1.298 + helper.dumpDB(); 1.299 + performWait(storeManyRunnable(session, expected)); 1.300 + 1.301 + helper.dumpDB(); 1.302 + performWait(fetchAllRunnable(session, expected)); 1.303 + 1.304 + closeDataAccessor(helper); 1.305 + dispose(session); 1.306 + } 1.307 + 1.308 + /* 1.309 + * Tests for clean 1.310 + */ 1.311 + // Input: 4 records; 2 which are to be cleaned, 2 which should remain after the clean 1.312 + protected void cleanMultipleRecords(Record delete0, Record delete1, Record keep0, Record keep1, Record keep2) { 1.313 + RepositorySession session = createAndBeginSession(); 1.314 + doStore(session, new Record[] { 1.315 + delete0, delete1, keep0, keep1, keep2 1.316 + }); 1.317 + 1.318 + // Force two records to appear deleted. 1.319 + AndroidBrowserRepositoryDataAccessor db = getDataAccessor(); 1.320 + ContentValues cv = new ContentValues(); 1.321 + cv.put(BrowserContract.SyncColumns.IS_DELETED, 1); 1.322 + db.updateByGuid(delete0.guid, cv); 1.323 + db.updateByGuid(delete1.guid, cv); 1.324 + 1.325 + final DefaultCleanDelegate delegate = new DefaultCleanDelegate() { 1.326 + public void onCleaned(Repository repo) { 1.327 + performNotify(); 1.328 + } 1.329 + }; 1.330 + 1.331 + final Runnable cleanRunnable = cleanRunnable( 1.332 + getRepository(), 1.333 + true, 1.334 + getApplicationContext(), 1.335 + delegate); 1.336 + 1.337 + performWait(cleanRunnable); 1.338 + performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(new Record[] { keep0, keep1, keep2}))); 1.339 + closeDataAccessor(db); 1.340 + dispose(session); 1.341 + } 1.342 + 1.343 + /* 1.344 + * Tests for guidsSince 1.345 + */ 1.346 + protected void guidsSinceReturnMultipleRecords(Record record0, Record record1) { 1.347 + RepositorySession session = createAndBeginSession(); 1.348 + long timestamp = System.currentTimeMillis(); 1.349 + 1.350 + String[] expected = new String[2]; 1.351 + expected[0] = record0.guid; 1.352 + expected[1] = record1.guid; 1.353 + 1.354 + Logger.debug(getName(), "Storing two records..."); 1.355 + performWait(storeManyRunnable(session, new Record[] { record0, record1 })); 1.356 + Logger.debug(getName(), "Getting guids since " + timestamp + "; expecting " + expected.length); 1.357 + performWait(guidsSinceRunnable(session, timestamp, expected)); 1.358 + dispose(session); 1.359 + } 1.360 + 1.361 + protected void guidsSinceReturnNoRecords(Record record0) { 1.362 + RepositorySession session = createAndBeginSession(); 1.363 + 1.364 + // Store 1 record in the past. 1.365 + performWait(storeRunnable(session, record0)); 1.366 + 1.367 + String[] expected = {}; 1.368 + performWait(guidsSinceRunnable(session, System.currentTimeMillis() + 1000, expected)); 1.369 + dispose(session); 1.370 + } 1.371 + 1.372 + /* 1.373 + * Tests for fetchSince 1.374 + */ 1.375 + protected void fetchSinceOneRecord(Record record0, Record record1) { 1.376 + RepositorySession session = createAndBeginSession(); 1.377 + 1.378 + performWait(storeRunnable(session, record0)); 1.379 + long timestamp = System.currentTimeMillis(); 1.380 + Logger.debug("fetchSinceOneRecord", "Entering synchronized section. Timestamp " + timestamp); 1.381 + synchronized(this) { 1.382 + try { 1.383 + wait(1000); 1.384 + } catch (InterruptedException e) { 1.385 + Logger.warn("fetchSinceOneRecord", "Interrupted.", e); 1.386 + } 1.387 + } 1.388 + Logger.debug("fetchSinceOneRecord", "Storing."); 1.389 + performWait(storeRunnable(session, record1)); 1.390 + 1.391 + Logger.debug("fetchSinceOneRecord", "Fetching record 1."); 1.392 + String[] expectedOne = new String[] { record1.guid }; 1.393 + performWait(fetchSinceRunnable(session, timestamp + 10, expectedOne)); 1.394 + 1.395 + Logger.debug("fetchSinceOneRecord", "Fetching both, relying on inclusiveness."); 1.396 + String[] expectedBoth = new String[] { record0.guid, record1.guid }; 1.397 + performWait(fetchSinceRunnable(session, timestamp - 3000, expectedBoth)); 1.398 + 1.399 + Logger.debug("fetchSinceOneRecord", "Done."); 1.400 + dispose(session); 1.401 + } 1.402 + 1.403 + protected void fetchSinceReturnNoRecords(Record record) { 1.404 + RepositorySession session = createAndBeginSession(); 1.405 + 1.406 + performWait(storeRunnable(session, record)); 1.407 + 1.408 + long timestamp = System.currentTimeMillis(); 1.409 + 1.410 + performWait(fetchSinceRunnable(session, timestamp + 2000, new String[] {})); 1.411 + dispose(session); 1.412 + } 1.413 + 1.414 + protected void fetchOneRecordByGuid(Record record0, Record record1) { 1.415 + RepositorySession session = createAndBeginSession(); 1.416 + 1.417 + Record[] store = new Record[] { record0, record1 }; 1.418 + performWait(storeManyRunnable(session, store)); 1.419 + 1.420 + String[] guids = new String[] { record0.guid }; 1.421 + Record[] expected = new Record[] { record0 }; 1.422 + performWait(fetchRunnable(session, guids, expected)); 1.423 + dispose(session); 1.424 + } 1.425 + 1.426 + protected void fetchMultipleRecordsByGuids(Record record0, 1.427 + Record record1, Record record2) { 1.428 + RepositorySession session = createAndBeginSession(); 1.429 + 1.430 + Record[] store = new Record[] { record0, record1, record2 }; 1.431 + performWait(storeManyRunnable(session, store)); 1.432 + 1.433 + String[] guids = new String[] { record0.guid, record2.guid }; 1.434 + Record[] expected = new Record[] { record0, record2 }; 1.435 + performWait(fetchRunnable(session, guids, expected)); 1.436 + dispose(session); 1.437 + } 1.438 + 1.439 + protected void fetchNoRecordByGuid(Record record) { 1.440 + RepositorySession session = createAndBeginSession(); 1.441 + 1.442 + performWait(storeRunnable(session, record)); 1.443 + performWait(fetchRunnable(session, 1.444 + new String[] { Utils.generateGuid() }, 1.445 + new Record[] {})); 1.446 + dispose(session); 1.447 + } 1.448 + 1.449 + /* 1.450 + * Test wipe 1.451 + */ 1.452 + protected void doWipe(final Record record0, final Record record1) { 1.453 + final RepositorySession session = createAndBeginSession(); 1.454 + final Runnable run = new Runnable() { 1.455 + @Override 1.456 + public void run() { 1.457 + session.wipe(new RepositorySessionWipeDelegate() { 1.458 + public void onWipeSucceeded() { 1.459 + performNotify(); 1.460 + } 1.461 + public void onWipeFailed(Exception ex) { 1.462 + fail("wipe should have succeeded"); 1.463 + performNotify(); 1.464 + } 1.465 + @Override 1.466 + public RepositorySessionWipeDelegate deferredWipeDelegate(final ExecutorService executor) { 1.467 + final RepositorySessionWipeDelegate self = this; 1.468 + return new RepositorySessionWipeDelegate() { 1.469 + 1.470 + @Override 1.471 + public void onWipeSucceeded() { 1.472 + new Thread(new Runnable() { 1.473 + @Override 1.474 + public void run() { 1.475 + self.onWipeSucceeded(); 1.476 + }}).start(); 1.477 + } 1.478 + 1.479 + @Override 1.480 + public void onWipeFailed(final Exception ex) { 1.481 + new Thread(new Runnable() { 1.482 + @Override 1.483 + public void run() { 1.484 + self.onWipeFailed(ex); 1.485 + }}).start(); 1.486 + } 1.487 + 1.488 + @Override 1.489 + public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService newExecutor) { 1.490 + if (newExecutor == executor) { 1.491 + return this; 1.492 + } 1.493 + throw new IllegalArgumentException("Can't re-defer this delegate."); 1.494 + } 1.495 + }; 1.496 + } 1.497 + }); 1.498 + } 1.499 + }; 1.500 + 1.501 + // Store 2 records. 1.502 + Record[] records = new Record[] { record0, record1 }; 1.503 + performWait(storeManyRunnable(session, records)); 1.504 + performWait(fetchAllRunnable(session, records)); 1.505 + 1.506 + // Wipe. 1.507 + performWait(run); 1.508 + dispose(session); 1.509 + } 1.510 + 1.511 + /* 1.512 + * TODO adding or subtracting from lastModified timestamps does NOTHING 1.513 + * since it gets overwritten when we store stuff. See other tests 1.514 + * for ways to do this properly. 1.515 + */ 1.516 + 1.517 + /* 1.518 + * Record being stored has newer timestamp than existing local record, local 1.519 + * record has not been modified since last sync. 1.520 + */ 1.521 + protected void remoteNewerTimeStamp(Record local, Record remote) { 1.522 + final RepositorySession session = createAndBeginSession(); 1.523 + 1.524 + // Record existing and hasn't changed since before lastSync. 1.525 + // Automatically will be assigned lastModified = current time. 1.526 + performWait(storeRunnable(session, local)); 1.527 + 1.528 + remote.guid = local.guid; 1.529 + 1.530 + // Get the timestamp and make remote newer than it 1.531 + ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local }); 1.532 + performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate)); 1.533 + remote.lastModified = timestampDelegate.records.get(0).lastModified + 1000; 1.534 + performWait(storeRunnable(session, remote)); 1.535 + 1.536 + Record[] expected = new Record[] { remote }; 1.537 + ExpectFetchDelegate delegate = preparedExpectFetchDelegate(expected); 1.538 + performWait(fetchAllRunnable(session, delegate)); 1.539 + dispose(session); 1.540 + } 1.541 + 1.542 + /* 1.543 + * Local record has a newer timestamp than the record being stored. For now, 1.544 + * we just take newer (local) record) 1.545 + */ 1.546 + protected void localNewerTimeStamp(Record local, Record remote) { 1.547 + final RepositorySession session = createAndBeginSession(); 1.548 + 1.549 + performWait(storeRunnable(session, local)); 1.550 + 1.551 + remote.guid = local.guid; 1.552 + 1.553 + // Get the timestamp and make remote older than it 1.554 + ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local }); 1.555 + performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate)); 1.556 + remote.lastModified = timestampDelegate.records.get(0).lastModified - 1000; 1.557 + performWait(storeRunnable(session, remote)); 1.558 + 1.559 + // Do a fetch and make sure that we get back the local record. 1.560 + Record[] expected = new Record[] { local }; 1.561 + performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected))); 1.562 + dispose(session); 1.563 + } 1.564 + 1.565 + /* 1.566 + * Insert a record that is marked as deleted, remote has newer timestamp 1.567 + */ 1.568 + protected void deleteRemoteNewer(Record local, Record remote) { 1.569 + final RepositorySession session = createAndBeginSession(); 1.570 + 1.571 + // Record existing and hasn't changed since before lastSync. 1.572 + // Automatically will be assigned lastModified = current time. 1.573 + performWait(storeRunnable(session, local)); 1.574 + 1.575 + // Pass the same record to store, but mark it deleted and modified 1.576 + // more recently 1.577 + ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local }); 1.578 + performWait(fetchRunnable(session, new String[] { local.guid }, timestampDelegate)); 1.579 + remote.lastModified = timestampDelegate.records.get(0).lastModified + 1000; 1.580 + remote.deleted = true; 1.581 + remote.guid = local.guid; 1.582 + performWait(storeRunnable(session, remote)); 1.583 + 1.584 + performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(new Record[]{}))); 1.585 + dispose(session); 1.586 + } 1.587 + 1.588 + // Store two records that are identical (this has different meanings based on the 1.589 + // type of record) other than their guids. The record existing locally already 1.590 + // should have its guid replaced (the assumption is that the record existed locally 1.591 + // and then sync was enabled and this record existed on another sync'd device). 1.592 + public void storeIdenticalExceptGuid(Record record0) { 1.593 + Logger.debug("storeIdenticalExceptGuid", "Started."); 1.594 + final RepositorySession session = createAndBeginSession(); 1.595 + Logger.debug("storeIdenticalExceptGuid", "Session is " + session); 1.596 + performWait(storeRunnable(session, record0)); 1.597 + Logger.debug("storeIdenticalExceptGuid", "Stored record0."); 1.598 + DefaultFetchDelegate timestampDelegate = getTimestampDelegate(record0.guid); 1.599 + 1.600 + performWait(fetchRunnable(session, new String[] { record0.guid }, timestampDelegate)); 1.601 + Logger.debug("storeIdenticalExceptGuid", "fetchRunnable done."); 1.602 + record0.lastModified = timestampDelegate.records.get(0).lastModified + 3000; 1.603 + record0.guid = Utils.generateGuid(); 1.604 + Logger.debug("storeIdenticalExceptGuid", "Storing modified..."); 1.605 + performWait(storeRunnable(session, record0)); 1.606 + Logger.debug("storeIdenticalExceptGuid", "Stored modified."); 1.607 + 1.608 + Record[] expected = new Record[] { record0 }; 1.609 + performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected))); 1.610 + Logger.debug("storeIdenticalExceptGuid", "Fetched all. Returning."); 1.611 + dispose(session); 1.612 + } 1.613 + 1.614 + // Special delegate so that we don't verify parenting is correct since 1.615 + // at some points it won't be since parent folder hasn't been stored. 1.616 + private DefaultFetchDelegate getTimestampDelegate(final String guid) { 1.617 + return new DefaultFetchDelegate() { 1.618 + @Override 1.619 + public void onFetchCompleted(final long fetchEnd) { 1.620 + assertEquals(guid, this.records.get(0).guid); 1.621 + performNotify(); 1.622 + } 1.623 + }; 1.624 + } 1.625 + 1.626 + /* 1.627 + * Insert a record that is marked as deleted, local has newer timestamp 1.628 + * and was not marked deleted (so keep it) 1.629 + */ 1.630 + protected void deleteLocalNewer(Record local, Record remote) { 1.631 + Logger.debug("deleteLocalNewer", "Begin."); 1.632 + final RepositorySession session = createAndBeginSession(); 1.633 + 1.634 + Logger.debug("deleteLocalNewer", "Storing local..."); 1.635 + performWait(storeRunnable(session, local)); 1.636 + 1.637 + // Create an older version of a record with the same GUID. 1.638 + remote.guid = local.guid; 1.639 + 1.640 + Logger.debug("deleteLocalNewer", "Fetching..."); 1.641 + 1.642 + // Get the timestamp and make remote older than it 1.643 + Record[] expected = new Record[] { local }; 1.644 + ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(expected); 1.645 + performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate)); 1.646 + 1.647 + Logger.debug("deleteLocalNewer", "Fetched."); 1.648 + remote.lastModified = timestampDelegate.records.get(0).lastModified - 1000; 1.649 + 1.650 + Logger.debug("deleteLocalNewer", "Last modified is " + remote.lastModified); 1.651 + remote.deleted = true; 1.652 + Logger.debug("deleteLocalNewer", "Storing deleted..."); 1.653 + performWait(quietStoreRunnable(session, remote)); // This appears to do a lot of work...?! 1.654 + 1.655 + // Do a fetch and make sure that we get back the first (local) record. 1.656 + performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected))); 1.657 + Logger.debug("deleteLocalNewer", "Fetched and done!"); 1.658 + dispose(session); 1.659 + } 1.660 + 1.661 + /* 1.662 + * Insert a record that is marked as deleted, record never existed locally 1.663 + */ 1.664 + protected void deleteRemoteLocalNonexistent(Record remote) { 1.665 + final RepositorySession session = createAndBeginSession(); 1.666 + 1.667 + long timestamp = 1000000000; 1.668 + 1.669 + // Pass a record marked deleted to store, doesn't exist locally 1.670 + remote.lastModified = timestamp; 1.671 + remote.deleted = true; 1.672 + performWait(quietStoreRunnable(session, remote)); 1.673 + 1.674 + ExpectFetchDelegate delegate = preparedExpectFetchDelegate(new Record[]{}); 1.675 + performWait(fetchAllRunnable(session, delegate)); 1.676 + dispose(session); 1.677 + } 1.678 + 1.679 + /* 1.680 + * Tests that don't require specific records based on type of repository. 1.681 + * These tests don't need to be overriden in subclasses, they will just work. 1.682 + */ 1.683 + public void testCreateSessionNullContext() { 1.684 + Logger.debug(LOG_TAG, "In testCreateSessionNullContext."); 1.685 + Repository repo = getRepository(); 1.686 + try { 1.687 + repo.createSession(new DefaultSessionCreationDelegate(), null); 1.688 + fail("Should throw."); 1.689 + } catch (Exception ex) { 1.690 + assertNotNull(ex); 1.691 + } 1.692 + } 1.693 + 1.694 + public void testStoreNullRecord() { 1.695 + final RepositorySession session = createAndBeginSession(); 1.696 + try { 1.697 + session.setStoreDelegate(new DefaultStoreDelegate()); 1.698 + session.store(null); 1.699 + fail("Should throw."); 1.700 + } catch (Exception ex) { 1.701 + assertNotNull(ex); 1.702 + } 1.703 + dispose(session); 1.704 + } 1.705 + 1.706 + public void testFetchNoGuids() { 1.707 + final RepositorySession session = createAndBeginSession(); 1.708 + performWait(fetchRunnable(session, new String[] {}, new ExpectInvalidRequestFetchDelegate())); 1.709 + dispose(session); 1.710 + } 1.711 + 1.712 + public void testFetchNullGuids() { 1.713 + final RepositorySession session = createAndBeginSession(); 1.714 + performWait(fetchRunnable(session, null, new ExpectInvalidRequestFetchDelegate())); 1.715 + dispose(session); 1.716 + } 1.717 + 1.718 + public void testBeginOnNewSession() { 1.719 + final RepositorySession session = createSession(); 1.720 + performWait(beginRunnable(session, new ExpectBeginDelegate())); 1.721 + dispose(session); 1.722 + } 1.723 + 1.724 + public void testBeginOnRunningSession() { 1.725 + final RepositorySession session = createAndBeginSession(); 1.726 + try { 1.727 + session.begin(new ExpectBeginFailDelegate()); 1.728 + } catch (InvalidSessionTransitionException e) { 1.729 + dispose(session); 1.730 + return; 1.731 + } 1.732 + fail("Should have caught InvalidSessionTransitionException."); 1.733 + } 1.734 + 1.735 + public void testBeginOnFinishedSession() throws InactiveSessionException { 1.736 + final RepositorySession session = createAndBeginSession(); 1.737 + performWait(finishRunnable(session, new ExpectFinishDelegate())); 1.738 + try { 1.739 + session.begin(new ExpectBeginFailDelegate()); 1.740 + } catch (InvalidSessionTransitionException e) { 1.741 + Logger.debug(getName(), "Yay! Got an exception.", e); 1.742 + dispose(session); 1.743 + return; 1.744 + } catch (Exception e) { 1.745 + Logger.debug(getName(), "Yay! Got an exception.", e); 1.746 + dispose(session); 1.747 + return; 1.748 + } 1.749 + fail("Should have caught InvalidSessionTransitionException."); 1.750 + } 1.751 + 1.752 + public void testFinishOnFinishedSession() throws InactiveSessionException { 1.753 + final RepositorySession session = createAndBeginSession(); 1.754 + performWait(finishRunnable(session, new ExpectFinishDelegate())); 1.755 + try { 1.756 + session.finish(new ExpectFinishFailDelegate()); 1.757 + } catch (InactiveSessionException e) { 1.758 + dispose(session); 1.759 + return; 1.760 + } 1.761 + fail("Should have caught InactiveSessionException."); 1.762 + } 1.763 + 1.764 + public void testFetchOnInactiveSession() throws InactiveSessionException { 1.765 + final RepositorySession session = createSession(); 1.766 + try { 1.767 + session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate()); 1.768 + } catch (InactiveSessionException e) { 1.769 + // Yay. 1.770 + dispose(session); 1.771 + return; 1.772 + }; 1.773 + fail("Should have caught InactiveSessionException."); 1.774 + } 1.775 + 1.776 + public void testFetchOnFinishedSession() { 1.777 + final RepositorySession session = createAndBeginSession(); 1.778 + Logger.debug(getName(), "Finishing..."); 1.779 + performWait(finishRunnable(session, new ExpectFinishDelegate())); 1.780 + try { 1.781 + session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate()); 1.782 + } catch (InactiveSessionException e) { 1.783 + // Yay. 1.784 + dispose(session); 1.785 + return; 1.786 + }; 1.787 + fail("Should have caught InactiveSessionException."); 1.788 + } 1.789 + 1.790 + public void testGuidsSinceOnUnstartedSession() { 1.791 + final RepositorySession session = createSession(); 1.792 + Runnable run = new Runnable() { 1.793 + @Override 1.794 + public void run() { 1.795 + session.guidsSince(System.currentTimeMillis(), 1.796 + new RepositorySessionGuidsSinceDelegate() { 1.797 + public void onGuidsSinceSucceeded(String[] guids) { 1.798 + fail("Session inactive, should fail"); 1.799 + performNotify(); 1.800 + } 1.801 + 1.802 + public void onGuidsSinceFailed(Exception ex) { 1.803 + verifyInactiveException(ex); 1.804 + performNotify(); 1.805 + } 1.806 + }); 1.807 + } 1.808 + }; 1.809 + performWait(run); 1.810 + dispose(session); 1.811 + } 1.812 + 1.813 + private static void verifyInactiveException(Exception ex) { 1.814 + if (!(ex instanceof InactiveSessionException)) { 1.815 + fail("Wrong exception type"); 1.816 + } 1.817 + } 1.818 + 1.819 + protected void closeDataAccessor(AndroidBrowserRepositoryDataAccessor dataAccessor) { 1.820 + } 1.821 +}