Skip to content

Commit 270725f

Browse files
committed
Port spec tests for realtime ppl
1 parent 34bbc4a commit 270725f

File tree

13 files changed

+2729
-34
lines changed

13 files changed

+2729
-34
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/core/PipelineUtil.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package com.google.firebase.firestore.core
1919
import com.google.firebase.firestore.RealtimePipeline
2020
import com.google.firebase.firestore.model.Document
2121
import com.google.firebase.firestore.model.ResourcePath
22-
import com.google.firebase.firestore.model.Values
2322
import com.google.firebase.firestore.pipeline.CollectionGroupSource
2423
import com.google.firebase.firestore.pipeline.CollectionSource
2524
import com.google.firebase.firestore.pipeline.DatabaseSource
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2024 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.firestore.spec;
16+
17+
public class MemoryPipelineSpecTest extends MemorySpecTest {
18+
public MemoryPipelineSpecTest() {
19+
usePipelineMode = true;
20+
}
21+
}

firebase-firestore/src/test/java/com/google/firebase/firestore/spec/QueryEvent.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@
1616

1717
import androidx.annotation.Nullable;
1818
import com.google.firebase.firestore.FirebaseFirestoreException;
19-
import com.google.firebase.firestore.core.Query;
19+
import com.google.firebase.firestore.core.QueryOrPipeline;
2020
import com.google.firebase.firestore.core.ViewSnapshot;
2121

