/* * 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()); } }