Skip to content

Commit d8bb9b3

Browse files
authored
gRPC: add more unit tests for Stream and Datastore (#1935)
1 parent 55acc03 commit d8bb9b3

File tree

9 files changed

+465
-103
lines changed

9 files changed

+465
-103
lines changed

Firestore/Example/Firestore.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
ABC1D7E42024AFDE00BA84F0 /* firebase_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */; };
199199
ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; };
200200
ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; };
201+
B60894F72170207200EBC644 /* fake_credentials_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = B60894F62170207100EBC644 /* fake_credentials_provider.cc */; };
201202
B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; };
202203
B65D34A9203C995B0076A5E1 /* FIRTimestampTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */; };
203204
B66D8996213609EE0086DA0C /* stream_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B66D8995213609EE0086DA0C /* stream_test.mm */; };
@@ -515,6 +516,8 @@
515516
ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = "<group>"; };
516517
B1A7E1959AF8141FA7E6B888 /* grpc_stream_tester.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = grpc_stream_tester.cc; sourceTree = "<group>"; };
517518
B3F5B3AAE791A5911B9EAA82 /* Pods-Firestore_Tests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.release.xcconfig"; sourceTree = "<group>"; };
519+
B60894F52170207100EBC644 /* fake_credentials_provider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fake_credentials_provider.h; sourceTree = "<group>"; };
520+
B60894F62170207100EBC644 /* fake_credentials_provider.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fake_credentials_provider.cc; sourceTree = "<group>"; };
518521
B6152AD5202A5385000E5744 /* document_key_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = document_key_test.cc; sourceTree = "<group>"; };
519522
B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRTimestampTest.m; sourceTree = "<group>"; };
520523
B66D8995213609EE0086DA0C /* stream_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = stream_test.mm; sourceTree = "<group>"; };
@@ -676,6 +679,8 @@
676679
54740A561FC913EB00713A1A /* util */ = {
677680
isa = PBXGroup;
678681
children = (
682+
B60894F62170207100EBC644 /* fake_credentials_provider.cc */,
683+
B60894F52170207100EBC644 /* fake_credentials_provider.h */,
679684
B67BF448216EB43000CA9097 /* create_noop_connectivity_monitor.cc */,
680685
B67BF447216EB42F00CA9097 /* create_noop_connectivity_monitor.h */,
681686
B6FB4680208EA0BE00554BA2 /* async_queue_libdispatch_test.mm */,
@@ -1829,6 +1834,7 @@
18291834
5467FB01203E5717009C9584 /* FIRFirestoreTests.mm in Sources */,
18301835
5492E052202154AB00B64F25 /* FIRGeoPointTests.mm in Sources */,
18311836
5492E059202154AB00B64F25 /* FIRQuerySnapshotTests.mm in Sources */,
1837+
B60894F72170207200EBC644 /* fake_credentials_provider.cc in Sources */,
18321838
5492E051202154AA00B64F25 /* FIRQueryTests.mm in Sources */,
18331839
5492E057202154AB00B64F25 /* FIRSnapshotMetadataTests.mm in Sources */,
18341840
B65D34A9203C995B0076A5E1 /* FIRTimestampTest.m in Sources */,

Firestore/core/src/firebase/firestore/remote/datastore.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ class Datastore : public std::enable_shared_from_this<Datastore> {
128128
static GrpcCall::Metadata ExtractWhitelistedHeaders(
129129
const GrpcCall::Metadata& headers);
130130

131+
// In case Auth tries to invoke a callback after `Datastore` has been shut
132+
// down.
133+
bool is_shut_down_ = false;
134+
131135
util::AsyncQueue* worker_queue_ = nullptr;
132136
auth::CredentialsProvider* credentials_ = nullptr;
133137

Firestore/core/src/firebase/firestore/remote/datastore.mm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ void LogGrpcCallFinished(absl::string_view rpc_name,
9999
}
100100

101101
void Datastore::Shutdown() {
102+
is_shut_down_ = true;
103+
102104
// Order matters here: shutting down `grpc_connection_`, which will quickly
103105
// finish any pending gRPC calls, must happen before shutting down the gRPC
104106
// queue.
@@ -262,6 +264,11 @@ void LogGrpcCallFinished(absl::string_view rpc_name,
262264
if (!strong_this) {
263265
return;
264266
}
267+
// In case Auth callback is invoked after Datastore has been shut
268+
// down.
269+
if (strong_this->is_shut_down_) {
270+
return;
271+
}
265272

266273
on_credentials(result);
267274
});

Firestore/core/src/firebase/firestore/remote/grpc_connection.mm

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@
6565

6666
void GrpcConnection::Shutdown() {
6767
// Fast finish any pending calls. This will not trigger the observers.
68-
for (GrpcCall* call : active_calls_) {
68+
// Calls may unregister themselves on finish, so make a protective copy.
69+
auto active_calls = active_calls_;
70+
for (GrpcCall* call : active_calls) {
6971
call->FinishImmediately();
7072
}
7173
}
@@ -161,7 +163,7 @@
161163
void GrpcConnection::RegisterConnectivityMonitor() {
162164
connectivity_monitor_->AddCallback(
163165
[this](ConnectivityMonitor::NetworkStatus /*ignored*/) {
164-
// Calls may unregister themselves on cancel, so make a protective copy.
166+
// Calls may unregister themselves on finish, so make a protective copy.
165167
auto calls = active_calls_;
166168
for (GrpcCall* call : calls) {
167169
// This will trigger the observers.

Firestore/core/test/firebase/firestore/remote/datastore_test.mm

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
#include <memory>
1818
#include <string>
1919

20-
#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h"
2120
#include "Firestore/core/src/firebase/firestore/remote/datastore.h"
2221
#include "Firestore/core/src/firebase/firestore/util/async_queue.h"
2322
#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h"
23+
#include "Firestore/core/test/firebase/firestore/util/fake_credentials_provider.h"
2424
#include "absl/memory/memory.h"
2525
#include "gtest/gtest.h"
2626

@@ -29,10 +29,10 @@
2929
namespace remote {
3030

3131
using auth::CredentialsProvider;
32-
using auth::EmptyCredentialsProvider;
3332
using core::DatabaseInfo;
3433
using model::DatabaseId;
3534
using util::AsyncQueue;
35+
using util::FakeCredentialsProvider;
3636
using util::internal::ExecutorLibdispatch;
3737

3838
namespace {
@@ -47,11 +47,11 @@ void OnStreamFinish(const util::Status& status) override {
4747
}
4848
};
4949

50-
std::unique_ptr<Datastore> CreateDatastore(const DatabaseInfo& database_info,
51-
AsyncQueue* async_queue,
50+
std::shared_ptr<Datastore> CreateDatastore(const DatabaseInfo& database_info,
51+
AsyncQueue* worker_queue,
5252
CredentialsProvider* credentials) {
53-
return absl::make_unique<Datastore>(
54-
database_info, async_queue, credentials,
53+
return std::make_shared<Datastore>(
54+
database_info, worker_queue, credentials,
5555
[[FSTSerializerBeta alloc]
5656
initWithDatabaseID:&database_info.database_id()]);
5757
}
@@ -61,32 +61,29 @@ void OnStreamFinish(const util::Status& status) override {
6161
class DatastoreTest : public testing::Test {
6262
public:
6363
DatastoreTest()
64-
: async_queue{absl::make_unique<ExecutorLibdispatch>(
64+
: worker_queue{absl::make_unique<ExecutorLibdispatch>(
6565
dispatch_queue_create("datastore_test", DISPATCH_QUEUE_SERIAL))},
66-
database_info_{DatabaseId{"foo", "bar"}, "", "", false},
67-
datastore{
68-
CreateDatastore(database_info_, &async_queue, &credentials_)} {
66+
database_info{DatabaseId{"foo", "bar"}, "", "", false},
67+
datastore{CreateDatastore(database_info, &worker_queue, &credentials)} {
6968
}
7069

7170
~DatastoreTest() {
72-
if (!is_shut_down_) {
71+
if (!is_shut_down) {
7372
Shutdown();
7473
}
7574
}
7675

7776
void Shutdown() {
78-
is_shut_down_ = true;
77+
is_shut_down = true;
7978
datastore->Shutdown();
8079
}
8180

82-
private:
83-
bool is_shut_down_ = false;
84-
DatabaseInfo database_info_;
85-
EmptyCredentialsProvider credentials_;
81+
bool is_shut_down = false;
82+
DatabaseInfo database_info;
83+
FakeCredentialsProvider credentials;
8684

87-
public:
88-
AsyncQueue async_queue;
89-
std::unique_ptr<Datastore> datastore;
85+
AsyncQueue worker_queue;
86+
std::shared_ptr<Datastore> datastore;
9087
};
9188

9289
TEST_F(DatastoreTest, CanShutdownWithNoOperations) {
@@ -113,6 +110,59 @@ void Shutdown() {
113110
"x-google-service: service 2\n");
114111
}
115112

113+
TEST_F(DatastoreTest, CommitMutationsAuthFailure) {
114+
credentials.FailGetToken();
115+
116+
__block NSError* resulting_error = nullptr;
117+
datastore->CommitMutations(@[], ^(NSError* _Nullable error) {
118+
resulting_error = error;
119+
});
120+
worker_queue.EnqueueBlocking([] {});
121+
EXPECT_NE(resulting_error, nullptr);
122+
}
123+
124+
TEST_F(DatastoreTest, LookupDocumentsAuthFailure) {
125+
credentials.FailGetToken();
126+
127+
__block NSError* resulting_error = nullptr;
128+
datastore->LookupDocuments(
129+
{}, ^(NSArray<FSTMaybeDocument*>* docs, NSError* _Nullable error) {
130+
resulting_error = error;
131+
});
132+
worker_queue.EnqueueBlocking([] {});
133+
EXPECT_NE(resulting_error, nullptr);
134+
}
135+
136+
TEST_F(DatastoreTest, AuthAfterDatastoreHasBeenShutDown) {
137+
credentials.DelayGetToken();
138+
139+
worker_queue.EnqueueBlocking([&] {
140+
datastore->CommitMutations(@[], ^(NSError* _Nullable error) {
141+
FAIL() << "Callback shouldn't be invoked";
142+
});
143+
});
144+
Shutdown();
145+
146+
EXPECT_NO_THROW(credentials.InvokeGetToken());
147+
}
148+
149+
// TODO(varconst): this test currently fails due to a gRPC issue, see here
150+
// https://github.com/firebase/firebase-ios-sdk/pull/1935#discussion_r224900667
151+
// for details. Reenable when/if possible.
152+
TEST_F(DatastoreTest, DISABLED_AuthOutlivesDatastore) {
153+
credentials.DelayGetToken();
154+
155+
worker_queue.EnqueueBlocking([&] {
156+
datastore->CommitMutations(@[], ^(NSError* _Nullable error) {
157+
FAIL() << "Callback shouldn't be invoked";
158+
});
159+
});
160+
Shutdown();
161+
datastore.reset();
162+
163+
EXPECT_NO_THROW(credentials.InvokeGetToken());
164+
}
165+
116166
} // namespace remote
117167
} // namespace firestore
118168
} // namespace firebase

Firestore/core/test/firebase/firestore/remote/grpc_connection_test.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,41 @@ TEST_F(GrpcConnectionTest, ConnectivityChangeWithSeveralActiveCalls) {
184184
EXPECT_EQ(changes_count, 3);
185185
}
186186

187+
TEST_F(GrpcConnectionTest, ShutdownFastFinishesActiveCalls) {
188+
class NoFinishObserver : public GrpcStreamObserver {
189+
public:
190+
void OnStreamStart() override {
191+
}
192+
void OnStreamRead(const grpc::ByteBuffer& message) override {
193+
}
194+
void OnStreamFinish(const util::Status& status) override {
195+
FAIL() << "Observer shouldn't have been invoked";
196+
}
197+
};
198+
199+
NoFinishObserver observer;
200+
std::unique_ptr<GrpcStream> foo = tester.CreateStream(&observer);
201+
foo->Start();
202+
203+
std::unique_ptr<GrpcStreamingReader> bar = tester.CreateStreamingReader();
204+
bar->Start([](const StatusOr<std::vector<grpc::ByteBuffer>>&) {
205+
FAIL() << "Callback shouldn't have been invoked";
206+
});
207+
208+
std::unique_ptr<GrpcUnaryCall> baz = tester.CreateUnaryCall();
209+
baz->Start([](const StatusOr<grpc::ByteBuffer>&) {
210+
FAIL() << "Callback shouldn't have been invoked";
211+
});
212+
213+
tester.KeepPollingGrpcQueue();
214+
worker_queue.EnqueueBlocking([&] { tester.grpc_connection()->Shutdown(); });
215+
216+
// Destroying a call will throw if it hasn't been properly shut down.
217+
EXPECT_NO_THROW(foo.reset());
218+
EXPECT_NO_THROW(bar.reset());
219+
EXPECT_NO_THROW(baz.reset());
220+
}
221+
187222
} // namespace remote
188223
} // namespace firestore
189224
} // namespace firebase

0 commit comments

Comments
 (0)