Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ profile
DerivedData
*.hmap
*.ipa
# Xcode index build files
.index-build/

# Swift Package Manager
Package.resolved
Expand Down Expand Up @@ -166,3 +168,4 @@ Firestore/Example/GoogleService-Info.plist

# FirebaseVertexAI test data
vertexai-sdk-test-data

3 changes: 2 additions & 1 deletion FirebaseFirestoreInternal.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling,
'"${PODS_TARGET_SRCROOT}" ' +
'"${PODS_TARGET_SRCROOT}/Firestore/Source/Public" ' +
'"${PODS_ROOT}/nanopb" ' +
'"${PODS_TARGET_SRCROOT}/Firestore/Protos/nanopb"'
'"${PODS_TARGET_SRCROOT}/Firestore/Protos/nanopb" ' +
'"$(PODS_ROOT)/gRPC-C++/third_party/re2"'
}

s.compiler_flags = '$(inherited) -Wreorder -Werror=reorder -Wno-comma'
Expand Down
474 changes: 393 additions & 81 deletions Firestore/Example/Firestore.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

7 changes: 0 additions & 7 deletions Firestore/Example/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,6 @@ if is_platform(:ios)

pod 'leveldb-library'
end

target 'Firestore_FuzzTests_iOS' do
inherit! :search_paths
platform :ios, '15.0'

pod 'LibFuzzer', :podspec => 'LibFuzzer.podspec', :inhibit_warnings => true
end
end
end

Expand Down
4 changes: 3 additions & 1 deletion Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#import "Firestore/Source/API/FIRQuerySnapshot+Internal.h"
#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"

