Skip to content

Commit 59ba273

Browse files
authored
Fix flaky tests in firestore_test.cc (port of cl/365835301) (#362)
1 parent 6bd8cf8 commit 59ba273

File tree

1 file changed

+106
-85
lines changed

1 file changed

+106
-85
lines changed

firestore/integration_test_internal/src/firestore_test.cc

Lines changed: 106 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "firebase/firestore.h"
22

3+
#include <algorithm>
4+
35
#if !defined(__ANDROID__)
46
#include <future> // NOLINT(build/c++11)
57
#endif
@@ -23,6 +25,7 @@
2325
#include "util/future_test_util.h"
2426

2527
#include "app/memory/unique_ptr.h"
28+
#include "app/src/mutex.h"
2629
#include "auth/src/include/firebase/auth.h"
2730
#include "firestore/src/common/macros.h"
2831
#include "gmock/gmock.h"
@@ -619,91 +622,105 @@ TEST_F(FirestoreIntegrationTest,
619622

620623
TEST_F(FirestoreIntegrationTest,
621624
TestSnapshotsInSyncListenerFiresAfterListenersInSync) {
625+
class TestData {
626+
public:
627+
void AddEvent(const std::string& event) {
628+
MutexLock lock(mutex_);
629+
events_.push_back(event);
630+
}
631+
632+
int GetEventCount() const {
633+
MutexLock lock(mutex_);
634+
return events_.size();
635+
}
636+
637+
void ClearEvents() {
638+
MutexLock lock(mutex_);
639+
events_.clear();
640+
}
641+
642+
void WaitForEventCount(const std::string& event, int expected_count) {
643+
while (true) {
644+
if (GetEventCount(event) >= expected_count) {
645+
break;
646+
}
647+
}
648+
}
649+
650+
int GetEventCount(const std::string& event) const {
651+
MutexLock lock(mutex_);
652+
return std::count_if(events_.begin(), events_.end(),
653+
[&event](const std::string& current_event) {
654+
return current_event == event;
655+
});
656+
}
657+
658+
std::vector<std::string> GetEvents() const {
659+
MutexLock lock(mutex_);
660+
return events_;
661+
}
662+
663+
private:
664+
mutable Mutex mutex_;
665+
std::vector<std::string> events_;
666+
};
667+
668+
TestData test_data;
669+
622670
DocumentReference document = Collection("rooms").Document();
623671
Await(document.Set(MapFieldValue{{"foo", FieldValue::Double(1.0)}}));
624-
std::vector<std::string> events;
625672

626673
class SnapshotTestEventListener : public TestEventListener<DocumentSnapshot> {
627674
public:
628-
SnapshotTestEventListener(std::string name,
629-
std::vector<std::string>* events)
630-
: TestEventListener(std::move(name)), events_(events) {}
675+
SnapshotTestEventListener(std::string name, TestData& test_data)
676+
: TestEventListener(std::move(name)), test_data_(test_data) {}
631677

632678
void OnEvent(const DocumentSnapshot& value, Error error_code,
633679
const std::string& error_message) override {
634680
TestEventListener::OnEvent(value, error_code, error_message);
635-
events_->push_back("doc");
681+
test_data_.AddEvent("doc");
636682
}
637683

638684
private:
639-
std::vector<std::string>* events_;
685+
TestData& test_data_;
640686
};
641-
SnapshotTestEventListener listener{"doc", &events};
687+
SnapshotTestEventListener listener{"doc", test_data};
642688
ListenerRegistration doc_registration = listener.AttachTo(&document);
643689
// Wait for the initial event from the backend so that we know we'll get
644690
// exactly one snapshot event for our local write below.
645691
Await(listener);
646-
EXPECT_EQ(1, events.size());
647-
events.clear();
648-
649-
#if defined(__APPLE__)
650-
// TODO(varconst): the implementation of `Semaphore::Post()` on Apple
651-
// platforms has a data race which may result in semaphore data being accessed
652-
// on the listener thread after it was destroyed on the main thread. To work
653-
// around this, use `std::promise`.
654-
std::promise<void> promise;
655-
#else
656-
Semaphore completed{0};
657-
#endif
692+
EXPECT_EQ(1, test_data.GetEventCount());
693+
test_data.ClearEvents();
658694

659695
#if defined(FIREBASE_USE_STD_FUNCTION)
660696
ListenerRegistration sync_registration =
661-
TestFirestore()->AddSnapshotsInSyncListener([&] {
662-
events.push_back("snapshots-in-sync");
663-
if (events.size() == 3) {
664-
#if defined(__APPLE__)
665-
promise.set_value();
666-
#else
667-
completed.Post();
668-
#endif
669-
}
670-
});
697+
TestFirestore()->AddSnapshotsInSyncListener(
698+
[&test_data] { test_data.AddEvent("snapshots-in-sync"); });
671699

672700
#else
673701
class SyncEventListener : public EventListener<void> {
674702
public:
675-
explicit SyncEventListener(std::vector<std::string>* events,
676-
Semaphore* completed)
677-
: events_(events), completed_(completed) {}
678-
679-
void OnEvent(Error) override {
680-
events_->push_back("snapshots-in-sync");
681-
if (events.size() == 3) {
682-
completed_->Post();
683-
}
684-
}
703+
explicit SyncEventListener(TestData& test_data) : test_data_(test_data) {}
704+
705+
void OnEvent(Error) override { test_data_.AddEvent("snapshots-in-sync"); }
685706

686707
private:
687-
std::vector<std::string>* events_ = nullptr;
688-
Semaphore* completed_ = nullptr;
708+
TestData& test_data_;
689709
};
690-
SyncEventListener sync_listener{&events, &completed};
710+
SyncEventListener sync_listener{test_data};
691711
ListenerRegistration sync_registration =
692712
TestFirestore()->AddSnapshotsInSyncListener(sync_listener);
693713
#endif // defined(FIREBASE_USE_STD_FUNCTION)
694714

695715
Await(document.Set(MapFieldValue{{"foo", FieldValue::Double(3.0)}}));
696716
// Wait for the snapshots-in-sync listener to fire afterwards.
697-
#if defined(__APPLE__)
698-
promise.get_future().wait();
699-
#else
700-
completed.Wait();
701-
#endif
717+
test_data.WaitForEventCount("snapshots-in-sync", 2);
702718

703719
// We should have an initial snapshots-in-sync event, then a snapshot event
704720
// for set(), then another event to indicate we're in sync again.
705-
EXPECT_EQ(events, std::vector<std::string>(
706-
{"snapshots-in-sync", "doc", "snapshots-in-sync"}));
721+
EXPECT_EQ(test_data.GetEvents(),
722+
std::vector<std::string>(
723+
{"snapshots-in-sync", "doc", "snapshots-in-sync"}));
707724
doc_registration.Remove();
708725
sync_registration.Remove();
709726
}
@@ -741,45 +758,49 @@ TEST_F(FirestoreIntegrationTest, TestQueriesAreValidatedOnClient) {
741758
// The test harness will generate Java JUnit test regardless whether this is
742759
// inside a #if or not. So we move #if inside instead of enclose the whole case.
743760
TEST_F(FirestoreIntegrationTest, TestListenCanBeCalledMultipleTimes) {
744-
// Note: this test is flaky -- the test case may finish, triggering the
745-
// destruction of Firestore, before the async callback finishes.
746761
#if defined(FIREBASE_USE_STD_FUNCTION)
762+
class TestData {
763+
public:
764+
void SetDocumentSnapshot(const DocumentSnapshot& document_snapshot) {
765+
MutexLock lock(mutex_);
766+
document_snapshot_ = document_snapshot;
767+
is_document_snapshot_set_ = true;
768+
}
769+
770+
DocumentSnapshot WaitForDocumentSnapshot() {
771+
while (true) {
772+
MutexLock lock(mutex_);
773+
if (is_document_snapshot_set_) {
774+
return document_snapshot_;
775+
}
776+
}
777+
}
778+
779+
private:
780+
Mutex mutex_;
781+
DocumentSnapshot document_snapshot_;
782+
bool is_document_snapshot_set_ = false;
783+
};
784+
747785
DocumentReference document = Collection("collection").Document();
748786
WriteDocument(document, MapFieldValue{{"foo", FieldValue::String("bar")}});
749-
#if defined(__APPLE__)
750-
// TODO(varconst): the implementation of `Semaphore::Post()` on Apple
751-
// platforms has a data race which may result in semaphore data being accessed
752-
// on the listener thread after it was destroyed on the main thread. To work
753-
// around this, use `std::promise`.
754-
std::promise<void> promise;
755-
#else
756-
Semaphore completed{0};
757-
#endif
758-
DocumentSnapshot resulting_data;
759-
document.AddSnapshotListener([&](const DocumentSnapshot& snapshot,
760-
Error error_code,
761-
const std::string& error_message) {
762-
EXPECT_EQ(Error::kErrorOk, error_code);
763-
EXPECT_EQ(std::string(), error_message);
764-
document.AddSnapshotListener([&](const DocumentSnapshot& snapshot,
765-
Error error_code,
766-
const std::string& error_message) {
767-
EXPECT_EQ(Error::kErrorOk, error_code);
768-
EXPECT_EQ(std::string(), error_message);
769-
resulting_data = snapshot;
770-
#if defined(__APPLE__)
771-
promise.set_value();
772-
#else
773-
completed.Post();
774-
#endif
775-
});
776-
});
777-
#if defined(__APPLE__)
778-
promise.get_future().wait();
779-
#else
780-
completed.Wait();
781-
#endif
782-
EXPECT_THAT(resulting_data.GetData(),
787+
TestData test_data;
788+
document.AddSnapshotListener(
789+
[&document, &test_data](const DocumentSnapshot& snapshot,
790+
Error error_code,
791+
const std::string& error_message) {
792+
EXPECT_EQ(Error::kErrorOk, error_code);
793+
EXPECT_EQ(std::string(), error_message);
794+
document.AddSnapshotListener(
795+
[&test_data](const DocumentSnapshot& snapshot, Error error_code,
796+
const std::string& error_message) {
797+
EXPECT_EQ(Error::kErrorOk, error_code);
798+
EXPECT_EQ(std::string(), error_message);
799+
test_data.SetDocumentSnapshot(snapshot);
800+
});
801+
});
802+
803+
EXPECT_THAT(test_data.WaitForDocumentSnapshot().GetData(),
783804
ContainerEq(MapFieldValue{{"foo", FieldValue::String("bar")}}));
784805
#endif // defined(FIREBASE_USE_STD_FUNCTION)
785806
}

0 commit comments

Comments
 (0)