Add tests for routeSocketDataToOutput.
Also fixes the problem where it could through IndexOutOfBoundsException
instead of more natuarl EOFExcpetion in case there is not enough data in
the chunk.
Bug: 38081946
Bug: 37983441
Test: runtest -p com.android.server.backup frameworks-services
Change-Id: Icac575a76b752922b9c2d9cc0b3cad50f4273241
diff --git a/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java b/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
index ff92340..b3e20dc 100644
--- a/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
@@ -17,15 +17,18 @@
package com.android.server.backup.utils;
import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_VERSION;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Build;
import android.os.ParcelFileDescriptor;
+import android.util.Slog;
import android.util.StringBuilderPrinter;
import java.io.DataInputStream;
+import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -55,6 +58,10 @@
while (chunkTotal > 0) {
int toRead = (chunkTotal > buffer.length) ? buffer.length : chunkTotal;
int nRead = in.read(buffer, 0, toRead);
+ if (nRead < 0) {
+ Slog.e(TAG, "Unexpectedly reached end of file while reading data");
+ throw new EOFException();
+ }
out.write(buffer, 0, nRead);
chunkTotal -= nRead;
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
new file mode 100644
index 0000000..4e3de64
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2017 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.server.backup.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Random;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class FullBackupUtilsTest {
+ @Mock private ParcelFileDescriptor mParcelFileDescriptorMock;
+ @Mock private OutputStream mOutputStreamMock;
+ private File mTemporaryFile;
+ private ByteArrayOutputStream mByteArrayOutputStream;
+ private ParcelFileDescriptor mTemporaryFileDescriptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mTemporaryFile = File.createTempFile("backup-data", ".txt");
+ mByteArrayOutputStream = new ByteArrayOutputStream();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mTemporaryFileDescriptor != null) {
+ mTemporaryFileDescriptor.close();
+ }
+ if (mTemporaryFile != null) {
+ mTemporaryFile.delete();
+ }
+ }
+
+ @Test
+ public void routeSocketDataToOutput_inPipeIsNull_throwsNPE() throws Exception {
+ try {
+ FullBackupUtils.routeSocketDataToOutput(null, mOutputStreamMock);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ @Test
+ public void routeSocketDataToOutput_outNull_throwsNPE() throws Exception {
+ try {
+ FullBackupUtils.routeSocketDataToOutput(mParcelFileDescriptorMock, null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ @Test
+ public void routeSocketDataToOutput_emptyInput_throwsEOFException() throws Exception {
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ try {
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mOutputStreamMock);
+ fail();
+ } catch (EOFException expected) {
+ }
+
+ verifyZeroInteractions(mOutputStreamMock);
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_incompleteChunkSizeInput_throwsEOFException()
+ throws Exception {
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeByte(100);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ try {
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mOutputStreamMock);
+ fail();
+ } catch (EOFException expected) {
+ }
+
+ verifyZeroInteractions(mOutputStreamMock);
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_validEmptyInput_doesNotWriteAnything() throws Exception {
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(0);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor, mOutputStreamMock);
+
+ verifyZeroInteractions(mOutputStreamMock);
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_notEnoughData_throwsEOFException() throws Exception {
+ byte[] data = createFakeDataArray(100);
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(data.length + 1);
+ outputStream.write(data);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ try {
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mByteArrayOutputStream);
+ fail();
+ } catch (EOFException expected) {
+ }
+
+ verify(mOutputStreamMock, never()).close();
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_oneSmallChunk_writesOutputCorrectly() throws Exception {
+ byte[] data = createFakeDataArray(100);
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(data.length);
+ outputStream.write(data);
+ outputStream.writeInt(0);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mByteArrayOutputStream);
+
+ assertThat(mByteArrayOutputStream.toByteArray()).isEqualTo(data);
+ verify(mOutputStreamMock, never()).close();
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_oneLargeChunk_writesOutputCorrectly() throws Exception {
+ byte[] data = createFakeDataArray(128000);
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(data.length);
+ outputStream.write(data);
+ outputStream.writeInt(0);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mByteArrayOutputStream);
+
+ assertThat(mByteArrayOutputStream.toByteArray()).isEqualTo(data);
+ verify(mOutputStreamMock, never()).close();
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_twoSmallChunks_writesOutputCorrectly() throws Exception {
+ byte[] data = createFakeDataArray(200);
+ int chunk1Length = 97;
+ int chunk2Length = data.length - chunk1Length;
+
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(chunk1Length);
+ outputStream.write(data, 0, chunk1Length);
+ outputStream.writeInt(chunk2Length);
+ outputStream.write(data, chunk1Length, chunk2Length);
+ outputStream.writeInt(0);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mByteArrayOutputStream);
+
+ assertThat(mByteArrayOutputStream.toByteArray()).isEqualTo(data);
+ verify(mOutputStreamMock, never()).close();
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_twoLargeChunks_writesOutputCorrectly() throws Exception {
+ byte[] data = createFakeDataArray(256000);
+ int chunk1Length = 127313;
+ int chunk2Length = data.length - chunk1Length;
+
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(chunk1Length);
+ outputStream.write(data, 0, chunk1Length);
+ outputStream.writeInt(chunk2Length);
+ outputStream.write(data, chunk1Length, chunk2Length);
+ outputStream.writeInt(0);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mByteArrayOutputStream);
+
+ assertThat(mByteArrayOutputStream.toByteArray()).isEqualTo(data);
+ verify(mOutputStreamMock, never()).close();
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ private static byte[] createFakeDataArray(int length) {
+ byte[] data = new byte[length];
+ new Random(3742).nextBytes(data);
+ return data;
+ }
+}