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,41 @@ - (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+     core::QueryOrPipeline expectedQoPForComparison = expectedPipeline;  //  Wrap expected pipeline
756+ 
757+     XCTAssertEqual (actualQueryOrPipeline.CanonicalId (), expectedQoPForComparison.CanonicalId (),
758+                    @" Pipeline canonical IDs do not match. Actual: %@ , Expected: %@ " 
759+                    MakeNSString (actualQueryOrPipeline.CanonicalId ()),
760+                    MakeNSString (expectedQoPForComparison.CanonicalId ()));
761+ 
762+   } else  {
763+     XCTAssertFalse (actualQueryOrPipeline.IsPipeline (),
764+                    @" In non-pipeline mode, actual event query should be a Query. Actual: %@ " 
765+                    MakeNSString (actualQueryOrPipeline.ToString ()));
766+     XCTAssertTrue (actualQueryOrPipeline.query () == expectedJSONQuery,
767+                   @" Queries do not match. Actual: %@ , Expected: %@ " 
768+                   MakeNSString (actualQueryOrPipeline.query ().ToString ()),
769+                   MakeNSString (expectedJSONQuery.ToString ()));
770+   }
771+ 
726772  if  ([expected[@" errorCode" integerValue ] != 0 ) {
727773    XCTAssertNotNil (actual.error );
728774    XCTAssertEqual (actual.error .code , [expected[@" errorCode" integerValue ]);
@@ -787,13 +833,40 @@ - (void)validateExpectedSnapshotEvents:(NSArray *_Nullable)expectedEvents {
787833  XCTAssertEqual (events.count , expectedEvents.count );
788834  events =
789835      [events sortedArrayUsingComparator: ^NSComparisonResult (FSTQueryEvent *q1, FSTQueryEvent *q2) {
790-         return  WrapCompare (q1.query .CanonicalId (), q2.query .CanonicalId ());
836+         //  Use QueryOrPipeline's CanonicalId for sorting
837+         return  WrapCompare (q1.queryOrPipeline .CanonicalId (), q2.queryOrPipeline .CanonicalId ());
791838      }];
792839  expectedEvents = [expectedEvents
793840      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 ());
841+         //  Expected query from JSON is always a core::Query.
842+         //  For sorting consistency with actual events (which might be pipelines),
843+         //  we convert the expected query to QueryOrPipeline then get its CanonicalId.
844+         //  If _convertToPipeline is true, this will effectively sort expected items
845+         //  by their pipeline canonical ID.
846+         Query leftJSONQuery = [self  parseQuery: left[@" query" 
847+         core::QueryOrPipeline leftQoP;
848+         if  (self->_convertToPipeline ) {
849+           std::vector<std::shared_ptr<api::EvaluableStage>> stages =
850+               core::ToPipelineStages (leftJSONQuery);
851+           auto  serializer =
852+               absl::make_unique<remote::Serializer>(self.driver .databaseInfo .database_id ());
853+           leftQoP = api::RealtimePipeline (std::move (stages), std::move (serializer));
854+         } else  {
855+           leftQoP = leftJSONQuery;
856+         }
857+ 
858+         Query rightJSONQuery = [self  parseQuery: right[@" query" 
859+         core::QueryOrPipeline rightQoP;
860+         if  (self->_convertToPipeline ) {
861+           std::vector<std::shared_ptr<api::EvaluableStage>> stages =
862+               core::ToPipelineStages (rightJSONQuery);
863+           auto  serializer =
864+               absl::make_unique<remote::Serializer>(self.driver .databaseInfo .database_id ());
865+           rightQoP = api::RealtimePipeline (std::move (stages), std::move (serializer));
866+         } else  {
867+           rightQoP = rightJSONQuery;
868+         }
869+         return  WrapCompare (leftQoP.CanonicalId (), rightQoP.CanonicalId ());
797870      }];
798871
799872  NSUInteger  i = 0 ;
@@ -849,14 +922,26 @@ - (void)validateExpectedState:(nullable NSDictionary *)expectedState {
849922            NSArray  *queriesJson = queryData[@" queries" 
850923            std::vector<TargetData> queries;
851924            for  (id  queryJson in queriesJson) {
925+               core::QueryOrPipeline qop;
852926              Query query = [self  parseQuery: queryJson];
853927
854928              QueryPurpose purpose = QueryPurpose::Listen;
855929              if  ([queryData objectForKey: @" targetPurpose" nil ) {
856930                purpose = [self  parseQueryPurpose: queryData[@" targetPurpose" 
857931              }
858932
859-               TargetData target_data (query.ToTarget (), targetID, 0 , purpose);
933+               if  (self->_convertToPipeline  &&
934+                   purpose != firebase::firestore::local::QueryPurpose::LimboResolution) {
935+                 std::vector<std::shared_ptr<api::EvaluableStage>> stages =
936+                     core::ToPipelineStages (query);
937+                 auto  serializer =
938+                     absl::make_unique<remote::Serializer>(self.driver .databaseInfo .database_id ());
939+                 qop = api::RealtimePipeline (std::move (stages), std::move (serializer));
940+               } else  {
941+                 qop = query;
942+               }
943+ 
944+               TargetData target_data (qop.ToTargetOrPipeline (), targetID, 0 , purpose);
860945              if  ([queryData objectForKey: @" resumeToken" nil ) {
861946                target_data = target_data.WithResumeToken (
862947                    MakeResumeToken (queryData[@" resumeToken" SnapshotVersion::None ());
@@ -980,9 +1065,13 @@ - (void)validateActiveTargets {
9801065    //  is ever made to be consistent.
9811066    //  XCTAssertEqualObjects(actualTargets[targetID], TargetData);
9821067    const  TargetData &actual = found->second ;
983- 
1068+     auto  left = actual.target_or_pipeline ();
1069+     auto  left_p = left.IsPipeline ();
1070+     auto  right = targetData.target_or_pipeline ();
1071+     auto  right_p = right.IsPipeline ();
9841072    XCTAssertEqual (actual.purpose (), targetData.purpose ());
985-     XCTAssertEqual (actual.target_or_pipeline (), targetData.target_or_pipeline ());
1073+     XCTAssertEqual (left_p, right_p);
1074+     XCTAssertEqual (left, right);
9861075    XCTAssertEqual (actual.target_id (), targetData.target_id ());
9871076    XCTAssertEqual (actual.snapshot_version (), targetData.snapshot_version ());
9881077    XCTAssertEqual (actual.resume_token (), targetData.resume_token ());
@@ -1032,6 +1121,8 @@ - (void)runSpecTestSteps:(NSArray *)steps config:(NSDictionary *)config {
10321121- (void )testSpecTests  {
10331122  if  ([self  isTestBaseClass ]) return ;
10341123
1124+   //  LogSetLevel(firebase::firestore::util::kLogLevelDebug);
1125+ 
10351126  //  Enumerate the .json files containing the spec tests.
10361127  NSMutableArray <NSString  *> *specFiles = [NSMutableArray  array ];
10371128  NSMutableArray <NSDictionary  *> *parsedSpecs = [NSMutableArray  array ];
@@ -1121,10 +1212,10 @@ - (void)testSpecTests {
11211212        ++testPassCount;
11221213      } else  {
11231214        ++testSkipCount;
1124-         NSLog (@"   [SKIPPED] Spec test: %@ " 
1215+         //   NSLog(@"  [SKIPPED] Spec test: %@", name);
11251216        NSString  *comment = testDescription[@" comment" 
11261217        if  (comment) {
1127-           NSLog (@"     %@ " 
1218+           //   NSLog(@"    %@", comment);
11281219        }
11291220      }
11301221    }];
0 commit comments