com.facebook.FacebookActivityTestCase.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.FacebookActivityTestCase.java

Source

/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
 * copy, modify, and distribute this software in source code or binary form for use
 * in connection with the web services and APIs provided by Facebook.
 *
 * As with any software that integrates with the Facebook platform, your use of
 * this software is subject to the Facebook Developer Principles and Policies
 * [http://developers.facebook.com/policy/]. This copyright notice shall be
 * included in all copies or substantial portions of the software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package com.facebook;

import android.app.Activity;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import com.facebook.internal.Utility;
import junit.framework.AssertionFailedError;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

public class FacebookActivityTestCase<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
    private static final String TAG = FacebookActivityTestCase.class.getSimpleName();

    private static String applicationId;
    private static String applicationSecret;
    private static String clientToken;
    private static TestUserManager testUserManager;

    public final static String SECOND_TEST_USER_TAG = "Second";
    public final static String THIRD_TEST_USER_TAG = "Third";

    private TestBlocker testBlocker;

    protected synchronized TestBlocker getTestBlocker() {
        if (testBlocker == null) {
            testBlocker = TestBlocker.createTestBlocker();
        }
        return testBlocker;
    }

    public FacebookActivityTestCase(Class<T> activityClass) {
        super("", activityClass);
    }

    protected String[] getDefaultPermissions() {
        return null;
    };

    protected AccessToken getAccessTokenForSharedUser() {
        return getAccessTokenForSharedUser(null);
    }

    protected AccessToken getAccessTokenForSharedUser(String sessionUniqueUserTag) {
        return getAccessTokenForSharedUserWithPermissions(sessionUniqueUserTag, getDefaultPermissions());
    }

    protected AccessToken getAccessTokenForSharedUserWithPermissions(String sessionUniqueUserTag,
            List<String> permissions) {
        return getTestUserManager().getAccessTokenForSharedUser(permissions, sessionUniqueUserTag);
    }

    protected AccessToken getAccessTokenForSharedUserWithPermissions(String sessionUniqueUserTag,
            String... permissions) {
        List<String> permissionList = (permissions != null) ? Arrays.asList(permissions) : null;
        return getAccessTokenForSharedUserWithPermissions(sessionUniqueUserTag, permissionList);
    }

    protected TestUserManager getTestUserManager() {
        if (testUserManager == null) {
            synchronized (FacebookActivityTestCase.class) {
                if (testUserManager == null) {
                    readApplicationIdAndSecret();
                    testUserManager = new TestUserManager(applicationSecret, applicationId);
                }
            }
        }

        return testUserManager;
    }

    // Turns exceptions from the TestBlocker into JUnit assertions
    protected void waitAndAssertSuccess(TestBlocker testBlocker, int numSignals) {
        try {
            testBlocker.waitForSignalsAndAssertSuccess(numSignals);
        } catch (AssertionFailedError e) {
            throw e;
        } catch (Exception e) {
            fail("Got exception: " + e.getMessage());
        }
    }

    protected void waitAndAssertSuccess(int numSignals) {
        waitAndAssertSuccess(getTestBlocker(), numSignals);
    }

    protected void waitAndAssertSuccessOrRethrow(int numSignals) throws Exception {
        getTestBlocker().waitForSignalsAndAssertSuccess(numSignals);
    }

    protected void runAndBlockOnUiThread(final int expectedSignals, final Runnable runnable) throws Throwable {
        final TestBlocker blocker = getTestBlocker();
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                runnable.run();
                blocker.signal();
            }
        });
        // We wait for the operation to complete; wait for as many other signals as we expect.
        blocker.waitForSignals(1 + expectedSignals);
        // Wait for the UI thread to become idle so any UI updates the runnable triggered have a chance
        // to finish before we return.
        getInstrumentation().waitForIdleSync();
    }

    protected synchronized void readApplicationIdAndSecret() {
        synchronized (FacebookTestCase.class) {
            if (applicationId != null && applicationSecret != null && clientToken != null) {
                return;
            }

            AssetManager assets = getInstrumentation().getTargetContext().getResources().getAssets();
            InputStream stream = null;
            final String errorMessage = "could not read applicationId and applicationSecret from config.json; ensure "
                    + "you have run 'configure_unit_tests.sh'. Error: ";
            try {
                stream = assets.open("config.json");
                String string = Utility.readStreamToString(stream);

                JSONTokener tokener = new JSONTokener(string);
                Object obj = tokener.nextValue();
                if (!(obj instanceof JSONObject)) {
                    fail(errorMessage + "could not deserialize a JSONObject");
                }
                JSONObject jsonObject = (JSONObject) obj;

                applicationId = jsonObject.optString("applicationId");
                applicationSecret = jsonObject.optString("applicationSecret");
                clientToken = jsonObject.optString("clientToken");

                if (Utility.isNullOrEmpty(applicationId) || Utility.isNullOrEmpty(applicationSecret)
                        || Utility.isNullOrEmpty(clientToken)) {
                    fail(errorMessage + "config values are missing");
                }
            } catch (IOException e) {
                fail(errorMessage + e.toString());
            } catch (JSONException e) {
                fail(errorMessage + e.toString());
            } finally {
                if (stream != null) {
                    try {
                        stream.close();
                    } catch (IOException e) {
                        fail(errorMessage + e.toString());
                    }
                }
            }
        }
    }

    protected static String getApplicationId() {
        return applicationId;
    }

    protected static String getApplicationSecret() {
        return applicationSecret;
    }

    protected void setUp() throws Exception {
        super.setUp();

        // Make sure the logging is turned on.
        FacebookSdk.setIsDebugEnabled(true);

        // Make sure we have read application ID and secret.
        readApplicationIdAndSecret();

        FacebookSdk.sdkInitialize(getInstrumentation().getTargetContext());
        FacebookSdk.setApplicationId(applicationId);
        FacebookSdk.setClientToken(clientToken);

        // These are useful for debugging unit test failures.
        FacebookSdk.addLoggingBehavior(LoggingBehavior.REQUESTS);
        FacebookSdk.addLoggingBehavior(LoggingBehavior.INCLUDE_ACCESS_TOKENS);

        // We want the UI thread to be in StrictMode to catch any violations.
        // TODO: reenable this
        // turnOnStrictModeForUiThread();

        // Needed to bypass a dexmaker bug for mockito
        System.setProperty("dexmaker.dexcache", getInstrumentation().getTargetContext().getCacheDir().getPath());
    }

    protected void tearDown() throws Exception {
        super.tearDown();

        synchronized (this) {
            if (testBlocker != null) {
                testBlocker.quit();
            }
        }
    }

    protected Bundle getNativeLinkingExtras(String token, String userId) {
        readApplicationIdAndSecret();

        Bundle extras = new Bundle();
        String extraLaunchUriString = String.format(
                "fbrpc://facebook/nativethirdparty?app_id=%s&package_name=com.facebook.sdk.tests&class_name=com.facebook.FacebookActivityTests$FacebookTestActivity&access_token=%s",
                applicationId, token);
        extras.putString("extra_launch_uri", extraLaunchUriString);
        extras.putString("expires_in", "3600");
        extras.putLong("app_id", Long.parseLong(applicationId));
        extras.putString("access_token", token);
        if (userId != null && !userId.isEmpty()) {
            extras.putString("user_id", userId);
        }

        return extras;
    }

    protected JSONObject getAndAssert(AccessToken accessToken, String id) {
        Bundle parameters = new Bundle();
        parameters.putString("fields", "message");

        GraphRequest request = new GraphRequest(accessToken, id, parameters, null);
        GraphResponse response = request.executeAndWait();
        assertNotNull(response);

        assertNull(response.getError());

        JSONObject result = response.getJSONObject();
        assertNotNull(result);

        return result;
    }

    protected JSONObject postGetAndAssert(AccessToken accessToken, String path, JSONObject graphObject) {
        GraphRequest request = GraphRequest.newPostRequest(accessToken, path, graphObject, null);
        GraphResponse response = request.executeAndWait();
        assertNotNull(response);

        assertNull(response.getError());

        JSONObject result = response.getJSONObject();
        assertNotNull(result);
        assertNotNull(result.optString("id"));

        return getAndAssert(accessToken, result.optString("id"));
    }

    protected void setBatchApplicationIdForTestApp() {
        readApplicationIdAndSecret();
        GraphRequest.setDefaultBatchApplicationId(applicationId);
    }

    protected JSONObject batchCreateAndGet(AccessToken accessToken, String graphPath, JSONObject graphObject,
            String fields) {
        GraphRequest create = GraphRequest.newPostRequest(accessToken, graphPath, graphObject,
                new ExpectSuccessCallback());
        create.setBatchEntryName("create");
        GraphRequest get = GraphRequest.newGraphPathRequest(accessToken, "{result=create:$.id}",
                new ExpectSuccessCallback());
        if (fields != null) {
            Bundle parameters = new Bundle();
            parameters.putString("fields", fields);
            get.setParameters(parameters);
        }

        return batchPostAndGet(create, get);
    }

    protected JSONObject batchUpdateAndGet(AccessToken accessToken, String graphPath, JSONObject graphObject,
            String fields) {
        GraphRequest update = GraphRequest.newPostRequest(accessToken, graphPath, graphObject,
                new ExpectSuccessCallback());
        GraphRequest get = GraphRequest.newGraphPathRequest(accessToken, graphPath, new ExpectSuccessCallback());
        if (fields != null) {
            Bundle parameters = new Bundle();
            parameters.putString("fields", fields);
            get.setParameters(parameters);
        }

        return batchPostAndGet(update, get);
    }

    protected JSONObject batchPostAndGet(GraphRequest post, GraphRequest get) {
        List<GraphResponse> responses = GraphRequest.executeBatchAndWait(post, get);
        assertEquals(2, responses.size());

        JSONObject resultGraphObject = responses.get(1).getJSONObject();
        assertNotNull(resultGraphObject);
        return resultGraphObject;
    }

    protected JSONObject createStatusUpdate(String unique) {
        JSONObject statusUpdate = new JSONObject();
        String message = String.format(
                "Check out my awesome new status update posted at: %s. Some chars for you: +\"[]:,%s", new Date(),
                unique);
        try {
            statusUpdate.put("message", message);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        return statusUpdate;
    }

    protected Bitmap createTestBitmap(int size) {
        Bitmap image = Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565);
        image.eraseColor(Color.BLUE);
        return image;
    }

    protected void assertDateEqualsWithinDelta(Date expected, Date actual, long deltaInMsec) {
        long delta = Math.abs(expected.getTime() - actual.getTime());
        assertTrue(delta < deltaInMsec);
    }

    protected void assertDateDiffersWithinDelta(Date expected, Date actual, long expectedDifference,
            long deltaInMsec) {
        long delta = Math.abs(expected.getTime() - actual.getTime()) - expectedDifference;
        assertTrue(delta < deltaInMsec);
    }

    protected void assertNoErrors(List<GraphResponse> responses) {
        for (int i = 0; i < responses.size(); ++i) {
            GraphResponse response = responses.get(i);
            assertNotNull(response);
            assertNull(response.getError());
        }
    }

    protected File createTempFileFromAsset(String assetPath) throws IOException {
        InputStream inputStream = null;
        FileOutputStream outStream = null;

        try {
            AssetManager assets = getActivity().getResources().getAssets();
            inputStream = assets.open(assetPath);

            File outputDir = getActivity().getCacheDir(); // context being the Activity pointer
            File outputFile = File.createTempFile("prefix", assetPath, outputDir);
            outStream = new FileOutputStream(outputFile);

            final int bufferSize = 1024 * 2;
            byte[] buffer = new byte[bufferSize];
            int n = 0;
            while ((n = inputStream.read(buffer)) != -1) {
                outStream.write(buffer, 0, n);
            }

            return outputFile;
        } finally {
            Utility.closeQuietly(outStream);
            Utility.closeQuietly(inputStream);
        }
    }

    protected void runOnBlockerThread(final Runnable runnable, boolean waitForCompletion) {
        Runnable runnableToPost = runnable;
        final ConditionVariable condition = waitForCompletion ? new ConditionVariable(!waitForCompletion) : null;

        if (waitForCompletion) {
            runnableToPost = new Runnable() {
                @Override
                public void run() {
                    runnable.run();
                    condition.open();
                }
            };
        }

        TestBlocker blocker = getTestBlocker();
        Handler handler = blocker.getHandler();
        handler.post(runnableToPost);

        if (waitForCompletion) {
            boolean success = condition.block(10000);
            assertTrue(success);
        }
    }

    protected void closeBlockerAndAssertSuccess() {
        TestBlocker blocker;
        synchronized (this) {
            blocker = getTestBlocker();
            testBlocker = null;
        }

        blocker.quit();

        boolean joined = false;
        while (!joined) {
            try {
                blocker.join();
                joined = true;
            } catch (InterruptedException e) {
            }
        }

        try {
            blocker.assertSuccess();
        } catch (Exception e) {
            fail(e.toString());
        }
    }

    protected TestGraphRequestAsyncTask createAsyncTaskOnUiThread(final GraphRequest... requests) throws Throwable {
        final ArrayList<TestGraphRequestAsyncTask> result = new ArrayList<TestGraphRequestAsyncTask>();
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                result.add(new TestGraphRequestAsyncTask(requests));
            }
        });
        return result.isEmpty() ? null : result.get(0);
    }

    /*
     * Classes and helpers related to asynchronous requests.
     */

    // A subclass of RequestAsyncTask that knows how to interact with TestBlocker to ensure that tests can wait
    // on and assert success of async tasks.
    protected class TestGraphRequestAsyncTask extends GraphRequestAsyncTask {
        private final TestBlocker blocker = FacebookActivityTestCase.this.getTestBlocker();

        public TestGraphRequestAsyncTask(GraphRequest... requests) {
            super(requests);
        }

        public TestGraphRequestAsyncTask(List<GraphRequest> requests) {
            super(requests);
        }

        public TestGraphRequestAsyncTask(GraphRequestBatch requests) {
            super(requests);
        }

        public TestGraphRequestAsyncTask(HttpURLConnection connection, GraphRequest... requests) {
            super(connection, requests);
        }

        public TestGraphRequestAsyncTask(HttpURLConnection connection, List<GraphRequest> requests) {
            super(connection, requests);
        }

        public TestGraphRequestAsyncTask(HttpURLConnection connection, GraphRequestBatch requests) {
            super(connection, requests);
        }

        public final TestBlocker getBlocker() {
            return blocker;
        }

        public final Exception getThrowable() {
            return getException();
        }

        protected void onPostExecute(List<GraphResponse> result) {
            try {
                super.onPostExecute(result);

                if (getException() != null) {
                    blocker.setException(getException());
                }
            } finally {
                Log.d("TestRequestAsyncTask", "signaling blocker");
                blocker.signal();
            }
        }

        // In order to be able to block and accumulate exceptions, we want to ensure the async task is really
        // being started on the blocker's thread, rather than the test's thread. Use this instead of calling
        // execute directly in unit tests.
        public void executeOnBlockerThread() {
            ensureAsyncTaskLoaded();

            Runnable runnable = new Runnable() {
                public void run() {
                    execute();
                }
            };
            Handler handler = new Handler(blocker.getLooper());
            handler.post(runnable);
        }

        private void ensureAsyncTaskLoaded() {
            // Work around this issue on earlier frameworks: http://stackoverflow.com/a/7818839/782044
            try {
                runAndBlockOnUiThread(0, new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Class.forName("android.os.AsyncTask");
                        } catch (ClassNotFoundException e) {
                        }
                    }
                });
            } catch (Throwable throwable) {
            }
        }
    }

    // Provides an implementation of Request.Callback that will assert either success (no error) or failure (error)
    // of a request, and allow derived classes to perform additional asserts.
    protected class TestCallback implements GraphRequest.Callback {
        private final TestBlocker blocker;
        private final boolean expectSuccess;

        public TestCallback(TestBlocker blocker, boolean expectSuccess) {
            this.blocker = blocker;
            this.expectSuccess = expectSuccess;
        }

        public TestCallback(boolean expectSuccess) {
            this(FacebookActivityTestCase.this.getTestBlocker(), expectSuccess);
        }

        @Override
        public void onCompleted(GraphResponse response) {
            try {
                // We expect to be called on the right thread.
                if (Thread.currentThread() != blocker) {
                    throw new FacebookException("Invalid thread " + Thread.currentThread().getId()
                            + "; expected to be called on thread " + blocker.getId());
                }

                // We expect either success or failure.
                if (expectSuccess && response.getError() != null) {
                    throw response.getError().getException();
                } else if (!expectSuccess && response.getError() == null) {
                    throw new FacebookException("Expected failure case, received no error");
                }

                // Some tests may want more fine-grained control and assert additional conditions.
                performAsserts(response);
            } catch (Exception e) {
                blocker.setException(e);
            } finally {
                // Tell anyone waiting on us that this callback was called.
                blocker.signal();
            }
        }

        protected void performAsserts(GraphResponse response) {
        }
    }

    // A callback that will assert if the request resulted in an error.
    protected class ExpectSuccessCallback extends TestCallback {
        public ExpectSuccessCallback() {
            super(true);
        }
    }

    // A callback that will assert if the request did NOT result in an error.
    protected class ExpectFailureCallback extends TestCallback {
        public ExpectFailureCallback() {
            super(false);
        }
    }

    public static abstract class MockGraphRequest extends GraphRequest {
        public abstract GraphResponse createResponse();
    }

    public static class MockGraphRequestBatch extends GraphRequestBatch {
        public MockGraphRequestBatch(MockGraphRequest... requests) {
            super(requests);
        }

        // Caller must ensure that all the requests in the batch are, in fact, MockRequests.
        public MockGraphRequestBatch(GraphRequestBatch requests) {
            super(requests);
        }

        @Override
        List<GraphResponse> executeAndWaitImpl() {
            List<GraphRequest> requests = getRequests();

            List<GraphResponse> responses = new ArrayList<GraphResponse>();
            for (GraphRequest request : requests) {
                MockGraphRequest mockRequest = (MockGraphRequest) request;
                responses.add(mockRequest.createResponse());
            }

            GraphRequest.runCallbacks(this, responses);

            return responses;
        }
    }

    private AtomicBoolean strictModeOnForUiThread = new AtomicBoolean();

    protected void turnOnStrictModeForUiThread() {
        // We only ever need to do this once. If the boolean is true, we know that the next runnable
        // posted to the UI thread will have strict mode on.
        if (strictModeOnForUiThread.get() == false) {
            try {
                runTestOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // Double-check whether we really need to still do this on the UI thread.
                        if (strictModeOnForUiThread.compareAndSet(false, true)) {
                            turnOnStrictModeForThisThread();
                        }
                    }
                });
            } catch (Throwable throwable) {
            }
        }
    }

    protected void turnOnStrictModeForThisThread() {
        // We use reflection, because Instrumentation will complain about any references to
        // StrictMode in API versions < 9 when attempting to run the unit tests. No particular
        // effort has been made to make this efficient, since we expect to call it just once.
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            Class<?> strictModeClass = Class.forName("android.os.StrictMode", true, loader);
            Class<?> threadPolicyClass = Class.forName("android.os.StrictMode$ThreadPolicy", true, loader);
            Class<?> threadPolicyBuilderClass = Class.forName("android.os.StrictMode$ThreadPolicy$Builder", true,
                    loader);

            Object threadPolicyBuilder = threadPolicyBuilderClass.getConstructor().newInstance();
            threadPolicyBuilder = threadPolicyBuilderClass.getMethod("detectAll").invoke(threadPolicyBuilder);
            threadPolicyBuilder = threadPolicyBuilderClass.getMethod("penaltyDeath").invoke(threadPolicyBuilder);

            Object threadPolicy = threadPolicyBuilderClass.getMethod("build").invoke(threadPolicyBuilder);
            strictModeClass.getMethod("setThreadPolicy", threadPolicyClass).invoke(strictModeClass, threadPolicy);
        } catch (Exception ex) {
        }
    }
}