blob: 44b6f959011024b3acca65d755530012f34f07b3 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/performance_manager/public/voting/voting.h"
#include "base/test/gtest_util.h"
#include "components/performance_manager/test_support/voting.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace performance_manager {
namespace {
using ::testing::AssertionFailure;
using ::testing::AssertionResult;
using ::testing::AssertionSuccess;
using TestVote = voting::Vote<void, int, 0>;
using TestVoteReceipt = voting::VoteReceipt<TestVote>;
using TestVotingChannel = voting::VotingChannel<TestVote>;
using TestVotingChannelFactory = voting::VotingChannelFactory<TestVote>;
using TestVoteConsumer = voting::VoteConsumer<TestVote>;
using TestAcceptedVote = voting::AcceptedVote<TestVote>;
using TestVotingChannelWrapper = voting::VotingChannelWrapper<TestVote>;
using DummyVoter = voting::test::DummyVoter<TestVote>;
using DummyVoteConsumer = voting::test::DummyVoteConsumer<TestVote>;
using DummyVoteObserver = voting::test::DummyVoteObserver<TestVote>;
// Some dummy contexts.
const void* kDummyContext1 = reinterpret_cast<const void*>(0xDEADBEEF);
const void* kDummyContext2 = reinterpret_cast<const void*>(0xBAADF00D);
AssertionResult IsEntangled(const TestVoteReceipt& receipt,
const TestAcceptedVote& vote) {
if (!receipt.HasVote(&vote))
return AssertionFailure() << "Receipt has wrong vote";
if (!vote.HasReceipt(&receipt))
return AssertionFailure() << "Vote has wrong receipt";
if (!vote.IsValid())
return AssertionFailure() << "Vote is not valid";
return AssertionSuccess();
}
AssertionResult IsNotEntangled(const TestVoteReceipt& receipt,
const TestAcceptedVote& vote) {
if (receipt.HasVote(&vote))
return AssertionFailure() << "Receipt has unexpected vote";
if (vote.HasReceipt(&receipt))
return AssertionFailure() << "Vote has unexpected receipt";
if (vote.IsValid())
return AssertionFailure() << "Vote is unexpectedly valid";
return AssertionSuccess();
}
static const char kReason[] = "reason";
} // namespace
TEST(VotingTest, DefaultAcceptedVoteIsInvalid) {
TestAcceptedVote vote;
EXPECT_FALSE(vote.IsValid());
}
TEST(VotingTest, VoteReceiptsWork) {
DummyVoteConsumer consumer;
DummyVoter voter;
EXPECT_FALSE(voter.voting_channel_.IsValid());
voter.SetVotingChannel(consumer.voting_channel_factory_.BuildVotingChannel());
EXPECT_EQ(&consumer.voting_channel_factory_,
voter.voting_channel_.factory_for_testing());
EXPECT_TRUE(voter.voting_channel_.voter_id());
EXPECT_TRUE(voter.voting_channel_.IsValid());
voter.EmitVote(kDummyContext1, 0);
EXPECT_EQ(1u, voter.receipts_.size());
EXPECT_EQ(1u, consumer.votes_.size());
EXPECT_EQ(1u, consumer.valid_vote_count_);
EXPECT_EQ(voter.voting_channel_.voter_id(), consumer.votes_[0].voter_id());
EXPECT_EQ(kDummyContext1, consumer.votes_[0].context());
EXPECT_TRUE(consumer.votes_[0].IsValid());
EXPECT_TRUE(IsEntangled(voter.receipts_[0], consumer.votes_[0]));
// Move the vote and the receipt out of their containers and back in.
// All should be well.
{
TestVoteReceipt receipt = std::move(voter.receipts_[0]);
EXPECT_FALSE(voter.receipts_[0].HasVote());
EXPECT_TRUE(IsEntangled(receipt, consumer.votes_[0]));
TestAcceptedVote vote = std::move(consumer.votes_[0]);
EXPECT_FALSE(consumer.votes_[0].IsValid());
EXPECT_TRUE(IsEntangled(receipt, vote));
voter.receipts_[0] = std::move(receipt);
EXPECT_FALSE(receipt.HasVote());
EXPECT_TRUE(IsEntangled(voter.receipts_[0], vote));
consumer.votes_[0] = std::move(vote);
EXPECT_FALSE(vote.IsValid());
EXPECT_TRUE(IsEntangled(voter.receipts_[0], consumer.votes_[0]));
}
voter.EmitVote(kDummyContext2, 0);
EXPECT_EQ(2u, voter.receipts_.size());
EXPECT_EQ(2u, consumer.votes_.size());
EXPECT_EQ(2u, consumer.valid_vote_count_);
EXPECT_EQ(kDummyContext1, consumer.votes_[0].context());
EXPECT_EQ(kDummyContext2, consumer.votes_[1].context());
EXPECT_TRUE(IsEntangled(voter.receipts_[0], consumer.votes_[0]));
EXPECT_TRUE(IsEntangled(voter.receipts_[1], consumer.votes_[1]));
// Change a vote, but making no change.
EXPECT_TRUE(IsEntangled(voter.receipts_[0], consumer.votes_[0]));
EXPECT_EQ(kDummyContext1, consumer.votes_[0].context());
EXPECT_EQ(0, consumer.votes_[0].vote().value());
EXPECT_EQ(DummyVoter::kReason, consumer.votes_[0].vote().reason());
voter.receipts_[0].ChangeVote(0, DummyVoter::kReason);
EXPECT_TRUE(IsEntangled(voter.receipts_[0], consumer.votes_[0]));
EXPECT_EQ(kDummyContext1, consumer.votes_[0].context());
EXPECT_EQ(0, consumer.votes_[0].vote().value());
EXPECT_EQ(DummyVoter::kReason, consumer.votes_[0].vote().reason());
// Change the vote and expect the change to propagate.
static const char kReason[] = "another reason";
voter.receipts_[0].ChangeVote(5, kReason);
EXPECT_TRUE(IsEntangled(voter.receipts_[0], consumer.votes_[0]));
EXPECT_EQ(kDummyContext1, consumer.votes_[0].context());
EXPECT_EQ(5, consumer.votes_[0].vote().value());
EXPECT_EQ(kReason, consumer.votes_[0].vote().reason());
// Cancel a vote.
voter.receipts_[0].Reset();
EXPECT_EQ(2u, voter.receipts_.size());
EXPECT_EQ(2u, consumer.votes_.size());
EXPECT_EQ(1u, consumer.valid_vote_count_);
EXPECT_EQ(kDummyContext1, consumer.votes_[0].context());
EXPECT_EQ(kDummyContext2, consumer.votes_[1].context());
EXPECT_TRUE(IsNotEntangled(voter.receipts_[0], consumer.votes_[0]));
EXPECT_TRUE(IsEntangled(voter.receipts_[1], consumer.votes_[1]));
// Cause the votes to be moved by deleting the invalid one.
consumer.votes_.erase(consumer.votes_.begin());
EXPECT_EQ(2u, voter.receipts_.size());
EXPECT_EQ(1u, consumer.votes_.size());
EXPECT_EQ(1u, consumer.valid_vote_count_);
EXPECT_EQ(kDummyContext2, consumer.votes_[0].context());
EXPECT_FALSE(voter.receipts_[0].HasVote());
EXPECT_TRUE(IsEntangled(voter.receipts_[1], consumer.votes_[0]));
// Cause the receipts to be moved by deleting the empty one.
voter.receipts_.erase(voter.receipts_.begin());
EXPECT_EQ(1u, voter.receipts_.size());
EXPECT_EQ(1u, consumer.votes_.size());
EXPECT_EQ(1u, consumer.valid_vote_count_);
EXPECT_EQ(kDummyContext2, consumer.votes_[0].context());
EXPECT_TRUE(IsEntangled(voter.receipts_[0], consumer.votes_[0]));
// Cancel the remaining vote by deleting the receipt.
voter.receipts_.clear();
EXPECT_EQ(0u, voter.receipts_.size());
EXPECT_EQ(1u, consumer.votes_.size());
EXPECT_EQ(0u, consumer.valid_vote_count_);
EXPECT_EQ(kDummyContext2, consumer.votes_[0].context());
EXPECT_FALSE(consumer.votes_[0].HasReceipt());
EXPECT_FALSE(consumer.votes_[0].IsValid());
}
// Tests that an overwritten vote receipt will property clean up its state.
TEST(VotingTest, OverwriteVoteReceipt) {
DummyVoteConsumer consumer;
TestVotingChannel voting_channel =
consumer.voting_channel_factory_.BuildVotingChannel();
TestVoteReceipt receipt =
voting_channel.SubmitVote(kDummyContext1, TestVote(5, kReason));
receipt = voting_channel.SubmitVote(kDummyContext2, TestVote(5, kReason));
// The first vote was invalidated because its vote receipt was cleaned up.
consumer.ExpectInvalidVote(0);
}
TEST(VotingTest, VoteObserver) {
DummyVoteObserver observer;
TestVotingChannel voting_channel = observer.BuildVotingChannel();
voting::VoterId<TestVote> voter_id = voting_channel.voter_id();
{
TestVoteReceipt receipt =
voting_channel.SubmitVote(kDummyContext1, TestVote(5, kReason));
EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 5, kReason));
}
EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1, 5, kReason));
}
TEST(VotingTest, VotingChannelWrapper) {
DummyVoteObserver observer;
TestVotingChannel voting_channel = observer.BuildVotingChannel();
voting::VoterId<TestVote> voter_id = voting_channel.voter_id();
TestVotingChannelWrapper voting_channel_wrapper;
voting_channel_wrapper.SetVotingChannel(std::move(voting_channel));
EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));
voting_channel_wrapper.SubmitVote(kDummyContext1, TestVote(5, kReason));
EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 5, kReason));
voting_channel_wrapper.ChangeVote(kDummyContext1, TestVote(10, kReason));
EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 10, kReason));
voting_channel_wrapper.InvalidateVote(kDummyContext1);
EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));
}
// Tests that submitting 2 votes for the same context using a
// VotingChannelWrapper results in a DCHECK.
TEST(VotingTest, VotingChannelWrapper_SubmitDuplicateVote) {
DummyVoteObserver observer;
TestVotingChannel voting_channel = observer.BuildVotingChannel();
voting::VoterId<TestVote> voter_id = voting_channel.voter_id();
TestVotingChannelWrapper voting_channel_wrapper;
voting_channel_wrapper.SetVotingChannel(std::move(voting_channel));
EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));
voting_channel_wrapper.SubmitVote(kDummyContext1, TestVote(5, kReason));
EXPECT_TRUE(observer.HasVote(voter_id, kDummyContext1, 5, kReason));
EXPECT_DCHECK_DEATH(
voting_channel_wrapper.SubmitVote(kDummyContext1, TestVote(10, kReason)));
}
// Tests that calling ChangeVote() for a context before a vote was submitted for
// that context results in a DCHECK.
TEST(VotingTest, VotingChannelWrapper_ChangeNonExisting) {
DummyVoteObserver observer;
TestVotingChannel voting_channel = observer.BuildVotingChannel();
voting::VoterId<TestVote> voter_id = voting_channel.voter_id();
TestVotingChannelWrapper voting_channel_wrapper;
voting_channel_wrapper.SetVotingChannel(std::move(voting_channel));
EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));
EXPECT_DCHECK_DEATH(
voting_channel_wrapper.ChangeVote(kDummyContext1, TestVote(5, kReason)));
}
// Tests that calling InvalidateVote() for a context before a vote was submitted
// for that context results in a DCHECK.
TEST(VotingTest, VotingChannelWrapper_InvalidateNonExisting) {
DummyVoteObserver observer;
TestVotingChannel voting_channel = observer.BuildVotingChannel();
voting::VoterId<TestVote> voter_id = voting_channel.voter_id();
TestVotingChannelWrapper voting_channel_wrapper;
voting_channel_wrapper.SetVotingChannel(std::move(voting_channel));
EXPECT_FALSE(observer.HasVote(voter_id, kDummyContext1));
EXPECT_DCHECK_DEATH(voting_channel_wrapper.InvalidateVote(kDummyContext1));
}
} // namespace performance_manager