Java tutorial
/* * Copyright (C) 2010 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 org.mozilla.gecko.background.fxa; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.security.GeneralSecurityException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.atomic.AtomicReference; import org.mozilla.gecko.background.sync.AndroidSyncTestCaseWithAccounts; import org.mozilla.gecko.background.sync.TestSyncAccounts; import org.mozilla.gecko.fxa.AccountLoader; import org.mozilla.gecko.fxa.FirefoxAccounts; import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.fxa.login.Separated; import org.mozilla.gecko.fxa.login.State; import org.mozilla.gecko.sync.SyncConstants; import org.mozilla.gecko.sync.setup.SyncAccounts; import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters; import android.accounts.Account; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v4.content.Loader; import android.support.v4.content.Loader.OnLoadCompleteListener; /** * A version of https://android.googlesource.com/platform/frameworks/base/+/c91893511dc1b9e634648406c9ae61b15476e65d/test-runner/src/android/test/LoaderTestCase.java, * hacked to work with the v4 support library, and patched to work around * https://code.google.com/p/android/issues/detail?id=40987. */ public class TestAccountLoader extends AndroidSyncTestCaseWithAccounts { // Test account names must start with TEST_USERNAME in order to be recognized // as test accounts and deleted in tearDown. private static final String TEST_USERNAME = "testAccount@mozilla.com"; private static final String TEST_ACCOUNTTYPE = FxAccountConstants.ACCOUNT_TYPE; private static final String TEST_SYNCKEY = "testSyncKey"; private static final String TEST_SYNCPASSWORD = "testSyncPassword"; private static final String TEST_TOKEN_SERVER_URI = "testTokenServerURI"; private static final String TEST_PROFILE_SERVER_URI = "testProfileServerURI"; private static final String TEST_AUTH_SERVER_URI = "testAuthServerURI"; private static final String TEST_PROFILE = "testProfile"; public TestAccountLoader() { super(TEST_ACCOUNTTYPE, TEST_USERNAME); } static { // Force class loading of AsyncTask on the main thread so that it's handlers are tied to // the main thread and responses from the worker thread get delivered on the main thread. // The tests are run on another thread, allowing them to block waiting on a response from // the code running on the main thread. The main thread can't block since the AsyncTask // results come in via the event loop. new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... args) { return null; } @Override protected void onPostExecute(Void result) { } }; } /** * Runs a Loader synchronously and returns the result of the load. The loader will * be started, stopped, and destroyed by this method so it cannot be reused. * * @param loader The loader to run synchronously * @return The result from the loader */ public <T> T getLoaderResultSynchronously(final Loader<T> loader) { // The test thread blocks on this queue until the loader puts it's result in final ArrayBlockingQueue<AtomicReference<T>> queue = new ArrayBlockingQueue<AtomicReference<T>>(1); // This callback runs on the "main" thread and unblocks the test thread // when it puts the result into the blocking queue final OnLoadCompleteListener<T> listener = new OnLoadCompleteListener<T>() { @Override public void onLoadComplete(Loader<T> completedLoader, T data) { // Shut the loader down completedLoader.unregisterListener(this); completedLoader.stopLoading(); completedLoader.reset(); // Store the result, unblocking the test thread queue.add(new AtomicReference<T>(data)); } }; // This handler runs on the "main" thread of the process since AsyncTask // is documented as needing to run on the main thread and many Loaders use // AsyncTask final Handler mainThreadHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { loader.registerListener(0, listener); loader.startLoading(); } }; // Ask the main thread to start the loading process mainThreadHandler.sendEmptyMessage(0); // Block on the queue waiting for the result of the load to be inserted T result; while (true) { try { result = queue.take().get(); break; } catch (InterruptedException e) { throw new RuntimeException("waiting thread interrupted", e); } } return result; } public void testInitialLoad() throws UnsupportedEncodingException, GeneralSecurityException, URISyntaxException { // This is tricky. We can't mock the AccountManager easily -- see // https://groups.google.com/d/msg/android-mock/VXyzvKTMUGs/Y26wVPrl50sJ -- // and we don't want to delete any existing accounts on device. So our test // needs to be adaptive (and therefore a little race-prone). final Context context = getApplicationContext(); final AccountLoader loader = new AccountLoader(context); final boolean syncAccountsExist = SyncAccounts.syncAccountsExist(context); final boolean firefoxAccountsExist = FirefoxAccounts.firefoxAccountsExist(context); if (firefoxAccountsExist) { assertFirefoxAccount(getLoaderResultSynchronously(loader)); return; } if (syncAccountsExist) { assertSyncAccount(getLoaderResultSynchronously(loader)); return; } // This account will not get cleaned up in tearDown -- it's a Sync account, // not a Firefox account -- so we must be careful. Account syncAccount = null; try { final SyncAccountParameters syncAccountParameters = new SyncAccountParameters(context, null, TEST_USERNAME, TEST_SYNCKEY, TEST_SYNCPASSWORD, null); syncAccount = SyncAccounts.createSyncAccount(syncAccountParameters, false); assertNotNull(syncAccount); assertSyncAccount(getLoaderResultSynchronously(loader)); } finally { if (syncAccount != null) { TestSyncAccounts.deleteAccount(this, accountManager, syncAccount); } } // This account will get cleaned up in tearDown. final State state = new Separated(TEST_USERNAME, "uid", false); // State choice is arbitrary. final AndroidFxAccount account = AndroidFxAccount.addAndroidAccount(context, TEST_USERNAME, TEST_PROFILE, TEST_AUTH_SERVER_URI, TEST_TOKEN_SERVER_URI, TEST_PROFILE_SERVER_URI, state, AndroidSyncTestCaseWithAccounts.TEST_SYNC_AUTOMATICALLY_MAP_WITH_ALL_AUTHORITIES_DISABLED); assertNotNull(account); assertFirefoxAccount(getLoaderResultSynchronously(loader)); } protected void assertFirefoxAccount(Account account) { assertNotNull(account); assertEquals(FxAccountConstants.ACCOUNT_TYPE, account.type); } protected void assertSyncAccount(Account account) { assertNotNull(account); assertEquals(SyncConstants.ACCOUNTTYPE_SYNC, account.type); } }