| // Copyright 2020 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "components/sync/engine/commit_processor.h" | |
| #include <memory> | |
| #include "components/sync/engine/commit_contribution.h" | |
| #include "components/sync/engine/commit_contributor.h" | |
| #include "testing/gmock/include/gmock/gmock.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| namespace syncer { | |
| namespace { | |
| constexpr int kMaxEntries = 17; | |
| using testing::_; | |
| using testing::IsEmpty; | |
| using testing::Pair; | |
| using testing::UnorderedElementsAre; | |
| MATCHER_P(HasNumEntries, num_entries, "") { | |
| return static_cast<int>(arg->GetNumEntries()) == num_entries; | |
| } | |
| // Simple implementation of CommitContribution that only implements | |
| // GetNumEntries(). | |
| class FakeCommitContribution : public CommitContribution { | |
| public: | |
| explicit FakeCommitContribution(size_t num_entries) | |
| : num_entries_(num_entries) {} | |
| ~FakeCommitContribution() override = default; | |
| // CommitContribution implementation. | |
| void AddToCommitMessage(sync_pb::ClientToServerMessage* msg) override {} | |
| SyncerError ProcessCommitResponse( | |
| const sync_pb::ClientToServerResponse& response, | |
| StatusController* status) override { | |
| return SyncerError::Success(); | |
| } | |
| void ProcessCommitFailure(SyncCommitError commit_error) override {} | |
| size_t GetNumEntries() const override { return num_entries_; } | |
| private: | |
| const size_t num_entries_; | |
| }; | |
| ACTION_P(ReturnContributionWithEntries, num_entries) { | |
| return std::make_unique<FakeCommitContribution>(num_entries); | |
| } | |
| class MockCommitContributor : public CommitContributor { | |
| public: | |
| MockCommitContributor() = default; | |
| ~MockCommitContributor() override = default; | |
| MOCK_METHOD(std::unique_ptr<CommitContribution>, | |
| GetContribution, | |
| (size_t max_entries), | |
| (override)); | |
| }; | |
| class CommitProcessorTest : public testing::Test { | |
| protected: | |
| CommitProcessorTest() | |
| : contributor_map_{{NIGORI, &nigori_contributor_}, | |
| {SHARING_MESSAGE, &sharing_message_contributor_}, | |
| {BOOKMARKS, &bookmark_contributor_}, | |
| {PREFERENCES, &preference_contributor_}, | |
| {HISTORY, &history_contributor_}}, | |
| processor_( | |
| /*commit_types=*/{NIGORI, SHARING_MESSAGE, BOOKMARKS, PREFERENCES, | |
| HISTORY}, | |
| &contributor_map_) { | |
| EXPECT_TRUE(HighPriorityUserTypes().Has(SHARING_MESSAGE)); | |
| EXPECT_FALSE(HighPriorityUserTypes().Has(BOOKMARKS)); | |
| EXPECT_FALSE(HighPriorityUserTypes().Has(PREFERENCES)); | |
| EXPECT_TRUE(LowPriorityUserTypes().Has(HISTORY)); | |
| } | |
| testing::NiceMock<MockCommitContributor> nigori_contributor_; | |
| // A high-priority user type. | |
| testing::NiceMock<MockCommitContributor> sharing_message_contributor_; | |
| // Regular user types. | |
| testing::NiceMock<MockCommitContributor> bookmark_contributor_; | |
| testing::NiceMock<MockCommitContributor> preference_contributor_; | |
| // A low-priority user type. | |
| testing::NiceMock<MockCommitContributor> history_contributor_; | |
| CommitContributorMap contributor_map_; | |
| CommitProcessor processor_; | |
| }; | |
| TEST_F(CommitProcessorTest, ShouldGatherNigoriOnlyContribution) { | |
| EXPECT_CALL(nigori_contributor_, GetContribution(kMaxEntries)) | |
| .WillOnce(ReturnContributionWithEntries(/*num_entries=*/1)); | |
| // No user types should be gathered and combined with NIGORI. | |
| EXPECT_CALL(sharing_message_contributor_, GetContribution).Times(0); | |
| EXPECT_CALL(bookmark_contributor_, GetContribution).Times(0); | |
| EXPECT_CALL(preference_contributor_, GetContribution).Times(0); | |
| EXPECT_CALL(history_contributor_, GetContribution).Times(0); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre(Pair(NIGORI, HasNumEntries(1)))); | |
| } | |
| TEST_F(CommitProcessorTest, ShouldGatherHighPriorityUserTypesOnlyContribution) { | |
| const int kNumReturnedEntries = 3; | |
| testing::Sequence s1, s2, s3; | |
| EXPECT_CALL(sharing_message_contributor_, GetContribution(kMaxEntries)) | |
| .InSequence(s1, s2, s3) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedEntries)); | |
| // Non-priority user types should be gathered after the high-priority types, | |
| // but the relative ordering between these is unspecified. | |
| EXPECT_CALL(bookmark_contributor_, GetContribution).InSequence(s1); | |
| EXPECT_CALL(preference_contributor_, GetContribution).InSequence(s2); | |
| EXPECT_CALL(history_contributor_, GetContribution).InSequence(s3); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre( | |
| Pair(SHARING_MESSAGE, HasNumEntries(kNumReturnedEntries)))); | |
| } | |
| TEST_F(CommitProcessorTest, ShouldGatherRegularUserTypes) { | |
| const int kNumReturnedBookmarks = 7; | |
| // High-priority types should be gathered, but no entries are produced. | |
| EXPECT_CALL(nigori_contributor_, GetContribution(kMaxEntries)); | |
| EXPECT_CALL(sharing_message_contributor_, GetContribution(kMaxEntries)); | |
| // Return `kNumReturnedBookmarks` bookmarks. | |
| EXPECT_CALL(bookmark_contributor_, GetContribution(kMaxEntries)) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedBookmarks)); | |
| // Preferences should also be gathered, but no entries are produced in this | |
| // test. The precise argument depends on the iteration order so it's not | |
| // verified in this test. | |
| EXPECT_CALL(preference_contributor_, GetContribution); | |
| // Since the regular types don't exhaust `kMaxEntries`, the low-priority types | |
| // should also be gathered (but don't have any contributions). | |
| EXPECT_CALL(history_contributor_, GetContribution); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre( | |
| Pair(BOOKMARKS, HasNumEntries(kNumReturnedBookmarks)))); | |
| } | |
| TEST_F(CommitProcessorTest, ShouldGatherLowPriorityUserTypes) { | |
| const int kNumReturnedHistory = 7; | |
| // High-priority types and regular types should be gathered, but no entries | |
| // are produced. | |
| EXPECT_CALL(nigori_contributor_, GetContribution(kMaxEntries)); | |
| EXPECT_CALL(sharing_message_contributor_, GetContribution(kMaxEntries)); | |
| EXPECT_CALL(bookmark_contributor_, GetContribution(kMaxEntries)); | |
| EXPECT_CALL(preference_contributor_, GetContribution(kMaxEntries)); | |
| // Return `kNumReturnedHistory` history entries. | |
| EXPECT_CALL(history_contributor_, GetContribution(kMaxEntries)) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedHistory)); | |
| EXPECT_THAT( | |
| processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre(Pair(HISTORY, HasNumEntries(kNumReturnedHistory)))); | |
| } | |
| TEST_F(CommitProcessorTest, ShouldGatherMultipleRegularUserTypes) { | |
| const int kNumReturnedBookmarks = 7; | |
| const int kNumReturnedPreferences = 8; | |
| static_assert(kNumReturnedBookmarks + kNumReturnedPreferences < kMaxEntries); | |
| // Return `kNumReturnedBookmarks` bookmarks and `kNumReturnedPreferences` | |
| // preferences. | |
| EXPECT_CALL(bookmark_contributor_, GetContribution) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedBookmarks)); | |
| EXPECT_CALL(preference_contributor_, GetContribution) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedPreferences)); | |
| // Since bookmarks+preferences don't exhaust `kMaxEntries`, the low-priority | |
| // types should also be gathered (but don't have any contributions). | |
| EXPECT_CALL(history_contributor_, GetContribution); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre( | |
| Pair(BOOKMARKS, HasNumEntries(kNumReturnedBookmarks)), | |
| Pair(PREFERENCES, HasNumEntries(kNumReturnedPreferences)))); | |
| } | |
| TEST_F(CommitProcessorTest, ShouldContinueGatheringHighPriorityContributions) { | |
| const int kNumReturnedSharingMessages = 3; | |
| // First, return `kMaxEntries` sharing messages. | |
| EXPECT_CALL(sharing_message_contributor_, GetContribution(kMaxEntries)) | |
| .WillOnce(ReturnContributionWithEntries(kMaxEntries)); | |
| // Non-priority user types shouldn't even be gathered. | |
| EXPECT_CALL(bookmark_contributor_, GetContribution).Times(0); | |
| EXPECT_CALL(preference_contributor_, GetContribution).Times(0); | |
| EXPECT_CALL(history_contributor_, GetContribution).Times(0); | |
| EXPECT_THAT( | |
| processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre(Pair(SHARING_MESSAGE, HasNumEntries(kMaxEntries)))); | |
| // Now, return only `kNumReturnedSharingMessages` sharing messages (all that's | |
| // left). | |
| EXPECT_CALL(sharing_message_contributor_, GetContribution) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedSharingMessages)); | |
| // At this point, there's capacity left, so the non-priority user types should | |
| // also be gathered (but they don't have any contributions). | |
| EXPECT_CALL(bookmark_contributor_, GetContribution); | |
| EXPECT_CALL(preference_contributor_, GetContribution); | |
| EXPECT_CALL(history_contributor_, GetContribution); | |
| EXPECT_THAT( | |
| processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre( | |
| Pair(SHARING_MESSAGE, HasNumEntries(kNumReturnedSharingMessages)))); | |
| // There are no contributions left, and the contributors should not even be | |
| // called again. | |
| EXPECT_CALL(sharing_message_contributor_, GetContribution).Times(0); | |
| EXPECT_CALL(bookmark_contributor_, GetContribution).Times(0); | |
| EXPECT_CALL(preference_contributor_, GetContribution).Times(0); | |
| EXPECT_CALL(history_contributor_, GetContribution).Times(0); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| IsEmpty()); | |
| } | |
| TEST_F(CommitProcessorTest, ShouldContinueGatheringRegularContributions) { | |
| const int kNumReturnedBookmarks = 7; | |
| // First, return `kMaxEntries` bookmarks. | |
| EXPECT_CALL(bookmark_contributor_, GetContribution(kMaxEntries)) | |
| .WillOnce(ReturnContributionWithEntries(kMaxEntries)); | |
| EXPECT_THAT( | |
| processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre(Pair(BOOKMARKS, HasNumEntries(kMaxEntries)))); | |
| // Now, return only `kNumReturnedBookmarks` bookmarks (all that's left). | |
| EXPECT_CALL(bookmark_contributor_, GetContribution) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedBookmarks)); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre( | |
| Pair(BOOKMARKS, HasNumEntries(kNumReturnedBookmarks)))); | |
| // There are no contributions left, do not return any further and do not even | |
| // call the contributor. | |
| EXPECT_CALL(bookmark_contributor_, GetContribution).Times(0); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| IsEmpty()); | |
| } | |
| TEST_F(CommitProcessorTest, ShouldContinueGatheringLowPriorityContributions) { | |
| const int kNumReturnedHistory = 7; | |
| // First, return `kMaxEntries` history entries. | |
| EXPECT_CALL(history_contributor_, GetContribution(kMaxEntries)) | |
| .WillOnce(ReturnContributionWithEntries(kMaxEntries)); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre(Pair(HISTORY, HasNumEntries(kMaxEntries)))); | |
| // Now, return only `kNumReturnedHistory` entries (all that's left). | |
| EXPECT_CALL(history_contributor_, GetContribution) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedHistory)); | |
| EXPECT_THAT( | |
| processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre(Pair(HISTORY, HasNumEntries(kNumReturnedHistory)))); | |
| // There are no contributions left, do not return any further and do not even | |
| // call the contributor. | |
| EXPECT_CALL(history_contributor_, GetContribution).Times(0); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| IsEmpty()); | |
| } | |
| TEST_F(CommitProcessorTest, | |
| ShouldContinueGatheringRegularContributionsIfMatchingMaxEntries) { | |
| // Return `kMaxEntries` bookmarks. | |
| EXPECT_CALL(bookmark_contributor_, GetContribution(kMaxEntries)) | |
| .WillOnce(ReturnContributionWithEntries(kMaxEntries)); | |
| EXPECT_THAT( | |
| processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre(Pair(BOOKMARKS, HasNumEntries(kMaxEntries)))); | |
| // There are no contributions left, do not return any further. | |
| // GetContribution() should however get called since `processor` cannot tell | |
| // that there are no left. | |
| EXPECT_CALL(bookmark_contributor_, GetContribution); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| IsEmpty()); | |
| } | |
| TEST_F(CommitProcessorTest, ShouldGatherInPriorityOrder) { | |
| const int kNumReturnedSharingMessages = 3; | |
| const int kNumReturnedBookmarks1 = kMaxEntries - kNumReturnedSharingMessages; | |
| const int kNumReturnedBookmarks2 = 4; | |
| const int kNumReturnedHistory = 5; | |
| // A high-priority type, a regular type, and a low-priority type all have | |
| // non-zero contributions. | |
| testing::InSequence s; | |
| // First pass: High-priority, and parts of regular. | |
| EXPECT_CALL(sharing_message_contributor_, GetContribution(kMaxEntries)) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedSharingMessages)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(bookmark_contributor_, GetContribution) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedBookmarks1)); | |
| // Second pass: Remaining regular, and low-priority. | |
| EXPECT_CALL(bookmark_contributor_, GetContribution) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedBookmarks2)); | |
| EXPECT_CALL(history_contributor_, GetContribution) | |
| .WillOnce(ReturnContributionWithEntries(kNumReturnedHistory)); | |
| // The first call should return the high-priority types, and as much of the | |
| // regular-priority types as still fits in the budget. | |
| EXPECT_THAT( | |
| processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre( | |
| Pair(SHARING_MESSAGE, HasNumEntries(kNumReturnedSharingMessages)), | |
| Pair(BOOKMARKS, HasNumEntries(kNumReturnedBookmarks1)))); | |
| // Processor has gathered all contributions for SHARING_MESSAGE previously, no | |
| // further call should happen. | |
| EXPECT_CALL(sharing_message_contributor_, GetContribution(kMaxEntries)) | |
| .Times(0); | |
| // The second call should return the remaining regular types as well as the | |
| // low-priority types. | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| UnorderedElementsAre( | |
| Pair(BOOKMARKS, HasNumEntries(kNumReturnedBookmarks2)), | |
| Pair(HISTORY, HasNumEntries(kNumReturnedHistory)))); | |
| // All contributions were gathered; no further calls should happen. | |
| EXPECT_CALL(bookmark_contributor_, GetContribution(kMaxEntries)).Times(0); | |
| EXPECT_CALL(preference_contributor_, GetContribution(kMaxEntries)).Times(0); | |
| EXPECT_CALL(history_contributor_, GetContribution(kMaxEntries)).Times(0); | |
| EXPECT_THAT(processor_.GatherCommitContributions(/*max_entries=*/kMaxEntries), | |
| IsEmpty()); | |
| } | |
| } // namespace | |
| } // namespace syncer |