Add tests for CallLogBackupAgent.

Adds tests for writing and reading the state.

Change-Id: Ie00b3ec0c976195e32275a0a9d75b47ee245966e
diff --git a/src/com/android/providers/contacts/CallLogBackupAgent.java b/src/com/android/providers/contacts/CallLogBackupAgent.java
index 1371635..0cb542d 100644
--- a/src/com/android/providers/contacts/CallLogBackupAgent.java
+++ b/src/com/android/providers/contacts/CallLogBackupAgent.java
@@ -25,8 +25,12 @@
 import android.provider.CallLog;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.BufferedOutputStream;
+import java.io.DataInput;
 import java.io.DataInputStream;
+import java.io.DataOutput;
 import java.io.DataOutputStream;
 import java.io.EOFException;
 import java.io.FileInputStream;
@@ -40,7 +44,8 @@
  */
 public class CallLogBackupAgent extends BackupAgent {
 
-    private static class CallLogBackupState {
+    @VisibleForTesting
+    static class CallLogBackupState {
         int version;
         SortedSet<Integer> callIds;
     }
@@ -60,7 +65,8 @@
     /** Current version of CallLogBackup. Used to track the backup format. */
     private static final int VERSION = 1;
     /** Version indicating that there exists no previous backup entry. */
-    private static final int VERSION_NO_PREVIOUS_STATE = 0;
+    @VisibleForTesting
+    static final int VERSION_NO_PREVIOUS_STATE = 0;
 
     private static final String[] CALL_LOG_PROJECTION = new String[] {
         CallLog.Calls._ID,
@@ -82,7 +88,39 @@
             ParcelFileDescriptor newStateDescriptor) throws IOException {
 
         // Get the list of the previous calls IDs which were backed up.
-        CallLogBackupState state = readState(oldStateDescriptor);
+        DataInputStream dataInput = new DataInputStream(
+                new FileInputStream(oldStateDescriptor.getFileDescriptor()));
+        final CallLogBackupState state;
+        try {
+            state = readState(dataInput);
+        } finally {
+            dataInput.close();
+        }
+
+        // Run the actual backup of data
+        runBackup(state, data);
+
+        // Rewrite the backup state.
+        DataOutputStream dataOutput = new DataOutputStream(new BufferedOutputStream(
+                new FileOutputStream(newStateDescriptor.getFileDescriptor())));
+        try {
+            writeState(dataOutput, state);
+        } finally {
+            dataOutput.close();
+        }
+    }
+
+    /** ${inheritDoc} */
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+            throws IOException {
+        if (DEBUG) {
+            Log.d(TAG, "Performing Restore");
+        }
+    }
+
+    @VisibleForTesting
+    void runBackup(CallLogBackupState state, BackupDataOutput data) {
         SortedSet<Integer> callsToRemove = new TreeSet<>(state.callIds);
 
         // Get all the existing call log entries.
@@ -127,22 +165,11 @@
                 state.callIds.remove(i);
             }
 
-            // Rewrite the backup state.
-            writeState(newStateDescriptor, state);
         } finally {
             cursor.close();
         }
     }
 
-    /** ${inheritDoc} */
-    @Override
-    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
-            throws IOException {
-        if (DEBUG) {
-            Log.d(TAG, "Performing Restore");
-        }
-    }
-
     private Cursor getAllCallLogEntries() {
         // We use the API here instead of querying ContactsDatabaseHelper directly because
         // CallLogProvider has special locks in place for sychronizing when to read.  Using the APIs
@@ -151,9 +178,8 @@
         return resolver.query(CallLog.Calls.CONTENT_URI, CALL_LOG_PROJECTION, null, null, null);
     }
 
-    private CallLogBackupState readState(ParcelFileDescriptor oldState) throws IOException {
-        DataInputStream dataInput = new DataInputStream(
-                new FileInputStream(oldState.getFileDescriptor()));
+    @VisibleForTesting
+    CallLogBackupState readState(DataInput dataInput) throws IOException {
         CallLogBackupState state = new CallLogBackupState();
         state.callIds = new TreeSet<>();
 
@@ -172,18 +198,14 @@
             }
         } catch (EOFException e) {
             state.version = VERSION_NO_PREVIOUS_STATE;
-        } finally {
-            dataInput.close();
         }
 
         return state;
     }
 
