diff --git a/src/main/java/com/google/firebase/FirebaseApp.java b/src/main/java/com/google/firebase/FirebaseApp.java
index 2dd21ee50..956167840 100644
--- a/src/main/java/com/google/firebase/FirebaseApp.java
+++ b/src/main/java/com/google/firebase/FirebaseApp.java
@@ -2,19 +2,19 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.io.BaseEncoding;
import com.google.firebase.internal.AuthStateListener;
import com.google.firebase.internal.FirebaseAppStore;
import com.google.firebase.internal.FirebaseExecutors;
+import com.google.firebase.internal.FirebaseService;
import com.google.firebase.internal.GetTokenResult;
-import com.google.firebase.internal.GuardedBy;
import com.google.firebase.internal.Joiner;
import com.google.firebase.internal.NonNull;
import com.google.firebase.internal.Nullable;
@@ -29,7 +29,6 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
-import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -37,10 +36,10 @@
/**
* The entry point of Firebase SDKs. It holds common configuration and state for Firebase APIs. Most
- * applications don't need to directly interact with FirebaseApp. *
+ * applications don't need to directly interact with FirebaseApp.
*
*
Firebase APIs use the default FirebaseApp by default, unless a different one is explicitly
- * passed to the API via FirebaseFoo.getInstance(firebaseApp). *
+ * passed to the API via FirebaseFoo.getInstance(firebaseApp).
*
*
{@link FirebaseApp#initializeApp(FirebaseOptions)} initializes the default app instance. This
* method should be invoked at startup.
@@ -48,27 +47,32 @@
public class FirebaseApp {
/** A map of (name, FirebaseApp) instances. */
- @GuardedBy("sLock")
private static final Map instances = new HashMap<>();
public static final String DEFAULT_APP_NAME = "[DEFAULT]";
private static final long TOKEN_REFRESH_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(55);
private static final TokenRefresher.Factory DEFAULT_TOKEN_REFRESHER_FACTORY =
new TokenRefresher.Factory();
- private static final Object sLock = new Object();
+
+ /**
+ * Global lock for synchronizing all SDK-wide application state changes. Specifically, any
+ * accesses to instances map should be protected by this lock.
+ */
+ private static final Object appsLock = new Object();
private final String name;
private final FirebaseOptions options;
private final TokenRefresher tokenRefresher;
private final AtomicBoolean deleted = new AtomicBoolean();
-
- private final List lifecycleListeners =
- new CopyOnWriteArrayList<>();
-
private final List authStateListeners = new ArrayList<>();
-
private final AtomicReference currentToken = new AtomicReference<>();
+ private final Map services = new HashMap<>();
+
+ /**
+ * Per application lock for synchronizing all internal FirebaseApp state changes.
+ */
+ private final Object lock = new Object();
/** Default constructor. */
private FirebaseApp(String name, FirebaseOptions options, TokenRefresher.Factory factory) {
@@ -81,7 +85,9 @@ private FirebaseApp(String name, FirebaseOptions options, TokenRefresher.Factory
/** Returns a mutable list of all FirebaseApps. */
public static List getApps() {
// TODO(arondeak): reenable persistence. See b/28158809.
- return new ArrayList<>(instances.values());
+ synchronized (appsLock) {
+ return ImmutableList.copyOf(instances.values());
+ }
}
/**
@@ -103,7 +109,7 @@ public static FirebaseApp getInstance() {
* #initializeApp(FirebaseOptions, String)} or {@link #getApps()}.
*/
public static FirebaseApp getInstance(@NonNull String name) {
- synchronized (sLock) {
+ synchronized (appsLock) {
FirebaseApp firebaseApp = instances.get(normalize(name));
if (firebaseApp != null) {
return firebaseApp;
@@ -154,8 +160,8 @@ static FirebaseApp initializeApp(
FirebaseAppStore appStore = FirebaseAppStore.initialize();
String normalizedName = normalize(name);
final FirebaseApp firebaseApp;
- synchronized (sLock) {
- Preconditions.checkState(
+ synchronized (appsLock) {
+ checkState(
!instances.containsKey(normalizedName),
"FirebaseApp name " + normalizedName + " already exists!");
@@ -170,8 +176,12 @@ static FirebaseApp initializeApp(
@VisibleForTesting
static void clearInstancesForTest() {
- // TODO(arondeak): also delete, once functionality is implemented.
- synchronized (sLock) {
+ synchronized (appsLock) {
+ // Copy the instances list before iterating, as delete() would attempt to remove from the
+ // original list.
+ for (FirebaseApp app : ImmutableList.copyOf(instances.values())) {
+ app.delete();
+ }
instances.clear();
}
}
@@ -191,7 +201,7 @@ String getPersistenceKey() {
private static List getAllAppNames() {
Set allAppNames = new HashSet<>();
- synchronized (sLock) {
+ synchronized (appsLock) {
for (FirebaseApp app : instances.values()) {
allAppNames.add(app.getName());
}
@@ -250,27 +260,33 @@ public String toString() {
*
* A no-op if delete was called before.
*/
- void delete() {
- boolean valueChanged = deleted.compareAndSet(false /* expected */, true);
- if (!valueChanged) {
- return;
+ public void delete() {
+ synchronized (lock) {
+ boolean valueChanged = deleted.compareAndSet(false /* expected */, true);
+ if (!valueChanged) {
+ return;
+ }
+
+ for (FirebaseService service : services.values()) {
+ service.destroy();
+ }
+ services.clear();
+ authStateListeners.clear();
+ tokenRefresher.cleanup();
}
- tokenRefresher.cleanup();
- synchronized (sLock) {
- instances.remove(this.name);
+ synchronized (appsLock) {
+ instances.remove(name);
}
FirebaseAppStore appStore = FirebaseAppStore.getInstance();
if (appStore != null) {
appStore.removeApp(name);
}
-
- notifyOnAppDeleted();
}
private void checkNotDeleted() {
- Preconditions.checkState(!deleted.get(), "FirebaseApp was deleted");
+ checkState(!deleted.get(), "FirebaseApp was deleted %s", this);
}
/**
@@ -293,10 +309,14 @@ public GetTokenResult then(@NonNull Task task) throws Exception {
GetTokenResult oldToken = currentToken.get();
List listenersCopy = null;
if (!newToken.equals(oldToken)) {
- synchronized (authStateListeners) {
+ synchronized (lock) {
+ if (deleted.get()) {
+ return newToken;
+ }
+
// Grab the lock before compareAndSet to avoid a potential race
- // condition
- // with addAuthStateListener
+ // condition with addAuthStateListener. The same lock also ensures serial
+ // access to the token refresher.
if (currentToken.compareAndSet(oldToken, newToken)) {
listenersCopy = ImmutableList.copyOf(authStateListeners);
tokenRefresher.scheduleRefresh(TOKEN_REFRESH_INTERVAL_MILLIS);
@@ -318,55 +338,48 @@ boolean isDefaultApp() {
return DEFAULT_APP_NAME.equals(getName());
}
- /**
- * If an API has locally stored data it must register lifecycle listeners at initialization time.
- */
- // TODO(arondeak): make sure that all APIs that are interested in these events are
- // initialized using reflection when an app is deleted (for v5).
- void addLifecycleEventListener(@NonNull FirebaseAppLifecycleListener listener) {
- checkNotDeleted();
- lifecycleListeners.add(checkNotNull(listener));
- }
-
- void removeLifecycleEventListener(@NonNull FirebaseAppLifecycleListener listener) {
- checkNotDeleted();
- lifecycleListeners.remove(checkNotNull(listener));
- }
-
void addAuthStateListener(@NonNull final AuthStateListener listener) {
- checkNotDeleted();
- checkNotNull(listener);
-
GetTokenResult currentToken;
- synchronized (authStateListeners) {
- authStateListeners.add(listener);
+ synchronized (lock) {
+ checkNotDeleted();
+ authStateListeners.add(checkNotNull(listener));
currentToken = this.currentToken.get();
}
if (currentToken != null) {
- // Task has copied the mAuthStateListeners before the listener was added.
+ // Task has copied the authStateListeners before the listener was added.
// Notify this listener explicitly.
listener.onAuthStateChanged(currentToken);
}
}
void removeAuthStateListener(@NonNull AuthStateListener listener) {
- checkNotDeleted();
- checkNotNull(listener);
- synchronized (authStateListeners) {
- authStateListeners.remove(listener);
+ synchronized (lock) {
+ checkNotDeleted();
+ authStateListeners.remove(checkNotNull(listener));
}
}
- /**
- * Notifies all listeners with the name and options of the deleted {@link FirebaseApp} instance.
- */
- private void notifyOnAppDeleted() {
- for (FirebaseAppLifecycleListener listener : lifecycleListeners) {
- listener.onDeleted(name, options);
+ void addService(FirebaseService service) {
+ synchronized (lock) {
+ checkNotDeleted();
+ checkArgument(!services.containsKey(checkNotNull(service).getId()));
+ services.put(service.getId(), service);
}
}
+ FirebaseService getService(String id) {
+ synchronized (lock) {
+ checkArgument(!Strings.isNullOrEmpty(id));
+ return services.get(id);
+ }
+ }
+
+ /**
+ * Utility class for scheduling proactive token refresh events. Each FirebaseApp should have
+ * its own instance of this class. This class is not thread safe. The caller (FirebaseApp) must
+ * ensure that methods are called serially.
+ */
static class TokenRefresher {
private final FirebaseApp firebaseApp;
@@ -382,7 +395,7 @@ static class TokenRefresher {
* @param delayMillis Duration in milliseconds, after which the token should be forcibly
* refreshed.
*/
- final synchronized void scheduleRefresh(long delayMillis) {
+ final void scheduleRefresh(long delayMillis) {
cancelPrevious();
scheduleNext(
new Callable>() {
@@ -410,14 +423,13 @@ protected void scheduleNext(Callable> task, long delayMilli
}
}
- protected synchronized void cleanup() {
+ protected void cleanup() {
if (future != null) {
future.cancel(true);
}
}
static class Factory {
-
TokenRefresher create(FirebaseApp app) {
return new TokenRefresher(app);
}
diff --git a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java
index 3761a2b4a..62c22747a 100644
--- a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java
+++ b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java
@@ -2,6 +2,7 @@
import com.google.firebase.auth.FirebaseCredential;
import com.google.firebase.internal.AuthStateListener;
+import com.google.firebase.internal.FirebaseService;
import com.google.firebase.internal.GetTokenResult;
import com.google.firebase.internal.NonNull;
import com.google.firebase.tasks.Task;
@@ -31,16 +32,6 @@ public static String getPersistenceKey(String name, FirebaseOptions options) {
return FirebaseApp.getPersistenceKey(name, options);
}
- public static void addLifecycleEventListener(
- @NonNull FirebaseApp app, @NonNull FirebaseAppLifecycleListener listener) {
- app.addLifecycleEventListener(listener);
- }
-
- public static void removeLifecycleEventListener(
- @NonNull FirebaseApp app, @NonNull FirebaseAppLifecycleListener listener) {
- app.removeLifecycleEventListener(listener);
- }
-
public static void addAuthStateChangeListener(
@NonNull FirebaseApp app, @NonNull AuthStateListener listener) {
app.addAuthStateListener(listener);
@@ -54,4 +45,15 @@ public static void removeAuthStateChangeListener(
public static Task getToken(@NonNull FirebaseApp app, boolean forceRefresh) {
return app.getToken(forceRefresh);
}
+
+ public static T getService(
+ @NonNull FirebaseApp app, @NonNull String id, @NonNull Class type) {
+ return type.cast(app.getService(id));
+ }
+
+ public static T addService(
+ @NonNull FirebaseApp app, @NonNull T service) {
+ app.addService(service);
+ return service;
+ }
}
diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuth.java b/src/main/java/com/google/firebase/auth/FirebaseAuth.java
index 1136423cd..f54d43875 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java
@@ -11,12 +11,12 @@
import com.google.firebase.ImplFirebaseTrampolines;
import com.google.firebase.auth.internal.FirebaseTokenFactory;
import com.google.firebase.auth.internal.FirebaseTokenVerifier;
+import com.google.firebase.internal.FirebaseService;
import com.google.firebase.internal.NonNull;
import com.google.firebase.tasks.Continuation;
import com.google.firebase.tasks.Task;
import com.google.firebase.tasks.Tasks;
-import java.util.HashMap;
import java.util.Map;
/**
@@ -31,11 +31,6 @@ public class FirebaseAuth {
/** A global, thread-safe Json Factory built using Gson. */
private static final JsonFactory jsonFactory = new GsonFactory();
- /**
- * A static map of FirebaseApp name to FirebaseAuth instance. To ensure thread- safety, it should
- * only be accessed in getInstance(), which is a synchronized method.
- */
- private static Map authInstances = new HashMap<>();
private final FirebaseApp firebaseApp;
private final GooglePublicKeysManager googlePublicKeysManager;
@@ -73,11 +68,12 @@ public static FirebaseAuth getInstance() {
* @return A FirebaseAuth instance.
*/
public static synchronized FirebaseAuth getInstance(FirebaseApp app) {
- if (!authInstances.containsKey(app.getName())) {
- authInstances.put(app.getName(), new FirebaseAuth(app));
+ FirebaseAuthService service = ImplFirebaseTrampolines.getService(app, SERVICE_ID,
+ FirebaseAuthService.class);
+ if (service == null) {
+ service = ImplFirebaseTrampolines.addService(app, new FirebaseAuthService(app));
}
-
- return authInstances.get(app.getName());
+ return service.getInstance();
}
/**
@@ -182,4 +178,20 @@ public FirebaseToken then(@NonNull Task task) throws Exception {
}
});
}
+
+ private static final String SERVICE_ID = FirebaseAuth.class.getName();
+
+ private static class FirebaseAuthService extends FirebaseService {
+
+ FirebaseAuthService(FirebaseApp app) {
+ super(SERVICE_ID, new FirebaseAuth(app));
+ }
+
+ @Override
+ public void destroy() {
+ // NOTE: We don't explicitly tear down anything here, but public methods of FirebaseAuth
+ // will now fail because calls to getCredential() will hit FirebaseApp.getOptions() which
+ // will throw once the app is deleted.
+ }
+ }
}
diff --git a/src/main/java/com/google/firebase/database/FirebaseDatabase.java b/src/main/java/com/google/firebase/database/FirebaseDatabase.java
index 68687da81..02b07f173 100644
--- a/src/main/java/com/google/firebase/database/FirebaseDatabase.java
+++ b/src/main/java/com/google/firebase/database/FirebaseDatabase.java
@@ -1,6 +1,7 @@
package com.google.firebase.database;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
@@ -13,12 +14,15 @@
import com.google.firebase.database.utilities.ParsedUrl;
import com.google.firebase.database.utilities.Utilities;
import com.google.firebase.database.utilities.Validation;
+import com.google.firebase.internal.FirebaseService;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* The entry point for accessing a Firebase Database. You can get an instance by calling {@link
@@ -30,22 +34,17 @@ public class FirebaseDatabase {
private static final String ADMIN_SDK_PROPERTIES = "admin_sdk.properties";
private static final String SDK_VERSION = loadSdkVersion();
- /**
- * A static map of FirebaseApp and RepoInfo to FirebaseDatabase instance. To ensure thread-
- * safety, it should only be accessed in getInstance(), which is a synchronized method.
- *
- * TODO(mikelehen): This serves a duplicate purpose as RepoManager. We should clean up.
- * TODO(mikelehen): We should maybe be conscious of leaks and make this a weak map or similar but
- * we have a lot of work to do to allow FirebaseDatabase/Repo etc. to be GC'd.
- */
- private static final Map>
- databaseInstances = new HashMap<>();
-
private final FirebaseApp app;
private final RepoInfo repoInfo;
private final DatabaseConfig config;
private Repo repo; // Usage must be guarded by a call to ensureRepo().
+ private final AtomicBoolean destroyed = new AtomicBoolean(false);
+
+ // Lock for synchronizing internal state changes. Protects accesses to repo and destroyed
+ // members.
+ private final Object lock = new Object();
+
private FirebaseDatabase(FirebaseApp app, RepoInfo repoInfo, DatabaseConfig config) {
this.app = app;
this.repoInfo = repoInfo;
@@ -97,19 +96,19 @@ public static FirebaseDatabase getInstance(FirebaseApp app) {
* @return A FirebaseDatabase instance.
*/
public static synchronized FirebaseDatabase getInstance(FirebaseApp app, String url) {
+ FirebaseDatabaseService service = ImplFirebaseTrampolines.getService(app, SERVICE_ID,
+ FirebaseDatabaseService.class);
+ if (service == null) {
+ service = ImplFirebaseTrampolines.addService(app, new FirebaseDatabaseService());
+ }
+
+ DatabaseInstances dbInstances = service.getInstance();
if (url == null || url.isEmpty()) {
throw new DatabaseException(
"Failed to get FirebaseDatabase instance: Specify DatabaseURL within "
+ "FirebaseApp or from your getInstance() call.");
}
- Map instances = databaseInstances.get(app.getName());
-
- if (instances == null) {
- instances = new HashMap<>();
- databaseInstances.put(app.getName(), instances);
- }
-
ParsedUrl parsedUrl = Utilities.parseUrl(url);
if (!parsedUrl.path.isEmpty()) {
throw new DatabaseException(
@@ -120,8 +119,7 @@ public static synchronized FirebaseDatabase getInstance(FirebaseApp app, String
+ parsedUrl.path.toString());
}
- FirebaseDatabase database = instances.get(parsedUrl.repoInfo);
-
+ FirebaseDatabase database = dbInstances.get(parsedUrl.repoInfo);
if (database == null) {
DatabaseConfig config = new DatabaseConfig();
// If this is the default app, don't set the session persistence key so that we use our
@@ -133,7 +131,7 @@ public static synchronized FirebaseDatabase getInstance(FirebaseApp app, String
config.setFirebaseApp(app);
database = new FirebaseDatabase(app, parsedUrl.repoInfo, config);
- instances.put(parsedUrl.repoInfo, database);
+ dbInstances.put(parsedUrl.repoInfo, database);
}
return database;
@@ -169,8 +167,7 @@ public FirebaseApp getApp() {
* @return A DatabaseReference pointing to the root node.
*/
public DatabaseReference getReference() {
- ensureRepo();
- return new DatabaseReference(this.repo, Path.getEmptyPath());
+ return new DatabaseReference(ensureRepo(), Path.getEmptyPath());
}
/**
@@ -180,16 +177,11 @@ public DatabaseReference getReference() {
* @return A DatabaseReference pointing to the specified path.
*/
public DatabaseReference getReference(String path) {
- ensureRepo();
-
- if (path == null) {
- throw new NullPointerException(
- "Can't pass null for argument 'pathString' in " + "FirebaseDatabase.getReference()");
- }
+ checkNotNull(path,
+ "Can't pass null for argument 'pathString' in FirebaseDatabase.getReference()");
Validation.validateRootPathString(path);
-
Path childPath = new Path(path);
- return new DatabaseReference(this.repo, childPath);
+ return new DatabaseReference(ensureRepo(), childPath);
}
/**
@@ -202,15 +194,11 @@ public DatabaseReference getReference(String path) {
* @return A DatabaseReference for the provided URL.
*/
public DatabaseReference getReferenceFromUrl(String url) {
- ensureRepo();
-
- if (url == null) {
- throw new NullPointerException(
- "Can't pass null for argument 'url' in " + "FirebaseDatabase.getReferenceFromUrl()");
- }
-
+ checkNotNull(url,
+ "Can't pass null for argument 'url' in FirebaseDatabase.getReferenceFromUrl()");
ParsedUrl parsedUrl = Utilities.parseUrl(url);
- if (!parsedUrl.repoInfo.host.equals(this.repo.getRepoInfo().host)) {
+ Repo repo = ensureRepo();
+ if (!parsedUrl.repoInfo.host.equals(repo.getRepoInfo().host)) {
throw new DatabaseException(
"Invalid URL ("
+ url
@@ -218,8 +206,7 @@ public DatabaseReference getReferenceFromUrl(String url) {
+ "URL was expected to match configured Database URL: "
+ getReference().toString());
}
-
- return new DatabaseReference(this.repo, parsedUrl.path);
+ return new DatabaseReference(repo, parsedUrl.path);
}
/**
@@ -233,8 +220,8 @@ public DatabaseReference getReferenceFromUrl(String url) {
* listeners, and the client will not (re-)send them to the Firebase backend.
*/
public void purgeOutstandingWrites() {
- ensureRepo();
- this.repo.scheduleNow(
+ final Repo repo = ensureRepo();
+ repo.scheduleNow(
new Runnable() {
@Override
public void run() {
@@ -248,16 +235,14 @@ public void run() {
* call.
*/
public void goOnline() {
- ensureRepo();
- RepoManager.resume(this.repo);
+ RepoManager.resume(ensureRepo());
}
/**
* Shuts down our connection to the Firebase Database backend until {@link #goOnline()} is called.
*/
public void goOffline() {
- ensureRepo();
- RepoManager.interrupt(this.repo);
+ RepoManager.interrupt(ensureRepo());
}
/**
@@ -269,8 +254,10 @@ public void goOffline() {
* @param logLevel The desired minimum log level
*/
public synchronized void setLogLevel(Logger.Level logLevel) {
- assertUnfrozen("setLogLevel");
- this.config.setLogLevel(logLevel);
+ synchronized (lock) {
+ assertUnfrozen("setLogLevel");
+ this.config.setLogLevel(logLevel);
+ }
}
/**
@@ -287,8 +274,10 @@ public synchronized void setLogLevel(Logger.Level logLevel) {
* @param isEnabled Set to true to enable disk persistence, set to false to disable it.
*/
public synchronized void setPersistenceEnabled(boolean isEnabled) {
- assertUnfrozen("setPersistenceEnabled");
- this.config.setPersistenceEnabled(isEnabled);
+ synchronized (lock) {
+ assertUnfrozen("setPersistenceEnabled");
+ this.config.setPersistenceEnabled(isEnabled);
+ }
}
/**
@@ -304,24 +293,44 @@ public synchronized void setPersistenceEnabled(boolean isEnabled) {
*
* @param cacheSizeInBytes The new size of the cache in bytes.
*/
- public synchronized void setPersistenceCacheSizeBytes(long cacheSizeInBytes) {
- assertUnfrozen("setPersistenceCacheSizeBytes");
- this.config.setPersistenceCacheSizeBytes(cacheSizeInBytes);
+ public void setPersistenceCacheSizeBytes(long cacheSizeInBytes) {
+ synchronized (lock) {
+ assertUnfrozen("setPersistenceCacheSizeBytes");
+ this.config.setPersistenceCacheSizeBytes(cacheSizeInBytes);
+ }
}
private void assertUnfrozen(String methodCalled) {
- if (this.repo != null) {
- throw new DatabaseException(
- "Calls to "
- + methodCalled
- + "() must be made before any "
- + "other usage of FirebaseDatabase instance.");
+ synchronized (lock) {
+ checkNotDestroyed();
+ if (this.repo != null) {
+ throw new DatabaseException(
+ "Calls to "
+ + methodCalled
+ + "() must be made before any "
+ + "other usage of FirebaseDatabase instance.");
+ }
}
}
- private synchronized void ensureRepo() {
- if (this.repo == null) {
- repo = RepoManager.createRepo(this.config, this.repoInfo, this);
+ /**
+ * Initializes the Repo if not already initialized.
+ */
+ private Repo ensureRepo() {
+ synchronized (lock) {
+ checkNotDestroyed();
+ if (repo == null) {
+ repo = RepoManager.createRepo(this.config, this.repoInfo, this);
+ }
+ return repo;
+ }
+ }
+
+ void checkNotDestroyed() {
+ synchronized (lock) {
+ checkState(!destroyed.get(),
+ "FirebaseDatabase instance is no longer alive. This happens when "
+ + "the parent FirebaseApp instance has been deleted.");
}
}
@@ -330,6 +339,21 @@ DatabaseConfig getConfig() {
return this.config;
}
+ void destroy() {
+ synchronized (lock) {
+ if (destroyed.get()) {
+ return;
+ }
+
+ if (repo != null) {
+ RepoManager.interrupt(repo);
+ repo = null;
+ }
+ RepoManager.interrupt(getConfig());
+ destroyed.compareAndSet(false, true);
+ }
+ }
+
private static String loadSdkVersion() {
try (InputStream in = FirebaseDatabase.class.getClassLoader()
.getResourceAsStream(ADMIN_SDK_PROPERTIES)) {
@@ -340,4 +364,39 @@ private static String loadSdkVersion() {
throw new RuntimeException(e);
}
}
+
+ private static final String SERVICE_ID = FirebaseDatabase.class.getName();
+
+ private static class DatabaseInstances {
+ private final Map databases =
+ Collections.synchronizedMap(new HashMap());
+
+ void put(RepoInfo repo, FirebaseDatabase database) {
+ databases.put(repo, database);
+ }
+
+ FirebaseDatabase get(RepoInfo repo) {
+ return databases.get(repo);
+ }
+
+ void destroy() {
+ synchronized (databases) {
+ for (FirebaseDatabase database : databases.values()) {
+ database.destroy();
+ }
+ databases.clear();
+ }
+ }
+ }
+
+ private static class FirebaseDatabaseService extends FirebaseService {
+ FirebaseDatabaseService() {
+ super(SERVICE_ID, new DatabaseInstances());
+ }
+
+ @Override
+ public void destroy() {
+ instance.destroy();
+ }
+ }
}
diff --git a/src/main/java/com/google/firebase/database/InternalHelpers.java b/src/main/java/com/google/firebase/database/InternalHelpers.java
index 6e5931bad..69ee09c70 100644
--- a/src/main/java/com/google/firebase/database/InternalHelpers.java
+++ b/src/main/java/com/google/firebase/database/InternalHelpers.java
@@ -35,4 +35,11 @@ public static FirebaseDatabase createDatabaseForTests(
public static MutableData createMutableData(Node node) {
return new MutableData(node);
}
+
+ /**
+ * For Repo to check if the database has been destroyed.
+ */
+ public static void checkNotDestroyed(Repo repo) {
+ repo.getDatabase().checkNotDestroyed();
+ }
}
diff --git a/src/main/java/com/google/firebase/database/Query.java b/src/main/java/com/google/firebase/database/Query.java
index 07808ec2b..683a9439f 100644
--- a/src/main/java/com/google/firebase/database/Query.java
+++ b/src/main/java/com/google/firebase/database/Query.java
@@ -41,18 +41,15 @@ public class Query {
private final boolean orderByCalled;
Query(Repo repo, Path path, QueryParams params, boolean orderByCalled) throws DatabaseException {
+ hardAssert(params.isValid(), "Validation of queries failed.");
this.repo = repo;
this.path = path;
this.params = params;
this.orderByCalled = orderByCalled;
- hardAssert(params.isValid(), "Validation of queries failed.");
}
Query(Repo repo, Path path) {
- this.repo = repo;
- this.path = path;
- this.params = QueryParams.DEFAULT_PARAMS;
- this.orderByCalled = false;
+ this(repo, path, QueryParams.DEFAULT_PARAMS, false);
}
/**
diff --git a/src/main/java/com/google/firebase/database/core/Repo.java b/src/main/java/com/google/firebase/database/core/Repo.java
index a1f247a4b..86a282d13 100644
--- a/src/main/java/com/google/firebase/database/core/Repo.java
+++ b/src/main/java/com/google/firebase/database/core/Repo.java
@@ -67,7 +67,7 @@ public class Repo implements PersistentConnection.Delegate {
private long nextWriteId = 1;
private SyncTree infoSyncTree;
private SyncTree serverSyncTree;
- private FirebaseDatabase database;
+ private final FirebaseDatabase database;
private boolean loggedTransactionPersistenceWarning = false;
private long transactionOrder = 0;
@@ -268,11 +268,13 @@ public RepoInfo getRepoInfo() {
}
public void scheduleNow(Runnable r) {
+ InternalHelpers.checkNotDestroyed(this);
ctx.requireStarted();
ctx.getRunLoop().scheduleNow(r);
}
public void postEvent(Runnable r) {
+ InternalHelpers.checkNotDestroyed(this);
ctx.requireStarted();
ctx.getEventTarget().postEvent(r);
}
@@ -616,6 +618,7 @@ void interrupt() {
}
void resume() {
+ InternalHelpers.checkNotDestroyed(this);
connection.resume(INTERRUPT_REASON);
}
diff --git a/src/main/java/com/google/firebase/internal/FirebaseService.java b/src/main/java/com/google/firebase/internal/FirebaseService.java
new file mode 100644
index 000000000..3a33bcb09
--- /dev/null
+++ b/src/main/java/com/google/firebase/internal/FirebaseService.java
@@ -0,0 +1,50 @@
+package com.google.firebase.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Strings;
+
+/**
+ * Represents a service exposed from the Admin SDK (e.g. auth, database). Each instance of this
+ * class is associated with exactly one instance of FirebaseApp. Also provides a lifecycle hook
+ * to gracefully tear down the service.
+ *
+ * @param Type of the service
+ */
+public abstract class FirebaseService {
+
+ private final String id;
+ protected final T instance;
+
+ protected FirebaseService(String id, T instance) {
+ checkArgument(!Strings.isNullOrEmpty(id));
+ this.id = id;
+ this.instance = checkNotNull(instance);
+ }
+
+ /**
+ * Returns the ID used to identify this FirebaseService. Implementations must return a string
+ * unique to this service (e.g. full qualified class name of the service type).
+ *
+ * @return an ID string unique to this service type
+ */
+ public final String getId() {
+ return id;
+ }
+
+ /**
+ * Returns the concrete object instance that provides a specific Firebase service.
+ *
+ * @return the service object wrapped in this FirebaseService instance
+ */
+ public final T getInstance() {
+ return instance;
+ }
+
+ /**
+ * Tear down this FirebaseService instance and the service object wrapped in it, cleaning up
+ * any allocated resources in the process.
+ */
+ public abstract void destroy();
+}
diff --git a/src/test/java/com/google/firebase/FirebaseAppTest.java b/src/test/java/com/google/firebase/FirebaseAppTest.java
index 8ce6b8b2d..be7bd6776 100644
--- a/src/test/java/com/google/firebase/FirebaseAppTest.java
+++ b/src/test/java/com/google/firebase/FirebaseAppTest.java
@@ -3,6 +3,8 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
@@ -92,18 +94,42 @@ public void testRehydratingDeletedInstanceThrows() {
}
@Test
- public void testDeleteCallback() {
- String appName = "myApp";
- FirebaseApp firebaseApp = FirebaseApp.initializeApp(OPTIONS, appName);
- FirebaseAppLifecycleListener listener = mock(FirebaseAppLifecycleListener.class);
- firebaseApp.addLifecycleEventListener(listener);
+ public void testDeleteDefaultApp() {
+ FirebaseApp firebaseApp = FirebaseApp.initializeApp(OPTIONS);
+ assertEquals(firebaseApp, FirebaseApp.getInstance());
firebaseApp.delete();
+ try {
+ FirebaseApp.getInstance();
+ fail();
+ } catch (IllegalStateException expected) {
+ // ignore
+ } finally {
+ TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+ }
+ }
- verify(listener).onDeleted(appName, OPTIONS);
- // Any further calls to delete are no-ops.
- reset(listener);
+ @Test
+ public void testDeleteApp() {
+ final String name = "myApp";
+ FirebaseApp firebaseApp = FirebaseApp.initializeApp(OPTIONS, name);
+ assertSame(firebaseApp, FirebaseApp.getInstance(name));
firebaseApp.delete();
- verify(listener, never()).onDeleted(appName, OPTIONS);
+
+ try {
+ FirebaseApp.getInstance(name);
+ fail();
+ } catch (IllegalStateException expected) {
+ // ignore
+ }
+
+ try {
+ // Verify we can reuse the same app name.
+ FirebaseApp firebaseApp2 = FirebaseApp.initializeApp(OPTIONS, name);
+ assertSame(firebaseApp2, FirebaseApp.getInstance(name));
+ assertNotSame(firebaseApp, firebaseApp2);
+ } finally {
+ TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+ }
}
@Test
diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
index a347a0023..74695d4f5 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
@@ -3,6 +3,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -17,6 +18,8 @@
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.auth.internal.FirebaseCustomAuthToken;
import com.google.firebase.database.MapBuilder;
+import com.google.firebase.internal.Log;
+import com.google.firebase.tasks.Task;
import com.google.firebase.tasks.Tasks;
import com.google.firebase.testing.ServiceAccount;
import com.google.firebase.testing.TestUtils;
@@ -32,6 +35,9 @@
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.After;
@@ -49,6 +55,7 @@ public class FirebaseAuthTest {
private static final String CLIENT_SECRET = "mockclientsecret";
private static final String CLIENT_ID = "mockclientid";
private static final String REFRESH_TOKEN = "mockrefreshtoken";
+ private static final String TAG = "FirebaseAuthTest";
private final FirebaseOptions firebaseOptions;
private final boolean isCertCredential;
@@ -159,6 +166,58 @@ public void testGetInstanceForApp() throws ExecutionException, InterruptedExcept
Assert.assertTrue(!token.isEmpty());
}
+ @Test
+ public void testAppDelete() throws ExecutionException, InterruptedException {
+ FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testAppDelete");
+ FirebaseAuth auth = FirebaseAuth.getInstance(app);
+ assertNotNull(auth);
+ app.delete();
+ try {
+ FirebaseAuth.getInstance(app);
+ fail("No error thrown when getting auth instance after deleting app");
+ } catch (IllegalStateException expected) {
+ // ignore
+ }
+ }
+
+ @Test
+ public void testInvokeAfterAppDelete() throws ExecutionException, InterruptedException {
+ if (!isCertCredential) {
+ Log.i(TAG, "Skipping testInvokeAfterAppDelete for non-cert credential");
+ return;
+ }
+ FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testInvokeAfterAppDelete");
+ FirebaseAuth auth = FirebaseAuth.getInstance(app);
+ assertNotNull(auth);
+ app.delete();
+ try {
+ auth.createCustomToken("foo");
+ fail("No error thrown when invoking auth after deleting app");
+ } catch (IllegalStateException expected) {
+ // ignore
+ }
+ }
+
+ @Test
+ public void testInitAfterAppDelete() throws ExecutionException, InterruptedException,
+ TimeoutException {
+ FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testInitAfterAppDelete");
+ FirebaseAuth auth1 = FirebaseAuth.getInstance(app);
+ assertNotNull(auth1);
+ app.delete();
+
+ app = FirebaseApp.initializeApp(firebaseOptions, "testInitAfterAppDelete");
+ FirebaseAuth auth2 = FirebaseAuth.getInstance(app);
+ assertNotNull(auth2);
+ assertNotSame(auth1, auth2);
+
+ if (isCertCredential) {
+ Task task = auth2.createCustomToken("foo");
+ assertNotNull(task);
+ assertNotNull(Tasks.await(task, TestUtils.TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+ }
+ }
+
@Test
public void testAppWithAuthVariableOverrides() throws ExecutionException, InterruptedException {
Map authVariableOverrides = Collections.singletonMap("uid", (Object) "uid1");
@@ -175,6 +234,7 @@ public void testAppWithAuthVariableOverrides() throws ExecutionException, Interr
@Test
public void testCreateCustomToken() throws Exception {
if (!isCertCredential) {
+ Log.i(TAG, "Skipping testCreateCustomToken for non-cert credential");
return;
}
@@ -194,6 +254,7 @@ public void testCreateCustomToken() throws Exception {
@Test
public void testCreateCustomTokenWithDeveloperClaims() throws Exception {
if (!isCertCredential) {
+ Log.i(TAG, "Skipping testCreateCustomTokenWithDeveloperClaims for non-cert credential");
return;
}
@@ -227,6 +288,7 @@ public void testServiceAccountUsedAsRefreshToken() throws Exception {
@Test
public void testCredentialCertificateRequired() throws Exception {
if (isCertCredential) {
+ Log.i(TAG, "Skipping testCredentialCertificateRequired for cert credential");
return;
}
diff --git a/src/test/java/com/google/firebase/database/DataSnapshotTest.java b/src/test/java/com/google/firebase/database/DataSnapshotTest.java
index ea20616ed..8e31510a9 100644
--- a/src/test/java/com/google/firebase/database/DataSnapshotTest.java
+++ b/src/test/java/com/google/firebase/database/DataSnapshotTest.java
@@ -8,6 +8,7 @@
import com.google.firebase.FirebaseOptions;
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.auth.FirebaseCredentials;
+import com.google.firebase.database.core.DatabaseConfig;
import com.google.firebase.database.snapshot.IndexedNode;
import com.google.firebase.database.snapshot.Node;
import com.google.firebase.database.snapshot.NodeUtilities;
@@ -21,6 +22,7 @@
public class DataSnapshotTest {
private static FirebaseApp testApp;
+ private static DatabaseConfig config;
@BeforeClass
public static void setUpClass() {
@@ -29,17 +31,21 @@ public static void setUpClass() {
.setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
.setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
.build());
+ // Obtain a new DatabaseConfig instance for testing. Since we are not connecting to an
+ // actual Firebase database, it is necessary to use a stand-in DatabaseConfig here.
+ config = TestHelpers.newTestConfig(testApp);
}
@AfterClass
- public static void tearDownClass() {
+ public static void tearDownClass() throws InterruptedException {
+ // Tear down and clean up the test DatabaseConfig.
+ TestHelpers.interruptConfig(config);
TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
}
private DataSnapshot snapFor(Object data) {
Node node = NodeUtilities.NodeFromJSON(data);
- DatabaseReference ref = new DatabaseReference("https://test.firebaseio.com", TestHelpers
- .newTestConfig(testApp));
+ DatabaseReference ref = new DatabaseReference("https://test.firebaseio.com", config);
return new DataSnapshot(ref, IndexedNode.from(node));
}
diff --git a/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java b/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java
new file mode 100644
index 000000000..bba52b4e6
--- /dev/null
+++ b/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java
@@ -0,0 +1,89 @@
+package com.google.firebase.database;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseOptions;
+import com.google.firebase.TestOnlyImplFirebaseTrampolines;
+import com.google.firebase.auth.FirebaseCredential;
+import com.google.firebase.auth.FirebaseCredentials;
+import com.google.firebase.testing.ServiceAccount;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.AfterClass;
+import org.junit.Test;
+
+public class FirebaseDatabaseTest {
+
+ private static FirebaseOptions firebaseOptions =
+ new FirebaseOptions.Builder()
+ .setCredential(createCertificateCredential())
+ .setDatabaseUrl("https://firebase-db-test.firebaseio.com")
+ .build();
+
+ @AfterClass
+ public static void tearDownClass() {
+ TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+ }
+
+ private static FirebaseCredential createCertificateCredential() {
+ return FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream());
+ }
+
+ @Test
+ public void testGetInstance() throws ExecutionException, InterruptedException {
+ FirebaseApp.initializeApp(firebaseOptions);
+ FirebaseDatabase defaultDatabase = FirebaseDatabase.getInstance();
+ assertNotNull(defaultDatabase);
+ assertSame(defaultDatabase, FirebaseDatabase.getInstance());
+ }
+
+ @Test
+ public void testGetInstanceForApp() throws ExecutionException, InterruptedException {
+ FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testGetInstanceForApp");
+ FirebaseDatabase db = FirebaseDatabase.getInstance(app);
+ assertNotNull(db);
+ assertSame(db, FirebaseDatabase.getInstance(app));
+ }
+
+ @Test
+ public void testAppDelete() throws ExecutionException, InterruptedException {
+ FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testAppDelete");
+ FirebaseDatabase db = FirebaseDatabase.getInstance(app);
+ assertNotNull(db);
+ app.delete();
+
+ try {
+ db.getReference();
+ fail("No error thrown when calling method on database after delete");
+ } catch (IllegalStateException expected) {
+ // ignore
+ }
+
+ try {
+ FirebaseDatabase.getInstance(app);
+ fail("No error thrown when getting db instance after deleting app");
+ } catch (IllegalStateException expected) {
+ // ignore
+ }
+ }
+
+ @Test
+ public void testInitAfterAppDelete() throws ExecutionException, InterruptedException,
+ TimeoutException {
+ FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testInitAfterAppDelete");
+ FirebaseDatabase db1 = FirebaseDatabase.getInstance(app);
+ assertNotNull(db1);
+ app.delete();
+
+ app = FirebaseApp.initializeApp(firebaseOptions, "testInitAfterAppDelete");
+ FirebaseDatabase db2 = FirebaseDatabase.getInstance(app);
+ assertNotNull(db2);
+ assertNotSame(db1, db2);
+ }
+}
diff --git a/src/test/java/com/google/firebase/database/TestHelpers.java b/src/test/java/com/google/firebase/database/TestHelpers.java
index 8fc4cd08e..9534f97d7 100644
--- a/src/test/java/com/google/firebase/database/TestHelpers.java
+++ b/src/test/java/com/google/firebase/database/TestHelpers.java
@@ -9,6 +9,7 @@
import com.google.firebase.database.core.CoreTestHelpers;
import com.google.firebase.database.core.DatabaseConfig;
import com.google.firebase.database.core.Path;
+import com.google.firebase.database.core.RepoManager;
import com.google.firebase.database.core.view.QuerySpec;
import com.google.firebase.database.future.WriteFuture;
import com.google.firebase.database.snapshot.ChildKey;
@@ -47,6 +48,19 @@ public static DatabaseConfig newTestConfig(FirebaseApp app) {
return config;
}
+ public static void interruptConfig(final DatabaseConfig config) throws InterruptedException {
+ RepoManager.interrupt(config);
+ long now = System.currentTimeMillis();
+ synchronized (config) {
+ while (System.currentTimeMillis() - now < TestUtils.TEST_TIMEOUT_MILLIS) {
+ if (config.isStopped()) {
+ break;
+ }
+ config.wait(10);
+ }
+ }
+ }
+
public static DatabaseConfig getDatabaseConfig(FirebaseApp app) {
return FirebaseDatabase.getInstance(app).getConfig();
}
@@ -267,6 +281,10 @@ public static void assertAndUnwrapErrorHandlers(FirebaseApp app) {
}
}
+ public static void assertTimeDelta(long timestamp) {
+ assertTrue(Math.abs(System.currentTimeMillis() - timestamp) < TestUtils.TEST_TIMEOUT_MILLIS);
+ }
+
private static class TestExceptionHandler implements UncaughtExceptionHandler {
private final AtomicReference throwable = new AtomicReference<>();
diff --git a/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java b/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java
index 38971895a..20283b9e9 100644
--- a/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java
+++ b/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java
@@ -4,7 +4,6 @@
import com.google.common.collect.ImmutableMap;
import com.google.firebase.FirebaseApp;
-import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.EventRecord;
import com.google.firebase.database.FirebaseDatabase;
@@ -17,7 +16,6 @@
import java.util.List;
import java.util.Map;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -29,12 +27,7 @@ public class KeepSyncedTestIT {
@BeforeClass
public static void setUpClass() throws IOException {
- masterApp = IntegrationTestUtils.initDefaultApp();
- }
-
- @AfterClass
- public static void tearDownClass() {
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+ masterApp = IntegrationTestUtils.ensureDefaultApp();
}
@Before
diff --git a/src/test/java/com/google/firebase/database/integration/DataTestIT.java b/src/test/java/com/google/firebase/database/integration/DataTestIT.java
index e143668f0..d38f387dd 100644
--- a/src/test/java/com/google/firebase/database/integration/DataTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/DataTestIT.java
@@ -11,7 +11,6 @@
import com.cedarsoftware.util.DeepEquals;
import com.google.common.collect.ImmutableList;
import com.google.firebase.FirebaseApp;
-import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
@@ -52,7 +51,6 @@
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
@@ -64,12 +62,7 @@ public class DataTestIT {
@BeforeClass
public static void setUpClass() {
- masterApp = IntegrationTestUtils.initDefaultApp();
- }
-
- @AfterClass
- public static void tearDownClass() {
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+ masterApp = IntegrationTestUtils.ensureDefaultApp();
}
@Before
@@ -2083,8 +2076,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) {
assertEquals(snap.getPriority().getClass(), Double.class);
assertEquals(snap.getPriority(), snap.child("b").getPriority());
assertEquals(snap.child("a").getValue(), snap.child("b").getValue());
- assert (Math.abs(
- System.currentTimeMillis() - Long.parseLong(snap.child("a").getValue().toString())) < 2000);
+ TestHelpers.assertTimeDelta(Long.parseLong(snap.child("a").getValue().toString()));
}
@Test
@@ -2145,8 +2137,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) {
TestHelpers.waitFor(opSemaphore);
TestHelpers.waitFor(valSemaphore);
-
- assert (Math.abs(System.currentTimeMillis() - priority.get()) < 2000);
+ TestHelpers.assertTimeDelta(priority.get());
}
@Test
@@ -2198,8 +2189,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) {
DataSnapshot snap = readerEventRecord.getSnapshot();
assertEquals(snap.child("a/b/c").getValue().getClass(), Long.class);
assertEquals(snap.child("a/b/d").getValue().getClass(), Long.class);
- assert (Math.abs(System.currentTimeMillis()
- - Long.parseLong(snap.child("a/b/c").getValue().toString())) < 2000);
+ TestHelpers.assertTimeDelta(Long.parseLong(snap.child("a/b/c").getValue().toString()));
}
@Test
@@ -2243,8 +2233,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) {
assertEquals(snap.getPriority().getClass(), Double.class);
assertEquals(snap.getPriority(), snap.child("b").getPriority());
assertEquals(snap.child("a").getValue(), snap.child("b").getValue());
- assert (Math.abs(
- System.currentTimeMillis() - Long.parseLong(snap.child("a").getValue().toString())) < 2000);
+ TestHelpers.assertTimeDelta(Long.parseLong(snap.child("a").getValue().toString()));
}
@Test
@@ -2303,8 +2292,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) {
TestHelpers.waitFor(opSemaphore);
TestHelpers.waitFor(valSemaphore);
-
- assert (Math.abs(System.currentTimeMillis() - priority.get()) < 2000);
+ TestHelpers.assertTimeDelta(priority.get());
}
@Test
@@ -2352,8 +2340,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) {
DataSnapshot snap = readerEventRecord.getSnapshot();
assertEquals(snap.child("a/b/c").getValue().getClass(), Long.class);
assertEquals(snap.child("a/b/d").getValue().getClass(), Long.class);
- assert (Math.abs(System.currentTimeMillis()
- - Long.parseLong(snap.child("a/b/c").getValue().toString())) < 2000);
+ TestHelpers.assertTimeDelta(Long.parseLong(snap.child("a/b/c").getValue().toString()));
}
@Test
@@ -2408,14 +2395,12 @@ public void onComplete(DatabaseError error, boolean committed, DataSnapshot curr
EventRecord writerEventRecord = writerFuture.timedGet().get(1);
DataSnapshot snap1 = writerEventRecord.getSnapshot();
assertEquals(snap1.getValue().getClass(), Long.class);
- assert (Math
- .abs(System.currentTimeMillis() - Long.parseLong(snap1.getValue().toString())) < 2000);
+ TestHelpers.assertTimeDelta(Long.parseLong(snap1.getValue().toString()));
EventRecord readerEventRecord = readerFuture.timedGet().get(1);
DataSnapshot snap2 = readerEventRecord.getSnapshot();
assertEquals(snap2.getValue().getClass(), Long.class);
- assert (Math
- .abs(System.currentTimeMillis() - Long.parseLong(snap2.getValue().toString())) < 2000);
+ TestHelpers.assertTimeDelta(Long.parseLong(snap2.getValue().toString()));
}
@Test
diff --git a/src/test/java/com/google/firebase/database/integration/EventTestIT.java b/src/test/java/com/google/firebase/database/integration/EventTestIT.java
index 8c04b9f66..aedb48075 100644
--- a/src/test/java/com/google/firebase/database/integration/EventTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/EventTestIT.java
@@ -43,12 +43,7 @@ public class EventTestIT {
@BeforeClass
public static void setUpClass() throws TestFailure, TimeoutException, InterruptedException {
- masterApp = IntegrationTestUtils.initDefaultApp();
- }
-
- @AfterClass
- public static void tearDownClass() {
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+ masterApp = IntegrationTestUtils.ensureDefaultApp();
}
@Before
diff --git a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java
index ccd3573ce..340d4e843 100644
--- a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java
@@ -6,7 +6,6 @@
import com.google.common.collect.ImmutableMap;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
-import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.auth.FirebaseCredentials;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
@@ -28,7 +27,6 @@
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -39,15 +37,20 @@ public class FirebaseDatabaseAuthTestIT {
@BeforeClass
public static void setUpClass() throws IOException {
- masterApp = IntegrationTestUtils.initDefaultApp();
+ masterApp = IntegrationTestUtils.ensureDefaultApp();
setDatabaseRules();
}
-
- @AfterClass
- public static void tearDownClass() {
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+
+ @Before
+ public void prepareApp() {
+ TestHelpers.wrapForErrorHandling(masterApp);
}
-
+
+ @After
+ public void checkAndCleanupApp() {
+ TestHelpers.assertAndUnwrapErrorHandlers(masterApp);
+ }
+
@Test
public void testAuthWithValidCertificateCredential() throws InterruptedException {
FirebaseDatabase db = FirebaseDatabase.getInstance();
@@ -67,16 +70,6 @@ public void testAuthWithInvalidCertificateCredential() throws InterruptedExcepti
// TODO: Ideally, we would find a way to verify the correct log output.
assertWriteTimeout(db.getReference());
}
-
- @Before
- public void prepareApp() {
- TestHelpers.wrapForErrorHandling(masterApp);
- }
-
- @After
- public void checkAndCleanupApp() {
- TestHelpers.assertAndUnwrapErrorHandlers(masterApp);
- }
@Test
public void testDatabaseAuthVariablesAuthorization() throws InterruptedException {
diff --git a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java
index 8be5703e0..543f84031 100644
--- a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java
@@ -5,26 +5,48 @@
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
-import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.auth.FirebaseCredentials;
+import com.google.firebase.database.ChildEventListener;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseException;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
+import com.google.firebase.database.MapBuilder;
+import com.google.firebase.database.TestFailure;
+import com.google.firebase.database.TestHelpers;
+import com.google.firebase.database.ValueEventListener;
+import com.google.firebase.database.future.ReadFuture;
+import com.google.firebase.tasks.Tasks;
import com.google.firebase.testing.IntegrationTestUtils;
-import org.junit.AfterClass;
+import com.google.firebase.testing.TestUtils;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.After;
+import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class FirebaseDatabaseTestIT {
-
+
+ private static FirebaseApp masterApp;
+
@BeforeClass
public static void setUpClass() {
- IntegrationTestUtils.initDefaultApp();
+ masterApp = IntegrationTestUtils.ensureDefaultApp();
}
-
- @AfterClass
- public static void tearDownClass() {
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+
+ @Before
+ public void prepareApp() {
+ TestHelpers.wrapForErrorHandling(masterApp);
+ }
+
+ @After
+ public void checkAndCleanupApp() {
+ TestHelpers.assertAndUnwrapErrorHandlers(masterApp);
}
@Test
@@ -35,10 +57,9 @@ public void testGetDefaultInstance() {
}
@Test
- public void testGetInstanceForApp() {
- FirebaseApp app = appWithDbUrl(IntegrationTestUtils.getDatabaseUrl(), "testGetInstanceForApp");
- FirebaseDatabase db = FirebaseDatabase.getInstance(app);
- assertEquals(app.getOptions().getDatabaseUrl(), db.getReference().toString());
+ public void testGetInstanceForApp() throws InterruptedException, TestFailure, TimeoutException {
+ FirebaseDatabase db = FirebaseDatabase.getInstance(masterApp);
+ assertEquals(masterApp.getOptions().getDatabaseUrl(), db.getReference().toString());
}
@Test
@@ -95,7 +116,7 @@ public void testDatabaseUrlWithPathInGetInstance() {
@Test
public void testGetReference() {
- FirebaseDatabase db = FirebaseDatabase.getInstance();
+ FirebaseDatabase db = FirebaseDatabase.getInstance(masterApp);
assertEquals(IntegrationTestUtils.getDatabaseUrl() + "/foo",
db.getReference("foo").toString());
}
@@ -103,7 +124,7 @@ public void testGetReference() {
@Test
public void testGetReferenceFromURLWithoutPath() {
String dbUrl = IntegrationTestUtils.getDatabaseUrl();
- FirebaseDatabase db = FirebaseDatabase.getInstance();
+ FirebaseDatabase db = FirebaseDatabase.getInstance(masterApp);
DatabaseReference ref = db.getReferenceFromUrl(dbUrl);
assertEquals(dbUrl, ref.toString());
}
@@ -111,7 +132,7 @@ public void testGetReferenceFromURLWithoutPath() {
@Test
public void testGetReferenceFromURLWithPath() {
String dbUrl = IntegrationTestUtils.getDatabaseUrl();
- FirebaseDatabase db = FirebaseDatabase.getInstance();
+ FirebaseDatabase db = FirebaseDatabase.getInstance(masterApp);
DatabaseReference ref = db.getReferenceFromUrl(dbUrl + "/foo/bar");
assertEquals(dbUrl + "/foo/bar", ref.toString());
}
@@ -121,6 +142,101 @@ public void testGetReferenceThrowsWithBadUrl() {
FirebaseDatabase db = FirebaseDatabase.getInstance();
db.getReferenceFromUrl("https://tests2.fake-firebaseio.com:9000");
}
+
+ @Test
+ public void testSetValue() throws InterruptedException, ExecutionException, TimeoutException,
+ TestFailure {
+ FirebaseDatabase db = FirebaseDatabase.getInstance(masterApp);
+ DatabaseReference ref = db.getReference("testSetValue");
+ Tasks.await(ref.setValue("foo"), TestUtils.TEST_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS);
+ ReadFuture readFuture = ReadFuture.untilEquals(ref, "foo");
+ readFuture.timedWait();
+ }
+
+ @Test
+ public void testDeleteApp() throws InterruptedException, TestFailure, TimeoutException,
+ ExecutionException {
+ FirebaseApp app = IntegrationTestUtils.initApp("testDeleteApp");
+ List ref = IntegrationTestUtils.getRandomNode(app, 2);
+ DatabaseReference writer = ref.get(0);
+ DatabaseReference reader = ref.get(1);
+ writer.setValue("test");
+ TestHelpers.waitForRoundtrip(writer.getRoot());
+ ReadFuture.untilEquals(reader, "test").timedWait();
+
+ app.delete();
+ try {
+ IntegrationTestUtils.getRandomNode(app);
+ fail("No error thrown for deleted app");
+ } catch (IllegalStateException expected) {
+ // ignore
+ }
+
+ try {
+ writer.setValue("foo");
+ fail("No error thrown for deleted app");
+ } catch (IllegalStateException expected) {
+ // ignore
+ }
+
+ try {
+ writer.updateChildren(MapBuilder.of("a", 1));
+ fail("No error thrown for deleted app");
+ } catch (IllegalStateException expected) {
+ // ignore
+ }
+
+ try {
+ writer.addValueEventListener(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot snapshot) {
+ }
+
+ @Override
+ public void onCancelled(DatabaseError error) {
+ }
+ });
+ fail("No error thrown for deleted app");
+ } catch (IllegalStateException expected) {
+ // ignore
+ }
+
+ try {
+ writer.addChildEventListener(new ChildEventListener() {
+ @Override
+ public void onChildAdded(DataSnapshot snapshot, String previousChildName) {
+ }
+
+ @Override
+ public void onChildChanged(DataSnapshot snapshot, String previousChildName) {
+ }
+
+ @Override
+ public void onChildRemoved(DataSnapshot snapshot) {
+ }
+
+ @Override
+ public void onChildMoved(DataSnapshot snapshot, String previousChildName) {
+ }
+
+ @Override
+ public void onCancelled(DatabaseError error) {
+ }
+ });
+ fail("No error thrown for deleted app");
+ } catch (IllegalStateException expected) {
+ // ignore
+ }
+
+ app = IntegrationTestUtils.initApp("testDeleteApp");
+ ref = IntegrationTestUtils.getRandomNode(app, 2);
+ writer = ref.get(0);
+ reader = ref.get(1);
+ writer.setValue("test2");
+ TestHelpers.waitForRoundtrip(writer.getRoot());
+ ReadFuture.untilEquals(reader, "test2").timedWait();
+ }
private static FirebaseApp appWithDbUrl(String dbUrl, String name) {
FirebaseOptions options = new FirebaseOptions.Builder()
diff --git a/src/test/java/com/google/firebase/database/integration/InfoTestIT.java b/src/test/java/com/google/firebase/database/integration/InfoTestIT.java
index 47c5a7025..528deef4f 100644
--- a/src/test/java/com/google/firebase/database/integration/InfoTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/InfoTestIT.java
@@ -6,7 +6,6 @@
import static org.junit.Assert.fail;
import com.google.firebase.FirebaseApp;
-import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseException;
@@ -23,7 +22,6 @@
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -31,12 +29,7 @@ public class InfoTestIT {
@BeforeClass
public static void setUpClass() throws IOException {
- IntegrationTestUtils.initDefaultApp();
- }
-
- @AfterClass
- public static void tearDownClass() {
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+ IntegrationTestUtils.ensureDefaultApp();
}
@Test
diff --git a/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java b/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java
index cb4f0afd7..86588cc40 100644
--- a/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java
@@ -5,7 +5,6 @@
import com.google.common.collect.ImmutableList;
import com.google.firebase.FirebaseApp;
-import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
@@ -45,13 +44,12 @@ public class OrderByTestIT {
@BeforeClass
public static void setUpClass() {
- masterApp = IntegrationTestUtils.initDefaultApp();
+ masterApp = IntegrationTestUtils.ensureDefaultApp();
}
@AfterClass
public static void tearDownClass() throws IOException {
uploadRules(masterApp, "{\"rules\": {\".read\": true, \".write\": true}}");
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
}
@Before
diff --git a/src/test/java/com/google/firebase/database/integration/OrderTestIT.java b/src/test/java/com/google/firebase/database/integration/OrderTestIT.java
index f0f8daaea..2eeb3332e 100644
--- a/src/test/java/com/google/firebase/database/integration/OrderTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/OrderTestIT.java
@@ -7,7 +7,6 @@
import com.google.common.collect.ImmutableList;
import com.google.firebase.FirebaseApp;
-import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
@@ -29,7 +28,6 @@
import java.util.concurrent.TimeoutException;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -40,17 +38,12 @@ public class OrderTestIT {
@BeforeClass
public static void setUpClass() throws TestFailure, TimeoutException, InterruptedException {
- masterApp = IntegrationTestUtils.initDefaultApp();
+ masterApp = IntegrationTestUtils.ensureDefaultApp();
// Make sure we're connected before any of these tests run
DatabaseReference ref = FirebaseDatabase.getInstance(masterApp).getReference();
ReadFuture.untilEquals(ref.child(".info/connected"), true).timedGet();
}
- @AfterClass
- public static void tearDownClass() {
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
- }
-
@Before
public void prepareApp() {
TestHelpers.wrapForErrorHandling(masterApp);
diff --git a/src/test/java/com/google/firebase/database/integration/QueryTestIT.java b/src/test/java/com/google/firebase/database/integration/QueryTestIT.java
index af74fdf12..fec348399 100644
--- a/src/test/java/com/google/firebase/database/integration/QueryTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/QueryTestIT.java
@@ -9,7 +9,6 @@
import com.google.common.collect.ImmutableList;
import com.google.firebase.FirebaseApp;
-import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
@@ -38,7 +37,6 @@
import java.util.concurrent.atomic.AtomicLong;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -49,12 +47,7 @@ public class QueryTestIT {
@BeforeClass
public static void setUpClass() throws IOException {
- masterApp = IntegrationTestUtils.initDefaultApp();
- }
-
- @AfterClass
- public static void tearDownClass() {
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+ masterApp = IntegrationTestUtils.ensureDefaultApp();
}
@Before
diff --git a/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java b/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java
index c9d5eb331..d44998a78 100644
--- a/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java
@@ -7,7 +7,6 @@
import static org.junit.Assert.fail;
import com.google.firebase.FirebaseApp;
-import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
@@ -26,7 +25,6 @@
import com.google.firebase.database.utilities.Utilities;
import com.google.firebase.testing.IntegrationTestUtils;
-import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
@@ -35,7 +33,6 @@
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
@@ -47,12 +44,7 @@ public class RealtimeTestIT {
@BeforeClass
public static void setUpClass() {
- masterApp = IntegrationTestUtils.initDefaultApp();
- }
-
- @AfterClass
- public static void tearDownClass() throws IOException {
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+ masterApp = IntegrationTestUtils.ensureDefaultApp();
}
@Before
@@ -737,8 +729,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) {
assertEquals(snap.getPriority().getClass(), Double.class);
assertEquals(snap.getPriority(), snap.child("b").getPriority());
assertEquals(snap.child("a").getValue(), snap.child("b").getValue());
- assert (Math.abs(
- System.currentTimeMillis() - Long.parseLong(snap.child("a").getValue().toString())) < 2000);
+ TestHelpers.assertTimeDelta(Long.parseLong(snap.child("a").getValue().toString()));
}
// TODO: Find better way to test shutdown behavior. This test is not worth a
diff --git a/src/test/java/com/google/firebase/database/integration/RulesTestIT.java b/src/test/java/com/google/firebase/database/integration/RulesTestIT.java
index ec179dfa9..3098875be 100644
--- a/src/test/java/com/google/firebase/database/integration/RulesTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/RulesTestIT.java
@@ -48,8 +48,8 @@
public class RulesTestIT {
- private static final String DEFAULT_RULES_STRING =
- "{\n \"rules\": {\n \".read\": true,\n \".write\": true\n }\n}";
+ private static final Map DEFAULT_RULES_STRING =
+ MapBuilder.of("rules", MapBuilder.of(".read", "auth != null", ".write", "auth != null"));
private static final Map testRules;
@@ -108,9 +108,8 @@ public static void setUpClass() throws IOException {
@AfterClass
public static void tearDownClass() throws IOException {
- uploadRules(DEFAULT_RULES_STRING);
+ uploadRules(JsonMapper.serializeJson(DEFAULT_RULES_STRING));
TestHelpers.waitForRoundtrip(writer.getRoot());
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
}
@Before
diff --git a/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java b/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java
index f34290a9d..fb3027559 100644
--- a/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java
@@ -10,7 +10,6 @@
import com.google.common.collect.Iterables;
import com.google.firebase.FirebaseApp;
-import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
@@ -49,7 +48,6 @@
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -62,12 +60,7 @@ public class TransactionTestIT {
@BeforeClass
public static void setUpClass() throws IOException {
- masterApp = IntegrationTestUtils.initDefaultApp();
- }
-
- @AfterClass
- public static void tearDownClass() {
- TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+ masterApp = IntegrationTestUtils.ensureDefaultApp();
}
@Before
diff --git a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java
index ba84add63..3d2388a15 100644
--- a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java
+++ b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java
@@ -34,6 +34,7 @@
public class IntegrationTestUtils {
private static JSONObject IT_SERVICE_ACCOUNT;
+ private static FirebaseApp masterApp;
private static synchronized JSONObject ensureServiceAccount() {
if (IT_SERVICE_ACCOUNT == null) {
@@ -62,13 +63,24 @@ public static String getDatabaseUrl() {
return "https://" + getProjectId() + ".firebaseio.com";
}
- public static FirebaseApp initDefaultApp() {
- FirebaseOptions options =
- new FirebaseOptions.Builder()
- .setDatabaseUrl(getDatabaseUrl())
- .setCredential(FirebaseCredentials.fromCertificate(getServiceAccountCertificate()))
- .build();
- return FirebaseApp.initializeApp(options);
+ /**
+ * Initializes the default FirebaseApp for integration testing (if not already initialized), and
+ * returns it. Integration tests that interact with the default FirebaseApp should call this
+ * method to obtain the app instance. This method ensures that all integration tests get the
+ * same FirebaseApp instance, instead of initializing an app per test.
+ *
+ * @return the default FirebaseApp instance
+ */
+ public static synchronized FirebaseApp ensureDefaultApp() {
+ if (masterApp == null) {
+ FirebaseOptions options =
+ new FirebaseOptions.Builder()
+ .setDatabaseUrl(getDatabaseUrl())
+ .setCredential(FirebaseCredentials.fromCertificate(getServiceAccountCertificate()))
+ .build();
+ masterApp = FirebaseApp.initializeApp(options);
+ }
+ return masterApp;
}
public static FirebaseApp initApp(String name) {