ImportTestProvider.java revision 48dd8e86a81d2ab40eb762975c8211c225002bf0
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.android.vcard.tests.testutils; 17 18import android.content.ContentProviderOperation; 19import android.content.ContentProviderResult; 20import android.content.ContentValues; 21import android.net.Uri; 22import android.provider.ContactsContract.CommonDataKinds.Email; 23import android.provider.ContactsContract.CommonDataKinds.Event; 24import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 25import android.provider.ContactsContract.CommonDataKinds.Im; 26import android.provider.ContactsContract.CommonDataKinds.Nickname; 27import android.provider.ContactsContract.CommonDataKinds.Note; 28import android.provider.ContactsContract.CommonDataKinds.Organization; 29import android.provider.ContactsContract.CommonDataKinds.Phone; 30import android.provider.ContactsContract.CommonDataKinds.Photo; 31import android.provider.ContactsContract.CommonDataKinds.Relation; 32import android.provider.ContactsContract.CommonDataKinds.SipAddress; 33import android.provider.ContactsContract.CommonDataKinds.StructuredName; 34import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 35import android.provider.ContactsContract.CommonDataKinds.Website; 36import android.provider.ContactsContract.Data; 37import android.provider.ContactsContract.RawContacts; 38import android.test.AndroidTestCase; 39import android.test.mock.MockContentProvider; 40import android.text.TextUtils; 41 42import junit.framework.TestCase; 43 44import java.util.ArrayList; 45import java.util.Arrays; 46import java.util.Collection; 47import java.util.HashMap; 48import java.util.HashSet; 49import java.util.Map; 50import java.util.Map.Entry; 51import java.util.Set; 52import java.util.SortedMap; 53import java.util.TreeMap; 54 55public class ImportTestProvider extends MockContentProvider { 56 private static final Set<String> sKnownMimeTypeSet = 57 new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE, 58 Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE, 59 Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE, 60 Im.CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE, 61 Event.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE, 62 Note.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE, 63 Relation.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE, 64 GroupMembership.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE)); 65 66 final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues; 67 68 public ImportTestProvider(AndroidTestCase androidTestCase) { 69 mMimeTypeToExpectedContentValues = 70 new HashMap<String, Collection<ContentValues>>(); 71 for (String acceptanbleMimeType : sKnownMimeTypeSet) { 72 // Do not use HashSet since the current implementation changes the content of 73 // ContentValues after the insertion, which make the result of hashCode() 74 // changes... 75 mMimeTypeToExpectedContentValues.put( 76 acceptanbleMimeType, new ArrayList<ContentValues>()); 77 } 78 } 79 80 public void addExpectedContentValues(ContentValues expectedContentValues) { 81 final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE); 82 if (!sKnownMimeTypeSet.contains(mimeType)) { 83 TestCase.fail(String.format( 84 "Unknow MimeType %s in the test code. Test code should be broken.", 85 mimeType)); 86 } 87 88 final Collection<ContentValues> contentValuesCollection = 89 mMimeTypeToExpectedContentValues.get(mimeType); 90 contentValuesCollection.add(expectedContentValues); 91 } 92 93 @Override 94 public ContentProviderResult[] applyBatch( 95 ArrayList<ContentProviderOperation> operations) { 96 if (operations == null) { 97 TestCase.fail("There is no operation."); 98 } 99 100 final int size = operations.size(); 101 ContentProviderResult[] fakeResultArray = new ContentProviderResult[size]; 102 for (int i = 0; i < size; i++) { 103 Uri uri = Uri.withAppendedPath(RawContacts.CONTENT_URI, String.valueOf(i)); 104 fakeResultArray[i] = new ContentProviderResult(uri); 105 } 106 107 for (int i = 0; i < size; i++) { 108 ContentProviderOperation operation = operations.get(i); 109 ContentValues contentValues = operation.resolveValueBackReferences( 110 fakeResultArray, i); 111 } 112 for (int i = 0; i < size; i++) { 113 ContentProviderOperation operation = operations.get(i); 114 ContentValues actualContentValues = operation.resolveValueBackReferences( 115 fakeResultArray, i); 116 final Uri uri = operation.getUri(); 117 if (uri.equals(RawContacts.CONTENT_URI)) { 118 TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME)); 119 TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE)); 120 } else if (uri.equals(Data.CONTENT_URI)) { 121 final String mimeType = actualContentValues.getAsString(Data.MIMETYPE); 122 if (!sKnownMimeTypeSet.contains(mimeType)) { 123 TestCase.fail(String.format( 124 "Unknown MimeType %s. " + 125 "(Maybe the MimeType is added after developing this test suite." + 126 "Please look into %s if you strongly believe the type is regitimate)", 127 ImportTestProvider.class.getSimpleName(), mimeType)); 128 } 129 // Remove data meaningless in this unit tests. 130 // Specifically, Data.DATA1 - DATA7 are set to null or empty String 131 // regardless of the input, but it may change depending on how 132 // resolver-related code handles it. 133 // Here, we ignore these implementation-dependent specs and 134 // just check whether vCard importer correctly inserts rellevent data. 135 Set<String> keyToBeRemoved = new HashSet<String>(); 136 for (Entry<String, Object> entry : actualContentValues.valueSet()) { 137 Object value = entry.getValue(); 138 if (value == null || TextUtils.isEmpty(value.toString())) { 139 keyToBeRemoved.add(entry.getKey()); 140 } 141 } 142 for (String key: keyToBeRemoved) { 143 actualContentValues.remove(key); 144 } 145 /* for testing 146 Log.d("@@@", 147 String.format("MimeType: %s, data: %s", 148 mimeType, actualContentValues.toString())); */ 149 // Remove RAW_CONTACT_ID entry just for safety, since we do not care 150 // how resolver-related code handles the entry in this unit test, 151 if (actualContentValues.containsKey(Data.RAW_CONTACT_ID)) { 152 actualContentValues.remove(Data.RAW_CONTACT_ID); 153 } 154 final Collection<ContentValues> contentValuesCollection = 155 mMimeTypeToExpectedContentValues.get(mimeType); 156 if (contentValuesCollection.isEmpty()) { 157 TestCase.fail("ContentValues for MimeType " + mimeType 158 + " is not expected at all (" + actualContentValues + ")"); 159 } 160 boolean checked = false; 161 for (ContentValues expectedContentValues : contentValuesCollection) { 162 /*for testing 163 Log.d("@@@", "expected: " 164 + convertToEasilyReadableString(expectedContentValues)); 165 Log.d("@@@", "actual : " 166 + convertToEasilyReadableString(actualContentValues));*/ 167 if (equalsForContentValues(expectedContentValues, 168 actualContentValues)) { 169 TestCase.assertTrue(contentValuesCollection.remove(expectedContentValues)); 170 checked = true; 171 break; 172 } 173 } 174 if (!checked) { 175 final StringBuilder builder = new StringBuilder(); 176 builder.append("\n"); 177 builder.append("Unexpected: "); 178 builder.append(convertToEasilyReadableString(actualContentValues)); 179 builder.append("\n"); 180 builder.append("Expected : "); 181 for (ContentValues expectedContentValues : contentValuesCollection) { 182 builder.append(convertToEasilyReadableString(expectedContentValues)); 183 } 184 TestCase.fail(builder.toString()); 185 } 186 } else { 187 TestCase.fail("Unexpected Uri has come: " + uri); 188 } 189 } // for (int i = 0; i < size; i++) { 190 return fakeResultArray; 191 } 192 193 /** 194 * Checks all expected ContentValues are consumed during import. 195 */ 196 public void verify() { 197 StringBuilder builder = new StringBuilder(); 198 for (Collection<ContentValues> contentValuesCollection : 199 mMimeTypeToExpectedContentValues.values()) { 200 for (ContentValues expectedContentValues: contentValuesCollection) { 201 builder.append(convertToEasilyReadableString(expectedContentValues)); 202 builder.append("\n"); 203 } 204 } 205 if (builder.length() > 0) { 206 final String failMsg = 207 "There is(are) remaining expected ContentValues instance(s): \n" 208 + builder.toString(); 209 TestCase.fail(failMsg); 210 } 211 } 212 213 /** 214 * Utility method to print ContentValues whose content is printed with sorted keys. 215 */ 216 private String convertToEasilyReadableString(ContentValues contentValues) { 217 if (contentValues == null) { 218 return "null"; 219 } 220 String mimeTypeValue = ""; 221 SortedMap<String, String> sortedMap = new TreeMap<String, String>(); 222 for (Entry<String, Object> entry : contentValues.valueSet()) { 223 final String key = entry.getKey(); 224 final Object value = entry.getValue(); 225 final String valueString = (value != null ? value.toString() : null); 226 if (Data.MIMETYPE.equals(key)) { 227 mimeTypeValue = valueString; 228 } else { 229 TestCase.assertNotNull(key); 230 sortedMap.put(key, valueString); 231 } 232 } 233 StringBuilder builder = new StringBuilder(); 234 builder.append(Data.MIMETYPE); 235 builder.append('='); 236 builder.append(mimeTypeValue); 237 for (Entry<String, String> entry : sortedMap.entrySet()) { 238 final String key = entry.getKey(); 239 final String value = entry.getValue(); 240 builder.append(' '); 241 builder.append(key); 242 builder.append("=\""); 243 builder.append(value); 244 builder.append('"'); 245 } 246 return builder.toString(); 247 } 248 249 private static boolean equalsForContentValues( 250 final ContentValues expected, final ContentValues actual) { 251 if (expected == actual) { 252 return true; 253 } else if (expected == null || actual == null || expected.size() != actual.size()) { 254 return false; 255 } 256 257 for (Entry<String, Object> entry : expected.valueSet()) { 258 final String key = entry.getKey(); 259 final Object value = entry.getValue(); 260 if (!actual.containsKey(key)) { 261 return false; 262 } 263 // Type mismatch usuall happens as importer doesn't care the type of each value. 264 // For example, variable type might be Integer when importing the type of TEL, 265 // while variable type would be String when importing the type of RELATION. 266 final Object actualValue = actual.get(key); 267 if (value instanceof byte[]) { 268 if (!Arrays.equals((byte[])value, (byte[])actualValue)) { 269 byte[] e = (byte[])value; 270 byte[] a = (byte[])actualValue; 271 return false; 272 } 273 } else if (!value.equals(actualValue) && 274 !value.toString().equals(actualValue.toString())) { 275 return false; 276 } 277 } 278 return true; 279 } 280}