#include "Firestore/core/src/core/pipeline_util.h"
#include "Firestore/core/src/core/query.h"
#include "Firestore/core/src/core/view_snapshot.h"
#include "Firestore/core/src/model/document.h"
Expand Down Expand Up @@ -101,7 +102,8 @@ - (void)testIncludeMetadataChanges {

std::shared_ptr<Firestore> firestore = FSTTestFirestore().wrapped;
core::Query query = Query("foo");
ViewSnapshot viewSnapshot(query, newDocuments, oldDocuments, std::move(documentChanges),
ViewSnapshot viewSnapshot(core::QueryOrPipeline(query), newDocuments, oldDocuments,
std::move(documentChanges),
/*mutated_keys=*/DocumentKeySet(),
/*from_cache=*/false,
/*sync_state_changed=*/true,
Expand Down
3 changes: 2 additions & 1 deletion Firestore/Example/Tests/API/FSTAPIHelpers.mm
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
#import "Firestore/Source/API/FSTUserDataReader.h"

#include "Firestore/core/src/core/pipeline_util.h"
#include "Firestore/core/src/core/view_snapshot.h"
#include "Firestore/core/src/model/document.h"
#include "Firestore/core/src/model/document_set.h"
Expand Down Expand Up @@ -148,7 +149,7 @@
}
newDocuments = newDocuments.insert(doc);
}
ViewSnapshot viewSnapshot{Query(path),
ViewSnapshot viewSnapshot{core::QueryOrPipeline(Query(path)),
newDocuments,
oldDocuments,
std::move(documentChanges),
Expand Down
4 changes: 3 additions & 1 deletion Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,9 @@ - (void)testCannotPerformMoreThanMaxAggregations {
[self awaitExpectation:expectation];

XCTAssertNotNil(result);
XCTAssertTrue([[result localizedDescription] containsString:@"maximum number of aggregations"]);
if (!FSTIntegrationTestCase.isRunningAgainstEmulator) {
XCTAssertTrue([[result localizedDescription] containsString:@"maximum number of aggregations"]);
}
}

- (void)testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation {
Expand Down
14 changes: 14 additions & 0 deletions Firestore/Example/Tests/SpecTests/FSTLevelDBSpecTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,18 @@ - (BOOL)shouldRunWithTags:(NSArray<NSString *> *)tags {

@end

/**
* An implementation of FSTLevelDBSpecTests that runs tests in pipeline mode.
*/
@interface FSTLevelDBPipelineSpecTests : FSTLevelDBSpecTests
@end

@implementation FSTLevelDBPipelineSpecTests

- (BOOL)usePipelineMode {
return YES;
}

@end

NS_ASSUME_NONNULL_END
14 changes: 14 additions & 0 deletions Firestore/Example/Tests/SpecTests/FSTMemorySpecTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,18 @@ - (BOOL)shouldRunWithTags:(NSArray<NSString *> *)tags {

@end

/**
* An implementation of FSTMemorySpecTests that runs tests in pipeline mode.
*/
@interface FSTMemoryPipelineSpecTests : FSTMemorySpecTests
@end

@implementation FSTMemoryPipelineSpecTests

- (BOOL)usePipelineMode {
return YES;
}

@end

NS_ASSUME_NONNULL_END
2 changes: 1 addition & 1 deletion Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ bool IsOpen() const override {
}

void WatchQuery(const TargetData& query) override {
LOG_DEBUG("WatchQuery: %s: %s, %s", query.target_id(), query.target().ToString(),
LOG_DEBUG("WatchQuery: %s: %s, %s", query.target_id(), query.target_or_pipeline().ToString(),
query.resume_token().ToString());

// Snapshot version is ignored on the wire
Expand Down
8 changes: 7 additions & 1 deletion Firestore/Example/Tests/SpecTests/FSTSpecTests.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,20 @@ extern NSString *const kDurablePersistence;
* + Subclass FSTSpecTests
* + override -persistence to create and return an appropriate Persistence implementation.
*/
@interface FSTSpecTests : XCTestCase
@interface FSTSpecTests : XCTestCase {
@protected
BOOL _convertToPipeline;
}

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

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

/** Determines if tests should run in pipeline mode. Subclasses can override. */
- (BOOL)usePipelineMode;

@end

NS_ASSUME_NONNULL_END
127 changes: 111 additions & 16 deletions Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@
// if `kRunBenchmarkTests` is set to 'YES'.
static NSString *const kBenchmarkTag = @"benchmark";

// A tag for tests that should skip its pipeline run.
static NSString *const kNoPipelineConversion = @"no-pipeline-conversion";

NSString *const kEagerGC = @"eager-gc";

NSString *const kDurablePersistence = @"durable-persistence";
Expand Down Expand Up @@ -236,11 +239,14 @@ - (BOOL)shouldRunWithTags:(NSArray<NSString *> *)tags {
return NO;
} else if (!kRunBenchmarkTests && [tags containsObject:kBenchmarkTag]) {
return NO;
} else if (self.usePipelineMode && [tags containsObject:kNoPipelineConversion]) {
return NO;
}
return YES;
}

- (void)setUpForSpecWithConfig:(NSDictionary *)config {
_convertToPipeline = [self usePipelineMode]; // Call new method
_reader = FSTTestUserDataReader();
std::unique_ptr<Executor> user_executor = Executor::CreateSerial("user executor");
user_executor_ = absl::ShareUniquePtr(std::move(user_executor));
Expand All @@ -261,6 +267,7 @@ - (void)setUpForSpecWithConfig:(NSDictionary *)config {
self.driver =
[[FSTSyncEngineTestDriver alloc] initWithPersistence:std::move(persistence)
eagerGC:_useEagerGCForMemory
convertToPipeline:_convertToPipeline // Pass the flag
initialUser:User::Unauthenticated()
outstandingWrites:{}
maxConcurrentLimboResolutions:_maxConcurrentLimboResolutions];
Expand All @@ -282,6 +289,11 @@ - (BOOL)isTestBaseClass {
return [self class] == [FSTSpecTests class];
}

// Default implementation for pipeline mode. Subclasses can override.
- (BOOL)usePipelineMode {
return NO;
}

#pragma mark - Methods for constructing objects from specs.

- (Query)parseQuery:(id)querySpec {
Expand Down Expand Up @@ -645,6 +657,7 @@ - (void)doRestart {
self.driver =
[[FSTSyncEngineTestDriver alloc] initWithPersistence:std::move(persistence)
eagerGC:_useEagerGCForMemory
convertToPipeline:_convertToPipeline // Pass the flag
initialUser:currentUser
outstandingWrites:outstandingWrites
maxConcurrentLimboResolutions:_maxConcurrentLimboResolutions];
Expand Down Expand Up @@ -721,8 +734,42 @@ - (void)doStep:(NSDictionary *)step {
}

- (void)validateEvent:(FSTQueryEvent *)actual matches:(NSDictionary *)expected {
Query expectedQuery = [self parseQuery:expected[@"query"]];
XCTAssertEqual(actual.query, expectedQuery);
// The 'expected' query from JSON is always a standard Query.
Query expectedJSONQuery = [self parseQuery:expected[@"query"]];
core::QueryOrPipeline actualQueryOrPipeline = actual.queryOrPipeline;

if (_convertToPipeline) {
XCTAssertTrue(actualQueryOrPipeline.IsPipeline(),
@"In pipeline mode, actual event query should be a pipeline. Actual: %@",
MakeNSString(actualQueryOrPipeline.ToString()));

// Convert the expected JSON Query to a RealtimePipeline for comparison.
std::vector<std::shared_ptr<api::EvaluableStage>> expectedStages =
core::ToPipelineStages(expectedJSONQuery);
// TODO(specstest): Need access to the database_id for the serializer.
// Assuming self.driver.databaseInfo is accessible and provides it.
// This might require making databaseInfo public or providing a getter in
// FSTSyncEngineTestDriver. For now, proceeding with the assumption it's available.
auto serializer = absl::make_unique<remote::Serializer>(self.driver.databaseInfo.database_id());
api::RealtimePipeline expectedPipeline(std::move(expectedStages), std::move(serializer));
auto expectedQoPForComparison =
core::QueryOrPipeline(expectedPipeline); // Wrap expected pipeline

XCTAssertEqual(actualQueryOrPipeline.CanonicalId(), expectedQoPForComparison.CanonicalId(),
@"Pipeline canonical IDs do not match. Actual: %@, Expected: %@",
MakeNSString(actualQueryOrPipeline.CanonicalId()),
MakeNSString(expectedQoPForComparison.CanonicalId()));

} else {
XCTAssertFalse(actualQueryOrPipeline.IsPipeline(),
@"In non-pipeline mode, actual event query should be a Query. Actual: %@",
MakeNSString(actualQueryOrPipeline.ToString()));
XCTAssertTrue(actualQueryOrPipeline.query() == expectedJSONQuery,
@"Queries do not match. Actual: %@, Expected: %@",
MakeNSString(actualQueryOrPipeline.query().ToString()),
MakeNSString(expectedJSONQuery.ToString()));
}

if ([expected[@"errorCode"] integerValue] != 0) {
XCTAssertNotNil(actual.error);
XCTAssertEqual(actual.error.code, [expected[@"errorCode"] integerValue]);
Expand Down Expand Up @@ -787,14 +834,43 @@ - (void)validateExpectedSnapshotEvents:(NSArray *_Nullable)expectedEvents {
XCTAssertEqual(events.count, expectedEvents.count);
events =
[events sortedArrayUsingComparator:^NSComparisonResult(FSTQueryEvent *q1, FSTQueryEvent *q2) {
return WrapCompare(q1.query.CanonicalId(), q2.query.CanonicalId());
}];
expectedEvents = [expectedEvents
sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *left, NSDictionary *right) {
Query leftQuery = [self parseQuery:left[@"query"]];
Query rightQuery = [self parseQuery:right[@"query"]];
return WrapCompare(leftQuery.CanonicalId(), rightQuery.CanonicalId());
// Use QueryOrPipeline's CanonicalId for sorting
return WrapCompare(q1.queryOrPipeline.CanonicalId(), q2.queryOrPipeline.CanonicalId());
}];
expectedEvents = [expectedEvents sortedArrayUsingComparator:^NSComparisonResult(
NSDictionary *left, NSDictionary *right) {
// Expected query from JSON is always a core::Query.
// For sorting consistency with actual events (which might be pipelines),
// we convert the expected query to QueryOrPipeline then get its CanonicalId.
// If _convertToPipeline is true, this will effectively sort expected items
// by their pipeline canonical ID.
Query leftJSONQuery = [self parseQuery:left[@"query"]];
core::QueryOrPipeline leftQoP;
if (self->_convertToPipeline) {
std::vector<std::shared_ptr<api::EvaluableStage>> stages =
core::ToPipelineStages(leftJSONQuery);
auto serializer =
absl::make_unique<remote::Serializer>(self.driver.databaseInfo.database_id());
leftQoP =
core::QueryOrPipeline(api::RealtimePipeline(std::move(stages), std::move(serializer)));
} else {
leftQoP = core::QueryOrPipeline(leftJSONQuery);
}

Query rightJSONQuery = [self parseQuery:right[@"query"]];
core::QueryOrPipeline rightQoP;
if (self->_convertToPipeline) {
std::vector<std::shared_ptr<api::EvaluableStage>> stages =
core::ToPipelineStages(rightJSONQuery);
auto serializer =
absl::make_unique<remote::Serializer>(self.driver.databaseInfo.database_id());
rightQoP =
core::QueryOrPipeline(api::RealtimePipeline(std::move(stages), std::move(serializer)));
} else {
rightQoP = core::QueryOrPipeline(rightJSONQuery);
}
return WrapCompare(leftQoP.CanonicalId(), rightQoP.CanonicalId());
}];

NSUInteger i = 0;
for (; i < expectedEvents.count && i < events.count; ++i) {
Expand Down Expand Up @@ -849,14 +925,27 @@ - (void)validateExpectedState:(nullable NSDictionary *)expectedState {
NSArray *queriesJson = queryData[@"queries"];
std::vector<TargetData> queries;
for (id queryJson in queriesJson) {
Query query = [self parseQuery:queryJson];

QueryPurpose purpose = QueryPurpose::Listen;
if ([queryData objectForKey:@"targetPurpose"] != nil) {
purpose = [self parseQueryPurpose:queryData[@"targetPurpose"]];
}

TargetData target_data(query.ToTarget(), targetID, 0, purpose);
core::TargetOrPipeline top;
Query query = [self parseQuery:queryJson];

if (self->_convertToPipeline &&
purpose != firebase::firestore::local::QueryPurpose::LimboResolution) {
std::vector<std::shared_ptr<api::EvaluableStage>> stages =
core::ToPipelineStages(query);
auto serializer =
absl::make_unique<remote::Serializer>(self.driver.databaseInfo.database_id());
top = core::TargetOrPipeline(
api::RealtimePipeline(std::move(stages), std::move(serializer)));
} else {
top = core::TargetOrPipeline(query.ToTarget());
}

TargetData target_data(top, targetID, 0, purpose);
if ([queryData objectForKey:@"resumeToken"] != nil) {
target_data = target_data.WithResumeToken(
MakeResumeToken(queryData[@"resumeToken"]), SnapshotVersion::None());
Expand Down Expand Up @@ -980,9 +1069,13 @@ - (void)validateActiveTargets {
// is ever made to be consistent.
// XCTAssertEqualObjects(actualTargets[targetID], TargetData);
const TargetData &actual = found->second;

auto left = actual.target_or_pipeline();
auto right = targetData.target_or_pipeline();
auto left_p = left.IsPipeline();
auto right_p = right.IsPipeline();
XCTAssertEqual(left_p, right_p);
XCTAssertEqual(left, right);
XCTAssertEqual(actual.purpose(), targetData.purpose());
XCTAssertEqual(actual.target(), targetData.target());
XCTAssertEqual(actual.target_id(), targetData.target_id());
XCTAssertEqual(actual.snapshot_version(), targetData.snapshot_version());
XCTAssertEqual(actual.resume_token(), targetData.resume_token());
Expand Down Expand Up @@ -1032,6 +1125,8 @@ - (void)runSpecTestSteps:(NSArray *)steps config:(NSDictionary *)config {
- (void)testSpecTests {
if ([self isTestBaseClass]) return;

// LogSetLevel(firebase::firestore::util::kLogLevelDebug);

// Enumerate the .json files containing the spec tests.
NSMutableArray<NSString *> *specFiles = [NSMutableArray array];
NSMutableArray<NSDictionary *> *parsedSpecs = [NSMutableArray array];
Expand Down Expand Up @@ -1121,10 +1216,10 @@ - (void)testSpecTests {
++testPassCount;
} else {
++testSkipCount;
NSLog(@" [SKIPPED] Spec test: %@", name);
// NSLog(@" [SKIPPED] Spec test: %@", name);
NSString *comment = testDescription[@"comment"];
if (comment) {
NSLog(@" %@", comment);
// NSLog(@" %@", comment);
}
}
}];
Expand Down
Loading