blob: 629102e0bc2f66ccc0141edced8368525b4c3f1b [file] [log] [blame]
// 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