158158//  if `kRunBenchmarkTests` is set to 'YES'.
159159static  NSString  *const  kBenchmarkTag  = @" benchmark" 
160160
161+ //  A tag for tests that should skip its pipeline run.
162+ static  NSString  *const  kNoPipelineConversion  = @" no-pipeline-conversion" 
163+ 
161164NSString  *const  kEagerGC  = @" eager-gc" 
162165
163166NSString  *const  kDurablePersistence  = @" durable-persistence" 
@@ -236,11 +239,14 @@ - (BOOL)shouldRunWithTags:(NSArray<NSString *> *)tags {
236239    return  NO ;
237240  } else  if  (!kRunBenchmarkTests  && [tags containsObject: kBenchmarkTag ]) {
238241    return  NO ;
242+   } else  if  (self.usePipelineMode  && [tags containsObject: kNoPipelineConversion ]) {
243+     return  NO ;
239244  }
240245  return  YES ;
241246}
242247
243248- (void )setUpForSpecWithConfig : (NSDictionary  *)config  {
249+   _convertToPipeline = [self  usePipelineMode ];  //  Call new method
244250  _reader = FSTTestUserDataReader ();
245251  std::unique_ptr<Executor> user_executor = Executor::CreateSerial (" user executor" 
246252  user_executor_ = absl::ShareUniquePtr (std::move (user_executor));
@@ -261,6 +267,7 @@ - (void)setUpForSpecWithConfig:(NSDictionary *)config {
261267  self.driver  =
262268      [[FSTSyncEngineTestDriver alloc ] initWithPersistence: std: :move (persistence)
263269                                                   eagerGC: _useEagerGCForMemory
270+                                          convertToPipeline: _convertToPipeline  //  Pass the flag
264271                                               initialUser:User: :Unauthenticated ()
265272                                         outstandingWrites: {}
266273                             maxConcurrentLimboResolutions: _maxConcurrentLimboResolutions];
@@ -282,6 +289,11 @@ - (BOOL)isTestBaseClass {
282289  return  [self  class ] == [FSTSpecTests class ];
283290}
284291
292+ //  Default implementation for pipeline mode. Subclasses can override.
293+ - (BOOL )usePipelineMode  {
294+   return  NO ;
295+ }
296+ 
285297#pragma mark  - Methods for constructing objects from specs.
286298
287299- (Query)parseQuery : (id )querySpec  {
@@ -645,6 +657,7 @@ - (void)doRestart {
645657  self.driver  =
646658      [[FSTSyncEngineTestDriver alloc ] initWithPersistence: std: :move (persistence)
647659                                                   eagerGC: _useEagerGCForMemory
660+                                          convertToPipeline: _convertToPipeline  //  Pass the flag
648661                                               initialUser: currentUser
649662                                         outstandingWrites: outstandingWrites
650663                             maxConcurrentLimboResolutions: _maxConcurrentLimboResolutions];
@@ -721,8 +734,42 @@ - (void)doStep:(NSDictionary *)step {
721734}
722735
723736- (void )validateEvent : (FSTQueryEvent *)actual  matches : (NSDictionary  *)expected  {
724-   Query expectedQuery = [self  parseQuery: expected[@" query" 
725-   XCTAssertEqual (actual.query , expectedQuery);
737+   //  The 'expected' query from JSON is always a standard Query.
738+   Query expectedJSONQuery = [self  parseQuery: expected[@" query" 
739+   core::QueryOrPipeline actualQueryOrPipeline = actual.queryOrPipeline ;
740+ 
741+   if  (_convertToPipeline) {
742+     XCTAssertTrue (actualQueryOrPipeline.IsPipeline (),
743+                   @" In pipeline mode, actual event query should be a pipeline. Actual: %@ " 
744+                   MakeNSString (actualQueryOrPipeline.ToString ()));
745+ 
746+     //  Convert the expected JSON Query to a RealtimePipeline for comparison.
747+     std::vector<std::shared_ptr<api::EvaluableStage>> expectedStages =
748+         core::ToPipelineStages (expectedJSONQuery);
749+     //  TODO(specstest): Need access to the database_id for the serializer.
750+     //  Assuming self.driver.databaseInfo is accessible and provides it.
751+     //  This might require making databaseInfo public or providing a getter in
752+     //  FSTSyncEngineTestDriver. For now, proceeding with the assumption it's available.
753+     auto  serializer = absl::make_unique<remote::Serializer>(self.driver .databaseInfo .database_id ());
754+     api::RealtimePipeline expectedPipeline (std::move (expectedStages), std::move (serializer));
755+     auto  expectedQoPForComparison =
756+         core::QueryOrPipeline (expectedPipeline);  //  Wrap expected pipeline
757+ 
758+     XCTAssertEqual (actualQueryOrPipeline.CanonicalId (), expectedQoPForComparison.CanonicalId (),
759+                    @" Pipeline canonical IDs do not match. Actual: %@ , Expected: %@ " 
760+                    MakeNSString (actualQueryOrPipeline.CanonicalId ()),
761+                    MakeNSString (expectedQoPForComparison.CanonicalId ()));
762+ 
763+   } else  {
764+     XCTAssertFalse (actualQueryOrPipeline.IsPipeline (),
765+                    @" In non-pipeline mode, actual event query should be a Query. Actual: %@ " 
766+                    MakeNSString (actualQueryOrPipeline.ToString ()));
767+     XCTAssertTrue (actualQueryOrPipeline.query () == expectedJSONQuery,
768+                   @" Queries do not match. Actual: %@ , Expected: %@ " 
769+                   MakeNSString (actualQueryOrPipeline.query ().ToString ()),
770+                   MakeNSString (expectedJSONQuery.ToString ()));
771+   }
772+ 
726773  if  ([expected[@" errorCode" integerValue ] != 0 ) {
727774    XCTAssertNotNil (actual.error );
728775    XCTAssertEqual (actual.error .code , [expected[@" errorCode" integerValue ]);
@@ -787,14 +834,43 @@ - (void)validateExpectedSnapshotEvents:(NSArray *_Nullable)expectedEvents {
787834  XCTAssertEqual (events.count , expectedEvents.count );
788835  events =
789836      [events sortedArrayUsingComparator: ^NSComparisonResult (FSTQueryEvent *q1, FSTQueryEvent *q2) {
790-         return  WrapCompare (q1.query .CanonicalId (), q2.query .CanonicalId ());
791-       }];
792-   expectedEvents = [expectedEvents
793-       sortedArrayUsingComparator: ^NSComparisonResult (NSDictionary  *left, NSDictionary  *right) {
794-         Query leftQuery = [self  parseQuery: left[@" query" 
795-         Query rightQuery = [self  parseQuery: right[@" query" 
796-         return  WrapCompare (leftQuery.CanonicalId (), rightQuery.CanonicalId ());
837+         //  Use QueryOrPipeline's CanonicalId for sorting
838+         return  WrapCompare (q1.queryOrPipeline .CanonicalId (), q2.queryOrPipeline .CanonicalId ());
797839      }];
840+   expectedEvents = [expectedEvents sortedArrayUsingComparator: ^NSComparisonResult (
841+                                        NSDictionary  *left, NSDictionary  *right) {
842+     //  Expected query from JSON is always a core::Query.
843+     //  For sorting consistency with actual events (which might be pipelines),
844+     //  we convert the expected query to QueryOrPipeline then get its CanonicalId.
845+     //  If _convertToPipeline is true, this will effectively sort expected items
846+     //  by their pipeline canonical ID.
847+     Query leftJSONQuery = [self  parseQuery: left[@" query" 
848+     core::QueryOrPipeline leftQoP;
849+     if  (self->_convertToPipeline ) {
850+       std::vector<std::shared_ptr<api::EvaluableStage>> stages =
851+           core::ToPipelineStages (leftJSONQuery);
852+       auto  serializer =
853+           absl::make_unique<remote::Serializer>(self.driver .databaseInfo .database_id ());
854+       leftQoP =
855+           core::QueryOrPipeline (api::RealtimePipeline (std::move (stages), std::move (serializer)));
856+     } else  {
857+       leftQoP = core::QueryOrPipeline (leftJSONQuery);
858+     }
859+ 
860+     Query rightJSONQuery = [self  parseQuery: right[@" query" 
861+     core::QueryOrPipeline rightQoP;
862+     if  (self->_convertToPipeline ) {
863+       std::vector<std::shared_ptr<api::EvaluableStage>> stages =
864+           core::ToPipelineStages (rightJSONQuery);
865+       auto  serializer =
866+           absl::make_unique<remote::Serializer>(self.driver .databaseInfo .database_id ());
867+       rightQoP =
868+           core::QueryOrPipeline (api::RealtimePipeline (std::move (stages), std::move (serializer)));
869+     } else  {
870+       rightQoP = core::QueryOrPipeline (rightJSONQuery);
871+     }
872+     return  WrapCompare (leftQoP.CanonicalId (), rightQoP.CanonicalId ());
873+   }];
798874
799875  NSUInteger  i = 0 ;
800876  for  (; i < expectedEvents.count  && i < events.count ; ++i) {
@@ -849,6 +925,7 @@ - (void)validateExpectedState:(nullable NSDictionary *)expectedState {
849925        NSArray  *queriesJson = queryData[@" queries" 
850926        std::vector<TargetData> queries;
851927        for  (id  queryJson in queriesJson) {
928+           core::QueryOrPipeline qop;
852929          Query query = [self  parseQuery: queryJson];
853930
854931          QueryPurpose purpose = QueryPurpose::Listen;
@@ -980,9 +1057,13 @@ - (void)validateActiveTargets {
9801057    //  is ever made to be consistent.
9811058    //  XCTAssertEqualObjects(actualTargets[targetID], TargetData);
9821059    const  TargetData &actual = found->second ;
983- 
1060+     auto  left = actual.target_or_pipeline ();
1061+     auto  left_p = left.IsPipeline ();
1062+     auto  right = targetData.target_or_pipeline ();
1063+     auto  right_p = right.IsPipeline ();
9841064    XCTAssertEqual (actual.purpose (), targetData.purpose ());
985-     XCTAssertEqual (actual.target_or_pipeline (), targetData.target_or_pipeline ());
1065+     XCTAssertEqual (left_p, right_p);
1066+     XCTAssertEqual (left, right);
9861067    XCTAssertEqual (actual.target_id (), targetData.target_id ());
9871068    XCTAssertEqual (actual.snapshot_version (), targetData.snapshot_version ());
9881069    XCTAssertEqual (actual.resume_token (), targetData.resume_token ());
@@ -1032,6 +1113,8 @@ - (void)runSpecTestSteps:(NSArray *)steps config:(NSDictionary *)config {
10321113- (void )testSpecTests  {
10331114  if  ([self  isTestBaseClass ]) return ;
10341115
1116+   //  LogSetLevel(firebase::firestore::util::kLogLevelDebug);
1117+ 
10351118  //  Enumerate the .json files containing the spec tests.
10361119  NSMutableArray <NSString  *> *specFiles = [NSMutableArray  array ];
10371120  NSMutableArray <NSDictionary  *> *parsedSpecs = [NSMutableArray  array ];
@@ -1121,10 +1204,10 @@ - (void)testSpecTests {
11211204        ++testPassCount;
11221205      } else  {
11231206        ++testSkipCount;
1124-         NSLog (@"   [SKIPPED] Spec test: %@ " 
1207+         //   NSLog(@"  [SKIPPED] Spec test: %@", name);
11251208        NSString  *comment = testDescription[@" comment" 
11261209        if  (comment) {
1127-           NSLog (@"     %@ " 
1210+           //   NSLog(@"    %@", comment);
11281211        }
11291212      }
11301213    }];
0 commit comments