blob: 8601a9c930638a5c185d632588dfc64e3f756490 [file] [log] [blame]
/*
* Copyright 2014 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: chenyu@google.com (Yu Chen), morlovich@google.com (Maks Orlovich)
#include "net/instaweb/rewriter/public/make_show_ads_async_filter.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_test_base.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/html/html_parse_test_base.h"
namespace net_instaweb {
namespace {
// Helper methods for generating test html page / snippet.
GoogleString GetPage(StringPiece content) {
return StrCat("<head><title>Something</title></head><body>",
content,
"</body>");
}
GoogleString GetAdsByGoogleInsWithContent(StringPiece content) {
return StrCat("<ins class=\"adsbygoogle\" ", content, " </ins>");
}
GoogleString GetShowAdsDataSnippetWithContent(StringPiece content) {
return StrCat("<script type=\"text/javascript\"> ", content, " </script>");
}
GoogleString GetPageWithOneAdsByGoogle(StringPiece content) {
return GetPage(GetAdsByGoogleInsWithContent(content));
}
// Constants used in input/output.
const char kAdsByGoogleJs[] = "<script async "
"src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\">"
"</script>";
const char kAdsByGoogleApiCall[] =
"<script>(adsbygoogle = window.adsbygoogle || []).push({})</script>";
const char kShowAdsApiCall[] =
"<script type=\"text/javascript\" "
"src=\"http://pagead2.googlesyndication.com/pagead/show_ads.js\">"
" </script>";
// Test data for adsbygoogle snippet1 and the expected output.
const char kAdsByGoogleContent1[] =
"style=\"display:inline-block;width:160px;height:600px\" "
"data-ad-client=\"test-publishercode-expected\" "
"data-ad-slot=\"1234567\" "
"data-ad-channel=\"test-original-channel\">"; // Original channel.
// Test data for adsbygoogle snippet2 and the expected output.
// No channel is set in this data.
const char kAdsByGoogleContent2[] =
"style=\"display:inline-block;width:162px;height:602px\" "
"data-ad-client=\"test-publishercode-expected\" "
"data-ad-slot=\"1234562\">";
// Test data for showads snippet1 and the expected output.
const char kShowAdsDataContentFormat1[] =
"%s"
"google_ad_client = \"test-publishercode-expected\"; "
"%s"
"google_ad_channel = \"test-original-channel\"; " // Original channel.
"google_ad_slot = \"1234567\";"
"google_ad_width = 728;"
"google_ad_height = 90;"
"%s";
const char kShowAdsDataContentFormat1Output[] =
"<ins class=\"adsbygoogle\" "
"style=\"display:inline-block;width:728px;height:90px\" "
"data-ad-channel=\"test-original-channel\" "
"data-ad-client=\"test-publishercode-expected\" "
"data-ad-slot=\"1234567\">"
"</ins>";
// Help methods to get variants of input and the expected output for showads
// snippet1.
GoogleString GetShowAdsDataContent1() {
return StringPrintf(kShowAdsDataContentFormat1, "", "", "");
}
GoogleString GetShowAdsDataContent1WithComments() {
return StringPrintf(kShowAdsDataContentFormat1, "", "/* comment */", "");
}
GoogleString GetShowAdsDataContent1WithCommentTags() {
return StringPrintf(kShowAdsDataContentFormat1,
"<!--", "", "//-->");
}
GoogleString GetShowAdsDataFormat1Output() {
return StrCat(kAdsByGoogleJs,
kShowAdsDataContentFormat1Output,
kAdsByGoogleApiCall);
}
// Test data for showads snippet2.
// No original channel is set in this data.
const char kShowAdsDataContentFormat2[] =
"<!--"
"google_ad_client = \"test-publishercode-expected\"; "
"/**/"
"google_ad_slot = \"1234562\";"
"google_ad_width = 722;"
"google_ad_height = 92;"
"google_ad_format = \"722x92\";"
"//-->";
const char kShowAdsDataContentFormat2Output[] =
"<ins class=\"adsbygoogle\" "
"style=\"display:inline-block;width:722px;height:92px\" "
"data-ad-client=\"test-publishercode-expected\" "
"data-ad-format=\"722x92\" "
"data-ad-slot=\"1234562\"></ins>";
GoogleString GetShowAdsDataFormat2Output() {
return StrCat(kAdsByGoogleJs,
kShowAdsDataContentFormat2Output,
kAdsByGoogleApiCall);
}
// Help methods for tesing pages with multiple showads snippets.
GoogleString GetHtmlPageMultipleShowAds() {
return GetPage(StrCat(
GetShowAdsDataSnippetWithContent(GetShowAdsDataContent1()),
kShowAdsApiCall,
GetShowAdsDataSnippetWithContent(kShowAdsDataContentFormat2),
kShowAdsApiCall));
}
// Test data for ad snippets for which conversion is not applicable.
const char kShowAdsHtmlPageWithMissingAttribute[] =
"<!--"
"google_ad_client = \"test-publishercode-expected\"; "
"google_ad_slot = \"1234567\";"
"google_ad_height = 90;" // Attribute google_ad_width is missing.
"//-->";
const char kShowAdsHtmlPageWithUnexpectedStatement[] =
"<!--"
"google_ad_client = \"test-publishercode-expected\"; "
"google_ad_slot = \"1234567\";"
"google_ad_height = 90;"
"if (a) google_ad_width = 180; else google_ad_width = 190;" // Invalid
"//-->";
const char kShowAdsHtmlPageWithInvalidGoogleAdFormat[] =
"<!--"
"google_ad_client = \"test-publishercode-expected\"; "
"google_ad_slot = \"1234567\";"
"google_ad_width = 722;"
"google_ad_height = 92;"
"google_ad_format = \"weird_722x92_as_rimg\";"
"//-->";
// Test fixture for MakeShowAdsAsyncFilter unit tests.
class MakeShowAdsAsyncFilterTest : public RewriteTestBase {
protected:
virtual void SetUp() {
RewriteTestBase::SetUp();
MakeShowAdsAsyncFilter::InitStats(rewrite_driver()->statistics());
filter_.reset(new MakeShowAdsAsyncFilter(rewrite_driver()));
rewrite_driver()->AddFilter(filter_.get());
}
int GetStat(const char* stat_name) {
return rewrite_driver()->statistics()->FindVariable(stat_name)->Get();
}
int GetStatShowAdsSnippetsConverted() {
return GetStat(MakeShowAdsAsyncFilter::kShowAdsSnippetsConverted);
}
int GetStatShowAdsSnippetsNotConverted() {
return GetStat(MakeShowAdsAsyncFilter::kShowAdsSnippetsNotConverted);
}
int GetStatShowAdsapiReplaced() {
return GetStat(MakeShowAdsAsyncFilter::kShowAdsApiReplacedForAsync);
}
void CheckStatForNoApplicableAds() {
EXPECT_EQ(0, GetStatShowAdsSnippetsConverted());
EXPECT_EQ(0, GetStatShowAdsSnippetsNotConverted());
EXPECT_EQ(0, GetStatShowAdsapiReplaced());
}
void CheckStatForShowAds(int count) {
EXPECT_EQ(count, GetStatShowAdsSnippetsConverted());
EXPECT_EQ(0, GetStatShowAdsSnippetsNotConverted());
EXPECT_EQ(count, GetStatShowAdsapiReplaced());
}
void CheckStatForShowAdsMissingAPICall(int count) {
EXPECT_EQ(count, GetStatShowAdsSnippetsConverted());
EXPECT_EQ(0, GetStatShowAdsSnippetsNotConverted());
EXPECT_EQ(0, GetStatShowAdsapiReplaced());
}
scoped_ptr<MakeShowAdsAsyncFilter> filter_;
};
TEST_F(MakeShowAdsAsyncFilterTest, NoAds) {
ValidateNoChanges(test_info_->name(), GetPage("HTML page with no ads"));
CheckStatForNoApplicableAds();
}
// Test fixture for Html page with showads ads.
TEST_F(MakeShowAdsAsyncFilterTest, OneShowAds) {
ValidateExpected(
test_info_->name(),
GetPage(StrCat(
GetShowAdsDataSnippetWithContent(GetShowAdsDataContent1()),
kShowAdsApiCall)),
GetPage(GetShowAdsDataFormat1Output()));
CheckStatForShowAds(1);
}
TEST_F(MakeShowAdsAsyncFilterTest, OneShowAdsWithComments) {
ValidateExpected(
test_info_->name(),
GetPage(StrCat(
GetShowAdsDataSnippetWithContent(
GetShowAdsDataContent1WithComments()),
kShowAdsApiCall)),
GetPage(GetShowAdsDataFormat1Output()));
CheckStatForShowAds(1);
}
TEST_F(MakeShowAdsAsyncFilterTest, OneShowAdsWithEnclosingCommentTags) {
ValidateExpected(
test_info_->name(),
GetPage(StrCat(
GetShowAdsDataSnippetWithContent(
GetShowAdsDataContent1WithCommentTags()),
kShowAdsApiCall)),
GetPage(GetShowAdsDataFormat1Output()));
CheckStatForShowAds(1);
}
TEST_F(MakeShowAdsAsyncFilterTest, OneShowAdsData2) {
ValidateExpected(
test_info_->name(),
GetPage(StrCat(
GetShowAdsDataSnippetWithContent(kShowAdsDataContentFormat2),
kShowAdsApiCall)),
GetPage(GetShowAdsDataFormat2Output()));
CheckStatForShowAds(1);
}
TEST_F(MakeShowAdsAsyncFilterTest, MultipleShowAds) {
ValidateExpected(
test_info_->name(),
GetHtmlPageMultipleShowAds(),
GetPage(StrCat(
GetShowAdsDataFormat1Output(),
kShowAdsDataContentFormat2Output,
kAdsByGoogleApiCall)));
CheckStatForShowAds(2);
}
TEST_F(MakeShowAdsAsyncFilterTest, ShowsAdsHtmlGoogleAdOutput) {
// Hack the <ins> tag to have the data- field for google_ad_output.
GoogleString output_with_ad_output = kShowAdsDataContentFormat1Output;
GlobalReplaceSubstring("data-ad-slot",
"data-ad-output=\"html\" data-ad-slot",
&output_with_ad_output);
ValidateExpected(
test_info_->name(),
GetPage(StrCat(
GetShowAdsDataSnippetWithContent(
StringPrintf(kShowAdsDataContentFormat1,
"google_ad_output='html';",
"", "")),
kShowAdsApiCall)),
GetPage(StrCat(kAdsByGoogleJs,
output_with_ad_output,
kAdsByGoogleApiCall)));
CheckStatForShowAds(1);
}
// Test fixture for mixed ads.
TEST_F(MakeShowAdsAsyncFilterTest, MixedAds) {
ValidateExpected(
test_info_->name(),
GetPage(StrCat(
kAdsByGoogleJs,
// An adsbygoogle snippet.
GetAdsByGoogleInsWithContent(kAdsByGoogleContent1),
kAdsByGoogleApiCall,
// A showads ad.
GetShowAdsDataSnippetWithContent(GetShowAdsDataContent1()),
kShowAdsApiCall,
// An adsbygoogle snippet.
GetAdsByGoogleInsWithContent(kAdsByGoogleContent2),
kAdsByGoogleApiCall,
// A showads ad.
GetShowAdsDataSnippetWithContent(GetShowAdsDataContent1()),
kShowAdsApiCall)),
GetPage(StrCat(
kAdsByGoogleJs,
// Output for an adsbygoogle snippet.
GetAdsByGoogleInsWithContent(
kAdsByGoogleContent1),
kAdsByGoogleApiCall,
// Output for an showads snippet.
kShowAdsDataContentFormat1Output,
kAdsByGoogleApiCall,
// Output for an adsbygoogle snippet.
GetAdsByGoogleInsWithContent(
kAdsByGoogleContent2),
kAdsByGoogleApiCall,
// Output for an showads snippet.
kShowAdsDataContentFormat1Output,
kAdsByGoogleApiCall)));
CheckStatForShowAds(2);
}
// Test fixture for misplaced ads.
TEST_F(MakeShowAdsAsyncFilterTest, ShowAdsMissingAPICallFlag) {
ValidateExpected(
test_info_->name(),
GetPage(GetShowAdsDataSnippetWithContent(GetShowAdsDataContent1())),
GetPage(StrCat(kAdsByGoogleJs,
kShowAdsDataContentFormat1Output)));
CheckStatForShowAdsMissingAPICall(1);
}
TEST_F(MakeShowAdsAsyncFilterTest, MispairedShowAdsFlag) {
ValidateExpected(
test_info_->name(),
GetPage(StrCat(
kShowAdsApiCall, // Extra showads API call.
GetShowAdsDataSnippetWithContent(GetShowAdsDataContent1()),
kShowAdsApiCall,
kShowAdsApiCall)), // Extra showads API call.
GetPage(StrCat(
kShowAdsApiCall,
kAdsByGoogleJs,
kShowAdsDataContentFormat1Output,
kAdsByGoogleApiCall,
kShowAdsApiCall)));
CheckStatForShowAds(1);
}
TEST_F(MakeShowAdsAsyncFilterTest, MixedAdsWithMisplacedSnippet) {
ValidateExpected(
test_info_->name(),
GetPage(StrCat(
kAdsByGoogleJs,
// An adsbygoogle snippet.
GetAdsByGoogleInsWithContent(kAdsByGoogleContent1),
kAdsByGoogleApiCall,
// A showads ad missing data
kShowAdsApiCall,
// An adsbygoogle snippet missing Api call.
GetAdsByGoogleInsWithContent(kAdsByGoogleContent2),
// A showads ad.
GetShowAdsDataSnippetWithContent(GetShowAdsDataContent1()),
kShowAdsApiCall)),
GetPage(StrCat(
kAdsByGoogleJs,
// Output for an adsbygoogle snippet.
GetAdsByGoogleInsWithContent(
kAdsByGoogleContent1),
kAdsByGoogleApiCall,
// Output for an showads snippet missing data.
kShowAdsApiCall,
// Output for an adsbygoogle snippet missing Api call.
GetAdsByGoogleInsWithContent(
kAdsByGoogleContent2),
// Output for an showads snippet.
kShowAdsDataContentFormat1Output,
kAdsByGoogleApiCall)));
EXPECT_EQ(1, GetStatShowAdsSnippetsConverted());
EXPECT_EQ(0, GetStatShowAdsSnippetsNotConverted());
EXPECT_EQ(1, GetStatShowAdsapiReplaced());
}
// Test fixture for non-applicable snippets.
TEST_F(MakeShowAdsAsyncFilterTest, ShowAdsMissingAttribute) {
ValidateNoChanges(
test_info_->name(),
GetPage(GetShowAdsDataSnippetWithContent(
kShowAdsHtmlPageWithMissingAttribute)));
CheckStatForNoApplicableAds();
}
TEST_F(MakeShowAdsAsyncFilterTest, ShowAdsUnexpectedStatement) {
ValidateNoChanges(
test_info_->name(),
GetPage(GetShowAdsDataSnippetWithContent(
kShowAdsHtmlPageWithUnexpectedStatement)));
CheckStatForNoApplicableAds();
}
TEST_F(MakeShowAdsAsyncFilterTest, ShowAdsInvalidGoogleAdFormat) {
ValidateNoChanges(
test_info_->name(),
GetPage(GetShowAdsDataSnippetWithContent(
kShowAdsHtmlPageWithInvalidGoogleAdFormat)));
}
TEST_F(MakeShowAdsAsyncFilterTest, ShowsAdsJsGoogleAdOutput) {
ValidateNoChanges(
test_info_->name(),
GetPage(StrCat(
GetShowAdsDataSnippetWithContent(
StringPrintf(kShowAdsDataContentFormat1, "google_ad_output='js';",
"", "")),
kShowAdsApiCall)));
}
TEST_F(MakeShowAdsAsyncFilterTest, FlushInTheMiddleOfShowAdsDataScript) {
// TODO(morlovich): Split more flush configurations, perhaps even
// arbitrary ones.
SetupWriter();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText(
"<head><title>Something</title></head><body>");
rewrite_driver()->ParseText(
"<script type=\"text/javascript\"> "
"google_ad_client = \"test-publishercode-expected\"; "
"google_ad_channel = \"test-original-channel\"; ");
// The flush makes the showads data script not rewritable.
rewrite_driver()->Flush();
rewrite_driver()->ParseText(
"google_ad_slot = \"1234567\";"
"google_ad_width = 728;"
"google_ad_height = 90;");
rewrite_driver()->ParseText("</script>");
rewrite_driver()->ParseText(
"<script type=\"text/javascript\" "
"src=\"http://pagead2.googlesyndication.com/pagead/show_ads.js\"> "
"</script>");
rewrite_driver()->ParseText("</body>");
rewrite_driver()->FinishParse();
// The showads data script element is rewritten because HtmlParse will
// buffer the <script>... until it sees "</script>"
EXPECT_STREQ(
"<head><title>Something</title></head><body><script async "
"src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\">"
"</script><ins class=\"adsbygoogle\" "
"style=\"display:inline-block;width:728px;height:90px\" "
"data-ad-channel=\"test-original-channel\" "
"data-ad-client=\"test-publishercode-expected\" "
"data-ad-slot=\"1234567\"></ins>"
"<script>(adsbygoogle = window.adsbygoogle || []).push({})</script>"
"</body>",
output_buffer_);
CheckStatForShowAds(1);
}
TEST_F(MakeShowAdsAsyncFilterTest, FlushInTheMiddleOfShowAdsApiCall) {
SetupWriter();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText(
"<head><title>Something</title></head><body>");
rewrite_driver()->ParseText(
"<script type=\"text/javascript\"> "
"google_ad_client = \"test-publishercode-expected\"; "
"google_ad_channel = \"test-original-channel\"; "
"google_ad_slot = \"1234567\";"
"google_ad_width = 728;"
"google_ad_height = 90;</script>");
rewrite_driver()->ParseText(
"<script type=\"text/javascript\" "
"src=\"http://pagead2.googlesyndication.com/pagead/show_ads.js\"> ");
// The flush makes the showads API call not rewritable.
rewrite_driver()->Flush();
rewrite_driver()->ParseText("</script>");
rewrite_driver()->ParseText("</body>");
rewrite_driver()->FinishParse();
// The showads data script element is rewritten and the showads api call is
// as well, because HtmlParse will buffer the <script> contents until it
// sees </script>.
EXPECT_STREQ(
"<head><title>Something</title></head><body><script async "
"src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\">"
"</script><ins class=\"adsbygoogle\" "
"style=\"display:inline-block;width:728px;height:90px\" "
"data-ad-channel=\"test-original-channel\" "
"data-ad-client=\"test-publishercode-expected\" "
"data-ad-slot=\"1234567\"></ins>"
"<script>(adsbygoogle = window.adsbygoogle || []).push({})</script>"
"</body>",
output_buffer_);
CheckStatForShowAds(1);
}
TEST_F(MakeShowAdsAsyncFilterTest, FlushInTheMiddleOfShowAdsDataAndApiCall) {
SetupWriter();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText(
"<head><title>Something</title></head><body>");
rewrite_driver()->ParseText(
"<script type=\"text/javascript\"> "
"google_ad_client = \"test-publishercode-expected\"; "
"google_ad_channel = \"test-original-channel\"; ");
rewrite_driver()->ParseText(
"google_ad_slot = \"1234567\";"
"google_ad_width = 728;"
"google_ad_height = 90;");
// The flush makes the showads data script not rewritable.
rewrite_driver()->Flush();
rewrite_driver()->ParseText("</script>");
rewrite_driver()->ParseText(
"<script type=\"text/javascript\" "
"src=\"http://pagead2.googlesyndication.com/pagead/show_ads.js\"> ");
// The flush makes the showads api call script not rewritable.
rewrite_driver()->Flush();
rewrite_driver()->ParseText("</script>");
rewrite_driver()->ParseText("</body>");
rewrite_driver()->FinishParse();
EXPECT_STREQ(
"<head><title>Something</title></head><body><script async "
"src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\">"
"</script><ins class=\"adsbygoogle\" "
"style=\"display:inline-block;width:728px;height:90px\" "
"data-ad-channel=\"test-original-channel\" "
"data-ad-client=\"test-publishercode-expected\" "
"data-ad-slot=\"1234567\"></ins>"
"<script>(adsbygoogle = window.adsbygoogle || []).push({})</script>"
"</body>",
output_buffer_);
CheckStatForShowAds(1);
}
} // namespace
} // namespace net_instaweb