From 270725fa78a397b1114a627499657a45d28c94eb Mon Sep 17 00:00:00 2001 From: wu-hui Date: Tue, 5 Aug 2025 10:42:33 -0400 Subject: [PATCH] Port spec tests for realtime ppl --- .../firebase/firestore/core/PipelineUtil.kt | 1 - .../spec/MemoryPipelineSpecTest.java | 21 + .../firebase/firestore/spec/QueryEvent.java | 6 +- .../spec/SQLitePipelineSpecTest.java | 21 + .../firebase/firestore/spec/SpecTestCase.java | 85 +- .../src/test/resources/json/README.md | 3 + .../test/resources/json/bundle_spec_test.json | 4 +- .../json/existence_filter_spec_test.json | 4 +- .../test/resources/json/index_spec_test.json | 12 +- .../test/resources/json/limbo_spec_test.json | 1916 ++++++++++++++++- .../json/listen_source_spec_test.json | 18 +- .../test/resources/json/listen_spec_test.json | 354 ++- .../test/resources/json/query_spec_test.json | 318 +++ 13 files changed, 2729 insertions(+), 34 deletions(-) create mode 100644 firebase-firestore/src/test/java/com/google/firebase/firestore/spec/MemoryPipelineSpecTest.java create mode 100644 firebase-firestore/src/test/java/com/google/firebase/firestore/spec/SQLitePipelineSpecTest.java create mode 100644 firebase-firestore/src/test/resources/json/README.md diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/PipelineUtil.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/PipelineUtil.kt index 1d215be76f8..a44bb26e969 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/PipelineUtil.kt +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/PipelineUtil.kt @@ -19,7 +19,6 @@ package com.google.firebase.firestore.core import com.google.firebase.firestore.RealtimePipeline import com.google.firebase.firestore.model.Document import com.google.firebase.firestore.model.ResourcePath -import com.google.firebase.firestore.model.Values import com.google.firebase.firestore.pipeline.CollectionGroupSource import com.google.firebase.firestore.pipeline.CollectionSource import com.google.firebase.firestore.pipeline.DatabaseSource diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/MemoryPipelineSpecTest.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/MemoryPipelineSpecTest.java new file mode 100644 index 00000000000..a7959e4d2a2 --- /dev/null +++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/MemoryPipelineSpecTest.java @@ -0,0 +1,21 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.firestore.spec; + +public class MemoryPipelineSpecTest extends MemorySpecTest { + public MemoryPipelineSpecTest() { + usePipelineMode = true; + } +} diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/QueryEvent.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/QueryEvent.java index ddb63712e2a..9497c7ed0eb 100644 --- a/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/QueryEvent.java +++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/QueryEvent.java @@ -16,17 +16,17 @@ import androidx.annotation.Nullable; import com.google.firebase.firestore.FirebaseFirestoreException; -import com.google.firebase.firestore.core.Query; +import com.google.firebase.firestore.core.QueryOrPipeline; import com.google.firebase.firestore.core.ViewSnapshot; /** Object that contains exactly one of either a view snapshot or an error for the given query. */ public class QueryEvent { - public Query query; + public QueryOrPipeline queryOrPipeline; public @Nullable ViewSnapshot view; public @Nullable FirebaseFirestoreException error; @Override public String toString() { - return "QueryEvent(" + query + ", " + view + ", " + error + ")"; + return "QueryEvent(" + queryOrPipeline + ", " + view + ", " + error + ")"; } } diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/SQLitePipelineSpecTest.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/SQLitePipelineSpecTest.java new file mode 100644 index 00000000000..b90b4906990 --- /dev/null +++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/SQLitePipelineSpecTest.java @@ -0,0 +1,21 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.firestore.spec; + +public class SQLitePipelineSpecTest extends SQLiteSpecTest { + public SQLitePipelineSpecTest() { + usePipelineMode = true; + } +} diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/SpecTestCase.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/SpecTestCase.java index f2757c267ea..3b5daa088c9 100644 --- a/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/SpecTestCase.java +++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/spec/SpecTestCase.java @@ -41,8 +41,10 @@ import com.google.firebase.firestore.EventListener; import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.FirebaseFirestoreIntegrationTestFactory; import com.google.firebase.firestore.ListenSource; import com.google.firebase.firestore.LoadBundleTask; +import com.google.firebase.firestore.UserDataReader; import com.google.firebase.firestore.auth.User; import com.google.firebase.firestore.bundle.BundleReader; import com.google.firebase.firestore.bundle.BundleSerializer; @@ -57,6 +59,7 @@ import com.google.firebase.firestore.core.QueryListener; import com.google.firebase.firestore.core.QueryOrPipeline; import com.google.firebase.firestore.core.SyncEngine; +import com.google.firebase.firestore.core.Target; import com.google.firebase.firestore.core.TargetOrPipeline; import com.google.firebase.firestore.local.LocalStore; import com.google.firebase.firestore.local.LruDelegate; @@ -104,6 +107,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -163,6 +167,8 @@ public abstract class SpecTestCase implements RemoteStoreCallback { // separated by a space character. private static final String TEST_FILTER_PROPERTY = "specTestFilter"; + private static final String NO_PIPELINE_CONVERSION_TAG = "no-pipeline-conversion"; + // Tags on tests that should be excluded from execution, useful to allow the platforms to // temporarily diverge or for features that are designed to be platform specific (such as // 'multi-client'). @@ -174,6 +180,9 @@ public abstract class SpecTestCase implements RemoteStoreCallback { private boolean useEagerGcForMemory; private int maxConcurrentLimboResolutions; private boolean networkEnabled = true; + protected boolean usePipelineMode = false; + + private FirebaseFirestore db; // // Parts of the Firestore system that the spec tests need to control. @@ -196,7 +205,7 @@ public abstract class SpecTestCase implements RemoteStoreCallback { * A dictionary for tracking the listens on queries. Note that the identity of the listeners is * used to remove them. */ - private Map queryListeners; + private Map queryListeners; /** * Set of documents that are expected to be in limbo with an active target. Verified at every @@ -291,6 +300,7 @@ protected void specSetUp(JSONObject config) { currentUser = User.UNAUTHENTICATED; databaseInfo = PersistenceTestHelpers.nextDatabaseInfo(); + db = new FirebaseFirestoreIntegrationTestFactory(databaseInfo.getDatabaseId()).firestore; if (config.optInt("numClients", 1) != 1) { throw Assert.fail("The Android client does not support multi-client tests"); @@ -577,13 +587,22 @@ private void doListen(JSONObject listenSpec) throws Exception { Query query = parseQuery(listenSpec.getJSONObject("query")); ListenOptions options = parseListenOptions(listenSpec); + QueryOrPipeline queryOrPipeline; + if (usePipelineMode) { + queryOrPipeline = + new QueryOrPipeline.PipelineWrapper( + query.toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId()))); + } else { + queryOrPipeline = new QueryOrPipeline.QueryWrapper(query); + } + QueryListener listener = new QueryListener( - new QueryOrPipeline.QueryWrapper(query), + queryOrPipeline, options, (value, error) -> { QueryEvent event = new QueryEvent(); - event.query = query; + event.queryOrPipeline = queryOrPipeline; if (value != null) { event.view = value; } else { @@ -591,7 +610,7 @@ private void doListen(JSONObject listenSpec) throws Exception { } events.add(event); }); - queryListeners.put(query, listener); + queryListeners.put(queryOrPipeline, listener); queue.runSync( () -> { int actualId = eventManager.addQueryListener(listener); @@ -601,7 +620,15 @@ private void doListen(JSONObject listenSpec) throws Exception { private void doUnlisten(JSONArray unlistenSpec) throws Exception { Query query = parseQuery(unlistenSpec.get(1)); - QueryListener listener = queryListeners.remove(query); + QueryOrPipeline queryOrPipeline; + if (usePipelineMode) { + queryOrPipeline = + new QueryOrPipeline.PipelineWrapper( + query.toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId()))); + } else { + queryOrPipeline = new QueryOrPipeline.QueryWrapper(query); + } + QueryListener listener = queryListeners.remove(queryOrPipeline); queue.runSync(() -> eventManager.removeQueryListener(listener)); } @@ -990,7 +1017,14 @@ private void doStep(JSONObject step) throws Exception { private void assertEventMatches(JSONObject expected, QueryEvent actual) throws JSONException { Query expectedQuery = parseQuery(expected.get("query")); - assertEquals(expectedQuery, actual.query); + if (usePipelineMode) { + assertEquals( + expectedQuery.toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId())), + actual.queryOrPipeline.pipeline()); + } else { + assertEquals(expectedQuery, actual.queryOrPipeline.query()); + } + if (expected.has("errorCode") && !Status.fromCodeValue(expected.getInt("errorCode")).isOk()) { assertNotNull(actual.error); assertEquals(expected.getInt("errorCode"), actual.error.getCode().value()); @@ -1041,7 +1075,7 @@ private void validateExpectedSnapshotEvents(@Nullable JSONArray expectedEventsJs } // Sort both the expected and actual events by the query's canonical ID. - events.sort((q1, q2) -> q1.query.getCanonicalId().compareTo(q2.query.getCanonicalId())); + events.sort(Comparator.comparing(q -> q.queryOrPipeline.canonicalId())); List expectedEvents = new ArrayList<>(); for (int i = 0; i < expectedEventsJson.length(); ++i) { @@ -1052,6 +1086,16 @@ private void validateExpectedSnapshotEvents(@Nullable JSONArray expectedEventsJs try { Query leftQuery = parseQuery(left.get("query")); Query rightQuery = parseQuery(right.get("query")); + if (usePipelineMode) { + return leftQuery + .toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId())) + .canonicalId() + .compareTo( + rightQuery + .toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId())) + .canonicalId()); + } + return leftQuery.getCanonicalId().compareTo(rightQuery.getCanonicalId()); } catch (JSONException e) { throw new RuntimeException("Failed to parse JSON during event sorting", e); @@ -1270,9 +1314,25 @@ private void validateActiveTargets() { // with the single assertEquals on the TargetData objects themselves if the sequenceNumber is // ever made to be consistent. // assertEquals(expectedTarget, actualTarget); - assertEquals(expectedTarget.getPurpose(), actualTarget.getPurpose()); - assertEquals(expectedTarget.getTarget(), actualTarget.getTarget()); + if (usePipelineMode && !expectedTarget.getPurpose().equals(QueryPurpose.LIMBO_RESOLUTION)) { + Target target = expectedTarget.getTarget().target(); + assertEquals( + new TargetOrPipeline.PipelineWrapper( + new Query( + target.getPath(), + target.getCollectionGroup(), + target.getFilters(), + target.getOrderBy(), + target.getLimit(), + Query.LimitType.LIMIT_TO_FIRST, + target.getStartAt(), + target.getEndAt()) + .toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId()))), + actualTarget.getTarget()); + } else { + assertEquals(expectedTarget.getTarget(), actualTarget.getTarget()); + } assertEquals(expectedTarget.getTargetId(), actualTarget.getTargetId()); assertEquals(expectedTarget.getSnapshotVersion(), actualTarget.getSnapshotVersion()); assertEquals( @@ -1392,6 +1452,10 @@ public void testSpecTests() throws Exception { JSONArray steps = testJSON.getJSONArray("steps"); Set tags = getTestTags(testJSON); + if (name.contains("Newer ")) { + info("Skipping test: " + name); + } + boolean runTest; if (!shouldRunTest(tags)) { runTest = false; @@ -1443,6 +1507,9 @@ private static boolean anyTestsAreMarkedExclusive(JSONObject fileJSON) throws JS /** Called before executing each test to see if it should be run. */ private boolean shouldRunTest(Set tags) { + if (usePipelineMode && tags.contains(NO_PIPELINE_CONVERSION_TAG)) { + return false; + } return shouldRun(tags); } diff --git a/firebase-firestore/src/test/resources/json/README.md b/firebase-firestore/src/test/resources/json/README.md new file mode 100644 index 00000000000..bcc9b38bc1d --- /dev/null +++ b/firebase-firestore/src/test/resources/json/README.md @@ -0,0 +1,3 @@ +These json files are generated from the web test sources. + +TODO(mikelehen): Re-add instructions for generating these. diff --git a/firebase-firestore/src/test/resources/json/bundle_spec_test.json b/firebase-firestore/src/test/resources/json/bundle_spec_test.json index 028895c50ac..53d26b5dce1 100644 --- a/firebase-firestore/src/test/resources/json/bundle_spec_test.json +++ b/firebase-firestore/src/test/resources/json/bundle_spec_test.json @@ -3,7 +3,8 @@ "describeName": "Bundles:", "itName": "Bundles query can be loaded and resumed from different tabs", "tags": [ - "multi-client" + "multi-client", + "no-pipeline-conversion" ], "config": { "numClients": 2, @@ -225,6 +226,7 @@ "describeName": "Bundles:", "itName": "Bundles query can be resumed from same query.", "tags": [ + "no-pipeline-conversion" ], "config": { "numClients": 1, diff --git a/firebase-firestore/src/test/resources/json/existence_filter_spec_test.json b/firebase-firestore/src/test/resources/json/existence_filter_spec_test.json index ae64f7aad82..cf0d49885d2 100644 --- a/firebase-firestore/src/test/resources/json/existence_filter_spec_test.json +++ b/firebase-firestore/src/test/resources/json/existence_filter_spec_test.json @@ -6967,9 +6967,9 @@ } ] }, - "Full re-query is triggered when bloom filter can not identify documents deleted": { + "Full re-query is triggered when bloom filter cannot identify documents deleted": { "describeName": "Existence Filters:", - "itName": "Full re-query is triggered when bloom filter can not identify documents deleted", + "itName": "Full re-query is triggered when bloom filter cannot identify documents deleted", "tags": [ ], "config": { diff --git a/firebase-firestore/src/test/resources/json/index_spec_test.json b/firebase-firestore/src/test/resources/json/index_spec_test.json index 9e704e75be1..c1880c15cee 100644 --- a/firebase-firestore/src/test/resources/json/index_spec_test.json +++ b/firebase-firestore/src/test/resources/json/index_spec_test.json @@ -71,7 +71,8 @@ "readTime": { "timestamp": { "nanoseconds": 0, - "seconds": 0 + "seconds": 0, + "type": "firestore/timestamp/1.0" } } }, @@ -115,7 +116,8 @@ "readTime": { "timestamp": { "nanoseconds": 0, - "seconds": 0 + "seconds": 0, + "type": "firestore/timestamp/1.0" } } }, @@ -192,7 +194,8 @@ "readTime": { "timestamp": { "nanoseconds": 0, - "seconds": 0 + "seconds": 0, + "type": "firestore/timestamp/1.0" } } }, @@ -236,7 +239,8 @@ "readTime": { "timestamp": { "nanoseconds": 0, - "seconds": 0 + "seconds": 0, + "type": "firestore/timestamp/1.0" } } }, diff --git a/firebase-firestore/src/test/resources/json/limbo_spec_test.json b/firebase-firestore/src/test/resources/json/limbo_spec_test.json index 6cb27ecc40d..19cdbaa2195 100644 --- a/firebase-firestore/src/test/resources/json/limbo_spec_test.json +++ b/firebase-firestore/src/test/resources/json/limbo_spec_test.json @@ -2944,6 +2944,1916 @@ } ] }, + "Fix #8474 - Handles code path of no ack for limbo resolution query before global snapshot": { + "describeName": "Limbo Documents:", + "itName": "Fix #8474 - Handles code path of no ack for limbo resolution query before global snapshot", + "tags": [ + "no-ios", + "no-android" + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": false, + "key": "a" + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1001" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1001 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": false, + "key": "a" + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchEntity": { + "key": "collection/c", + "removedTargets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1002" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1002 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeLimboDocs": [ + "collection/c" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/c" + } + ], + "resumeToken": "", + "targetPurpose": "TargetPurposeLimboResolution" + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "userListen": { + "options": { + "includeMetadataChanges": true, + "waitForSyncWhenOnline": true + }, + "query": { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/c" + } + ], + "resumeToken": "", + "targetPurpose": "TargetPurposeLimboResolution" + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "4": { + "queries": [ + { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 4 + ], + "expectedState": { + "activeLimboDocs": [ + "collection/c" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/c" + } + ], + "resumeToken": "", + "targetPurpose": "TargetPurposeLimboResolution" + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "4": { + "queries": [ + { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchEntity": { + "key": "collection/a", + "removedTargets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1004" + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1005" + ] + }, + { + "watchEntity": { + "key": "collection/c", + "removedTargets": [ + 4 + ] + } + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "a" + }, + "version": 1007 + } + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1007" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + 2, + 1 + ], + "version": 1010 + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1010 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "modified": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "a" + }, + "version": 1007 + } + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + }, + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "a" + }, + "version": 1007 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchAck": [ + 1 + ] + }, + { + "watchEntity": { + "doc": { + "createTime": 0, + "key": "collection/c", + "value": null, + "version": 1009 + }, + "removedTargets": [ + 1 + ] + } + }, + { + "watchCurrent": [ + [ + 1 + ], + "resume-token-1009" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + 1 + ], + "version": 1100 + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1101 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ] + }, + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ] + } + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "4": { + "queries": [ + { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + } + ] + }, + "Fix #8474 - Limbo resolution for document is removed even if document updates for the document occurred before documentDelete in the global snapshot window": { + "describeName": "Limbo Documents:", + "itName": "Fix #8474 - Limbo resolution for document is removed even if document updates for the document occurred before documentDelete in the global snapshot window", + "tags": [ + "no-ios", + "no-android" + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": false, + "key": "a" + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1001" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1001 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": false, + "key": "a" + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchEntity": { + "key": "collection/c", + "removedTargets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1002" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1002 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeLimboDocs": [ + "collection/c" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/c" + } + ], + "resumeToken": "", + "targetPurpose": "TargetPurposeLimboResolution" + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "userListen": { + "options": { + "includeMetadataChanges": true, + "waitForSyncWhenOnline": true + }, + "query": { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/c" + } + ], + "resumeToken": "", + "targetPurpose": "TargetPurposeLimboResolution" + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "4": { + "queries": [ + { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 1 + ] + }, + { + "watchAck": [ + 4 + ] + }, + { + "watchEntity": { + "doc": { + "createTime": 0, + "key": "collection/c", + "value": null, + "version": 1009 + }, + "removedTargets": [ + 1 + ] + } + }, + { + "watchCurrent": [ + [ + 1 + ], + "resume-token-1009" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + 1, + 2 + ], + "version": 1009 + }, + "expectedState": { + "activeLimboDocs": [ + "collection/c" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/c" + } + ], + "resumeToken": "", + "targetPurpose": "TargetPurposeLimboResolution" + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "4": { + "queries": [ + { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchEntity": { + "key": "collection/a", + "removedTargets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1004" + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1005" + ] + }, + { + "watchEntity": { + "key": "collection/c", + "removedTargets": [ + 4 + ] + } + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "a" + }, + "version": 1007 + } + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1007" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + 2, + 1 + ], + "version": 1010 + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1010 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "modified": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "a" + }, + "version": 1007 + } + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ] + }, + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "a" + }, + "version": 1007 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ] + } + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "4": { + "queries": [ + { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1100 + } + } + ] + }, + "Fix #8474 - Limbo resolution for document is removed even if document updates for the document occurred in the global snapshot window and no document delete was received for the limbo resolution query": { + "describeName": "Limbo Documents:", + "itName": "Fix #8474 - Limbo resolution for document is removed even if document updates for the document occurred in the global snapshot window and no document delete was received for the limbo resolution query", + "tags": [ + "no-ios", + "no-android" + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": false, + "key": "a" + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1001" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1001 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": false, + "key": "a" + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchEntity": { + "key": "collection/c", + "removedTargets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1002" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1002 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeLimboDocs": [ + "collection/c" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/c" + } + ], + "resumeToken": "", + "targetPurpose": "TargetPurposeLimboResolution" + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "userListen": { + "options": { + "includeMetadataChanges": true, + "waitForSyncWhenOnline": true + }, + "query": { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/c" + } + ], + "resumeToken": "", + "targetPurpose": "TargetPurposeLimboResolution" + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "4": { + "queries": [ + { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 1 + ] + }, + { + "watchAck": [ + 4 + ] + }, + { + "watchCurrent": [ + [ + 1 + ], + "resume-token-1009" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + 1, + 2 + ], + "version": 1009 + }, + "expectedState": { + "activeLimboDocs": [ + "collection/c" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/c" + } + ], + "resumeToken": "", + "targetPurpose": "TargetPurposeLimboResolution" + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "4": { + "queries": [ + { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchEntity": { + "key": "collection/a", + "removedTargets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1004" + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1005" + ] + }, + { + "watchEntity": { + "key": "collection/c", + "removedTargets": [ + 4 + ] + } + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "a" + }, + "version": 1007 + } + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-1007" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + 2, + 1 + ], + "version": 1010 + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1010 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "modified": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "a" + }, + "version": 1007 + } + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ] + }, + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "a" + }, + "version": 1007 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "included": true, + "key": "c" + }, + "version": 1002 + } + ] + } + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "4": { + "queries": [ + { + "filters": [ + [ + "included", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1100 + } + } + ] + }, "Limbo docs are resolved by primary client": { "describeName": "Limbo Documents:", "itName": "Limbo docs are resolved by primary client", @@ -10103,7 +12013,8 @@ "describeName": "Limbo Documents:", "itName": "LimitToLast query from secondary results in expected limbo doc", "tags": [ - "multi-client" + "multi-client", + "no-pipeline-conversion" ], "config": { "numClients": 2, @@ -10462,7 +12373,8 @@ "describeName": "Limbo Documents:", "itName": "LimitToLast query from secondary results in no expected limbo doc", "tags": [ - "multi-client" + "multi-client", + "no-pipeline-conversion" ], "config": { "numClients": 2, diff --git a/firebase-firestore/src/test/resources/json/listen_source_spec_test.json b/firebase-firestore/src/test/resources/json/listen_source_spec_test.json index 1912afc320f..e390612aaaf 100644 --- a/firebase-firestore/src/test/resources/json/listen_source_spec_test.json +++ b/firebase-firestore/src/test/resources/json/listen_source_spec_test.json @@ -1603,7 +1603,7 @@ } ], "errorCode": 0, - "fromCache": true, + "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ @@ -1655,7 +1655,7 @@ } ], "errorCode": 0, - "fromCache": true, + "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ @@ -1996,7 +1996,8 @@ "describeName": "Listens source options:", "itName": "Mirror queries being listened from different sources while listening to server in primary tab", "tags": [ - "multi-client" + "multi-client", + "no-pipeline-conversion" ], "config": { "numClients": 2, @@ -2211,7 +2212,7 @@ } ], "errorCode": 0, - "fromCache": true, + "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ @@ -3233,7 +3234,8 @@ "describeName": "Listens source options:", "itName": "Mirror queries from different sources while listening to server in secondary tab", "tags": [ - "multi-client" + "multi-client", + "no-pipeline-conversion" ], "config": { "numClients": 2, @@ -3482,7 +3484,7 @@ } ], "errorCode": 0, - "fromCache": true, + "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ @@ -5490,7 +5492,7 @@ } ], "errorCode": 0, - "fromCache": true, + "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ @@ -5556,7 +5558,7 @@ } ], "errorCode": 0, - "fromCache": true, + "fromCache": false, "hasPendingWrites": true, "query": { "filters": [ diff --git a/firebase-firestore/src/test/resources/json/listen_spec_test.json b/firebase-firestore/src/test/resources/json/listen_spec_test.json index 7370a0cd675..b2810738225 100644 --- a/firebase-firestore/src/test/resources/json/listen_spec_test.json +++ b/firebase-firestore/src/test/resources/json/listen_spec_test.json @@ -333,6 +333,7 @@ "describeName": "Listens:", "itName": "Can listen/unlisten to mirror queries.", "tags": [ + "no-pipeline-conversion" ], "config": { "numClients": 1, @@ -3534,6 +3535,345 @@ } ] }, + "Global snapshots would not alter query state if there is no changes": { + "describeName": "Listens:", + "itName": "Global snapshots would not alter query state if there is no changes", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0, + "expectedState": { + "isPrimary": true + } + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 0, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "resume-token-1000" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "resumeToken": "resume-token-3000", + "targetIds": [ + ], + "version": 3000 + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + } + ] + }, "Ignores update from inactive target": { "describeName": "Listens:", "itName": "Ignores update from inactive target", @@ -5984,7 +6324,8 @@ "describeName": "Listens:", "itName": "Mirror queries from different secondary client", "tags": [ - "multi-client" + "multi-client", + "no-pipeline-conversion" ], "config": { "numClients": 3, @@ -6424,7 +6765,8 @@ "describeName": "Listens:", "itName": "Mirror queries from primary and secondary client", "tags": [ - "multi-client" + "multi-client", + "no-pipeline-conversion" ], "config": { "numClients": 2, @@ -7136,7 +7478,8 @@ "describeName": "Listens:", "itName": "Mirror queries from same secondary client", "tags": [ - "multi-client" + "multi-client", + "no-pipeline-conversion" ], "config": { "numClients": 2, @@ -13270,7 +13613,10 @@ "describeName": "Listens:", "itName": "Secondary client advances query state with global snapshot from primary", "tags": [ - "multi-client" + "multi-client", + "no-web", + "no-ios", + "no-android" ], "config": { "numClients": 2, diff --git a/firebase-firestore/src/test/resources/json/query_spec_test.json b/firebase-firestore/src/test/resources/json/query_spec_test.json index 7aed45ec207..986a8307be5 100644 --- a/firebase-firestore/src/test/resources/json/query_spec_test.json +++ b/firebase-firestore/src/test/resources/json/query_spec_test.json @@ -1617,5 +1617,323 @@ } } ] + }, + "Queries in different tabs will not interfere": { + "describeName": "Queries:", + "itName": "Queries in different tabs will not interfere", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0, + "expectedState": { + "isPrimary": true + } + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + [ + "key", + "==", + "a" + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + [ + "key", + "==", + "a" + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "query": { + "filters": [ + [ + "key", + "==", + "b" + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedState": { + "activeTargets": { + "4": { + "queries": [ + { + "filters": [ + [ + "key", + "==", + "b" + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + [ + "key", + "==", + "a" + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "4": { + "queries": [ + { + "filters": [ + [ + "key", + "==", + "b" + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "key", + "==", + "a" + ] + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "watchAck": [ + 4 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b" + }, + "version": 1000 + } + ], + "targets": [ + 4 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 4 + ], + "resume-token-2000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + } + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "key", + "==", + "b" + ] + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] } }