Skip to content

Commit 6d02d69

Browse files
committed
[realppl 8] realppl spec tests
1 parent 8fd9dc6 commit 6d02d69

28 files changed

+1429731
-319
lines changed

Firestore/Example/Firestore.xcodeproj/project.pbxproj

Lines changed: 8 additions & 60 deletions
Large diffs are not rendered by default.

Firestore/Example/Tests/SpecTests/FSTLevelDBSpecTests.mm

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,18 @@ - (BOOL)shouldRunWithTags:(NSArray<NSString *> *)tags {
6262

6363
@end
6464

65+
/**
66+
* An implementation of FSTLevelDBSpecTests that runs tests in pipeline mode.
67+
*/
68+
@interface FSTLevelDBPipelineSpecTests : FSTLevelDBSpecTests
69+
@end
70+
71+
@implementation FSTLevelDBPipelineSpecTests
72+
73+
- (BOOL)usePipelineMode {
74+
return YES;
75+
}
76+
77+
@end
78+
6579
NS_ASSUME_NONNULL_END

Firestore/Example/Tests/SpecTests/FSTMemorySpecTests.mm

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,18 @@ - (BOOL)shouldRunWithTags:(NSArray<NSString *> *)tags {
5757

5858
@end
5959

60+
/**
61+
* An implementation of FSTMemorySpecTests that runs tests in pipeline mode.
62+
*/
63+
@interface FSTMemoryPipelineSpecTests : FSTMemorySpecTests
64+
@end
65+
66+
@implementation FSTMemoryPipelineSpecTests
67+
68+
- (BOOL)usePipelineMode {
69+
return YES;
70+
}
71+
72+
@end
73+
6074
NS_ASSUME_NONNULL_END

Firestore/Example/Tests/SpecTests/FSTSpecTests.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,20 @@ extern NSString *const kDurablePersistence;
3737
* + Subclass FSTSpecTests
3838
* + override -persistence to create and return an appropriate Persistence implementation.
3939
*/
40-
@interface FSTSpecTests : XCTestCase
40+
@interface FSTSpecTests : XCTestCase {
41+
@protected
42+
BOOL _convertToPipeline;
43+
}
4144

4245
/** Based on its tags, determine whether the test case should run. */
4346
- (BOOL)shouldRunWithTags:(NSArray<NSString *> *)tags;
4447

4548
/** Do any necessary setup for a single spec test */
4649
- (void)setUpForSpecWithConfig:(NSDictionary *)config;
4750

51+
/** Determines if tests should run in pipeline mode. Subclasses can override. */
52+
- (BOOL)usePipelineMode;
53+
4854
@end
4955

5056
NS_ASSUME_NONNULL_END

Firestore/Example/Tests/SpecTests/FSTSpecTests.mm

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@
158158
// if `kRunBenchmarkTests` is set to 'YES'.
159159
static 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+
161164
NSString *const kEagerGC = @"eager-gc";
162165

163166
NSString *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: %@", name);
1215+
// NSLog(@" [SKIPPED] Spec test: %@", name);
11251216
NSString *comment = testDescription[@"comment"];
11261217
if (comment) {
1127-
NSLog(@" %@", comment);
1218+
// NSLog(@" %@", comment);
11281219
}
11291220
}
11301221
}];

Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "Firestore/core/src/api/load_bundle_task.h"
2727
#include "Firestore/core/src/bundle/bundle_reader.h"
2828
#include "Firestore/core/src/core/database_info.h"
29+
#include "Firestore/core/src/core/pipeline_util.h" // For QueryOrPipeline
2930
#include "Firestore/core/src/core/query.h"
3031
#include "Firestore/core/src/core/view_snapshot.h"
3132
#include "Firestore/core/src/credentials/user.h"
@@ -66,7 +67,7 @@ NS_ASSUME_NONNULL_BEGIN
6667
* given query.
6768
*/
6869
@interface FSTQueryEvent : NSObject
69-
@property(nonatomic, assign) core::Query query;
70+
@property(nonatomic, assign) core::QueryOrPipeline queryOrPipeline;
7071
@property(nonatomic, strong, nullable) NSError *error;
7172

7273
- (const absl::optional<core::ViewSnapshot> &)viewSnapshot;
@@ -115,7 +116,10 @@ typedef std::
115116
*
116117
* Each method on the driver injects a different event into the system.
117118
*/
118-
@interface FSTSyncEngineTestDriver : NSObject
119+
@interface FSTSyncEngineTestDriver : NSObject {
120+
@protected
121+
BOOL _convertToPipeline;
122+
}
119123

120124
/**
121125
* Initializes the underlying FSTSyncEngine with the given local persistence implementation and
@@ -124,6 +128,7 @@ typedef std::
124128
*/
125129
- (instancetype)initWithPersistence:(std::unique_ptr<local::Persistence>)persistence
126130
eagerGC:(BOOL)eagerGC
131+
convertToPipeline:(BOOL)convertToPipeline
127132
initialUser:(const credentials::User &)initialUser
128133
outstandingWrites:(const FSTOutstandingWriteQueues &)outstandingWrites
129134
maxConcurrentLimboResolutions:(size_t)maxConcurrentLimboResolutions NS_DESIGNATED_INITIALIZER;

0 commit comments

Comments
 (0)