blob: 3bceb1b074fa6454885a41e3b9815b57f5b1aa92 [file] [log] [blame]
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: mukerjee@google.com (Matt Mukerjee)
// Unit-test for ExperimentMatcher
#include "net/instaweb/rewriter/public/experiment_matcher.h"
#include "net/instaweb/rewriter/public/experiment_util.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/rewrite_options_test_base.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/null_message_handler.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/time_util.h"
#include "pagespeed/kernel/http/http_names.h" // for HttpAttributes, etc
#include "pagespeed/kernel/http/user_agent_matcher.h"
#include "pagespeed/kernel/http/user_agent_matcher_test_base.h" // for User Agent constants
#include "pagespeed/kernel/http/request_headers.h"
#include "pagespeed/kernel/http/response_headers.h"
namespace net_instaweb {
class ExperimentMatcherTest : public RewriteOptionsTestBase<RewriteOptions> {
protected:
ExperimentMatcher experiment_matcher_;
};
// Test that the experiment utils are working together correctly. First tests
// that we can add an experiment spec then classifies the client into an
// experiment. Then manually inserts a cookie and checks that client will not
// ask for another cookie. Then we remove this cookie and ask for classification
// again. We then have the experiment frameworka store what side of the
// experiment we ended on in a cookie for us, which we also check.
TEST_F(ExperimentMatcherTest, ClassifyIntoExperiment) {
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec("id=1;percent=100", &handler));
ASSERT_EQ(1, options.num_experiments());
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(req_headers,
matcher,
&options);
// We expect 1 here because we set up an experiment above (id=1;percent=100)
// that takes 100% of the traffic and puts it into an experiment with id=1.
ASSERT_EQ(1, options.experiment_id());
ASSERT_TRUE(need_cookie);
req_headers.Add(HttpAttributes::kCookie, "PageSpeedExperiment=1");
need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
ASSERT_FALSE(need_cookie);
experiment::RemoveExperimentCookie(&req_headers);
need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
// Same as above comment.
ASSERT_EQ(1, options.experiment_id());
ASSERT_TRUE(need_cookie);
// Same test used for experiment::SetExperimentCookie in experiment_util_test.
ResponseHeaders resp_headers;
GoogleString url = "http://www.test.com/stuff/some_page.html";
experiment_matcher_.StoreExperimentData(
options.experiment_id(), url, 0, &resp_headers);
ASSERT_TRUE(resp_headers.Has(HttpAttributes::kSetCookie));
ConstStringStarVector v;
EXPECT_TRUE(resp_headers.Lookup(HttpAttributes::kSetCookie, &v));
ASSERT_EQ(1, v.size());
GoogleString expires;
ConvertTimeToString(0, &expires);
GoogleString expected = StringPrintf(
"PageSpeedExperiment=1; Expires=%s; Domain=.www.test.com; Path=/",
expires.c_str());
EXPECT_EQ(expected, *v[0]);
}
TEST_F(ExperimentMatcherTest, ClassifyIntoExperimentStaleCookie) {
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec("id=1;percent=100", &handler));
// Test that a new cookie is set when the incoming cookie has an invalid id.
req_headers.Add(HttpAttributes::kCookie, "PageSpeedExperiment=4");
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
ASSERT_TRUE(need_cookie);
}
TEST_F(ExperimentMatcherTest, ClassifyIntoExperimentNoExptCookie) {
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec("id=1;percent=100", &handler));
// Test that a new cookie is not assigned when the incoming cookie has the
// no-expt cookie.
req_headers.Add(HttpAttributes::kCookie, "PageSpeedExperiment=0");
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
ASSERT_FALSE(need_cookie);
}
TEST_F(ExperimentMatcherTest, ClassifyIntoExperimentEnrollExperiment) {
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
options.set_enroll_experiment_id(0);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec("id=1;percent=100", &handler));
req_headers.Add(HttpAttributes::kCookie, "PageSpeedExperiment=1");
// User should be force-assigned to id=0, even though 0 is for 0% of users and
// they're already in group 1.
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
ASSERT_TRUE(need_cookie);
ASSERT_EQ(0, options.experiment_id());
}
TEST_F(ExperimentMatcherTest, ClassifyIntoExperimentEnrollNotSet) {
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
options.set_enroll_experiment_id(experiment::kExperimentNotSet);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec("id=1;percent=100", &handler));
req_headers.Add(HttpAttributes::kCookie, "PageSpeedExperiment=0");
// User should be assigned to id=1, even though they're already in group 0.
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
ASSERT_TRUE(need_cookie);
ASSERT_EQ(1, options.experiment_id());
}
TEST_F(ExperimentMatcherTest, ClassifyIntoExperimentEnrollBadNum) {
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
options.set_enroll_experiment_id(2);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec("id=1;percent=100", &handler));
req_headers.Add(HttpAttributes::kCookie, "PageSpeedExperiment=0");
// User should remain in group 0 because forcing a nonexistent experiment
// should do nothing.
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
ASSERT_FALSE(need_cookie);
ASSERT_EQ(0, options.experiment_id());
}
TEST_F(ExperimentMatcherTest, ClassifyIntoExperimentNoActiveExperiments) {
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec("id=1;percent=0", &handler));
// No cookie should be set because there's no active experiment.
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
ASSERT_FALSE(need_cookie);
ASSERT_EQ(experiment::kExperimentNotSet, options.experiment_id());
}
TEST_F(ExperimentMatcherTest, ClassifyIntoExperimentNoActiveExperimentsKeep) {
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec("id=1;percent=0", &handler));
req_headers.Add(HttpAttributes::kCookie, "PageSpeedExperiment=1");
// Even though there's no active experiment, keep the user in group 1.
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
ASSERT_FALSE(need_cookie);
ASSERT_EQ(1, options.experiment_id());
}
TEST_F(ExperimentMatcherTest, ClassifyIntoExperimentNoActiveExperimentsUnset) {
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
options.set_enroll_experiment_id(experiment::kExperimentNotSet);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec("id=1;percent=0", &handler));
// Normally no cookie would be set because there's no active experiment, but
// we forced kExperimentNotSet which is for resetting cookie values so set the
// cookie to 0.
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
ASSERT_TRUE(need_cookie);
ASSERT_EQ(0, options.experiment_id());
}
TEST_F(ExperimentMatcherTest, ClassifyIntoExperimentNoActiveExperimentsEnroll) {
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
options.set_enroll_experiment_id(1);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec("id=1;percent=0", &handler));
// We should still be able to force-assign users to percent=0 categories.
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(
req_headers, matcher, &options);
ASSERT_TRUE(need_cookie);
ASSERT_EQ(1, options.experiment_id());
}
TEST_F(ExperimentMatcherTest, ExperimentMatchesDeviceType) {
// Ideally these tests would be performed with a mock UserAgentMatcher,
// but we don't have one. These constants ought to be good enough.
{
// Desktop only experiment does not apply to mobile User Agent
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec(
"id=1;percent=100;matches_device_type=desktop", &handler));
req_headers.Replace(HttpAttributes::kUserAgent,
UserAgentMatcherTestBase::kAndroidChrome21UserAgent);
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(req_headers,
matcher,
&options);
ASSERT_TRUE(need_cookie);
EXPECT_EQ(0, options.experiment_id());
}
{
// Desktop only experiment applies to desktop User Agent
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec(
"id=1;percent=100;matches_device_type=desktop", &handler));
req_headers.Replace(HttpAttributes::kUserAgent,
UserAgentMatcherTestBase::kChrome18UserAgent);
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(req_headers,
matcher,
&options);
ASSERT_TRUE(need_cookie);
EXPECT_EQ(1, options.experiment_id());
}
{
// tablet+mobile experiment applies to tablet User Agent
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec(
"id=1;percent=100;matches_device_type=tablet,mobile", &handler));
req_headers.Replace(HttpAttributes::kUserAgent,
UserAgentMatcherTestBase::kIPadChrome36UserAgent);
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(req_headers,
matcher,
&options);
ASSERT_TRUE(need_cookie);
EXPECT_EQ(1, options.experiment_id());
}
{
// tablet+mobile experiment does not apply to desktop User Agent
RequestHeaders req_headers;
RewriteOptions options(thread_system_.get());
options.set_running_experiment(true);
NullMessageHandler handler;
UserAgentMatcher matcher;
ASSERT_TRUE(options.AddExperimentSpec(
"id=1;percent=100;matches_device_type=tablet,mobile", &handler));
req_headers.Replace(HttpAttributes::kUserAgent,
UserAgentMatcherTestBase::kChrome18UserAgent);
bool need_cookie = experiment_matcher_.ClassifyIntoExperiment(req_headers,
matcher,
&options);
ASSERT_TRUE(need_cookie);
EXPECT_EQ(0, options.experiment_id());
}
}
} // namespace net_instaweb