/* * Copyright 2017 Google Inc. * * 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.google.firebase; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.MockGoogleCredentials; import com.google.firebase.internal.NonNull; import com.google.firebase.internal.Nullable; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; public class ThreadManagerTest { private ExecutorService executor; @Before public void setUp() { executor = Executors.newSingleThreadExecutor(); } @After public void tearDown() { TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); executor.shutdownNow(); } @Test public void testBareBonesAppLifecycle() { MockThreadManager threadManager = new MockThreadManager(executor); // Initializing an app should initialize the executor. FirebaseApp app = FirebaseApp.initializeApp(buildOptions(threadManager)); assertEquals(1, threadManager.events.size()); Event event = threadManager.events.get(0); assertEquals(Event.TYPE_GET_EXECUTOR, event.type); assertEquals(app, event.app); // Deleting the app should clean up the executor. app.delete(); assertEquals(2, threadManager.events.size()); event = threadManager.events.get(1); assertEquals(Event.TYPE_RELEASE_EXECUTOR, event.type); assertEquals(app, event.app); } @Test public void testBareBonesAppLifecycleWithMultipleApps() { MockThreadManager threadManager = new MockThreadManager(executor); // Initializing an app should initialize the executor. final FirebaseApp app1 = FirebaseApp.initializeApp(buildOptions(threadManager)); assertEquals(1, threadManager.events.size()); Event event = threadManager.events.get(0); assertEquals(Event.TYPE_GET_EXECUTOR, event.type); assertEquals(app1, event.app); final FirebaseApp app2 = FirebaseApp.initializeApp(buildOptions(threadManager), "otherApp"); assertEquals(2, threadManager.events.size()); event = threadManager.events.get(1); assertEquals(Event.TYPE_GET_EXECUTOR, event.type); assertEquals(app2, event.app); // Deleting the app should clean up the executor. app1.delete(); assertEquals(3, threadManager.events.size()); event = threadManager.events.get(2); assertEquals(Event.TYPE_RELEASE_EXECUTOR, event.type); assertEquals(app1, event.app); app2.delete(); assertEquals(4, threadManager.events.size()); event = threadManager.events.get(3); assertEquals(Event.TYPE_RELEASE_EXECUTOR, event.type); assertEquals(app2, event.app); } @Test public void testAppLifecycleWithExecutor() { MockThreadManager threadManager = new MockThreadManager(executor); // Initializing an app should initialize the executor. FirebaseApp app = FirebaseApp.initializeApp(buildOptions(threadManager)); assertEquals(1, threadManager.events.size()); Event event = threadManager.events.get(0); assertEquals(Event.TYPE_GET_EXECUTOR, event.type); assertSame(app, event.app); // Should not re-initialize TestOnlyImplFirebaseTrampolines.forceThreadManagerInit(app); assertEquals(1, threadManager.events.size()); // Deleting app should tear down threading resources. app.delete(); assertEquals(2, threadManager.events.size()); event = threadManager.events.get(1); assertEquals(Event.TYPE_RELEASE_EXECUTOR, event.type); assertSame(app, event.app); assertSame(executor, event.executor); } @Test public void testAppLifecycleWithTokenRefresh() { MockThreadManager threadManager = new MockThreadManager(executor); // Initializing an app should initialize the executor. FirebaseApp app = FirebaseApp.initializeApp(buildOptions(threadManager)); assertEquals(1, threadManager.events.size()); Event event = threadManager.events.get(0); assertEquals(Event.TYPE_GET_EXECUTOR, event.type); assertEquals(app, event.app); // Starting the token refresher should start a long-lived thread using the ThreadFactory. app.startTokenRefresher(); assertEquals(2, threadManager.events.size()); event = threadManager.events.get(1); assertEquals(Event.TYPE_GET_THREAD_FACTORY, event.type); TestOnlyImplFirebaseTrampolines.forceThreadManagerInit(app); assertEquals(2, threadManager.events.size()); // Deleting app should tear down threading resources. app.delete(); assertEquals(3, threadManager.events.size()); event = threadManager.events.get(2); assertEquals(Event.TYPE_RELEASE_EXECUTOR, event.type); assertSame(app, event.app); assertSame(executor, event.executor); } @Test public void testAppLifecycleWithServiceCall() { MockThreadManager threadManager = new MockThreadManager(executor); // Initializing an app should initialize the executor. FirebaseApp app = FirebaseApp.initializeApp(buildOptions(threadManager)); assertEquals(1, threadManager.events.size()); Event event = threadManager.events.get(0); assertEquals(Event.TYPE_GET_EXECUTOR, event.type); assertEquals(app, event.app); // Initializing auth should not initialize any more threading resources. FirebaseAuth auth = FirebaseAuth.getInstance(app); assertEquals(1, threadManager.events.size()); // Calling an async method on auth should not re-initialize threading resources. try { auth.verifyIdTokenAsync("foo").get(); } catch (InterruptedException | ExecutionException ignored) { // ignored } assertEquals(1, threadManager.events.size()); event = threadManager.events.get(0); assertEquals(Event.TYPE_GET_EXECUTOR, event.type); assertSame(app, event.app); // Deleting app should tear down threading resources. app.delete(); assertEquals(2, threadManager.events.size()); event = threadManager.events.get(1); assertEquals(Event.TYPE_RELEASE_EXECUTOR, event.type); assertSame(app, event.app); assertSame(executor, event.executor); } @Test public void testAppLifecycleWithMultipleServiceCalls() { MockThreadManager threadManager = new MockThreadManager(executor); // Initializing an app should initialize the executor. FirebaseApp app = FirebaseApp.initializeApp(buildOptions(threadManager)); assertEquals(1, threadManager.events.size()); Event event = threadManager.events.get(0); assertEquals(Event.TYPE_GET_EXECUTOR, event.type); assertEquals(app, event.app); // Initializing auth should not re-initialize threading resources. FirebaseAuth auth = FirebaseAuth.getInstance(app); assertEquals(1, threadManager.events.size()); // Calling an async method on auth should not re-initialize threading resources. try { auth.verifyIdTokenAsync("foo").get(); } catch (InterruptedException | ExecutionException ignored) { // ignored } assertEquals(1, threadManager.events.size()); event = threadManager.events.get(0); assertEquals(Event.TYPE_GET_EXECUTOR, event.type); assertSame(app, event.app); // Calling a method again should not re-initialize threading resources. try { auth.verifyIdTokenAsync("foo").get(); } catch (InterruptedException | ExecutionException ignored) { // ignored } assertEquals(1, threadManager.events.size()); // Should not re-initialize threading resources. TestOnlyImplFirebaseTrampolines.forceThreadManagerInit(app); assertEquals(1, threadManager.events.size()); // Deleting app should tear down threading resources. app.delete(); assertEquals(2, threadManager.events.size()); event = threadManager.events.get(1); assertEquals(Event.TYPE_RELEASE_EXECUTOR, event.type); assertSame(app, event.app); assertSame(executor, event.executor); } private FirebaseOptions buildOptions(ThreadManager threadManager) { return FirebaseOptions.builder() .setCredentials(new MockGoogleCredentials()) .setProjectId("mock-project-id") .setThreadManager(threadManager) .build(); } private static class MockThreadManager extends ThreadManager { private final List events = new ArrayList<>(); private final ExecutorService executor; MockThreadManager(ExecutorService executor) { this.executor = executor; } @Override protected ExecutorService getExecutor(@NonNull FirebaseApp app) { events.add(new Event(Event.TYPE_GET_EXECUTOR, app, null)); return executor; } @Override protected void releaseExecutor(@NonNull FirebaseApp app, @NonNull ExecutorService executor) { events.add(new Event(Event.TYPE_RELEASE_EXECUTOR, app, executor)); } @Override protected ThreadFactory getThreadFactory() { events.add(new Event(Event.TYPE_GET_THREAD_FACTORY, null, null)); return Executors.defaultThreadFactory(); } } private static class Event { private static final int TYPE_GET_EXECUTOR = 100; private static final int TYPE_RELEASE_EXECUTOR = 101; private static final int TYPE_GET_THREAD_FACTORY = 102; private final int type; private final FirebaseApp app; private ExecutorService executor; Event(int type, @Nullable FirebaseApp app, @Nullable ExecutorService executor) { this.type = type; this.app = app; this.executor = executor; } } }