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