4141import com .google .firebase .firestore .EventListener ;
4242import com .google .firebase .firestore .FirebaseFirestore ;
4343import com .google .firebase .firestore .FirebaseFirestoreException ;
44+ import com .google .firebase .firestore .FirebaseFirestoreIntegrationTestFactory ;
4445import com .google .firebase .firestore .ListenSource ;
4546import com .google .firebase .firestore .LoadBundleTask ;
47+ import com .google .firebase .firestore .UserDataReader ;
4648import com .google .firebase .firestore .auth .User ;
4749import com .google .firebase .firestore .bundle .BundleReader ;
4850import com .google .firebase .firestore .bundle .BundleSerializer ;
5759import com .google .firebase .firestore .core .QueryListener ;
5860import com .google .firebase .firestore .core .QueryOrPipeline ;
5961import com .google .firebase .firestore .core .SyncEngine ;
62+ import com .google .firebase .firestore .core .Target ;
6063import com .google .firebase .firestore .core .TargetOrPipeline ;
6164import com .google .firebase .firestore .local .LocalStore ;
6265import com .google .firebase .firestore .local .LruDelegate ;
104107import java .util .ArrayList ;
105108import java .util .Arrays ;
106109import java .util .Collections ;
110+ import java .util .Comparator ;
107111import java .util .HashMap ;
108112import java .util .HashSet ;
109113import 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
0 commit comments