1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package android.pim.vcard.test_utils; 17 18 import android.content.ContentResolver; 19 import android.content.Context; 20 import android.content.EntityIterator; 21 import android.net.Uri; 22 import android.pim.vcard.VCardComposer; 23 import android.pim.vcard.VCardConfig; 24 import android.pim.vcard.VCardEntryConstructor; 25 import android.pim.vcard.VCardInterpreter; 26 import android.pim.vcard.VCardInterpreterCollection; 27 import android.pim.vcard.VCardParser; 28 import android.pim.vcard.VCardUtils; 29 import android.pim.vcard.exception.VCardException; 30 import android.test.AndroidTestCase; 31 import android.test.mock.MockContext; 32 import android.text.TextUtils; 33 import android.util.Log; 34 35 import junit.framework.TestCase; 36 37 import java.io.ByteArrayInputStream; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.lang.reflect.Method; 41 import java.util.Arrays; 42 43 /** 44 * <p> 45 * The class lets users checks that given expected vCard data are same as given actual vCard data. 46 * Able to verify both vCard importer/exporter. 47 * </p> 48 * <p> 49 * First a user has to initialize the object by calling either 50 * {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}. 51 * "Round trip test" (import -> export -> import, or export -> import -> export) is not supported. 52 * </p> 53 */ 54 public class VCardVerifier { 55 private static final String LOG_TAG = "VCardVerifier"; 56 57 private static class CustomMockContext extends MockContext { 58 final ContentResolver mResolver; 59 public CustomMockContext(ContentResolver resolver) { 60 mResolver = resolver; 61 } 62 63 @Override 64 public ContentResolver getContentResolver() { 65 return mResolver; 66 } 67 } 68 69 private class VCardVerifierInternal implements VCardComposer.OneEntryHandler { 70 public boolean onInit(Context context) { 71 return true; 72 } 73 public boolean onEntryCreated(String vcard) { 74 verifyOneVCardForExport(vcard); 75 return true; 76 } 77 public void onTerminate() { 78 } 79 } 80 81 private final AndroidTestCase mAndroidTestCase; 82 private final VCardVerifierInternal mVCardVerifierInternal; 83 private int mVCardType; 84 private boolean mIsDoCoMo; 85 86 // Only one of them must be non-empty. 87 private ExportTestResolver mExportTestResolver; 88 private InputStream mInputStream; 89 90 // To allow duplication, use list instead of set. 91 // When null, we don't need to do the verification. 92 private PropertyNodesVerifier mPropertyNodesVerifier; 93 private LineVerifier mLineVerifier; 94 private ContentValuesVerifier mContentValuesVerifier; 95 private boolean mInitialized; 96 private boolean mVerified = false; 97 private String mCharset; 98 99 // Called by VCardTestsBase 100 public VCardVerifier(AndroidTestCase androidTestCase) { 101 mAndroidTestCase = androidTestCase; 102 mVCardVerifierInternal = new VCardVerifierInternal(); 103 mExportTestResolver = null; 104 mInputStream = null; 105 mInitialized = false; 106 mVerified = false; 107 } 108 109 // Should be called at the beginning of each import test. 110 public void initForImportTest(int vcardType, int resId) { 111 if (mInitialized) { 112 AndroidTestCase.fail("Already initialized"); 113 } 114 mVCardType = vcardType; 115 mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); 116 setInputResourceId(resId); 117 mInitialized = true; 118 } 119 120 // Should be called at the beginning of each export test. 121 public void initForExportTest(int vcardType) { 122 initForExportTest(vcardType, "UTF-8"); 123 } 124 125 public void initForExportTest(int vcardType, String charset) { 126 if (mInitialized) { 127 AndroidTestCase.fail("Already initialized"); 128 } 129 mExportTestResolver = new ExportTestResolver(mAndroidTestCase); 130 mVCardType = vcardType; 131 mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); 132 mInitialized = true; 133 if (TextUtils.isEmpty(charset)) { 134 mCharset = "UTF-8"; 135 } else { 136 mCharset = charset; 137 } 138 } 139 140 private void setInputResourceId(int resId) { 141 InputStream inputStream = mAndroidTestCase.getContext().getResources().openRawResource(resId); 142 if (inputStream == null) { 143 AndroidTestCase.fail("Wrong resId: " + resId); 144 } 145 setInputStream(inputStream); 146 } 147 148 private void setInputStream(InputStream inputStream) { 149 if (mExportTestResolver != null) { 150 AndroidTestCase.fail("addInputEntry() is called."); 151 } else if (mInputStream != null) { 152 AndroidTestCase.fail("InputStream is already set"); 153 } 154 mInputStream = inputStream; 155 } 156 157 public ContactEntry addInputEntry() { 158 if (!mInitialized) { 159 AndroidTestCase.fail("Not initialized"); 160 } 161 if (mInputStream != null) { 162 AndroidTestCase.fail("setInputStream is called"); 163 } 164 return mExportTestResolver.addInputContactEntry(); 165 } 166 167 public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithoutVersion() { 168 if (!mInitialized) { 169 AndroidTestCase.fail("Not initialized"); 170 } 171 if (mPropertyNodesVerifier == null) { 172 mPropertyNodesVerifier = new PropertyNodesVerifier(mAndroidTestCase); 173 } 174 return mPropertyNodesVerifier.addPropertyNodesVerifierElem(); 175 } 176 177 public PropertyNodesVerifierElem addPropertyNodesVerifierElem() { 178 final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElemWithoutVersion(); 179 final String versionString; 180 if (VCardConfig.isVersion21(mVCardType)) { 181 versionString = "2.1"; 182 } else if (VCardConfig.isVersion30(mVCardType)) { 183 versionString = "3.0"; 184 } else if (VCardConfig.isVersion40(mVCardType)) { 185 versionString = "4.0"; 186 } else { 187 throw new RuntimeException("Unexpected vcard type during a unit test"); 188 } 189 elem.addExpectedNodeWithOrder("VERSION", versionString); 190 191 return elem; 192 } 193 194 public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() { 195 if (!mInitialized) { 196 AndroidTestCase.fail("Not initialized"); 197 } 198 final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem(); 199 if (VCardConfig.isVersion40(mVCardType)) { 200 elem.addExpectedNodeWithOrder("FN", ""); 201 } else if (VCardConfig.isVersion30(mVCardType)) { 202 elem.addExpectedNodeWithOrder("N", ""); 203 elem.addExpectedNodeWithOrder("FN", ""); 204 } else if (mIsDoCoMo) { 205 elem.addExpectedNodeWithOrder("N", ""); 206 } 207 return elem; 208 } 209 210 public LineVerifierElem addLineVerifierElem() { 211 if (!mInitialized) { 212 AndroidTestCase.fail("Not initialized"); 213 } 214 if (mLineVerifier == null) { 215 mLineVerifier = new LineVerifier(mAndroidTestCase, mVCardType); 216 } 217 return mLineVerifier.addLineVerifierElem(); 218 } 219 220 public ContentValuesVerifierElem addContentValuesVerifierElem() { 221 if (!mInitialized) { 222 AndroidTestCase.fail("Not initialized"); 223 } 224 if (mContentValuesVerifier == null) { 225 mContentValuesVerifier = new ContentValuesVerifier(); 226 } 227 228 return mContentValuesVerifier.addElem(mAndroidTestCase); 229 } 230 231 /** 232 * Sets up sub-verifiers correctly and try parse given vCard as InputStream. 233 * Errors around InputStream must be handled outside this method. 234 * 235 * Used both from {@link #verifyForImportTest()} and from {@link #verifyForExportTest()}. 236 */ 237 private void verifyWithInputStream(InputStream is) throws IOException { 238 final VCardInterpreter interpreter; 239 if (mContentValuesVerifier != null) { 240 final VCardEntryConstructor constructor = new VCardEntryConstructor(mVCardType); 241 constructor.addEntryHandler(mContentValuesVerifier); 242 if (mPropertyNodesVerifier != null) { 243 interpreter = new VCardInterpreterCollection(Arrays.asList( 244 mPropertyNodesVerifier, constructor)); 245 } else { 246 interpreter = constructor; 247 } 248 } else { 249 if (mPropertyNodesVerifier != null) { 250 interpreter = mPropertyNodesVerifier; 251 } else { 252 interpreter = null; 253 } 254 } 255 256 try { 257 // Note: we must not specify charset toward vCard parsers. This code checks whether 258 // those parsers are able to encode given binary without any extra information for 259 // charset. 260 final VCardParser parser = VCardUtils.getAppropriateParser(mVCardType); 261 parser.parse(is, interpreter); 262 } catch (VCardException e) { 263 AndroidTestCase.fail("Unexpected VCardException: " + e.getMessage()); 264 } 265 } 266 267 private void verifyOneVCardForExport(final String vcard) { 268 Log.d(LOG_TAG, vcard); 269 InputStream is = null; 270 try { 271 is = new ByteArrayInputStream(vcard.getBytes(mCharset)); 272 verifyWithInputStream(is); 273 } catch (IOException e) { 274 AndroidTestCase.fail("Unexpected IOException: " + e.getMessage()); 275 } finally { 276 if (is != null) { 277 try { 278 is.close(); 279 } catch (IOException e) { 280 AndroidTestCase.fail("Unexpected IOException: " + e.getMessage()); 281 } 282 } 283 } 284 } 285 286 public void verify() { 287 if (!mInitialized) { 288 TestCase.fail("Not initialized."); 289 } 290 if (mVerified) { 291 TestCase.fail("verify() was called twice."); 292 } 293 294 if (mInputStream != null) { 295 if (mExportTestResolver != null){ 296 TestCase.fail("There are two input sources."); 297 } 298 verifyForImportTest(); 299 } else if (mExportTestResolver != null){ 300 verifyForExportTest(); 301 } else { 302 TestCase.fail("No input is determined"); 303 } 304 mVerified = true; 305 } 306 307 private void verifyForImportTest() { 308 if (mLineVerifier != null) { 309 AndroidTestCase.fail("Not supported now."); 310 } 311 312 try { 313 verifyWithInputStream(mInputStream); 314 } catch (IOException e) { 315 AndroidTestCase.fail("IOException was thrown: " + e.getMessage()); 316 } finally { 317 if (mInputStream != null) { 318 try { 319 mInputStream.close(); 320 } catch (IOException e) { 321 } 322 } 323 } 324 } 325 326 public static EntityIterator mockGetEntityIteratorMethod( 327 final ContentResolver resolver, 328 final Uri uri, final String selection, 329 final String[] selectionArgs, final String sortOrder) { 330 if (ExportTestResolver.class.equals(resolver.getClass())) { 331 return ((ExportTestResolver)resolver).getProvider().queryEntities( 332 uri, selection, selectionArgs, sortOrder); 333 } 334 335 Log.e(LOG_TAG, "Unexpected provider given."); 336 return null; 337 } 338 339 private Method getMockGetEntityIteratorMethod() 340 throws SecurityException, NoSuchMethodException { 341 return this.getClass().getMethod("mockGetEntityIteratorMethod", 342 ContentResolver.class, Uri.class, String.class, String[].class, String.class); 343 } 344 345 private void verifyForExportTest() { 346 final VCardComposer composer = 347 new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType, mCharset); 348 composer.addHandler(mLineVerifier); 349 composer.addHandler(mVCardVerifierInternal); 350 if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) { 351 AndroidTestCase.fail("init() failed. Reason: " + composer.getErrorReason()); 352 } 353 AndroidTestCase.assertFalse(composer.isAfterLast()); 354 try { 355 while (!composer.isAfterLast()) { 356 try { 357 final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod(); 358 AndroidTestCase.assertNotNull(mockGetEntityIteratorMethod); 359 AndroidTestCase.assertTrue( 360 composer.createOneEntry(mockGetEntityIteratorMethod)); 361 } catch (Exception e) { 362 e.printStackTrace(); 363 AndroidTestCase.fail(e.toString()); 364 } 365 } 366 } finally { 367 composer.terminate(); 368 } 369 } 370 } 371