-    private void writeState(ParcelFileDescriptor descriptor, CallLogBackupState state)
+    @VisibleForTesting
+    void writeState(DataOutput dataOutput, CallLogBackupState state)
             throws IOException {
-        DataOutputStream dataOutput = new DataOutputStream(new BufferedOutputStream(
-                new FileOutputStream(descriptor.getFileDescriptor())));
-
         // Write version first of all
         dataOutput.writeInt(VERSION);
 
@@ -193,9 +215,6 @@
         for (Integer i : state.callIds) {
             dataOutput.writeInt(i);
         }
-
-        // Done!
-        dataOutput.close();
     }
 
     private Call readCallFromCursor(Cursor cursor) {
diff --git a/tests/Android.mk b/tests/Android.mk
index ec48f5a..35a6b39 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -4,14 +4,18 @@
 # We only want this apk build for tests.
 LOCAL_MODULE_TAGS := tests
 
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
 # Only compile source java files in this apk.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := ContactsProviderTests
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
 LOCAL_INSTRUMENTATION_FOR := ContactsProvider
 LOCAL_CERTIFICATE := shared
 
+LOCAL_PROGUARD_ENABLED := disabled
+
 include $(BUILD_PACKAGE)
diff --git a/tests/src/com/android/providers/contacts/CallLogBackupAgentTest.java b/tests/src/com/android/providers/contacts/CallLogBackupAgentTest.java
new file mode 100644
index 0000000..2699f65
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/CallLogBackupAgentTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.contacts;
+
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.doReturn;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.providers.contacts.CallLogBackupAgent.CallLogBackupState;
+
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.util.TreeSet;
+
+/**
+ * Test cases for {@link com.android.providers.contacts.CallLogBackupAgent}
+ */
+@SmallTest
+public class CallLogBackupAgentTest extends AndroidTestCase {
+
+    @Mock DataInput mDataInput;
+    @Mock DataOutput mDataOutput;
+
+    CallLogBackupAgent mCallLogBackupAgent;
+
+    MockitoHelper mMockitoHelper = new MockitoHelper();
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mMockitoHelper.setUp(getClass());
+        // Since we're testing a system app, AppDataDirGuesser doesn't find our
+        // cache dir, so set it explicitly.
+        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
+
+        MockitoAnnotations.initMocks(this);
+
+        mCallLogBackupAgent = new CallLogBackupAgent();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mMockitoHelper.tearDown();
+    }
+
+    public void testReadState_NoCall() throws Exception {
+        when(mDataInput.readInt()).thenThrow(new EOFException());
+
+        CallLogBackupState state = mCallLogBackupAgent.readState(mDataInput);
+
+        assertEquals(state.version, CallLogBackupAgent.VERSION_NO_PREVIOUS_STATE);
+        assertEquals(state.callIds.size(), 0);
+    }
+
+    public void testReadState_OneCall() throws Exception {
+        when(mDataInput.readInt()).thenReturn(
+                1 /* version */,
+                1 /* size */,
+                101 /* call-ID */ );
+
+        CallLogBackupState state = mCallLogBackupAgent.readState(mDataInput);
+
+        assertEquals(1, state.version);
+        assertEquals(1, state.callIds.size());
+        assertTrue(state.callIds.contains(101));
+    }
+
+    public void testReadState_MultipleCalls() throws Exception {
+        when(mDataInput.readInt()).thenReturn(
+                1 /* version */,
+                2 /* size */,
+                101 /* call-ID */,
+                102 /* call-ID */);
+
+        CallLogBackupState state = mCallLogBackupAgent.readState(mDataInput);
+
+        assertEquals(1, state.version);
+        assertEquals(2, state.callIds.size());
+        assertTrue(state.callIds.contains(101));
+        assertTrue(state.callIds.contains(102));
+    }
+
+    public void testWriteState_NoCalls() throws Exception {
+        CallLogBackupState state = new CallLogBackupState();
+        state.version = 1;
+        state.callIds = new TreeSet<>();
+
+        mCallLogBackupAgent.writeState(mDataOutput, state);
+
+        InOrder inOrder = Mockito.inOrder(mDataOutput);
+        inOrder.verify(mDataOutput).writeInt(1 /* version */);
+        inOrder.verify(mDataOutput).writeInt(0 /* size */);
+    }
+
+    public void testWriteState_OneCall() throws Exception {
+        CallLogBackupState state = new CallLogBackupState();
+        state.version = 1;
+        state.callIds = new TreeSet<>();
+        state.callIds.add(101);
+
+        mCallLogBackupAgent.writeState(mDataOutput, state);
+
+        InOrder inOrder = Mockito.inOrder(mDataOutput);
+        inOrder.verify(mDataOutput, times(2)).writeInt(1);
+        inOrder.verify(mDataOutput).writeInt(101 /* call-ID */);
+    }
+
+    public void testWriteState_MultipleCalls() throws Exception {
+        CallLogBackupState state = new CallLogBackupState();
+        state.version = 1;
+        state.callIds = new TreeSet<>();
+        state.callIds.add(101);
+        state.callIds.add(102);
+        state.callIds.add(103);
+
+        mCallLogBackupAgent.writeState(mDataOutput, state);
+
+        InOrder inOrder = Mockito.inOrder(mDataOutput);
+        inOrder.verify(mDataOutput).writeInt(1 /* version */);
+        inOrder.verify(mDataOutput).writeInt(3 /* size */);
+        inOrder.verify(mDataOutput).writeInt(101 /* call-ID */);
+        inOrder.verify(mDataOutput).writeInt(102 /* call-ID */);
+        inOrder.verify(mDataOutput).writeInt(103 /* call-ID */);
+    }
+}
diff --git a/tests/src/com/android/providers/contacts/MockitoHelper.java b/tests/src/com/android/providers/contacts/MockitoHelper.java
new file mode 100644
index 0000000..3f3f7f6
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/MockitoHelper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.contacts;
+
+import android.util.Log;
+
+/**
+ * Helper for Mockito-based test cases.
+ */
+public final class MockitoHelper {
+    private static final String TAG = "MockitoHelper";
+
+    private ClassLoader mOriginalClassLoader;
+    private Thread mContextThread;
+
+    /**
+     * Creates a new helper, which in turn will set the context classloader so
+     * it can load Mockito resources.
+     *
+     * @param packageClass test case class
+     */
+    public void setUp(Class<?> packageClass) throws Exception {
+        // makes a copy of the context classloader
+        mContextThread = Thread.currentThread();
+        mOriginalClassLoader = mContextThread.getContextClassLoader();
+        ClassLoader newClassLoader = packageClass.getClassLoader();
+        Log.v(TAG, "Changing context classloader from " + mOriginalClassLoader
+                + " to " + newClassLoader);
+        mContextThread.setContextClassLoader(newClassLoader);
+    }
+
+    /**
+     * Restores the context classloader to the previous value.
+     */
+    public void tearDown() throws Exception {
+        Log.v(TAG, "Restoring context classloader to " + mOriginalClassLoader);
+        mContextThread.setContextClassLoader(mOriginalClassLoader);
+    }
+}