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 android.content.ContentResolver; 19import android.content.EntityIterator; 20import android.database.Cursor; 21import android.net.Uri; 22import android.test.AndroidTestCase; 23import android.test.mock.MockContext; 24import android.text.TextUtils; 25import android.util.Log; 26 27import com.android.vcard.VCardComposer; 28import com.android.vcard.VCardConfig; 29import com.android.vcard.VCardEntryConstructor; 30import com.android.vcard.VCardParser; 31import com.android.vcard.VCardUtils; 32import com.android.vcard.exception.VCardException; 33 34import junit.framework.TestCase; 35 36import java.io.ByteArrayInputStream; 37import java.io.IOException; 38import java.io.InputStream; 39import java.lang.reflect.Method; 40 41/** 42 * <p> 43 * The class lets users checks that given expected vCard data are same as given actual vCard data. 44 * Able to verify both vCard importer/exporter. 45 * </p> 46 * <p> 47 * First a user has to initialize the object by calling either 48 * {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}. 49 * "Round trip test" (import -> export -> import, or export -> import -> export) is not supported. 50 * </p> 51 */ 52public class VCardVerifier { 53 private static final String LOG_TAG = "VCardVerifier"; 54 private static final boolean DEBUG = true; 55 56 /** 57 * Special URI for testing. 58 */ 59 /* package */ static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard"; 60 private static final Uri VCARD_TEST_AUTHORITY_URI = 61 Uri.parse("content://" + VCARD_TEST_AUTHORITY); 62 /* package */ static final Uri CONTACTS_TEST_CONTENT_URI = 63 Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts"); 64 65 private static class CustomMockContext extends MockContext { 66 final ContentResolver mResolver; 67 public CustomMockContext(ContentResolver resolver) { 68 mResolver = resolver; 69 } 70 71 @Override 72 public ContentResolver getContentResolver() { 73 return mResolver; 74 } 75 } 76 77 private final AndroidTestCase mAndroidTestCase; 78 private int mVCardType; 79 private boolean mIsDoCoMo; 80 81 // Only one of them must be non-empty. 82 private ExportTestResolver mExportTestResolver; 83 private InputStream mInputStream; 84 85 // To allow duplication, use list instead of set. 86 // When null, we don't need to do the verification. 87 private PropertyNodesVerifier mPropertyNodesVerifier; 88 private LineVerifier mLineVerifier; 89 private ContentValuesVerifier mContentValuesVerifier; 90 private boolean mInitialized; 91 private boolean mVerified = false; 92 private String mCharset; 93 94 private String mExceptionContents; 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 public void addVCardExceptionVerifier(String contents) { 229 mExceptionContents = contents; 230 } 231 232 /** 233 * Sets up sub-verifiers correctly and tries to parse vCard as {@link InputStream}. 234 * Errors around InputStream must be handled outside this method. 235 * 236 * Used both from {@link #verifyForImportTest()} and from {@link #verifyForExportTest()}. 237 */ 238 private void verifyWithInputStream(InputStream is) throws IOException { 239 try { 240 // Note: we must not specify charset toward vCard parsers. This code checks whether 241 // those parsers are able to encode given binary without any extra information for 242 // charset. 243 final VCardParser parser = VCardUtils.getAppropriateParser(mVCardType); 244 if (mContentValuesVerifier != null) { 245 final VCardEntryConstructor constructor = new VCardEntryConstructor(mVCardType); 246 constructor.addEntryHandler(mContentValuesVerifier); 247 parser.addInterpreter(constructor); 248 } 249 if (mPropertyNodesVerifier != null) { 250 parser.addInterpreter(mPropertyNodesVerifier); 251 } 252 parser.parse(is); 253 if (mExceptionContents != null) { 254 // exception contents exists, we expect an exception to occur. 255 AndroidTestCase.fail(); 256 } 257 } catch (VCardException e) { 258 if (mExceptionContents != null) { 259 AndroidTestCase.assertTrue(e.getMessage().contains(mExceptionContents)); 260 } else { 261 Log.e(LOG_TAG, "VCardException", e); 262 AndroidTestCase.fail("Unexpected VCardException: " + e.getMessage()); 263 } 264 } 265 } 266 267 private void verifyOneVCardForExport(final String vcard) { 268 if (DEBUG) 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 CustomMockContext context = new CustomMockContext(mExportTestResolver); 347 final ContentResolver resolver = context.getContentResolver(); 348 final VCardComposer composer = new VCardComposer(context, mVCardType, mCharset); 349 // projection is ignored. 350 final Cursor cursor = resolver.query(CONTACTS_TEST_CONTENT_URI, null, null, null, null); 351 if (!composer.init(cursor)) { 352 AndroidTestCase.fail("init() failed. Reason: " + composer.getErrorReason()); 353 } 354 AndroidTestCase.assertFalse(composer.isAfterLast()); 355 try { 356 while (!composer.isAfterLast()) { 357 Method mockGetEntityIteratorMethod = null; 358 try { 359 mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod(); 360 } catch (Exception e) { 361 AndroidTestCase.fail("Exception thrown: " + e); 362 } 363 AndroidTestCase.assertNotNull(mockGetEntityIteratorMethod); 364 final String vcard = composer.createOneEntry(mockGetEntityIteratorMethod); 365 AndroidTestCase.assertNotNull(vcard); 366 if (mLineVerifier != null) { 367 mLineVerifier.verify(vcard); 368 } 369 verifyOneVCardForExport(vcard); 370 } 371 } finally { 372 composer.terminate(); 373 } 374 } 375} 376