mobile/android/tests/background/junit3/src/db/AndroidBrowserRepositoryTestCase.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* Any copyright is dedicated to the Public Domain.
     2    http://creativecommons.org/publicdomain/zero/1.0/ */
     4 package org.mozilla.gecko.background.db;
     6 import java.util.concurrent.ExecutorService;
     8 import org.mozilla.gecko.background.common.log.Logger;
     9 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
    10 import org.mozilla.gecko.background.sync.helpers.DefaultBeginDelegate;
    11 import org.mozilla.gecko.background.sync.helpers.DefaultCleanDelegate;
    12 import org.mozilla.gecko.background.sync.helpers.DefaultFetchDelegate;
    13 import org.mozilla.gecko.background.sync.helpers.DefaultFinishDelegate;
    14 import org.mozilla.gecko.background.sync.helpers.DefaultSessionCreationDelegate;
    15 import org.mozilla.gecko.background.sync.helpers.DefaultStoreDelegate;
    16 import org.mozilla.gecko.background.sync.helpers.ExpectBeginDelegate;
    17 import org.mozilla.gecko.background.sync.helpers.ExpectBeginFailDelegate;
    18 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
    19 import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate;
    20 import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate;
    21 import org.mozilla.gecko.background.sync.helpers.ExpectFinishFailDelegate;
    22 import org.mozilla.gecko.background.sync.helpers.ExpectGuidsSinceDelegate;
    23 import org.mozilla.gecko.background.sync.helpers.ExpectInvalidRequestFetchDelegate;
    24 import org.mozilla.gecko.background.sync.helpers.ExpectManyStoredDelegate;
    25 import org.mozilla.gecko.background.sync.helpers.ExpectStoreCompletedDelegate;
    26 import org.mozilla.gecko.background.sync.helpers.ExpectStoredDelegate;
    27 import org.mozilla.gecko.background.sync.helpers.SessionTestHelper;
    28 import org.mozilla.gecko.background.testhelpers.WaitHelper;
    29 import org.mozilla.gecko.db.BrowserContract;
    30 import org.mozilla.gecko.sync.Utils;
    31 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
    32 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
    33 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
    34 import org.mozilla.gecko.sync.repositories.Repository;
    35 import org.mozilla.gecko.sync.repositories.RepositorySession;
    36 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositoryDataAccessor;
    37 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
    38 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
    39 import org.mozilla.gecko.sync.repositories.domain.Record;
    41 import android.content.ContentValues;
    42 import android.content.Context;
    44 public abstract class AndroidBrowserRepositoryTestCase extends AndroidSyncTestCase {
    45   protected static String LOG_TAG = "BrowserRepositoryTest";
    47   protected static void wipe(AndroidBrowserRepositoryDataAccessor helper) {
    48     Logger.debug(LOG_TAG, "Wiping.");
    49     try {
    50       helper.wipe();
    51     } catch (NullPointerException e) {
    52       // This will be handled in begin, here we can just ignore
    53       // the error if it actually occurs since this is just test
    54       // code. We will throw a ProfileDatabaseException. This
    55       // error shouldn't occur in the future, but results from
    56       // trying to access content providers before Fennec has
    57       // been run at least once.
    58       Logger.error(LOG_TAG, "ProfileDatabaseException seen in wipe. Begin should fail");
    59       fail("NullPointerException in wipe.");
    60     }
    61   }
    63   @Override
    64   public void setUp() {
    65     AndroidBrowserRepositoryDataAccessor helper = getDataAccessor();
    66     wipe(helper);
    67     assertTrue(WaitHelper.getTestWaiter().isIdle());
    68     closeDataAccessor(helper);
    69   }
    71   public void tearDown() {
    72     assertTrue(WaitHelper.getTestWaiter().isIdle());
    73   }
    75   protected RepositorySession createSession() {
    76     return SessionTestHelper.createSession(
    77         getApplicationContext(),
    78         getRepository());
    79   }
    81   protected RepositorySession createAndBeginSession() {
    82     return SessionTestHelper.createAndBeginSession(
    83         getApplicationContext(),
    84         getRepository());
    85   }
    87   protected static void dispose(RepositorySession session) {
    88     if (session != null) {
    89       session.abort();
    90     }
    91   }
    93   /**
    94    * Hook to return an ExpectFetchDelegate, possibly with special GUIDs ignored.
    95    */
    96   public ExpectFetchDelegate preparedExpectFetchDelegate(Record[] expected) {
    97     return new ExpectFetchDelegate(expected);
    98   }
   100   /**
   101    * Hook to return an ExpectGuidsSinceDelegate, possibly with special GUIDs ignored.
   102    */
   103   public ExpectGuidsSinceDelegate preparedExpectGuidsSinceDelegate(String[] expected) {
   104     return new ExpectGuidsSinceDelegate(expected);
   105   }
   107   /**
   108    * Hook to return an ExpectGuidsSinceDelegate expecting only special GUIDs (if there are any).
   109    */
   110   public ExpectGuidsSinceDelegate preparedExpectOnlySpecialGuidsSinceDelegate() {
   111     return new ExpectGuidsSinceDelegate(new String[] {});
   112   }
   114   /**
   115    * Hook to return an ExpectFetchSinceDelegate, possibly with special GUIDs ignored.
   116    */
   117   public ExpectFetchSinceDelegate preparedExpectFetchSinceDelegate(long timestamp, String[] expected) {
   118     return new ExpectFetchSinceDelegate(timestamp, expected);
   119   }
   121   public static Runnable storeRunnable(final RepositorySession session, final Record record, final DefaultStoreDelegate delegate) {
   122     return new Runnable() {
   123       @Override
   124       public void run() {
   125         session.setStoreDelegate(delegate);
   126         try {
   127           session.store(record);
   128           session.storeDone();
   129         } catch (NoStoreDelegateException e) {
   130           fail("NoStoreDelegateException should not occur.");
   131         }
   132       }
   133     };
   134   }
   136   public static Runnable storeRunnable(final RepositorySession session, final Record record) {
   137     return storeRunnable(session, record, new ExpectStoredDelegate(record.guid));
   138   }
   140   public static Runnable storeManyRunnable(final RepositorySession session, final Record[] records, final DefaultStoreDelegate delegate) {
   141     return new Runnable() {
   142       @Override
   143       public void run() {
   144         session.setStoreDelegate(delegate);
   145         try {
   146           for (Record record : records) {
   147             session.store(record);
   148           }
   149           session.storeDone();
   150         } catch (NoStoreDelegateException e) {
   151           fail("NoStoreDelegateException should not occur.");
   152         }
   153       }
   154     };
   155   }
   157   public static Runnable storeManyRunnable(final RepositorySession session, final Record[] records) {
   158     return storeManyRunnable(session, records, new ExpectManyStoredDelegate(records));
   159   }
   161   /**
   162    * Store a record and don't expect a store callback until we're done.
   163    *
   164    * @param session
   165    * @param record
   166    * @return Runnable.
   167    */
   168   public static Runnable quietStoreRunnable(final RepositorySession session, final Record record) {
   169     return storeRunnable(session, record, new ExpectStoreCompletedDelegate());
   170   }
   172   public static Runnable beginRunnable(final RepositorySession session, final DefaultBeginDelegate delegate) {
   173     return new Runnable() {
   174       @Override
   175       public void run() {
   176         try {
   177           session.begin(delegate);
   178         } catch (InvalidSessionTransitionException e) {
   179           performNotify(e);
   180         }
   181       }
   182     };
   183   }
   185   public static Runnable finishRunnable(final RepositorySession session, final DefaultFinishDelegate delegate) {
   186     return new Runnable() {
   187       @Override
   188       public void run() {
   189         try {
   190           session.finish(delegate);
   191         } catch (InactiveSessionException e) {
   192           performNotify(e);
   193         }
   194       }
   195     };
   196   }
   198   public static Runnable fetchAllRunnable(final RepositorySession session, final ExpectFetchDelegate delegate) {
   199     return new Runnable() {
   200       @Override
   201       public void run() {
   202         session.fetchAll(delegate);
   203       }
   204     };
   205   }
   207   public Runnable fetchAllRunnable(final RepositorySession session, final Record[] expectedRecords) {
   208     return fetchAllRunnable(session, preparedExpectFetchDelegate(expectedRecords));
   209   }
   211   public Runnable guidsSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) {
   212     return new Runnable() {
   213       @Override
   214       public void run() {
   215         session.guidsSince(timestamp, preparedExpectGuidsSinceDelegate(expected));
   216       }
   217     };
   218   }
   220   public Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) {
   221     return new Runnable() {
   222       @Override
   223       public void run() {
   224         session.fetchSince(timestamp, preparedExpectFetchSinceDelegate(timestamp, expected));
   225       }
   226     };
   227   }
   229   public static Runnable fetchRunnable(final RepositorySession session, final String[] guids, final DefaultFetchDelegate delegate) {
   230     return new Runnable() {
   231       @Override
   232       public void run() {
   233         try {
   234           session.fetch(guids, delegate);
   235         } catch (InactiveSessionException e) {
   236           performNotify(e);
   237         }
   238       }
   239     };
   240   }
   241   public Runnable fetchRunnable(final RepositorySession session, final String[] guids, final Record[] expected) {
   242     return fetchRunnable(session, guids, preparedExpectFetchDelegate(expected));
   243   }
   245   public static Runnable cleanRunnable(final Repository repository, final boolean success, final Context context, final DefaultCleanDelegate delegate) {
   246     return new Runnable() {
   247       @Override
   248       public void run() {
   249         repository.clean(success, delegate, context);
   250       }
   251     };
   252   }
   254   protected abstract Repository getRepository();
   255   protected abstract AndroidBrowserRepositoryDataAccessor getDataAccessor();
   257   protected static void doStore(RepositorySession session, Record[] records) {
   258     performWait(storeManyRunnable(session, records));
   259   }
   261   // Tests to implement
   262   public abstract void testFetchAll();
   263   public abstract void testGuidsSinceReturnMultipleRecords();
   264   public abstract void testGuidsSinceReturnNoRecords();
   265   public abstract void testFetchSinceOneRecord();
   266   public abstract void testFetchSinceReturnNoRecords();
   267   public abstract void testFetchOneRecordByGuid();
   268   public abstract void testFetchMultipleRecordsByGuids();
   269   public abstract void testFetchNoRecordByGuid();
   270   public abstract void testWipe();
   271   public abstract void testStore();
   272   public abstract void testRemoteNewerTimeStamp();
   273   public abstract void testLocalNewerTimeStamp();
   274   public abstract void testDeleteRemoteNewer();
   275   public abstract void testDeleteLocalNewer();
   276   public abstract void testDeleteRemoteLocalNonexistent();
   277   public abstract void testStoreIdenticalExceptGuid();
   278   public abstract void testCleanMultipleRecords();
   281   /*
   282    * Test abstractions
   283    */
   284   protected void basicStoreTest(Record record) {
   285     final RepositorySession session = createAndBeginSession();
   286     performWait(storeRunnable(session, record));
   287   }
   289   protected void basicFetchAllTest(Record[] expected) {
   290     Logger.debug("rnewman", "Starting testFetchAll.");
   291     RepositorySession session = createAndBeginSession();
   292     Logger.debug("rnewman", "Prepared.");
   294     AndroidBrowserRepositoryDataAccessor helper = getDataAccessor();
   295     helper.dumpDB();
   296     performWait(storeManyRunnable(session, expected));
   298     helper.dumpDB();
   299     performWait(fetchAllRunnable(session, expected));
   301     closeDataAccessor(helper);
   302     dispose(session);
   303   }
   305   /*
   306    * Tests for clean
   307    */
   308   // Input: 4 records; 2 which are to be cleaned, 2 which should remain after the clean
   309   protected void cleanMultipleRecords(Record delete0, Record delete1, Record keep0, Record keep1, Record keep2) {
   310     RepositorySession session = createAndBeginSession();
   311     doStore(session, new Record[] {
   312         delete0, delete1, keep0, keep1, keep2
   313     });
   315     // Force two records to appear deleted.
   316     AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
   317     ContentValues cv = new ContentValues();
   318     cv.put(BrowserContract.SyncColumns.IS_DELETED, 1);
   319     db.updateByGuid(delete0.guid, cv);
   320     db.updateByGuid(delete1.guid, cv);
   322     final DefaultCleanDelegate delegate = new DefaultCleanDelegate() {
   323       public void onCleaned(Repository repo) {
   324         performNotify();
   325       }
   326     };
   328     final Runnable cleanRunnable = cleanRunnable(
   329         getRepository(),
   330         true,
   331         getApplicationContext(),
   332         delegate);
   334     performWait(cleanRunnable);
   335     performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(new Record[] { keep0, keep1, keep2})));
   336     closeDataAccessor(db);
   337     dispose(session);
   338   }
   340   /*
   341    * Tests for guidsSince
   342    */
   343   protected void guidsSinceReturnMultipleRecords(Record record0, Record record1) {
   344     RepositorySession session = createAndBeginSession();
   345     long timestamp = System.currentTimeMillis();
   347     String[] expected = new String[2];
   348     expected[0] = record0.guid;
   349     expected[1] = record1.guid;
   351     Logger.debug(getName(), "Storing two records...");
   352     performWait(storeManyRunnable(session, new Record[] { record0, record1 }));
   353     Logger.debug(getName(), "Getting guids since " + timestamp + "; expecting " + expected.length);
   354     performWait(guidsSinceRunnable(session, timestamp, expected));
   355     dispose(session);
   356   }
   358   protected void guidsSinceReturnNoRecords(Record record0) {
   359     RepositorySession session = createAndBeginSession();
   361     //  Store 1 record in the past.
   362     performWait(storeRunnable(session, record0));
   364     String[] expected = {};
   365     performWait(guidsSinceRunnable(session, System.currentTimeMillis() + 1000, expected));
   366     dispose(session);
   367   }
   369   /*
   370    * Tests for fetchSince
   371    */
   372   protected void fetchSinceOneRecord(Record record0, Record record1) {
   373     RepositorySession session = createAndBeginSession();
   375     performWait(storeRunnable(session, record0));
   376     long timestamp = System.currentTimeMillis();
   377     Logger.debug("fetchSinceOneRecord", "Entering synchronized section. Timestamp " + timestamp);
   378     synchronized(this) {
   379       try {
   380         wait(1000);
   381       } catch (InterruptedException e) {
   382         Logger.warn("fetchSinceOneRecord", "Interrupted.", e);
   383       }
   384     }
   385     Logger.debug("fetchSinceOneRecord", "Storing.");
   386     performWait(storeRunnable(session, record1));
   388     Logger.debug("fetchSinceOneRecord", "Fetching record 1.");
   389     String[] expectedOne = new String[] { record1.guid };
   390     performWait(fetchSinceRunnable(session, timestamp + 10, expectedOne));
   392     Logger.debug("fetchSinceOneRecord", "Fetching both, relying on inclusiveness.");
   393     String[] expectedBoth = new String[] { record0.guid, record1.guid };
   394     performWait(fetchSinceRunnable(session, timestamp - 3000, expectedBoth));
   396     Logger.debug("fetchSinceOneRecord", "Done.");
   397     dispose(session);
   398   }
   400   protected void fetchSinceReturnNoRecords(Record record) {
   401     RepositorySession session = createAndBeginSession();
   403     performWait(storeRunnable(session, record));
   405     long timestamp = System.currentTimeMillis();
   407     performWait(fetchSinceRunnable(session, timestamp + 2000, new String[] {}));
   408     dispose(session);
   409   }
   411   protected void fetchOneRecordByGuid(Record record0, Record record1) {
   412     RepositorySession session = createAndBeginSession();
   414     Record[] store = new Record[] { record0, record1 };
   415     performWait(storeManyRunnable(session, store));
   417     String[] guids = new String[] { record0.guid };
   418     Record[] expected = new Record[] { record0 };
   419     performWait(fetchRunnable(session, guids, expected));
   420     dispose(session);
   421   }
   423   protected void fetchMultipleRecordsByGuids(Record record0,
   424       Record record1, Record record2) {
   425     RepositorySession session = createAndBeginSession();
   427     Record[] store = new Record[] { record0, record1, record2 };
   428     performWait(storeManyRunnable(session, store));
   430     String[] guids = new String[] { record0.guid, record2.guid };
   431     Record[] expected = new Record[] { record0, record2 };
   432     performWait(fetchRunnable(session, guids, expected));
   433     dispose(session);
   434   }
   436   protected void fetchNoRecordByGuid(Record record) {
   437     RepositorySession session = createAndBeginSession();
   439     performWait(storeRunnable(session, record));
   440     performWait(fetchRunnable(session,
   441         new String[] { Utils.generateGuid() },
   442         new Record[] {}));
   443     dispose(session);
   444   }
   446   /*
   447    * Test wipe
   448    */
   449    protected void doWipe(final Record record0, final Record record1) {
   450      final RepositorySession session = createAndBeginSession();
   451      final Runnable run = new Runnable() {
   452        @Override
   453        public void run() {
   454          session.wipe(new RepositorySessionWipeDelegate() {
   455            public void onWipeSucceeded() {
   456              performNotify();
   457            }
   458            public void onWipeFailed(Exception ex) {
   459              fail("wipe should have succeeded");
   460              performNotify();
   461            }
   462            @Override
   463            public RepositorySessionWipeDelegate deferredWipeDelegate(final ExecutorService executor) {
   464              final RepositorySessionWipeDelegate self = this;
   465              return new RepositorySessionWipeDelegate() {
   467                @Override
   468                public void onWipeSucceeded() {
   469                  new Thread(new Runnable() {
   470                    @Override
   471                    public void run() {
   472                      self.onWipeSucceeded();
   473                    }}).start();
   474                }
   476                @Override
   477                public void onWipeFailed(final Exception ex) {
   478                  new Thread(new Runnable() {
   479                    @Override
   480                    public void run() {
   481                      self.onWipeFailed(ex);
   482                    }}).start();
   483                }
   485                @Override
   486                public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService newExecutor) {
   487                  if (newExecutor == executor) {
   488                    return this;
   489                  }
   490                  throw new IllegalArgumentException("Can't re-defer this delegate.");
   491                }
   492              };
   493            }
   494          });
   495        }
   496      };
   498      // Store 2 records.
   499      Record[] records = new Record[] { record0, record1 };
   500      performWait(storeManyRunnable(session, records));
   501      performWait(fetchAllRunnable(session, records));
   503      // Wipe.
   504      performWait(run);
   505      dispose(session);
   506    }
   508    /*
   509     * TODO adding or subtracting from lastModified timestamps does NOTHING
   510     * since it gets overwritten when we store stuff. See other tests
   511     * for ways to do this properly.
   512     */
   514    /*
   515     * Record being stored has newer timestamp than existing local record, local
   516     * record has not been modified since last sync.
   517     */
   518    protected void remoteNewerTimeStamp(Record local, Record remote) {
   519      final RepositorySession session = createAndBeginSession();
   521      // Record existing and hasn't changed since before lastSync.
   522      // Automatically will be assigned lastModified = current time.
   523      performWait(storeRunnable(session, local));
   525      remote.guid = local.guid;
   527      // Get the timestamp and make remote newer than it
   528      ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local });
   529      performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate));
   530      remote.lastModified = timestampDelegate.records.get(0).lastModified + 1000;
   531      performWait(storeRunnable(session, remote));
   533      Record[] expected = new Record[] { remote };
   534      ExpectFetchDelegate delegate = preparedExpectFetchDelegate(expected);
   535      performWait(fetchAllRunnable(session, delegate));
   536      dispose(session);
   537    }
   539    /*
   540     * Local record has a newer timestamp than the record being stored. For now,
   541     * we just take newer (local) record)
   542     */
   543    protected void localNewerTimeStamp(Record local, Record remote) {
   544      final RepositorySession session = createAndBeginSession();
   546      performWait(storeRunnable(session, local));
   548      remote.guid = local.guid;
   550      // Get the timestamp and make remote older than it
   551      ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local });
   552      performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate));
   553      remote.lastModified = timestampDelegate.records.get(0).lastModified - 1000;
   554      performWait(storeRunnable(session, remote));
   556      // Do a fetch and make sure that we get back the local record.
   557      Record[] expected = new Record[] { local };
   558      performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected)));
   559      dispose(session);
   560    }
   562    /*
   563     * Insert a record that is marked as deleted, remote has newer timestamp
   564     */
   565    protected void deleteRemoteNewer(Record local, Record remote) {
   566      final RepositorySession session = createAndBeginSession();
   568      // Record existing and hasn't changed since before lastSync.
   569      // Automatically will be assigned lastModified = current time.
   570      performWait(storeRunnable(session, local));
   572      // Pass the same record to store, but mark it deleted and modified
   573      // more recently
   574      ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local });
   575      performWait(fetchRunnable(session, new String[] { local.guid }, timestampDelegate));
   576      remote.lastModified = timestampDelegate.records.get(0).lastModified + 1000;
   577      remote.deleted = true;
   578      remote.guid = local.guid;
   579      performWait(storeRunnable(session, remote));
   581      performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(new Record[]{})));
   582      dispose(session);
   583    }
   585    // Store two records that are identical (this has different meanings based on the
   586    // type of record) other than their guids. The record existing locally already
   587    // should have its guid replaced (the assumption is that the record existed locally
   588    // and then sync was enabled and this record existed on another sync'd device).
   589    public void storeIdenticalExceptGuid(Record record0) {
   590      Logger.debug("storeIdenticalExceptGuid", "Started.");
   591      final RepositorySession session = createAndBeginSession();
   592      Logger.debug("storeIdenticalExceptGuid", "Session is " + session);
   593      performWait(storeRunnable(session, record0));
   594      Logger.debug("storeIdenticalExceptGuid", "Stored record0.");
   595      DefaultFetchDelegate timestampDelegate = getTimestampDelegate(record0.guid);
   597      performWait(fetchRunnable(session, new String[] { record0.guid }, timestampDelegate));
   598      Logger.debug("storeIdenticalExceptGuid", "fetchRunnable done.");
   599      record0.lastModified = timestampDelegate.records.get(0).lastModified + 3000;
   600      record0.guid = Utils.generateGuid();
   601      Logger.debug("storeIdenticalExceptGuid", "Storing modified...");
   602      performWait(storeRunnable(session, record0));
   603      Logger.debug("storeIdenticalExceptGuid", "Stored modified.");
   605      Record[] expected = new Record[] { record0 };
   606      performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected)));
   607      Logger.debug("storeIdenticalExceptGuid", "Fetched all. Returning.");
   608      dispose(session);
   609    }
   611    // Special delegate so that we don't verify parenting is correct since
   612    // at some points it won't be since parent folder hasn't been stored.
   613    private DefaultFetchDelegate getTimestampDelegate(final String guid) {
   614      return new DefaultFetchDelegate() {
   615        @Override
   616        public void onFetchCompleted(final long fetchEnd) {
   617          assertEquals(guid, this.records.get(0).guid);
   618          performNotify();
   619        }
   620      };
   621    }
   623    /*
   624     * Insert a record that is marked as deleted, local has newer timestamp
   625     * and was not marked deleted (so keep it)
   626     */
   627    protected void deleteLocalNewer(Record local, Record remote) {
   628      Logger.debug("deleteLocalNewer", "Begin.");
   629      final RepositorySession session = createAndBeginSession();
   631      Logger.debug("deleteLocalNewer", "Storing local...");
   632      performWait(storeRunnable(session, local));
   634      // Create an older version of a record with the same GUID.
   635      remote.guid = local.guid;
   637      Logger.debug("deleteLocalNewer", "Fetching...");
   639      // Get the timestamp and make remote older than it
   640      Record[] expected = new Record[] { local };
   641      ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(expected);
   642      performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate));
   644      Logger.debug("deleteLocalNewer", "Fetched.");
   645      remote.lastModified = timestampDelegate.records.get(0).lastModified - 1000;
   647      Logger.debug("deleteLocalNewer", "Last modified is " + remote.lastModified);
   648      remote.deleted = true;
   649      Logger.debug("deleteLocalNewer", "Storing deleted...");
   650      performWait(quietStoreRunnable(session, remote));      // This appears to do a lot of work...?!
   652      // Do a fetch and make sure that we get back the first (local) record.
   653      performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected)));
   654      Logger.debug("deleteLocalNewer", "Fetched and done!");
   655      dispose(session);
   656    }
   658    /*
   659     * Insert a record that is marked as deleted, record never existed locally
   660     */
   661    protected void deleteRemoteLocalNonexistent(Record remote) {
   662      final RepositorySession session = createAndBeginSession();
   664      long timestamp = 1000000000;
   666      // Pass a record marked deleted to store, doesn't exist locally
   667      remote.lastModified = timestamp;
   668      remote.deleted = true;
   669      performWait(quietStoreRunnable(session, remote));
   671      ExpectFetchDelegate delegate = preparedExpectFetchDelegate(new Record[]{});
   672      performWait(fetchAllRunnable(session, delegate));
   673      dispose(session);
   674    }
   676    /*
   677     * Tests that don't require specific records based on type of repository.
   678     * These tests don't need to be overriden in subclasses, they will just work.
   679     */
   680    public void testCreateSessionNullContext() {
   681      Logger.debug(LOG_TAG, "In testCreateSessionNullContext.");
   682      Repository repo = getRepository();
   683      try {
   684        repo.createSession(new DefaultSessionCreationDelegate(), null);
   685        fail("Should throw.");
   686      } catch (Exception ex) {
   687        assertNotNull(ex);
   688      }
   689    }
   691    public void testStoreNullRecord() {
   692      final RepositorySession session = createAndBeginSession();
   693      try {
   694        session.setStoreDelegate(new DefaultStoreDelegate());
   695        session.store(null);
   696        fail("Should throw.");
   697      } catch (Exception ex) {
   698        assertNotNull(ex);
   699      }
   700      dispose(session);
   701    }
   703    public void testFetchNoGuids() {
   704      final RepositorySession session = createAndBeginSession();
   705      performWait(fetchRunnable(session, new String[] {}, new ExpectInvalidRequestFetchDelegate()));
   706      dispose(session);
   707    }
   709    public void testFetchNullGuids() {
   710      final RepositorySession session = createAndBeginSession();
   711      performWait(fetchRunnable(session, null, new ExpectInvalidRequestFetchDelegate()));
   712      dispose(session);
   713    }
   715    public void testBeginOnNewSession() {
   716      final RepositorySession session = createSession();
   717      performWait(beginRunnable(session, new ExpectBeginDelegate()));
   718      dispose(session);
   719    }
   721    public void testBeginOnRunningSession() {
   722      final RepositorySession session = createAndBeginSession();
   723      try {
   724        session.begin(new ExpectBeginFailDelegate());
   725      } catch (InvalidSessionTransitionException e) {
   726        dispose(session);
   727        return;
   728      }
   729      fail("Should have caught InvalidSessionTransitionException.");
   730    }
   732    public void testBeginOnFinishedSession() throws InactiveSessionException {
   733      final RepositorySession session = createAndBeginSession();
   734      performWait(finishRunnable(session, new ExpectFinishDelegate()));
   735      try {
   736        session.begin(new ExpectBeginFailDelegate());
   737      } catch (InvalidSessionTransitionException e) {
   738        Logger.debug(getName(), "Yay! Got an exception.", e);
   739        dispose(session);
   740        return;
   741      } catch (Exception e) {
   742        Logger.debug(getName(), "Yay! Got an exception.", e);
   743        dispose(session);
   744        return;
   745      }
   746      fail("Should have caught InvalidSessionTransitionException.");
   747    }
   749    public void testFinishOnFinishedSession() throws InactiveSessionException {
   750      final RepositorySession session = createAndBeginSession();
   751      performWait(finishRunnable(session, new ExpectFinishDelegate()));
   752      try {
   753        session.finish(new ExpectFinishFailDelegate());
   754      } catch (InactiveSessionException e) {
   755        dispose(session);
   756        return;
   757      }
   758      fail("Should have caught InactiveSessionException.");
   759    }
   761    public void testFetchOnInactiveSession() throws InactiveSessionException {
   762      final RepositorySession session = createSession();
   763      try {
   764        session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate());
   765      } catch (InactiveSessionException e) {
   766        // Yay.
   767        dispose(session);
   768        return;
   769      };
   770      fail("Should have caught InactiveSessionException.");
   771    }
   773    public void testFetchOnFinishedSession() {
   774      final RepositorySession session = createAndBeginSession();
   775      Logger.debug(getName(), "Finishing...");
   776      performWait(finishRunnable(session, new ExpectFinishDelegate()));
   777      try {
   778        session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate());
   779      } catch (InactiveSessionException e) {
   780        // Yay.
   781        dispose(session);
   782        return;
   783      };
   784      fail("Should have caught InactiveSessionException.");
   785    }
   787    public void testGuidsSinceOnUnstartedSession() {
   788      final RepositorySession session = createSession();
   789      Runnable run = new Runnable() {
   790        @Override
   791        public void run() {
   792          session.guidsSince(System.currentTimeMillis(),
   793              new RepositorySessionGuidsSinceDelegate() {
   794            public void onGuidsSinceSucceeded(String[] guids) {
   795              fail("Session inactive, should fail");
   796              performNotify();
   797            }
   799            public void onGuidsSinceFailed(Exception ex) {
   800              verifyInactiveException(ex);
   801              performNotify();
   802            }
   803          });
   804        }
   805      };
   806      performWait(run);
   807      dispose(session);
   808    }
   810    private static void verifyInactiveException(Exception ex) {
   811      if (!(ex instanceof InactiveSessionException)) {
   812        fail("Wrong exception type");
   813      }
   814    }
   816    protected void closeDataAccessor(AndroidBrowserRepositoryDataAccessor dataAccessor) {
   817    }
   818 }

mercurial