/* * Copyright (C) 2007 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.calendar; import com.android.calendar.AsyncQueryService.Operation; import com.android.calendar.AsyncQueryServiceHelper.OperationInfo; import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.test.IsolatedContext; import android.test.RenamingDelegatingContext; import android.test.ServiceTestCase; import android.test.mock.MockContentResolver; import android.test.mock.MockContext; import android.test.mock.MockCursor; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.Smoke; import android.util.Log; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * Unit tests for {@link android.text.format.DateUtils#formatDateRange}. */ public class AsyncQueryServiceTest extends ServiceTestCase { private static final String TAG = "AsyncQueryServiceTest"; private static final String AUTHORITY_URI = "content://AsyncQueryAuthority/"; private static final String AUTHORITY = "AsyncQueryAuthority"; private static final int MIN_DELAY = 50; private static final int BASE_TEST_WAIT_TIME = MIN_DELAY * 5; private static int mId = 0; private static final String[] TEST_PROJECTION = new String[] { "col1", "col2", "col3" }; private static final String TEST_SELECTION = "selection"; private static final String[] TEST_SELECTION_ARGS = new String[] { "arg1", "arg2", "arg3" }; public AsyncQueryServiceTest() { super(AsyncQueryServiceHelper.class); } @Override protected void setUp() throws Exception { super.setUp(); } private class MockContext2 extends MockContext { @Override public Resources getResources() { return getContext().getResources(); } @Override public File getDir(String name, int mode) { return getContext().getDir("mockcontext2_+" + name, mode); } @Override public Context getApplicationContext() { return this; } } @Smoke @SmallTest public void testQuery() throws Exception { int index = 0; final OperationInfo[] work = new OperationInfo[1]; work[index] = new OperationInfo(); work[index].op = Operation.EVENT_ARG_QUERY; work[index].token = ++mId; work[index].cookie = ++mId; work[index].uri = Uri.parse(AUTHORITY_URI + "blah"); work[index].projection = TEST_PROJECTION; work[index].selection = TEST_SELECTION; work[index].selectionArgs = TEST_SELECTION_ARGS; work[index].orderBy = "order"; work[index].delayMillis = 0; work[index].result = new TestCursor(); TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work); aqs.startQuery(work[index].token, work[index].cookie, work[index].uri, work[index].projection, work[index].selection, work[index].selectionArgs, work[index].orderBy); Log.d(TAG, "testQuery Waiting >>>>>>>>>>>"); assertEquals("Not all operations were executed.", work.length, aqs .waitForCompletion(BASE_TEST_WAIT_TIME)); Log.d(TAG, "testQuery Done <<<<<<<<<<<<<<"); } @SmallTest public void testInsert() throws Exception { int index = 0; final OperationInfo[] work = new OperationInfo[1]; work[index] = new OperationInfo(); work[index].op = Operation.EVENT_ARG_INSERT; work[index].token = ++mId; work[index].cookie = ++mId; work[index].uri = Uri.parse(AUTHORITY_URI + "blah"); work[index].values = new ContentValues(); work[index].values.put("key", ++mId); work[index].delayMillis = 0; work[index].result = Uri.parse(AUTHORITY_URI + "Result=" + ++mId); TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work); aqs.startInsert(work[index].token, work[index].cookie, work[index].uri, work[index].values, work[index].delayMillis); Log.d(TAG, "testInsert Waiting >>>>>>>>>>>"); assertEquals("Not all operations were executed.", work.length, aqs .waitForCompletion(BASE_TEST_WAIT_TIME)); Log.d(TAG, "testInsert Done <<<<<<<<<<<<<<"); } @SmallTest public void testUpdate() throws Exception { int index = 0; final OperationInfo[] work = new OperationInfo[1]; work[index] = new OperationInfo(); work[index].op = Operation.EVENT_ARG_UPDATE; work[index].token = ++mId; work[index].cookie = ++mId; work[index].uri = Uri.parse(AUTHORITY_URI + ++mId); work[index].values = new ContentValues(); work[index].values.put("key", ++mId); work[index].selection = TEST_SELECTION; work[index].selectionArgs = TEST_SELECTION_ARGS; work[index].delayMillis = 0; work[index].result = ++mId; TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work); aqs.startUpdate(work[index].token, work[index].cookie, work[index].uri, work[index].values, work[index].selection, work[index].selectionArgs, work[index].delayMillis); Log.d(TAG, "testUpdate Waiting >>>>>>>>>>>"); assertEquals("Not all operations were executed.", work.length, aqs .waitForCompletion(BASE_TEST_WAIT_TIME)); Log.d(TAG, "testUpdate Done <<<<<<<<<<<<<<"); } @SmallTest public void testDelete() throws Exception { int index = 0; final OperationInfo[] work = new OperationInfo[1]; work[index] = new OperationInfo(); work[index].op = Operation.EVENT_ARG_DELETE; work[index].token = ++mId; work[index].cookie = ++mId; work[index].uri = Uri.parse(AUTHORITY_URI + "blah"); work[index].selection = TEST_SELECTION; work[index].selectionArgs = TEST_SELECTION_ARGS; work[index].delayMillis = 0; work[index].result = ++mId; TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work); aqs.startDelete(work[index].token, work[index].cookie, work[index].uri, work[index].selection, work[index].selectionArgs, work[index].delayMillis); Log.d(TAG, "testDelete Waiting >>>>>>>>>>>"); assertEquals("Not all operations were executed.", work.length, aqs .waitForCompletion(BASE_TEST_WAIT_TIME)); Log.d(TAG, "testDelete Done <<<<<<<<<<<<<<"); } @SmallTest public void testBatch() throws Exception { int index = 0; final OperationInfo[] work = new OperationInfo[1]; work[index] = new OperationInfo(); work[index].op = Operation.EVENT_ARG_BATCH; work[index].token = ++mId; work[index].cookie = ++mId; work[index].authority = AUTHORITY; work[index].cpo = new ArrayList(); work[index].cpo.add(ContentProviderOperation.newInsert(Uri.parse(AUTHORITY_URI + ++mId)) .build()); work[index].delayMillis = 0; ContentProviderResult[] resultArray = new ContentProviderResult[1]; resultArray[0] = new ContentProviderResult(++mId); work[index].result = resultArray; TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work); aqs.startBatch(work[index].token, work[index].cookie, work[index].authority, work[index].cpo, work[index].delayMillis); Log.d(TAG, "testBatch Waiting >>>>>>>>>>>"); assertEquals("Not all operations were executed.", work.length, aqs .waitForCompletion(BASE_TEST_WAIT_TIME)); Log.d(TAG, "testBatch Done <<<<<<<<<<<<<<"); } @LargeTest public void testDelay() throws Exception { // Tests the ordering of the workqueue int index = 0; OperationInfo[] work = new OperationInfo[5]; work[index++] = generateWork(MIN_DELAY * 2); work[index++] = generateWork(0); work[index++] = generateWork(MIN_DELAY * 1); work[index++] = generateWork(0); work[index++] = generateWork(MIN_DELAY * 3); OperationInfo[] sorted = generateSortedWork(work, work.length); TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(sorted), sorted); startWork(aqs, work); Log.d(TAG, "testDelay Waiting >>>>>>>>>>>"); assertEquals("Not all operations were executed.", work.length, aqs .waitForCompletion(BASE_TEST_WAIT_TIME)); Log.d(TAG, "testDelay Done <<<<<<<<<<<<<<"); } @LargeTest public void testCancel_simpleCancelLastTest() throws Exception { int index = 0; OperationInfo[] work = new OperationInfo[5]; work[index++] = generateWork(MIN_DELAY * 2); work[index++] = generateWork(0); work[index++] = generateWork(MIN_DELAY); work[index++] = generateWork(0); work[index] = generateWork(MIN_DELAY * 3); // Not part of the expected as it will be canceled OperationInfo toBeCancelled1 = work[index]; OperationInfo[] expected = generateSortedWork(work, work.length - 1); TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(expected), expected); startWork(aqs, work); Operation lastOne = aqs.getLastCancelableOperation(); // Log.d(TAG, "lastOne = " + lastOne.toString()); // Log.d(TAG, "toBeCancelled1 = " + toBeCancelled1.toString()); assertTrue("1) delay=3 is not last", toBeCancelled1.equivalent(lastOne)); assertEquals("Can't cancel delay 3", 1, aqs.cancelOperation(lastOne.token)); Log.d(TAG, "testCancel_simpleCancelLastTest Waiting >>>>>>>>>>>"); assertEquals("Not all operations were executed.", expected.length, aqs .waitForCompletion(BASE_TEST_WAIT_TIME)); Log.d(TAG, "testCancel_simpleCancelLastTest Done <<<<<<<<<<<<<<"); } @LargeTest public void testCancel_cancelSecondToLast() throws Exception { int index = 0; OperationInfo[] work = new OperationInfo[5]; work[index++] = generateWork(MIN_DELAY * 2); work[index++] = generateWork(0); work[index++] = generateWork(MIN_DELAY); work[index++] = generateWork(0); work[index] = generateWork(MIN_DELAY * 3); // Not part of the expected as it will be canceled OperationInfo toBeCancelled1 = work[index]; OperationInfo[] expected = new OperationInfo[4]; expected[0] = work[1]; // delay = 0 expected[1] = work[3]; // delay = 0 expected[2] = work[2]; // delay = MIN_DELAY expected[3] = work[4]; // delay = MIN_DELAY * 3 TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(expected), expected); startWork(aqs, work); Operation lastOne = aqs.getLastCancelableOperation(); // delay = 3 assertTrue("2) delay=3 is not last", toBeCancelled1.equivalent(lastOne)); assertEquals("Can't cancel delay 2", 1, aqs.cancelOperation(work[0].token)); assertEquals("Delay 2 should be gone", 0, aqs.cancelOperation(work[0].token)); Log.d(TAG, "testCancel_cancelSecondToLast Waiting >>>>>>>>>>>"); assertEquals("Not all operations were executed.", expected.length, aqs .waitForCompletion(BASE_TEST_WAIT_TIME)); Log.d(TAG, "testCancel_cancelSecondToLast Done <<<<<<<<<<<<<<"); } @LargeTest public void testCancel_multipleCancels() throws Exception { int index = 0; OperationInfo[] work = new OperationInfo[5]; work[index++] = generateWork(MIN_DELAY * 2); work[index++] = generateWork(0); work[index++] = generateWork(MIN_DELAY); work[index++] = generateWork(0); work[index] = generateWork(MIN_DELAY * 3); // Not part of the expected as it will be canceled OperationInfo[] expected = new OperationInfo[3]; expected[0] = work[1]; // delay = 0 expected[1] = work[3]; // delay = 0 expected[2] = work[2]; // delay = MIN_DELAY TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(expected), expected); startWork(aqs, work); Operation lastOne = aqs.getLastCancelableOperation(); // delay = 3 assertTrue("3) delay=3 is not last", work[4].equivalent(lastOne)); assertEquals("Can't cancel delay 2", 1, aqs.cancelOperation(work[0].token)); assertEquals("Delay 2 should be gone", 0, aqs.cancelOperation(work[0].token)); assertEquals("Can't cancel delay 3", 1, aqs.cancelOperation(work[4].token)); assertEquals("Delay 3 should be gone", 0, aqs.cancelOperation(work[4].token)); Log.d(TAG, "testCancel_multipleCancels Waiting >>>>>>>>>>>"); assertEquals("Not all operations were executed.", expected.length, aqs .waitForCompletion(BASE_TEST_WAIT_TIME)); Log.d(TAG, "testCancel_multipleCancels Done <<<<<<<<<<<<<<"); } private OperationInfo generateWork(long delayMillis) { OperationInfo work = new OperationInfo(); work.op = Operation.EVENT_ARG_DELETE; work.token = ++mId; work.cookie = 100 + work.token; work.uri = Uri.parse(AUTHORITY_URI + "blah"); work.selection = TEST_SELECTION; work.selectionArgs = TEST_SELECTION_ARGS; work.delayMillis = delayMillis; work.result = 1000 + work.token; return work; } private void startWork(TestAsyncQueryService aqs, OperationInfo[] work) { for (OperationInfo w : work) { if (w != null) { aqs.startDelete(w.token, w.cookie, w.uri, w.selection, w.selectionArgs, w.delayMillis); } } } OperationInfo[] generateSortedWork(OperationInfo[] work, int length) { OperationInfo[] sorted = new OperationInfo[length]; System.arraycopy(work, 0, sorted, 0, length); // Set the scheduled time so they get sorted properly for (OperationInfo w : sorted) { if (w != null) { w.calculateScheduledTime(); } } // Stable sort by scheduled time Arrays.sort(sorted); Log.d(TAG, "Unsorted work: " + work.length); for (OperationInfo w : work) { if (w != null) { Log.d(TAG, "Token#" + w.token + " delay=" + w.delayMillis); } } Log.d(TAG, "Sorted work: " + sorted.length); for (OperationInfo w : sorted) { if (w != null) { Log.d(TAG, "Token#" + w.token + " delay=" + w.delayMillis); } } return sorted; } private Context buildTestContext(final OperationInfo[] work) { MockContext context = new MockContext() { MockContentResolver mResolver; @Override public ContentResolver getContentResolver() { if (mResolver == null) { mResolver = new MockContentResolver(); final String filenamePrefix = "test."; RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( new MockContext2(), getContext(), filenamePrefix); IsolatedContext providerContext = new IsolatedContext(mResolver, targetContextWrapper); ContentProvider provider = new TestProvider(work); provider.attachInfo(providerContext, null); mResolver.addProvider(AUTHORITY, provider); } return mResolver; } @Override public String getPackageName() { return AsyncQueryServiceTest.class.getPackage().getName(); } @Override public ComponentName startService(Intent service) { AsyncQueryServiceTest.this.startService(service); return service.getComponent(); } }; return context; } private final class TestCursor extends MockCursor { int mUnique = ++mId; @Override public int getCount() { return mUnique; } } /** * TestAsyncQueryService takes the expected results in the constructor. They * are used to verify the data passed to the callbacks. */ class TestAsyncQueryService extends AsyncQueryService { int mIndex = 0; private OperationInfo[] mWork; private Semaphore mCountingSemaphore; public TestAsyncQueryService(Context context, OperationInfo[] work) { super(context); mCountingSemaphore = new Semaphore(0); // run in a separate thread but call the same code HandlerThread thread = new HandlerThread("TestAsyncQueryService"); thread.start(); super.setTestHandler(new Handler(thread.getLooper()) { @Override public void handleMessage(Message msg) { TestAsyncQueryService.this.handleMessage(msg); } }); mWork = work; } @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { Log.d(TAG, "onQueryComplete tid=" + Thread.currentThread().getId()); Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex); assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_QUERY); assertEquals(mWork[mIndex].token, token); /* * Even though our TestProvider returned mWork[mIndex].result, it is * wrapped with new'ed CursorWrapperInner and there's no equal() in * CursorWrapperInner. assertEquals the two cursor will always fail. * So just compare the count which will be unique in our TestCursor; */ assertEquals(((Cursor) mWork[mIndex].result).getCount(), cursor.getCount()); mIndex++; mCountingSemaphore.release(); } @Override protected void onInsertComplete(int token, Object cookie, Uri uri) { Log.d(TAG, "onInsertComplete tid=" + Thread.currentThread().getId()); Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex); assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_INSERT); assertEquals(mWork[mIndex].token, token); assertEquals(mWork[mIndex].result, uri); mIndex++; mCountingSemaphore.release(); } @Override protected void onUpdateComplete(int token, Object cookie, int result) { Log.d(TAG, "onUpdateComplete tid=" + Thread.currentThread().getId()); Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex); assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_UPDATE); assertEquals(mWork[mIndex].token, token); assertEquals(mWork[mIndex].result, result); mIndex++; mCountingSemaphore.release(); } @Override protected void onDeleteComplete(int token, Object cookie, int result) { Log.d(TAG, "onDeleteComplete tid=" + Thread.currentThread().getId()); Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex); assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_DELETE); assertEquals(mWork[mIndex].token, token); assertEquals(mWork[mIndex].result, result); mIndex++; mCountingSemaphore.release(); } @Override protected void onBatchComplete(int token, Object cookie, ContentProviderResult[] results) { Log.d(TAG, "onBatchComplete tid=" + Thread.currentThread().getId()); Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex); assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_BATCH); assertEquals(mWork[mIndex].token, token); ContentProviderResult[] expected = (ContentProviderResult[]) mWork[mIndex].result; assertEquals(expected.length, results.length); for (int i = 0; i < expected.length; ++i) { assertEquals(expected[i].count, results[i].count); assertEquals(expected[i].uri, results[i].uri); } mIndex++; mCountingSemaphore.release(); } public int waitForCompletion(long timeoutMills) { Log.d(TAG, "waitForCompletion tid=" + Thread.currentThread().getId()); int count = 0; try { while (count < mWork.length) { if (!mCountingSemaphore.tryAcquire(timeoutMills, TimeUnit.MILLISECONDS)) { break; } count++; } } catch (InterruptedException e) { } return count; } } /** * This gets called by AsyncQueryServiceHelper to read or write the data. It * also verifies the data against the data passed in the constructor */ class TestProvider extends ContentProvider { OperationInfo[] mWork; int index = 0; public TestProvider(OperationInfo[] work) { mWork = work; } @Override public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy) { Log.d(TAG, "Provider query index=" + index); assertEquals(mWork[index].op, Operation.EVENT_ARG_QUERY); assertEquals(mWork[index].uri, uri); assertEquals(mWork[index].projection, projection); assertEquals(mWork[index].selection, selection); assertEquals(mWork[index].selectionArgs, selectionArgs); assertEquals(mWork[index].orderBy, orderBy); return (Cursor) mWork[index++].result; } @Override public Uri insert(Uri uri, ContentValues values) { Log.d(TAG, "Provider insert index=" + index); assertEquals(mWork[index].op, Operation.EVENT_ARG_INSERT); assertEquals(mWork[index].uri, uri); assertEquals(mWork[index].values, values); return (Uri) mWork[index++].result; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.d(TAG, "Provider update index=" + index); assertEquals(mWork[index].op, Operation.EVENT_ARG_UPDATE); assertEquals(mWork[index].uri, uri); assertEquals(mWork[index].values, values); assertEquals(mWork[index].selection, selection); assertEquals(mWork[index].selectionArgs, selectionArgs); return (Integer) mWork[index++].result; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.d(TAG, "Provider delete index=" + index); assertEquals(mWork[index].op, Operation.EVENT_ARG_DELETE); assertEquals(mWork[index].uri, uri); assertEquals(mWork[index].selection, selection); assertEquals(mWork[index].selectionArgs, selectionArgs); return (Integer) mWork[index++].result; } @Override public ContentProviderResult[] applyBatch(ArrayList operations) { Log.d(TAG, "Provider applyBatch index=" + index); assertEquals(mWork[index].op, Operation.EVENT_ARG_BATCH); assertEquals(mWork[index].cpo, operations); return (ContentProviderResult[]) mWork[index++].result; } @Override public String getType(Uri uri) { return null; } @Override public boolean onCreate() { return false; } } }