|
32 | 32 | import static java.util.Collections.emptyList;
|
33 | 33 | import static java.util.Collections.singletonList;
|
34 | 34 |
|
| 35 | +import com.google.firebase.Timestamp; |
35 | 36 | import com.google.firebase.firestore.FieldValue;
|
36 | 37 | import com.google.firebase.firestore.core.Query;
|
| 38 | +import com.google.firebase.firestore.model.DocumentKey; |
37 | 39 | import com.google.firebase.firestore.model.FieldIndex;
|
| 40 | +import com.google.firebase.firestore.model.FieldPath; |
| 41 | +import com.google.firebase.firestore.model.ObjectValue; |
| 42 | +import com.google.firebase.firestore.model.ServerTimestamps; |
| 43 | +import com.google.firebase.firestore.model.mutation.FieldMask; |
| 44 | +import com.google.firebase.firestore.model.mutation.FieldTransform; |
| 45 | +import com.google.firebase.firestore.model.mutation.PatchMutation; |
| 46 | +import com.google.firebase.firestore.model.mutation.Precondition; |
| 47 | +import com.google.firebase.firestore.model.mutation.ServerTimestampOperation; |
| 48 | +import com.google.firestore.v1.Value; |
| 49 | +import java.util.ArrayList; |
38 | 50 | import java.util.Arrays;
|
39 | 51 | import java.util.Collection;
|
40 | 52 | import java.util.Collections;
|
| 53 | +import java.util.HashMap; |
| 54 | +import java.util.HashSet; |
| 55 | +import java.util.List; |
| 56 | +import java.util.Map; |
| 57 | +import java.util.concurrent.atomic.AtomicReference; |
41 | 58 | import org.junit.Test;
|
42 | 59 | import org.junit.runner.RunWith;
|
43 | 60 | import org.robolectric.RobolectricTestRunner;
|
@@ -290,4 +307,63 @@ public void testIndexesServerTimestamps() {
|
290 | 307 | assertOverlayTypes(keyMap("coll/a", CountingQueryEngine.OverlayType.Set));
|
291 | 308 | assertQueryReturned("coll/a");
|
292 | 309 | }
|
| 310 | + |
| 311 | + @Test |
| 312 | + public void testDeeplyNestedServerTimestamps() { |
| 313 | + Timestamp timestamp = Timestamp.now(); |
| 314 | + Value initialServerTimestamp = ServerTimestamps.valueOf(timestamp, null); |
| 315 | + Map<String, Value> fields = |
| 316 | + new HashMap<String, Value>() { |
| 317 | + { |
| 318 | + put("timestamp", ServerTimestamps.valueOf(timestamp, initialServerTimestamp)); |
| 319 | + } |
| 320 | + }; |
| 321 | + FieldPath path = FieldPath.fromSingleSegment("timestamp"); |
| 322 | + FieldMask mask = |
| 323 | + FieldMask.fromSet( |
| 324 | + new HashSet<FieldPath>() { |
| 325 | + { |
| 326 | + add(path); |
| 327 | + } |
| 328 | + }); |
| 329 | + FieldTransform fieldTransform = |
| 330 | + new FieldTransform(path, ServerTimestampOperation.getInstance()); |
| 331 | + List<FieldTransform> fieldTransforms = |
| 332 | + new ArrayList<FieldTransform>() { |
| 333 | + { |
| 334 | + add(fieldTransform); |
| 335 | + } |
| 336 | + }; |
| 337 | + // The purpose of this test is to ensure that deeply nested server timestamps do not result in |
| 338 | + // a stack overflow error. Below we use a `Thread` object to create a large number of mutations |
| 339 | + // because the `Thread` class allows us to specify the maximum stack size. |
| 340 | + AtomicReference<Throwable> error = new AtomicReference<>(); |
| 341 | + Thread thread = |
| 342 | + new Thread( |
| 343 | + Thread.currentThread().getThreadGroup(), |
| 344 | + () -> { |
| 345 | + try { |
| 346 | + for (int i = 0; i < 1000; ++i) { |
| 347 | + writeMutation( |
| 348 | + new PatchMutation( |
| 349 | + DocumentKey.fromPathString("some/object/for/test"), |
| 350 | + ObjectValue.fromMap(fields), |
| 351 | + mask, |
| 352 | + Precondition.NONE, |
| 353 | + fieldTransforms)); |
| 354 | + } |
| 355 | + } catch (Throwable e) { |
| 356 | + error.set(e); |
| 357 | + } |
| 358 | + }, |
| 359 | + /* name */ "test", |
| 360 | + /* stackSize */ 1024 * 1024); |
| 361 | + try { |
| 362 | + thread.start(); |
| 363 | + thread.join(); |
| 364 | + } catch (InterruptedException e) { |
| 365 | + throw new AssertionError(e); |
| 366 | + } |
| 367 | + assertThat(error.get()).isNull(); |
| 368 | + } |
293 | 369 | }
|
0 commit comments