Home | History | Annotate | Download | only in testutils
      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 com.android.vcard.tests.testutils;
     17 
     18 import com.android.vcard.VCardComposer;
     19 import com.android.vcard.VCardConfig;
     20 import com.android.vcard.VCardEntryConstructor;
     21 import com.android.vcard.VCardInterpreter;
     22 import com.android.vcard.VCardParser;
     23 import com.android.vcard.VCardUtils;
     24 import com.android.vcard.exception.VCardException;
     25 
     26 import android.content.ContentResolver;
     27 import android.content.EntityIterator;
     28 import android.database.Cursor;
     29 import android.net.Uri;
     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     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