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