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