16
16
17
17
import static com .google .firebase .firestore .testutil .TestUtil .map ;
18
18
import static com .google .firebase .firestore .util .Util .autoId ;
19
+ import static junit .framework .Assert .assertEquals ;
19
20
import static junit .framework .Assert .assertNull ;
20
21
import static junit .framework .Assert .fail ;
21
22
@@ -62,20 +63,18 @@ public class IntegrationTestUtil {
62
63
/** Online status of all active Firestore clients. */
63
64
private static final Map <FirebaseFirestore , Boolean > firestoreStatus = new HashMap <>();
64
65
65
- private static final long SEMAPHORE_WAIT_TIMEOUT_MS = 30000 ;
66
- private static final long SHUTDOWN_WAIT_TIMEOUT_MS = 10000 ;
67
- private static final long BATCH_WAIT_TIMEOUT_MS = 120000 ;
68
-
69
- private static final FirestoreProvider provider = new FirestoreProvider ();
66
+ /** Default amount of time to wait for a given operation to complete, used by waitFor() helper. */
67
+ static final long OPERATION_WAIT_TIMEOUT_MS = 10000 ;
70
68
71
69
/**
72
- * TODO: There's some flakiness with hexa / emulator / whatever that causes the first write in a
73
- * run to frequently time out. So for now we always send an initial write with an extra long
74
- * timeout to improve test reliability.
70
+ * Firestore databases can be subject to a ~30s "cold start" delay if they have not been used
71
+ * recently, so before any tests run we "prime" the backend.
75
72
*/
76
- private static final long FIRST_WRITE_TIMEOUT_MS = 60000 ;
73
+ private static final long PRIMING_TIMEOUT_MS = 45000 ;
74
+
75
+ private static final FirestoreProvider provider = new FirestoreProvider ();
77
76
78
- private static boolean sentFirstWrite = false ;
77
+ private static boolean backendPrimed = false ;
79
78
80
79
public static FirestoreProvider provider () {
81
80
return provider ;
@@ -113,15 +112,37 @@ public static FirebaseFirestore testFirestore() {
113
112
*/
114
113
public static FirebaseFirestore testFirestore (FirebaseFirestoreSettings settings ) {
115
114
FirebaseFirestore firestore = testFirestore (provider .projectId (), Level .DEBUG , settings );
116
- if (!sentFirstWrite ) {
117
- sentFirstWrite = true ;
118
- waitFor (
119
- firestore .document ("test-collection/initial-write-doc" ).set (map ("foo" , 1 )),
120
- FIRST_WRITE_TIMEOUT_MS );
115
+ if (!backendPrimed ) {
116
+ backendPrimed = true ;
117
+ primeBackend ();
121
118
}
122
119
return firestore ;
123
120
}
124
121
122
+ private static void primeBackend () {
123
+ EventAccumulator <DocumentSnapshot > accumulator = new EventAccumulator <>();
124
+ DocumentReference docRef = testDocument ();
125
+ ListenerRegistration listenerRegistration = docRef .addSnapshotListener (accumulator .listener ());
126
+
127
+ // Wait for watch to initialize and deliver first event.
128
+ accumulator .awaitRemoteEvent ();
129
+
130
+ // Use a transaction to perform a write without triggering any local events.
131
+ docRef
132
+ .getFirestore ()
133
+ .runTransaction (
134
+ transaction -> {
135
+ transaction .set (docRef , map ("value" , "done" ));
136
+ return null ;
137
+ });
138
+
139
+ // Wait to see the write on the watch stream.
140
+ DocumentSnapshot docSnap = accumulator .await (PRIMING_TIMEOUT_MS );
141
+ assertEquals ("done" , docSnap .get ("value" ));
142
+
143
+ listenerRegistration .remove ();
144
+ }
145
+
125
146
/** Initializes a new Firestore instance that uses a non-existing default project. */
126
147
public static FirebaseFirestore testAlternateFirestore () {
127
148
return testFirestore (BAD_PROJECT_ID , Level .DEBUG , newTestSettings ());
@@ -181,11 +202,7 @@ public static void tearDown() {
181
202
try {
182
203
for (FirebaseFirestore firestore : firestoreStatus .keySet ()) {
183
204
Task <Void > result = AccessHelper .shutdown (firestore );
184
- try {
185
- Tasks .await (result , SHUTDOWN_WAIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
186
- } catch (TimeoutException | ExecutionException | InterruptedException e ) {
187
- throw new RuntimeException (e );
188
- }
205
+ waitFor (result );
189
206
}
190
207
} finally {
191
208
firestoreStatus .clear ();
@@ -246,7 +263,7 @@ public static void waitFor(Semaphore semaphore) {
246
263
public static void waitFor (Semaphore semaphore , int count ) {
247
264
try {
248
265
boolean acquired =
249
- semaphore .tryAcquire (count , SEMAPHORE_WAIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
266
+ semaphore .tryAcquire (count , OPERATION_WAIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
250
267
if (!acquired ) {
251
268
throw new TimeoutException ("Failed to acquire semaphore within test timeout" );
252
269
}
@@ -257,7 +274,7 @@ public static void waitFor(Semaphore semaphore, int count) {
257
274
258
275
public static void waitFor (CountDownLatch countDownLatch ) {
259
276
try {
260
- boolean acquired = countDownLatch .await (SEMAPHORE_WAIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
277
+ boolean acquired = countDownLatch .await (OPERATION_WAIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
261
278
if (!acquired ) {
262
279
throw new TimeoutException ("Failed to acquire countdown latch within test timeout" );
263
280
}
@@ -266,16 +283,8 @@ public static void waitFor(CountDownLatch countDownLatch) {
266
283
}
267
284
}
268
285
269
- public static void waitFor (List <Task <?>> task ) {
270
- try {
271
- Tasks .await (Tasks .whenAll (task ), BATCH_WAIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
272
- } catch (TimeoutException | ExecutionException | InterruptedException e ) {
273
- throw new RuntimeException (e );
274
- }
275
- }
276
-
277
286
public static <T > T waitFor (Task <T > task ) {
278
- return waitFor (task , SEMAPHORE_WAIT_TIMEOUT_MS );
287
+ return waitFor (task , OPERATION_WAIT_TIMEOUT_MS );
279
288
}
280
289
281
290
public static <T > T waitFor (Task <T > task , long timeoutMS ) {
@@ -288,7 +297,7 @@ public static <T> T waitFor(Task<T> task, long timeoutMS) {
288
297
289
298
public static <T > Exception waitForException (Task <T > task ) {
290
299
try {
291
- Tasks .await (task , SEMAPHORE_WAIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
300
+ Tasks .await (task , OPERATION_WAIT_TIMEOUT_MS , TimeUnit .MILLISECONDS );
292
301
throw new RuntimeException ("Expected Exception but Task completed successfully." );
293
302
} catch (ExecutionException e ) {
294
303
return (Exception ) e .getCause ();
0 commit comments