Skip to content

Commit 5e5111b

Browse files
authored
Unified emulator settings for Database (#1672)
1 parent a7096ac commit 5e5111b

File tree

9 files changed

+331
-18
lines changed

9 files changed

+331
-18
lines changed

firebase-common/src/androidTest/java/com/google/firebase/FirebaseAppTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
import com.google.firebase.components.TestComponentOne;
4646
import com.google.firebase.components.TestComponentTwo;
4747
import com.google.firebase.components.TestUserAgentDependentComponent;
48+
import com.google.firebase.emulators.EmulatedServiceSettings;
49+
import com.google.firebase.emulators.EmulatorSettings;
50+
import com.google.firebase.emulators.FirebaseEmulator;
4851
import com.google.firebase.platforminfo.UserAgentPublisher;
4952
import com.google.firebase.testing.FirebaseAppRule;
5053
import java.lang.reflect.InvocationTargetException;
@@ -417,6 +420,43 @@ public void testDirectBoot_shouldPreserveDataCollectionAfterUnlock() {
417420
assertTrue(firebaseApp.isDataCollectionDefaultEnabled());
418421
}
419422

423+
@Test
424+
public void testEnableEmulators_shouldAllowDoubleSetBeforeAccess() {
425+
Context mockContext = createForwardingMockContext();
426+
FirebaseApp firebaseApp = FirebaseApp.initializeApp(mockContext);
427+
428+
// A developer would call FirebaseDatabase.EMULATOR but we can't introduce that
429+
// dependency for this test.
430+
FirebaseEmulator emulator = FirebaseEmulator.forName("database");
431+
432+
EmulatedServiceSettings databaseSettings = new EmulatedServiceSettings("10.0.2.2", 9000);
433+
EmulatorSettings emulatorSettings =
434+
new EmulatorSettings.Builder().addEmulatedService(emulator, databaseSettings).build();
435+
436+
// Set twice
437+
firebaseApp.enableEmulators(emulatorSettings);
438+
firebaseApp.enableEmulators(emulatorSettings);
439+
}
440+
441+
@Test
442+
public void testEnableEmulators_shouldThrowIfSetAfterAccess() {
443+
Context mockContext = createForwardingMockContext();
444+
FirebaseApp firebaseApp = FirebaseApp.initializeApp(mockContext);
445+
446+
FirebaseEmulator emulator = FirebaseEmulator.forName("database");
447+
448+
EmulatedServiceSettings databaseSettings = new EmulatedServiceSettings("10.0.2.2", 9000);
449+
EmulatorSettings emulatorSettings =
450+
new EmulatorSettings.Builder().addEmulatedService(emulator, databaseSettings).build();
451+
firebaseApp.enableEmulators(emulatorSettings);
452+
453+
// Access (as if from the Database SDK)
454+
firebaseApp.getEmulatorSettings().getServiceSettings(emulator);
455+
456+
// Try to set again
457+
assertThrows(IllegalStateException.class, () -> firebaseApp.enableEmulators(emulatorSettings));
458+
}
459+
420460
/** Returns mock context that forwards calls to targetContext and localBroadcastManager. */
421461
private Context createForwardingMockContext() {
422462
final UserManager spyUserManager = spy(targetContext.getSystemService(UserManager.class));

firebase-common/src/main/java/com/google/firebase/FirebaseApp.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import com.google.firebase.components.ComponentRegistrar;
4545
import com.google.firebase.components.ComponentRuntime;
4646
import com.google.firebase.components.Lazy;
47+
import com.google.firebase.emulators.EmulatorSettings;
4748
import com.google.firebase.events.Publisher;
4849
import com.google.firebase.heartbeatinfo.DefaultHeartBeatInfo;
4950
import com.google.firebase.internal.DataCollectionConfigStorage;
@@ -110,6 +111,9 @@ public class FirebaseApp {
110111
private final FirebaseOptions options;
111112
private final ComponentRuntime componentRuntime;
112113

114+
private final AtomicBoolean emulatorSettingsFrozen = new AtomicBoolean(false);
115+
private EmulatorSettings emulatorSettings = EmulatorSettings.DEFAULT;
116+
113117
// Default disabled. We released Firebase publicly without this feature, so making it default
114118
// enabled is a backwards incompatible change.
115119
private final AtomicBoolean automaticResourceManagementEnabled = new AtomicBoolean(false);
@@ -142,6 +146,20 @@ public FirebaseOptions getOptions() {
142146
return options;
143147
}
144148

149+
/**
150+
* Returns the specified {@link EmulatorSettings} or a default.
151+
*
152+
* <p>TODO(samstern): Un-hide this once Firestore, Database, and Functions are implemented
153+
*
154+
* @hide
155+
*/
156+
@NonNull
157+
public EmulatorSettings getEmulatorSettings() {
158+
checkNotDeleted();
159+
emulatorSettingsFrozen.set(true);
160+
return emulatorSettings;
161+
}
162+
145163
@Override
146164
public boolean equals(Object o) {
147165
if (!(o instanceof FirebaseApp)) {
@@ -305,6 +323,26 @@ public static FirebaseApp initializeApp(
305323
return firebaseApp;
306324
}
307325

326+
/**
327+
* Specify which services should access local emulators for this FirebaseApp instance.
328+
*
329+
* <p>For example, if the {@link EmulatorSettings} contain {@link
330+
* com.google.firebase.emulators.EmulatedServiceSettings} for {@link FirebaseDatabase#EMULATOR},
331+
* then calls to Cloud Firestore will communicate with the emulator rather than production.
332+
*
333+
* <p>TODO(samstern): Un-hide this once Firestore, Database, and Functions are implemented
334+
*
335+
* @param emulatorSettings the emulator settings for all services.
336+
* @hide
337+
*/
338+
public void enableEmulators(@NonNull EmulatorSettings emulatorSettings) {
339+
checkNotDeleted();
340+
Preconditions.checkState(
341+
!this.emulatorSettingsFrozen.get(),
342+
"Cannot enable emulators after Firebase SDKs have already been used.");
343+
this.emulatorSettings = emulatorSettings;
344+
}
345+
308346
/**
309347
* Deletes the {@link FirebaseApp} and all its data. All calls to this {@link FirebaseApp}
310348
* instance will throw once it has been called.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.emulators;
16+
17+
import androidx.annotation.NonNull;
18+
19+
/**
20+
* Settings to connect a single Firebase service to a local emulator.
21+
*
22+
* <p>TODO(samstern): Un-hide this once Firestore, Database, and Functions are implemented
23+
*
24+
* @see EmulatorSettings
25+
* @hide
26+
*/
27+
public final class EmulatedServiceSettings {
28+
29+
private final String host;
30+
private final int port;
31+
32+
public EmulatedServiceSettings(@NonNull String host, int port) {
33+
this.host = host;
34+
this.port = port;
35+
}
36+
37+
public String getHost() {
38+
return host;
39+
}
40+
41+
public int getPort() {
42+
return port;
43+
}
44+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.emulators;
16+
17+
import androidx.annotation.NonNull;
18+
import androidx.annotation.Nullable;
19+
import com.google.firebase.components.Preconditions;
20+
import java.util.Collections;
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
24+
/**
25+
* Settings that control which Firebase services should access a local emulator, rather than
26+
* production.
27+
*
28+
* <p>TODO(samstern): Un-hide this once Firestore, Database, and Functions are implemented
29+
*
30+
* @see com.google.firebase.FirebaseApp#enableEmulators(EmulatorSettings)
31+
* @hide
32+
*/
33+
public final class EmulatorSettings {
34+
35+
/** Empty emulator settings to be used as an internal default */
36+
public static final EmulatorSettings DEFAULT = new EmulatorSettings.Builder().build();
37+
38+
public static final class Builder {
39+
40+
private final Map<FirebaseEmulator, EmulatedServiceSettings> settingsMap = new HashMap<>();
41+
42+
/** Constructs an empty builder. */
43+
public Builder() {}
44+
45+
/**
46+
* Specify the emulator settings for a single service.
47+
*
48+
* @param emulator the emulated service.
49+
* @param settings the emulator settings.
50+
* @return the builder, for chaining.
51+
*/
52+
@NonNull
53+
public Builder addEmulatedService(
54+
@NonNull FirebaseEmulator emulator, @NonNull EmulatedServiceSettings settings) {
55+
Preconditions.checkState(
56+
!settingsMap.containsKey(emulator),
57+
"Cannot call addEmulatedService twice for " + emulator.toString());
58+
this.settingsMap.put(emulator, settings);
59+
return this;
60+
}
61+
62+
@NonNull
63+
public EmulatorSettings build() {
64+
return new EmulatorSettings(new HashMap<>(settingsMap));
65+
}
66+
}
67+
68+
private final Map<FirebaseEmulator, EmulatedServiceSettings> settingsMap;
69+
70+
private EmulatorSettings(@NonNull Map<FirebaseEmulator, EmulatedServiceSettings> settingsMap) {
71+
this.settingsMap = Collections.unmodifiableMap(settingsMap);
72+
}
73+
74+
/**
75+
* Fetch the emulation settings for a single Firebase service.
76+
*
77+
* @hide
78+
*/
79+
@Nullable
80+
public EmulatedServiceSettings getServiceSettings(@NonNull FirebaseEmulator emulator) {
81+
if (settingsMap.containsKey(emulator)) {
82+
return settingsMap.get(emulator);
83+
}
84+
85+
return null;
86+
}
87+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.emulators;
16+
17+
import androidx.annotation.NonNull;
18+
19+
/**
20+
* Identifier Firebase services that can be emulated using the Firebase Emulator Suite.
21+
*
22+
* <p>TODO(samstern): Un-hide this once Firestore, Database, and Functions are implemented
23+
*
24+
* @see com.google.firebase.FirebaseApp#enableEmulators(EmulatorSettings)
25+
* @see EmulatorSettings
26+
* @see EmulatedServiceSettings
27+
* @hide
28+
*/
29+
public final class FirebaseEmulator {
30+
31+
private final String name;
32+
33+
/**
34+
* Only to be called by SDKs which support emulators in order to make constants.
35+
*
36+
* @hide
37+
*/
38+
@NonNull
39+
public static FirebaseEmulator forName(String name) {
40+
return new FirebaseEmulator(name);
41+
}
42+
43+
private FirebaseEmulator(String name) {
44+
this.name = name;
45+
}
46+
47+
public String getName() {
48+
return name;
49+
}
50+
}

firebase-database/src/androidTest/java/com/google/firebase/database/FirebaseDatabaseTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import com.google.firebase.database.core.persistence.MockPersistenceStorageEngine;
3131
import com.google.firebase.database.core.persistence.PersistenceManager;
3232
import com.google.firebase.database.future.WriteFuture;
33+
import com.google.firebase.emulators.EmulatedServiceSettings;
34+
import com.google.firebase.emulators.EmulatorSettings;
3335
import java.util.ArrayList;
3436
import java.util.Arrays;
3537
import java.util.List;
@@ -64,6 +66,26 @@ public void getInstanceForApp() {
6466
assertEquals(IntegrationTestValues.getAltNamespace(), db.getReference().toString());
6567
}
6668

69+
@Test
70+
public void getInstanceForAppWithEmulator() {
71+
FirebaseApp app =
72+
appForDatabaseUrl(IntegrationTestValues.getAltNamespace(), "getInstanceForAppWithEmulator");
73+
74+
EmulatedServiceSettings serviceSettings = new EmulatedServiceSettings("10.0.2.2", 9000);
75+
EmulatorSettings emulatorSettings =
76+
new EmulatorSettings.Builder()
77+
.addEmulatedService(FirebaseDatabase.EMULATOR, serviceSettings)
78+
.build();
79+
app.enableEmulators(emulatorSettings);
80+
81+
FirebaseDatabase db = FirebaseDatabase.getInstance(app);
82+
DatabaseReference rootRef = db.getReference();
83+
assertEquals(rootRef.toString(), "http://10.0.2.2:9000");
84+
85+
DatabaseReference urlReference = db.getReferenceFromUrl("https://otherns.firebaseio.com");
86+
assertEquals(urlReference.toString(), "http://10.0.2.2:9000");
87+
}
88+
6789
@Test
6890
public void getInstanceForAppWithUrl() {
6991
FirebaseApp app =

0 commit comments

Comments
 (0)