Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2290efde authored by Fyodor Kupolov's avatar Fyodor Kupolov
Browse files

Added concurrency test

Test 2 threads repeatedly calling getAccounts while 1 thread calls
setAuthToken.

Example output:
I AccountManagerServiceTest: readTotalTime=1468 avg=36.7
I AccountManagerServiceTest: writeTotalTime=813 avg=40

Bug: 36485175
Test: AccountManagerServiceTest
Change-Id: Iee66339ceeb8f149eb9fc0906c537db60465d475
parent 73b84186
Loading
Loading
Loading
Loading
+142 −7
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.nullable;
import static org.mockito.Mockito.times;
@@ -32,7 +31,6 @@ import static org.mockito.Mockito.when;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerInternal;
import android.accounts.AuthenticatorDescription;
import android.accounts.CantAddAccountActivity;
import android.accounts.IAccountManagerResponse;
import android.app.AppOpsManager;
@@ -49,11 +47,9 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCacheListener;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.content.pm.RegisteredServicesCache.ServiceInfo;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
@@ -62,6 +58,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.test.AndroidTestCase;
@@ -78,16 +75,18 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;


/**
@@ -2616,6 +2615,142 @@ public class AccountManagerServiceTest extends AndroidTestCase {
        }
    }

    @SmallTest
    public void testConcurrencyReadWrite() throws Exception {
        // Test 2 threads calling getAccounts and 1 thread setAuthToken
        unlockSystemUser();
        String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
        when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);

        Account a1 = new Account("account1",
                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
        mAms.addAccountExplicitly(a1, "p1", null);
        List<String> errors = Collections.synchronizedList(new ArrayList<>());
        int readerCount = 2;
        ExecutorService es = Executors.newFixedThreadPool(readerCount + 1);
        AtomicLong readTotalTime = new AtomicLong(0);
        AtomicLong writeTotalTime = new AtomicLong(0);
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(readerCount + 1);

        final int loopSize = 20;
        for (int t = 0; t < readerCount; t++) {
            es.submit(() -> {
                for (int i = 0; i < loopSize; i++) {
                    String logPrefix = Thread.currentThread().getName() + " " + i;
                    waitForCyclicBarrier(cyclicBarrier);
                    cyclicBarrier.reset();
                    SystemClock.sleep(1); // Ensure that writer wins
                    Log.d(TAG, logPrefix + " getAccounts started");
                    long ti = System.currentTimeMillis();
                    try {
                        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
                        if (accounts == null || accounts.length != 1
                                || !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals(
                                accounts[0].type)) {
                            String msg = logPrefix + ": Unexpected accounts: " + Arrays
                                    .toString(accounts);
                            Log.e(TAG, "    " + msg);
                            errors.add(msg);
                        }
                        Log.d(TAG, logPrefix + " getAccounts done");
                    } catch (Exception e) {
                        String msg = logPrefix + ": getAccounts failed " + e;
                        Log.e(TAG, msg, e);
                        errors.add(msg);
                    }
                    ti = System.currentTimeMillis() - ti;
                    readTotalTime.addAndGet(ti);
                }
            });
        }

        es.submit(() -> {
            for (int i = 0; i < loopSize; i++) {
                String logPrefix = Thread.currentThread().getName() + " " + i;
                waitForCyclicBarrier(cyclicBarrier);
                long ti = System.currentTimeMillis();
                Log.d(TAG, logPrefix + " setAuthToken started");
                try {
                    mAms.setAuthToken(a1, "t1", "v" + i);
                    Log.d(TAG, logPrefix + " setAuthToken done");
                } catch (Exception e) {
                    errors.add(logPrefix + ": setAuthToken failed: " + e);
                }
                ti = System.currentTimeMillis() - ti;
                writeTotalTime.addAndGet(ti);
            }
        });
        es.shutdown();
        assertTrue("Time-out waiting for jobs to finish",
                es.awaitTermination(10, TimeUnit.SECONDS));
        es.shutdownNow();
        assertTrue("Errors: " + errors, errors.isEmpty());
        Log.i(TAG, "testConcurrencyReadWrite: readTotalTime=" + readTotalTime + " avg="
                + (readTotalTime.doubleValue() / readerCount / loopSize));
        Log.i(TAG, "testConcurrencyReadWrite: writeTotalTime=" + writeTotalTime + " avg="
                + (writeTotalTime.doubleValue() / loopSize));
    }

    @SmallTest
    public void testConcurrencyRead() throws Exception {
        // Test 2 threads calling getAccounts
        unlockSystemUser();
        String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
        when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);

        Account a1 = new Account("account1",
                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
        mAms.addAccountExplicitly(a1, "p1", null);
        List<String> errors = Collections.synchronizedList(new ArrayList<>());
        int readerCount = 2;
        ExecutorService es = Executors.newFixedThreadPool(readerCount + 1);
        AtomicLong readTotalTime = new AtomicLong(0);

        final int loopSize = 20;
        for (int t = 0; t < readerCount; t++) {
            es.submit(() -> {
                for (int i = 0; i < loopSize; i++) {
                    String logPrefix = Thread.currentThread().getName() + " " + i;
                    Log.d(TAG, logPrefix + " getAccounts started");
                    long ti = System.currentTimeMillis();
                    try {
                        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
                        if (accounts == null || accounts.length != 1
                                || !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals(
                                accounts[0].type)) {
                            String msg = logPrefix + ": Unexpected accounts: " + Arrays
                                    .toString(accounts);
                            Log.e(TAG, "    " + msg);
                            errors.add(msg);
                        }
                        Log.d(TAG, logPrefix + " getAccounts done");
                    } catch (Exception e) {
                        String msg = logPrefix + ": getAccounts failed " + e;
                        Log.e(TAG, msg, e);
                        errors.add(msg);
                    }
                    ti = System.currentTimeMillis() - ti;
                    readTotalTime.addAndGet(ti);
                }
            });
        }
        es.shutdown();
        assertTrue("Time-out waiting for jobs to finish",
                es.awaitTermination(10, TimeUnit.SECONDS));
        es.shutdownNow();
        assertTrue("Errors: " + errors, errors.isEmpty());
        Log.i(TAG, "testConcurrencyRead: readTotalTime=" + readTotalTime + " avg="
                + (readTotalTime.doubleValue() / readerCount / loopSize));
    }

    private void waitForCyclicBarrier(CyclicBarrier cyclicBarrier) {
        try {
            cyclicBarrier.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            throw new IllegalStateException("Should not throw " + e, e);
        }
    }

    private void waitForLatch(CountDownLatch latch) {
        try {
            latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);