/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.android.vcard.tests.testutils; import android.content.ContentResolver; import android.content.EntityIterator; import android.database.Cursor; import android.net.Uri; import android.test.AndroidTestCase; import android.test.mock.MockContext; import android.text.TextUtils; import android.util.Log; import com.android.vcard.VCardComposer; import com.android.vcard.VCardConfig; import com.android.vcard.VCardEntryConstructor; import com.android.vcard.VCardParser; import com.android.vcard.VCardUtils; import com.android.vcard.exception.VCardException; import junit.framework.TestCase; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; /** *

* The class lets users checks that given expected vCard data are same as given actual vCard data. * Able to verify both vCard importer/exporter. *

*

* First a user has to initialize the object by calling either * {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}. * "Round trip test" (import -> export -> import, or export -> import -> export) is not supported. *

*/ public class VCardVerifier { private static final String LOG_TAG = "VCardVerifier"; private static final boolean DEBUG = true; /** * Special URI for testing. */ /* package */ static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard"; private static final Uri VCARD_TEST_AUTHORITY_URI = Uri.parse("content://" + VCARD_TEST_AUTHORITY); /* package */ static final Uri CONTACTS_TEST_CONTENT_URI = Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts"); private static class CustomMockContext extends MockContext { final ContentResolver mResolver; public CustomMockContext(ContentResolver resolver) { mResolver = resolver; } @Override public ContentResolver getContentResolver() { return mResolver; } } private final AndroidTestCase mAndroidTestCase; private int mVCardType; private boolean mIsDoCoMo; // Only one of them must be non-empty. private ExportTestResolver mExportTestResolver; private InputStream mInputStream; // To allow duplication, use list instead of set. // When null, we don't need to do the verification. private PropertyNodesVerifier mPropertyNodesVerifier; private LineVerifier mLineVerifier; private ContentValuesVerifier mContentValuesVerifier; private boolean mInitialized; private boolean mVerified = false; private String mCharset; private String mExceptionContents; // Called by VCardTestsBase public VCardVerifier(AndroidTestCase androidTestCase) { mAndroidTestCase = androidTestCase; mExportTestResolver = null; mInputStream = null; mInitialized = false; mVerified = false; } // Should be called at the beginning of each import test. public void initForImportTest(int vcardType, int resId) { if (mInitialized) { AndroidTestCase.fail("Already initialized"); } mVCardType = vcardType; mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); setInputResourceId(resId); mInitialized = true; } // Should be called at the beginning of each export test. public void initForExportTest(int vcardType) { initForExportTest(vcardType, "UTF-8"); } public void initForExportTest(int vcardType, String charset) { if (mInitialized) { AndroidTestCase.fail("Already initialized"); } mExportTestResolver = new ExportTestResolver(mAndroidTestCase); mVCardType = vcardType; mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); mInitialized = true; if (TextUtils.isEmpty(charset)) { mCharset = "UTF-8"; } else { mCharset = charset; } } private void setInputResourceId(int resId) { final InputStream inputStream = mAndroidTestCase.getContext().getResources().openRawResource(resId); if (inputStream == null) { AndroidTestCase.fail("Wrong resId: " + resId); } setInputStream(inputStream); } private void setInputStream(InputStream inputStream) { if (mExportTestResolver != null) { AndroidTestCase.fail("addInputEntry() is called."); } else if (mInputStream != null) { AndroidTestCase.fail("InputStream is already set"); } mInputStream = inputStream; } public ContactEntry addInputEntry() { if (!mInitialized) { AndroidTestCase.fail("Not initialized"); } if (mInputStream != null) { AndroidTestCase.fail("setInputStream is called"); } return mExportTestResolver.addInputContactEntry(); } public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithoutVersion() { if (!mInitialized) { AndroidTestCase.fail("Not initialized"); } if (mPropertyNodesVerifier == null) { mPropertyNodesVerifier = new PropertyNodesVerifier(mAndroidTestCase); } return mPropertyNodesVerifier.addPropertyNodesVerifierElem(); } public PropertyNodesVerifierElem addPropertyNodesVerifierElem() { final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElemWithoutVersion(); final String versionString; if (VCardConfig.isVersion21(mVCardType)) { versionString = "2.1"; } else if (VCardConfig.isVersion30(mVCardType)) { versionString = "3.0"; } else if (VCardConfig.isVersion40(mVCardType)) { versionString = "4.0"; } else { throw new RuntimeException("Unexpected vcard type during a unit test"); } elem.addExpectedNodeWithOrder("VERSION", versionString); return elem; } public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() { if (!mInitialized) { AndroidTestCase.fail("Not initialized"); } final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem(); if (VCardConfig.isVersion40(mVCardType)) { elem.addExpectedNodeWithOrder("FN", ""); } else if (VCardConfig.isVersion30(mVCardType)) { elem.addExpectedNodeWithOrder("N", ""); elem.addExpectedNodeWithOrder("FN", ""); } else if (mIsDoCoMo) { elem.addExpectedNodeWithOrder("N", ""); } return elem; } public LineVerifierElem addLineVerifierElem() { if (!mInitialized) { AndroidTestCase.fail("Not initialized"); } if (mLineVerifier == null) { mLineVerifier = new LineVerifier(mAndroidTestCase, mVCardType); } return mLineVerifier.addLineVerifierElem(); } public ContentValuesVerifierElem addContentValuesVerifierElem() { if (!mInitialized) { AndroidTestCase.fail("Not initialized"); } if (mContentValuesVerifier == null) { mContentValuesVerifier = new ContentValuesVerifier(); } return mContentValuesVerifier.addElem(mAndroidTestCase); } public void addVCardExceptionVerifier(String contents) { mExceptionContents = contents; } /** * Sets up sub-verifiers correctly and tries to parse vCard as {@link InputStream}. * Errors around InputStream must be handled outside this method. * * Used both from {@link #verifyForImportTest()} and from {@link #verifyForExportTest()}. */ private void verifyWithInputStream(InputStream is) throws IOException { try { // Note: we must not specify charset toward vCard parsers. This code checks whether // those parsers are able to encode given binary without any extra information for // charset. final VCardParser parser = VCardUtils.getAppropriateParser(mVCardType); if (mContentValuesVerifier != null) { final VCardEntryConstructor constructor = new VCardEntryConstructor(mVCardType); constructor.addEntryHandler(mContentValuesVerifier); parser.addInterpreter(constructor); } if (mPropertyNodesVerifier != null) { parser.addInterpreter(mPropertyNodesVerifier); } parser.parse(is); if (mExceptionContents != null) { // exception contents exists, we expect an exception to occur. AndroidTestCase.fail(); } } catch (VCardException e) { if (mExceptionContents != null) { AndroidTestCase.assertTrue(e.getMessage().contains(mExceptionContents)); } else { Log.e(LOG_TAG, "VCardException", e); AndroidTestCase.fail("Unexpected VCardException: " + e.getMessage()); } } } private void verifyOneVCardForExport(final String vcard) { if (DEBUG) Log.d(LOG_TAG, vcard); InputStream is = null; try { is = new ByteArrayInputStream(vcard.getBytes(mCharset)); verifyWithInputStream(is); } catch (IOException e) { AndroidTestCase.fail("Unexpected IOException: " + e.getMessage()); } finally { if (is != null) { try { is.close(); } catch (IOException e) { AndroidTestCase.fail("Unexpected IOException: " + e.getMessage()); } } } } public void verify() { if (!mInitialized) { TestCase.fail("Not initialized."); } if (mVerified) { TestCase.fail("verify() was called twice."); } if (mInputStream != null) { if (mExportTestResolver != null){ TestCase.fail("There are two input sources."); } verifyForImportTest(); } else if (mExportTestResolver != null){ verifyForExportTest(); } else { TestCase.fail("No input is determined"); } mVerified = true; } private void verifyForImportTest() { if (mLineVerifier != null) { AndroidTestCase.fail("Not supported now."); } try { verifyWithInputStream(mInputStream); } catch (IOException e) { AndroidTestCase.fail("IOException was thrown: " + e.getMessage()); } finally { if (mInputStream != null) { try { mInputStream.close(); } catch (IOException e) { } } } } public static EntityIterator mockGetEntityIteratorMethod( final ContentResolver resolver, final Uri uri, final String selection, final String[] selectionArgs, final String sortOrder) { if (ExportTestResolver.class.equals(resolver.getClass())) { return ((ExportTestResolver)resolver).getProvider().queryEntities( uri, selection, selectionArgs, sortOrder); } Log.e(LOG_TAG, "Unexpected provider given."); return null; } private Method getMockGetEntityIteratorMethod() throws SecurityException, NoSuchMethodException { return this.getClass().getMethod("mockGetEntityIteratorMethod", ContentResolver.class, Uri.class, String.class, String[].class, String.class); } private void verifyForExportTest() { final CustomMockContext context = new CustomMockContext(mExportTestResolver); final ContentResolver resolver = context.getContentResolver(); final VCardComposer composer = new VCardComposer(context, mVCardType, mCharset); // projection is ignored. final Cursor cursor = resolver.query(CONTACTS_TEST_CONTENT_URI, null, null, null, null); if (!composer.init(cursor)) { AndroidTestCase.fail("init() failed. Reason: " + composer.getErrorReason()); } AndroidTestCase.assertFalse(composer.isAfterLast()); try { while (!composer.isAfterLast()) { Method mockGetEntityIteratorMethod = null; try { mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod(); } catch (Exception e) { AndroidTestCase.fail("Exception thrown: " + e); } AndroidTestCase.assertNotNull(mockGetEntityIteratorMethod); final String vcard = composer.createOneEntry(mockGetEntityIteratorMethod); AndroidTestCase.assertNotNull(vcard); if (mLineVerifier != null) { mLineVerifier.verify(vcard); } verifyOneVCardForExport(vcard); } } finally { composer.terminate(); } } }