| /* |
| * Copyright (C) 2013 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 android.app.backup; |
| |
| import android.app.backup.BackupDataInput; |
| import android.app.backup.BackupDataOutput; |
| import android.content.res.AssetFileDescriptor; |
| import android.content.res.AssetManager; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.ParcelFileDescriptor; |
| import android.test.AndroidTestCase; |
| import android.test.InstrumentationTestCase; |
| import android.util.Base64; |
| import android.util.Log; |
| import org.json.JSONObject; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.lang.Exception; |
| import java.nio.ByteBuffer; |
| |
| public class BackupDataTest extends AndroidTestCase { |
| private static final String KEY1 = "key1"; |
| private static final String KEY2 = "key2a"; |
| private static final String KEY3 = "key3bc"; |
| private static final String KEY4 = "key4dad"; // variable key lengths to test padding |
| private static final String[] KEYS = {KEY1, KEY2, KEY3, KEY4}; |
| |
| private static final String DATA1 = "abcdef"; |
| private static final String DATA2 = "abcdefg"; |
| private static final String DATA3 = "abcdefgh"; |
| private static final String DATA4 = "abcdeffhi"; //variable data lengths to test padding |
| private static final String[] DATA = {DATA1, DATA2, DATA3, DATA4}; |
| private static final String TAG = "BackupDataTest"; |
| |
| private File mFile; |
| private ParcelFileDescriptor mDataFile; |
| private File mDirectory; |
| private Bundle mStatusBundle; |
| private AssetManager mAssets; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mDirectory = new File(Environment.getExternalStorageDirectory(), "test_data"); |
| mDirectory.mkdirs(); |
| mAssets = mContext.getAssets(); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| super.tearDown(); |
| if (mDataFile != null) { |
| mDataFile.close(); |
| } |
| } |
| |
| public void testSingle() throws IOException { |
| mFile = new File(mDirectory, "backup_mixed_sinlge.dat"); |
| openForWriting(); |
| BackupDataOutput bdo = new BackupDataOutput(mDataFile.getFileDescriptor()); |
| |
| writeEntity(bdo, KEY1, DATA1.getBytes()); |
| |
| mDataFile.close(); |
| openForReading(); |
| |
| BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor()); |
| int count = 0; |
| while (bdi.readNextHeader()) { |
| readAndVerifyEntity(bdi, KEY1, DATA1.getBytes()); |
| count++; |
| } |
| assertEquals("only one entity in this stream", 1, count); |
| } |
| |
| public void testMultiple() throws IOException { |
| mFile = new File(mDirectory, "backup_multiple_test.dat"); |
| openForWriting(); |
| BackupDataOutput bdo = new BackupDataOutput(mDataFile.getFileDescriptor()); |
| |
| for(int i = 0; i < KEYS.length; i++) { |
| writeEntity(bdo, KEYS[i], DATA[i].getBytes()); |
| } |
| |
| mDataFile.close(); |
| openForReading(); |
| |
| BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor()); |
| int count = 0; |
| while (bdi.readNextHeader()) { |
| readAndVerifyEntity(bdi, KEYS[count], DATA[count].getBytes()); |
| count++; |
| } |
| assertEquals("four entities in this stream", KEYS.length, count); |
| } |
| |
| public void testDelete() throws IOException { |
| mFile = new File(mDirectory, "backup_delete_test.dat"); |
| openForWriting(); |
| BackupDataOutput bdo = new BackupDataOutput(mDataFile.getFileDescriptor()); |
| |
| for(int i = 0; i < KEYS.length; i++) { |
| deleteEntity(bdo, KEYS[i]); |
| } |
| |
| mDataFile.close(); |
| openForReading(); |
| |
| BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor()); |
| int count = 0; |
| while (bdi.readNextHeader()) { |
| readAndVerifyDeletedEntity(bdi, KEYS[count]); |
| count++; |
| } |
| assertEquals("four deletes in this stream", KEYS.length, count); |
| } |
| |
| public void testMixed() throws IOException { |
| mFile = new File(mDirectory, "backup_mixed_test.dat"); |
| openForWriting(); |
| |
| BackupDataOutput bdo = new BackupDataOutput(mDataFile.getFileDescriptor()); |
| |
| int i = 0; |
| deleteEntity(bdo, KEYS[i]); i++; |
| writeEntity(bdo, KEYS[i], DATA[i].getBytes()); i++; |
| writeEntity(bdo, KEYS[i], DATA[i].getBytes()); i++; |
| deleteEntity(bdo, KEYS[i]); i++; |
| |
| mDataFile.close(); |
| openForReading(); |
| |
| BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor()); |
| int out = 0; |
| assertTrue(bdi.readNextHeader()); |
| readAndVerifyDeletedEntity(bdi, KEYS[out]); out++; |
| assertTrue(bdi.readNextHeader()); |
| readAndVerifyEntity(bdi, KEYS[out], DATA[out].getBytes()); out++; |
| assertTrue(bdi.readNextHeader()); |
| readAndVerifyEntity(bdi, KEYS[out], DATA[out].getBytes()); out++; |
| assertTrue(bdi.readNextHeader()); |
| readAndVerifyDeletedEntity(bdi, KEYS[out]); out++; |
| assertFalse("four items in this stream", |
| bdi.readNextHeader()); |
| } |
| |
| public void testReadMockData() throws IOException { |
| copyAssetToFile("backup_mock.dat", "backup_read_mock_test.dat"); |
| |
| openForReading(); |
| BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor()); |
| BufferedReader truth = new BufferedReader(new InputStreamReader( |
| mAssets.openFd("backup_mock.gld").createInputStream())); |
| while( bdi.readNextHeader()) { |
| String[] expected = truth.readLine().split(":"); |
| byte[] expectedBytes = null; |
| if (expected.length > 1) { |
| expectedBytes = Base64.decode(expected[1], Base64.DEFAULT); |
| } |
| String key = bdi.getKey(); |
| int dataSize = bdi.getDataSize(); |
| |
| assertEquals("wrong key", expected[0], key); |
| assertEquals("wrong length for key " + key, |
| (expectedBytes == null ? -1: expectedBytes.length), dataSize); |
| if (dataSize != -1) { |
| byte[] buffer = new byte[dataSize]; |
| bdi.readEntityData(buffer, 0, dataSize); |
| assertEquals("wrong data for key " + key, expected[1], |
| Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP)); |
| } |
| } |
| assertNull("there are unused entries in the golden file", truth.readLine()); |
| } |
| |
| public void testReadRealData() throws IOException { |
| copyAssetToFile("backup_real.dat", "backup_read_real_test.dat"); |
| |
| openForReading(); |
| BackupDataInput bdi = new BackupDataInput(mDataFile.getFileDescriptor()); |
| BufferedReader truth = new BufferedReader(new InputStreamReader( |
| mAssets.openFd("backup_real.gld").createInputStream())); |
| |
| while(bdi.readNextHeader()) { |
| String[] expected = truth.readLine().split(":"); |
| byte[] expectedBytes = null; |
| if (expected.length > 1) { |
| expectedBytes = Base64.decode(expected[1], Base64.DEFAULT); |
| } |
| String key = bdi.getKey(); |
| int dataSize = bdi.getDataSize(); |
| |
| assertEquals("wrong key", expected[0], key); |
| assertEquals("wrong length for key " + key, |
| (expectedBytes == null ? -1: expectedBytes.length), dataSize); |
| if (dataSize != -1) { |
| byte[] buffer = new byte[dataSize]; |
| bdi.readEntityData(buffer, 0, dataSize); |
| assertEquals("wrong data for key " + key, expected[1], |
| Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP)); |
| } |
| } |
| assertNull("there are unused entries in the golden file", truth.readLine()); |
| } |
| |
| private void copyAssetToFile(String source, String destination) throws IOException { |
| mFile = new File(mDirectory, destination); |
| openForWriting(); |
| FileInputStream fileInputStream = mAssets.openFd(source).createInputStream(); |
| FileOutputStream fileOutputStream = new FileOutputStream(mDataFile.getFileDescriptor()); |
| byte[] copybuffer = new byte[1024]; |
| int numBytes = fileInputStream.read(copybuffer); |
| fileOutputStream.write(copybuffer, 0, numBytes); |
| fileOutputStream.close(); |
| } |
| |
| private void openForWriting() throws FileNotFoundException { |
| mDataFile = ParcelFileDescriptor.open(mFile, |
| ParcelFileDescriptor.MODE_WRITE_ONLY | |
| ParcelFileDescriptor.MODE_CREATE | |
| ParcelFileDescriptor.MODE_TRUNCATE); // Make an empty file if necessary |
| } |
| |
| private void openForReading() throws FileNotFoundException { |
| mDataFile = ParcelFileDescriptor.open(mFile, |
| ParcelFileDescriptor.MODE_READ_ONLY | |
| ParcelFileDescriptor.MODE_CREATE); // Make an empty file if necessary |
| } |
| |
| private void writeEntity(BackupDataOutput bdo, String key, byte[] data) throws IOException { |
| int status = bdo.writeEntityHeader(key, data.length); |
| // documentation says "number of bytes written" but that's not what we get: |
| assertEquals(0, status); |
| |
| status = bdo.writeEntityData(data, data.length); |
| // documentation says "number of bytes written" but that's not what we get: |
| assertEquals(0, status); |
| } |
| |
| private void deleteEntity(BackupDataOutput bdo, String key) throws IOException { |
| int status = bdo.writeEntityHeader(key, -1); |
| // documentation says "number of bytes written" but that's not what we get: |
| assertEquals(0, status); |
| } |
| |
| private void readAndVerifyEntity(BackupDataInput bdi, String expectedKey, byte[] expectedData) |
| throws IOException { |
| assertEquals("Key mismatch", |
| expectedKey, bdi.getKey()); |
| assertEquals("data size mismatch", |
| expectedData.length, bdi.getDataSize()); |
| byte[] data = new byte[bdi.getDataSize()]; |
| bdi.readEntityData(data, 0, bdi.getDataSize()); |
| assertEquals("payload size is wrong", |
| expectedData.length, data.length); |
| for (int i = 0; i < data.length; i++) { |
| assertEquals("payload mismatch", |
| expectedData[i], data[i]); |
| } |
| } |
| private void readAndVerifyDeletedEntity(BackupDataInput bdi, String expectedKey) |
| throws IOException { |
| assertEquals("Key mismatch", |
| expectedKey, bdi.getKey()); |
| assertEquals("deletion mis-reported", |
| -1, bdi.getDataSize()); |
| } |
| } |