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: %@ " , name);
1207+ // NSLog(@" [SKIPPED] Spec test: %@", name);
11251208 NSString *comment = testDescription[@" comment" ];
11261209 if (comment) {
1127- NSLog (@" %@ " , comment);
1210+ // NSLog(@" %@", comment);
11281211 }
11291212 }
11301213 }];
0 commit comments