| // 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 |