2222
/** Object that contains exactly one of either a view snapshot or an error for the given query. */
2323
public class QueryEvent {
24-
public Query query;
24+
public QueryOrPipeline queryOrPipeline;
2525
public @Nullable ViewSnapshot view;
2626
public @Nullable FirebaseFirestoreException error;
2727

2828
@Override
2929
public String toString() {
30-
return "QueryEvent(" + query + ", " + view + ", " + error + ")";
30+
return "QueryEvent(" + queryOrPipeline + ", " + view + ", " + error + ")";
3131
}
3232
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2024 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.firestore.spec;
16+
17+
public class SQLitePipelineSpecTest extends SQLiteSpecTest {
18+
public SQLitePipelineSpecTest() {
19+
usePipelineMode = true;
20+
}
21+
}

firebase-firestore/src/test/java/com/google/firebase/firestore/spec/SpecTestCase.java

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141
import com.google.firebase.firestore.EventListener;
4242
import com.google.firebase.firestore.FirebaseFirestore;
4343
import com.google.firebase.firestore.FirebaseFirestoreException;
44+
import com.google.firebase.firestore.FirebaseFirestoreIntegrationTestFactory;
4445
import com.google.firebase.firestore.ListenSource;
4546
import com.google.firebase.firestore.LoadBundleTask;
47+
import com.google.firebase.firestore.UserDataReader;
4648
import com.google.firebase.firestore.auth.User;
4749
import com.google.firebase.firestore.bundle.BundleReader;
4850
import com.google.firebase.firestore.bundle.BundleSerializer;
@@ -57,6 +59,7 @@
5759
import com.google.firebase.firestore.core.QueryListener;
5860
import com.google.firebase.firestore.core.QueryOrPipeline;
5961
import com.google.firebase.firestore.core.SyncEngine;
62+
import com.google.firebase.firestore.core.Target;
6063
import com.google.firebase.firestore.core.TargetOrPipeline;
6164
import com.google.firebase.firestore.local.LocalStore;
6265
import com.google.firebase.firestore.local.LruDelegate;
@@ -104,6 +107,7 @@
104107
import java.util.ArrayList;
105108
import java.util.Arrays;
106109
import java.util.Collections;
110+
import java.util.Comparator;
107111
import java.util.HashMap;
108112
import java.util.HashSet;
109113
import java.util.Iterator;
@@ -163,6 +167,8 @@ public abstract class SpecTestCase implements RemoteStoreCallback {
163167
// separated by a space character.
164168
private static final String TEST_FILTER_PROPERTY = "specTestFilter";
165169

170+
private static final String NO_PIPELINE_CONVERSION_TAG = "no-pipeline-conversion";
171+
166172
// Tags on tests that should be excluded from execution, useful to allow the platforms to
167173
// temporarily diverge or for features that are designed to be platform specific (such as
168174
// 'multi-client').
@@ -174,6 +180,9 @@ public abstract class SpecTestCase implements RemoteStoreCallback {
174180
private boolean useEagerGcForMemory;
175181
private int maxConcurrentLimboResolutions;
176182
private boolean networkEnabled = true;
183+
protected boolean usePipelineMode = false;
184+
185+
private FirebaseFirestore db;
177186

178187
//
179188
// Parts of the Firestore system that the spec tests need to control.
@@ -196,7 +205,7 @@ public abstract class SpecTestCase implements RemoteStoreCallback {
196205
* A dictionary for tracking the listens on queries. Note that the identity of the listeners is
197206
* used to remove them.
198207
*/
199-
private Map<Query, QueryListener> queryListeners;
208+
private Map<QueryOrPipeline, QueryListener> queryListeners;
200209

201210
/**
202211
* 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) {
291300

292301
currentUser = User.UNAUTHENTICATED;
293302
databaseInfo = PersistenceTestHelpers.nextDatabaseInfo();
303+
db = new FirebaseFirestoreIntegrationTestFactory(databaseInfo.getDatabaseId()).firestore;
294304

295305
if (config.optInt("numClients", 1) != 1) {
296306
throw Assert.fail("The Android client does not support multi-client tests");
@@ -577,21 +587,30 @@ private void doListen(JSONObject listenSpec) throws Exception {
577587
Query query = parseQuery(listenSpec.getJSONObject("query"));
578588
ListenOptions options = parseListenOptions(listenSpec);
579589

590+
QueryOrPipeline queryOrPipeline;
591+
if (usePipelineMode) {
592+
queryOrPipeline =
593+
new QueryOrPipeline.PipelineWrapper(
594+
query.toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId())));
595+
} else {
596+
queryOrPipeline = new QueryOrPipeline.QueryWrapper(query);
597+
}
598+
580599
QueryListener listener =
581600
new QueryListener(
582-
new QueryOrPipeline.QueryWrapper(query),
601+
queryOrPipeline,
583602
options,
584603
(value, error) -> {
585604
QueryEvent event = new QueryEvent();
586-
event.query = query;
605+
event.queryOrPipeline = queryOrPipeline;
587606
if (value != null) {
588607
event.view = value;
589608
} else {
590609
event.error = error;
591610
}
592611
events.add(event);
593612
});
594-
queryListeners.put(query, listener);
613+
queryListeners.put(queryOrPipeline, listener);
595614
queue.runSync(
596615
() -> {
597616
int actualId = eventManager.addQueryListener(listener);
@@ -601,7 +620,15 @@ private void doListen(JSONObject listenSpec) throws Exception {
601620

602621
private void doUnlisten(JSONArray unlistenSpec) throws Exception {
603622
Query query = parseQuery(unlistenSpec.get(1));
604-
QueryListener listener = queryListeners.remove(query);
623+
QueryOrPipeline queryOrPipeline;
624+
if (usePipelineMode) {
625+
queryOrPipeline =
626+
new QueryOrPipeline.PipelineWrapper(
627+
query.toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId())));
628+
} else {
629+
queryOrPipeline = new QueryOrPipeline.QueryWrapper(query);
630+
}
631+
QueryListener listener = queryListeners.remove(queryOrPipeline);
605632
queue.runSync(() -> eventManager.removeQueryListener(listener));
606633
}
607634

@@ -990,7 +1017,14 @@ private void doStep(JSONObject step) throws Exception {
9901017

9911018
private void assertEventMatches(JSONObject expected, QueryEvent actual) throws JSONException {
9921019
Query expectedQuery = parseQuery(expected.get("query"));
993-
assertEquals(expectedQuery, actual.query);
1020+
if (usePipelineMode) {
1021+
assertEquals(
1022+
expectedQuery.toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId())),
1023+
actual.queryOrPipeline.pipeline());
1024+
} else {
1025+
assertEquals(expectedQuery, actual.queryOrPipeline.query());
1026+
}
1027+
9941028
if (expected.has("errorCode") && !Status.fromCodeValue(expected.getInt("errorCode")).isOk()) {
9951029
assertNotNull(actual.error);
9961030
assertEquals(expected.getInt("errorCode"), actual.error.getCode().value());
@@ -1041,7 +1075,7 @@ private void validateExpectedSnapshotEvents(@Nullable JSONArray expectedEventsJs
10411075
}
10421076

10431077
// Sort both the expected and actual events by the query's canonical ID.
1044-
events.sort((q1, q2) -> q1.query.getCanonicalId().compareTo(q2.query.getCanonicalId()));
1078+
events.sort(Comparator.comparing(q -> q.queryOrPipeline.canonicalId()));
10451079

10461080
List<JSONObject> expectedEvents = new ArrayList<>();
10471081
for (int i = 0; i < expectedEventsJson.length(); ++i) {
@@ -1052,6 +1086,16 @@ private void validateExpectedSnapshotEvents(@Nullable JSONArray expectedEventsJs
10521086
try {
10531087
Query leftQuery = parseQuery(left.get("query"));
10541088
Query rightQuery = parseQuery(right.get("query"));
1089+
if (usePipelineMode) {
1090+
return leftQuery
1091+
.toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId()))
1092+
.canonicalId()
1093+
.compareTo(
1094+
rightQuery
1095+
.toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId()))
1096+
.canonicalId());
1097+
}
1098+
10551099
return leftQuery.getCanonicalId().compareTo(rightQuery.getCanonicalId());
10561100
} catch (JSONException e) {
10571101
throw new RuntimeException("Failed to parse JSON during event sorting", e);
@@ -1270,9 +1314,25 @@ private void validateActiveTargets() {
12701314
// with the single assertEquals on the TargetData objects themselves if the sequenceNumber is
12711315
// ever made to be consistent.
12721316
// assertEquals(expectedTarget, actualTarget);
1273-
12741317
assertEquals(expectedTarget.getPurpose(), actualTarget.getPurpose());
1275-
assertEquals(expectedTarget.getTarget(), actualTarget.getTarget());
1318+
if (usePipelineMode && !expectedTarget.getPurpose().equals(QueryPurpose.LIMBO_RESOLUTION)) {
1319+
Target target = expectedTarget.getTarget().target();
1320+
assertEquals(
1321+
new TargetOrPipeline.PipelineWrapper(
1322+
new Query(
1323+
target.getPath(),
1324+
target.getCollectionGroup(),
1325+
target.getFilters(),
1326+
target.getOrderBy(),
1327+
target.getLimit(),
1328+
Query.LimitType.LIMIT_TO_FIRST,
1329+
target.getStartAt(),
1330+
target.getEndAt())
1331+
.toRealtimePipeline(db, new UserDataReader(databaseInfo.getDatabaseId()))),
1332+
actualTarget.getTarget());
1333+
} else {
1334+
assertEquals(expectedTarget.getTarget(), actualTarget.getTarget());
1335+
}
12761336
assertEquals(expectedTarget.getTargetId(), actualTarget.getTargetId());
12771337
assertEquals(expectedTarget.getSnapshotVersion(), actualTarget.getSnapshotVersion());
12781338
assertEquals(
@@ -1392,6 +1452,10 @@ public void testSpecTests() throws Exception {
13921452
JSONArray steps = testJSON.getJSONArray("steps");
13931453
Set<String> tags = getTestTags(testJSON);
13941454

1455+
if (name.contains("Newer ")) {
1456+
info("Skipping test: " + name);
1457+
}
1458+
13951459
boolean runTest;
13961460
if (!shouldRunTest(tags)) {
13971461
runTest = false;
@@ -1443,6 +1507,9 @@ private static boolean anyTestsAreMarkedExclusive(JSONObject fileJSON) throws JS
14431507

14441508
/** Called before executing each test to see if it should be run. */
14451509
private boolean shouldRunTest(Set<String> tags) {
1510+
if (usePipelineMode && tags.contains(NO_PIPELINE_CONVERSION_TAG)) {
1511+
return false;
1512+
}
14461513
return shouldRun(tags);
14471514
}
14481515

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
These json files are generated from the web test sources.
2+
3+
TODO(mikelehen): Re-add instructions for generating these.

firebase-firestore/src/test/resources/json/bundle_spec_test.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"describeName": "Bundles:",
44
"itName": "Bundles query can be loaded and resumed from different tabs",
55
"tags": [
6-
"multi-client"
6+
"multi-client",
7+
"no-pipeline-conversion"
78
],
89
"config": {
910
"numClients": 2,
@@ -225,6 +226,7 @@
225226
"describeName": "Bundles:",
226227
"itName": "Bundles query can be resumed from same query.",
227228
"tags": [
229+
"no-pipeline-conversion"
228230
],
229231
"config": {
230232
"numClients": 1,

firebase-firestore/src/test/resources/json/existence_filter_spec_test.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6967,9 +6967,9 @@
69676967
}
69686968
]
69696969
},
6970-
"Full re-query is triggered when bloom filter can not identify documents deleted": {
6970+
"Full re-query is triggered when bloom filter cannot identify documents deleted": {
69716971
"describeName": "Existence Filters:",
6972-
"itName": "Full re-query is triggered when bloom filter can not identify documents deleted",
6972+
"itName": "Full re-query is triggered when bloom filter cannot identify documents deleted",
69736973
"tags": [
69746974
],
69756975
"config": {

firebase-firestore/src/test/resources/json/index_spec_test.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@
7171
"readTime": {
7272
"timestamp": {
7373
"nanoseconds": 0,
74-
"seconds": 0
74+
"seconds": 0,
75+
"type": "firestore/timestamp/1.0"
7576
}
7677
}
7778
},
@@ -115,7 +116,8 @@
115116
"readTime": {
116117
"timestamp": {
117118
"nanoseconds": 0,
118-
"seconds": 0
119+
"seconds": 0,
120+
"type": "firestore/timestamp/1.0"
119121
}
120122
}
121123
},
@@ -192,7 +194,8 @@
192194
"readTime": {
193195
"timestamp": {
194196
"nanoseconds": 0,
195-
"seconds": 0
197+
"seconds": 0,
198+
"type": "firestore/timestamp/1.0"
196199
}
197200
}
198201
},
@@ -236,7 +239,8 @@
236239
"readTime": {
237240
"timestamp": {
238241
"nanoseconds": 0,
239-
"seconds": 0
242+
"seconds": 0,
243+
"type": "firestore/timestamp/1.0"
240244
}
241245
}
242246
},

0 commit comments

Comments
 (0)