blob: d6d566643bd36967276187161059bd6b027c9991 [file] [log] [blame]
// Copyright 2013 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/variations/variations_associated_data.h"
#include "base/metrics/field_trial.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/mock_entropy_provider.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace variations {
namespace {
constexpr VariationID TEST_VALUE_A = 3300200;
constexpr VariationID TEST_VALUE_B = 3300201;
constexpr IDCollectionKey APP = GOOGLE_APP;
constexpr std::string_view TRIAL = "trial";
constexpr std::string_view GROUP = "group";
// Convenience helper to retrieve the variations::VariationID for a FieldTrial.
// Note that this will do the group assignment in |trial| if not already done.
VariationID GetIDForTrial(IDCollectionKey key, base::FieldTrial* trial) {
return GetGoogleVariationID(key, trial->trial_name(), trial->group_name());
}
// Call FieldTrialList::FactoryGetFieldTrial().
scoped_refptr<base::FieldTrial> CreateFieldTrial(
const std::string& trial_name,
int total_probability,
const std::string& default_group_name) {
base::MockEntropyProvider entropy_provider(0.9);
return base::FieldTrialList::FactoryGetFieldTrial(
trial_name, total_probability, default_group_name, entropy_provider);
}
} // namespace
class VariationsAssociatedDataTest : public ::testing::Test {
public:
VariationsAssociatedDataTest() = default;
VariationsAssociatedDataTest(const VariationsAssociatedDataTest&) = delete;
VariationsAssociatedDataTest& operator=(const VariationsAssociatedDataTest&) =
delete;
~VariationsAssociatedDataTest() override {
// Ensure that the maps are cleared between tests, since they are stored as
// process singletons.
testing::ClearAllVariationIDs();
}
};
// Test that if the trial is immediately disabled, GetGoogleVariationID just
// returns the empty ID.
TEST_F(VariationsAssociatedDataTest, DisableImmediately) {
scoped_refptr<base::FieldTrial> trial(
CreateFieldTrial("trial", 100, "default"));
for (int i = 0; i < ID_COLLECTION_COUNT; ++i) {
ASSERT_EQ(EMPTY_ID,
GetIDForTrial(static_cast<IDCollectionKey>(i), trial.get()));
}
}
// Test various successful association cases.
TEST_F(VariationsAssociatedDataTest, AssociateGoogleVariationID) {
const std::string default_name1 = "default";
scoped_refptr<base::FieldTrial> trial_true(
CreateFieldTrial("d1", 10, default_name1));
const std::string winner = "TheWinner";
trial_true->AppendGroup(winner, 10);
// Set GoogleVariationIDs so we can verify that they were chosen correctly.
AssociateGoogleVariationID(GOOGLE_APP, trial_true->trial_name(),
default_name1, TEST_VALUE_A);
AssociateGoogleVariationID(GOOGLE_APP, trial_true->trial_name(), winner,
TEST_VALUE_B);
EXPECT_EQ(winner, trial_true->group_name());
EXPECT_EQ(TEST_VALUE_B, GetIDForTrial(GOOGLE_APP, trial_true.get()));
const std::string default_name2 = "default2";
scoped_refptr<base::FieldTrial> trial_false(
CreateFieldTrial("d2", 10, default_name2));
const std::string loser = "ALoser";
trial_false->AppendGroup(loser, 0);
AssociateGoogleVariationID(GOOGLE_APP, trial_false->trial_name(),
default_name2, TEST_VALUE_A);
AssociateGoogleVariationID(GOOGLE_APP, trial_false->trial_name(), loser,
TEST_VALUE_B);
EXPECT_NE(loser, trial_false->group_name());
EXPECT_EQ(TEST_VALUE_A, GetIDForTrial(GOOGLE_APP, trial_false.get()));
}
// Test that not associating a FieldTrial with any IDs ensure that the empty ID
// will be returned.
TEST_F(VariationsAssociatedDataTest, NoAssociation) {
const std::string default_name = "default";
scoped_refptr<base::FieldTrial> no_id_trial(
CreateFieldTrial("d3", 10, default_name));
const std::string winner = "TheWinner";
no_id_trial->AppendGroup(winner, 10);
// Ensure that despite the fact that a normal winner is elected, it does not
// have a valid VariationID associated with it.
EXPECT_EQ(winner, no_id_trial->group_name());
for (int i = 0; i < ID_COLLECTION_COUNT; ++i) {
ASSERT_EQ(EMPTY_ID, GetIDForTrial(static_cast<IDCollectionKey>(i),
no_id_trial.get()));
}
}
// Ensure that the overwrite behavior of AssociateGoogleVariationID works as
// expected.
TEST_F(VariationsAssociatedDataTest, ForceAssociation) {
EXPECT_EQ(EMPTY_ID, GetGoogleVariationID(APP, TRIAL, GROUP));
AssociateGoogleVariationID(APP, TRIAL, GROUP, TEST_VALUE_A);
EXPECT_EQ(TEST_VALUE_A, GetGoogleVariationID(APP, TRIAL, GROUP));
AssociateGoogleVariationID(APP, TRIAL, GROUP, TEST_VALUE_B);
EXPECT_EQ(TEST_VALUE_B, GetGoogleVariationID(APP, TRIAL, GROUP));
}
// Ensure that timeboxing works as expected.
TEST_F(VariationsAssociatedDataTest, Timeboxing) {
// Associate a variation id that becomes visible in 7 days, for 7 days.
const base::Time timestamp = base::Time::Now();
const base::Time start = timestamp + base::Days(7);
const base::Time end = timestamp + base::Days(14);
AssociateGoogleVariationID(APP, TRIAL, GROUP, TEST_VALUE_A, {start, end});
// The associated variation id is not visible before the time window starts.
EXPECT_EQ(EMPTY_ID,
GetGoogleVariationID(APP, TRIAL, GROUP, start - base::Days(3)));
EXPECT_EQ(EMPTY_ID,
GetGoogleVariationID(APP, TRIAL, GROUP, start - base::Seconds(1)));
// The associated variation id is visible between 7 days and 14 days.
EXPECT_EQ(TEST_VALUE_A, GetGoogleVariationID(APP, TRIAL, GROUP, start));
EXPECT_EQ(TEST_VALUE_A,
GetGoogleVariationID(APP, TRIAL, GROUP, start + base::Seconds(1)));
EXPECT_EQ(TEST_VALUE_A,
GetGoogleVariationID(APP, TRIAL, GROUP, start + base::Days(2)));
EXPECT_EQ(TEST_VALUE_A,
GetGoogleVariationID(APP, TRIAL, GROUP, end - base::Seconds(1)));
EXPECT_EQ(TEST_VALUE_A, GetGoogleVariationID(APP, TRIAL, GROUP, end));
// The associated variation id is not visible after 14 days.
EXPECT_EQ(EMPTY_ID,
GetGoogleVariationID(APP, TRIAL, GROUP, end + base::Seconds(1)));
EXPECT_EQ(EMPTY_ID,
GetGoogleVariationID(APP, TRIAL, GROUP, end + base::Days(15)));
}
TEST_F(VariationsAssociatedDataTest, GetNextTimeWindowEvent_Basic) {
const base::Time timestamp = base::Time::Now();
const base::Time start = timestamp + base::Days(7);
const base::Time end = timestamp + base::Days(14);
EXPECT_EQ(base::Time::Max(), GetNextTimeWindowEvent(start));
EXPECT_EQ(base::Time::Max(), GetNextTimeWindowEvent(end));
// Associate a variation id that becomes visible in 7 days, for 7 days.
AssociateGoogleVariationID(APP, TRIAL, GROUP, TEST_VALUE_A, {start, end});
// Validate the next time window event as 'current_time' moves forward.
EXPECT_EQ(start, GetNextTimeWindowEvent(start - base::Days(1)));
EXPECT_EQ(start, GetNextTimeWindowEvent(start - base::Seconds(1)));
EXPECT_EQ(end, GetNextTimeWindowEvent(start));
EXPECT_EQ(end, GetNextTimeWindowEvent(end - base::Days(1)));
EXPECT_EQ(end, GetNextTimeWindowEvent(end - base::Seconds(1)));
EXPECT_EQ(base::Time::Max(), GetNextTimeWindowEvent(end));
EXPECT_EQ(base::Time::Max(), GetNextTimeWindowEvent(end + base::Seconds(1)));
}
TEST_F(VariationsAssociatedDataTest,
GetNextTimeWindowEvent_DisjointAndOverlapping) {
const base::Time timestamp = base::Time::Now();
const TimeWindow windows[] = {
{timestamp + base::Days(3), timestamp + base::Days(6)}, // Disjoint.
{timestamp + base::Days(7), timestamp + base::Days(14)}, // Overlapped.
{timestamp + base::Days(9), timestamp + base::Days(11)}, // Contained.
{timestamp + base::Days(10), timestamp + base::Days(16)}, // Partial.
};
// Associate a variation id for each time window.
int i = 0;
for (const auto& window : windows) {
AssociateGoogleVariationID(
APP, base::StrCat({TRIAL, "_", base::NumberToString(i)}), GROUP,
TEST_VALUE_A + i, window);
++i;
}
// Put all of the time window events into vector, then sort that vector. This
// is the order that GetNextTimeWindowEvent() should return them.
std::vector<base::Time> times;
for (const auto& window : windows) {
times.push_back(window.start());
times.push_back(window.end());
}
std::sort(times.begin(), times.end());
// Validate that the next time window event is always the next event in the
// sorted event list.
base::Time prev = base::Time::Min();
for (const auto& time : times) {
EXPECT_EQ(time, GetNextTimeWindowEvent(prev));
prev = time;
}
EXPECT_EQ(base::Time::Max(), GetNextTimeWindowEvent(prev));
}
} // namespace variations