|
1 | 1 | #include "firebase/firestore.h" |
2 | 2 |
|
| 3 | +#include <algorithm> |
| 4 | + |
3 | 5 | #if !defined(__ANDROID__) |
4 | 6 | #include <future> // NOLINT(build/c++11) |
5 | 7 | #endif |
|
23 | 25 | #include "util/future_test_util.h" |
24 | 26 |
|
25 | 27 | #include "app/memory/unique_ptr.h" |
| 28 | +#include "app/src/mutex.h" |
26 | 29 | #include "auth/src/include/firebase/auth.h" |
27 | 30 | #include "firestore/src/common/macros.h" |
28 | 31 | #include "gmock/gmock.h" |
@@ -619,91 +622,105 @@ TEST_F(FirestoreIntegrationTest, |
619 | 622 |
|
620 | 623 | TEST_F(FirestoreIntegrationTest, |
621 | 624 | 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 | + |
622 | 670 | DocumentReference document = Collection("rooms").Document(); |
623 | 671 | Await(document.Set(MapFieldValue{{"foo", FieldValue::Double(1.0)}})); |
624 | | - std::vector<std::string> events; |
625 | 672 |
|
626 | 673 | class SnapshotTestEventListener : public TestEventListener<DocumentSnapshot> { |
627 | 674 | 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) {} |
631 | 677 |
|
632 | 678 | void OnEvent(const DocumentSnapshot& value, Error error_code, |
633 | 679 | const std::string& error_message) override { |
634 | 680 | TestEventListener::OnEvent(value, error_code, error_message); |
635 | | - events_->push_back("doc"); |
| 681 | + test_data_.AddEvent("doc"); |
636 | 682 | } |
637 | 683 |
|
638 | 684 | private: |
639 | | - std::vector<std::string>* events_; |
| 685 | + TestData& test_data_; |
640 | 686 | }; |
641 | | - SnapshotTestEventListener listener{"doc", &events}; |
| 687 | + SnapshotTestEventListener listener{"doc", test_data}; |
642 | 688 | ListenerRegistration doc_registration = listener.AttachTo(&document); |
643 | 689 | // Wait for the initial event from the backend so that we know we'll get |
644 | 690 | // exactly one snapshot event for our local write below. |
645 | 691 | 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(); |
658 | 694 |
|
659 | 695 | #if defined(FIREBASE_USE_STD_FUNCTION) |
660 | 696 | 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"); }); |
671 | 699 |
|
672 | 700 | #else |
673 | 701 | class SyncEventListener : public EventListener<void> { |
674 | 702 | 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"); } |
685 | 706 |
|
686 | 707 | private: |
687 | | - std::vector<std::string>* events_ = nullptr; |
688 | | - Semaphore* completed_ = nullptr; |
| 708 | + TestData& test_data_; |
689 | 709 | }; |
690 | | - SyncEventListener sync_listener{&events, &completed}; |
| 710 | + SyncEventListener sync_listener{test_data}; |
691 | 711 | ListenerRegistration sync_registration = |
692 | 712 | TestFirestore()->AddSnapshotsInSyncListener(sync_listener); |
693 | 713 | #endif // defined(FIREBASE_USE_STD_FUNCTION) |
694 | 714 |
|
695 | 715 | Await(document.Set(MapFieldValue{{"foo", FieldValue::Double(3.0)}})); |
696 | 716 | // 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); |
702 | 718 |
|
703 | 719 | // We should have an initial snapshots-in-sync event, then a snapshot event |
704 | 720 | // 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"})); |
707 | 724 | doc_registration.Remove(); |
708 | 725 | sync_registration.Remove(); |
709 | 726 | } |
@@ -741,45 +758,49 @@ TEST_F(FirestoreIntegrationTest, TestQueriesAreValidatedOnClient) { |
741 | 758 | // The test harness will generate Java JUnit test regardless whether this is |
742 | 759 | // inside a #if or not. So we move #if inside instead of enclose the whole case. |
743 | 760 | 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. |
746 | 761 | #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 | + |
747 | 785 | DocumentReference document = Collection("collection").Document(); |
748 | 786 | 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(), |
783 | 804 | ContainerEq(MapFieldValue{{"foo", FieldValue::String("bar")}})); |
784 | 805 | #endif // defined(FIREBASE_USE_STD_FUNCTION) |
785 | 806 | } |
|
0 commit comments