Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(491)

Side by Side Diff: chrome/browser/bookmarks/bookmark_model_unittest.cc

Issue 8759017: BookmarkModel cleanup. synced_node is now mobile_node and I'm nuking (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Merge to trunk fix sync_integration_tests and extension test Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <set> 5 #include <set>
6 #include <string> 6 #include <string>
7 7
8 #include "base/base_paths.h" 8 #include "base/base_paths.h"
9 #include "base/basictypes.h" 9 #include "base/basictypes.h"
10 #include "base/command_line.h" 10 #include "base/command_line.h"
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after
204 const BookmarkNode* bb_node = model_.bookmark_bar_node(); 204 const BookmarkNode* bb_node = model_.bookmark_bar_node();
205 ASSERT_TRUE(bb_node != NULL); 205 ASSERT_TRUE(bb_node != NULL);
206 EXPECT_EQ(0, bb_node->child_count()); 206 EXPECT_EQ(0, bb_node->child_count());
207 EXPECT_EQ(BookmarkNode::BOOKMARK_BAR, bb_node->type()); 207 EXPECT_EQ(BookmarkNode::BOOKMARK_BAR, bb_node->type());
208 208
209 const BookmarkNode* other_node = model_.other_node(); 209 const BookmarkNode* other_node = model_.other_node();
210 ASSERT_TRUE(other_node != NULL); 210 ASSERT_TRUE(other_node != NULL);
211 EXPECT_EQ(0, other_node->child_count()); 211 EXPECT_EQ(0, other_node->child_count());
212 EXPECT_EQ(BookmarkNode::OTHER_NODE, other_node->type()); 212 EXPECT_EQ(BookmarkNode::OTHER_NODE, other_node->type());
213 213
214 const BookmarkNode* synced_node = model_.synced_node(); 214 const BookmarkNode* mobile_node = model_.mobile_node();
215 ASSERT_TRUE(synced_node != NULL); 215 ASSERT_TRUE(mobile_node != NULL);
216 EXPECT_EQ(0, synced_node->child_count()); 216 EXPECT_EQ(0, mobile_node->child_count());
217 EXPECT_EQ(BookmarkNode::SYNCED, synced_node->type()); 217 EXPECT_EQ(BookmarkNode::MOBILE, mobile_node->type());
218 218
219 EXPECT_TRUE(bb_node->id() != other_node->id()); 219 EXPECT_TRUE(bb_node->id() != other_node->id());
220 EXPECT_TRUE(bb_node->id() != synced_node->id()); 220 EXPECT_TRUE(bb_node->id() != mobile_node->id());
221 EXPECT_TRUE(other_node->id() != synced_node->id()); 221 EXPECT_TRUE(other_node->id() != mobile_node->id());
222 } 222 }
223 223
224 TEST_F(BookmarkModelTest, AddURL) { 224 TEST_F(BookmarkModelTest, AddURL) {
225 const BookmarkNode* root = model_.bookmark_bar_node(); 225 const BookmarkNode* root = model_.bookmark_bar_node();
226 const string16 title(ASCIIToUTF16("foo")); 226 const string16 title(ASCIIToUTF16("foo"));
227 const GURL url("http://foo.com"); 227 const GURL url("http://foo.com");
228 228
229 const BookmarkNode* new_node = model_.AddURL(root, 0, title, url); 229 const BookmarkNode* new_node = model_.AddURL(root, 0, title, url);
230 AssertObserverCount(1, 0, 0, 0, 0); 230 AssertObserverCount(1, 0, 0, 0, 0);
231 observer_details_.ExpectEquals(root, NULL, 0, -1); 231 observer_details_.ExpectEquals(root, NULL, 0, -1);
232 232
233 ASSERT_EQ(1, root->child_count()); 233 ASSERT_EQ(1, root->child_count());
234 ASSERT_EQ(title, new_node->GetTitle()); 234 ASSERT_EQ(title, new_node->GetTitle());
235 ASSERT_TRUE(url == new_node->url()); 235 ASSERT_TRUE(url == new_node->url());
236 ASSERT_EQ(BookmarkNode::URL, new_node->type()); 236 ASSERT_EQ(BookmarkNode::URL, new_node->type());
237 ASSERT_TRUE(new_node == model_.GetMostRecentlyAddedNodeForURL(url)); 237 ASSERT_TRUE(new_node == model_.GetMostRecentlyAddedNodeForURL(url));
238 238
239 EXPECT_TRUE(new_node->id() != root->id() && 239 EXPECT_TRUE(new_node->id() != root->id() &&
240 new_node->id() != model_.other_node()->id() && 240 new_node->id() != model_.other_node()->id() &&
241 new_node->id() != model_.synced_node()->id()); 241 new_node->id() != model_.mobile_node()->id());
242 } 242 }
243 243
244 TEST_F(BookmarkModelTest, AddURLWithWhitespaceTitle) { 244 TEST_F(BookmarkModelTest, AddURLWithWhitespaceTitle) {
245 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(whitespace_test_cases); ++i) { 245 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(whitespace_test_cases); ++i) {
246 const BookmarkNode* root = model_.bookmark_bar_node(); 246 const BookmarkNode* root = model_.bookmark_bar_node();
247 const string16 title(ASCIIToUTF16(whitespace_test_cases[i].input_title)); 247 const string16 title(ASCIIToUTF16(whitespace_test_cases[i].input_title));
248 const GURL url("http://foo.com"); 248 const GURL url("http://foo.com");
249 249
250 const BookmarkNode* new_node = model_.AddURL(root, i, title, url); 250 const BookmarkNode* new_node = model_.AddURL(root, i, title, url);
251 251
252 int size = i + 1; 252 int size = i + 1;
253 EXPECT_EQ(size, root->child_count()); 253 EXPECT_EQ(size, root->child_count());
254 EXPECT_EQ(ASCIIToUTF16(whitespace_test_cases[i].expected_title), 254 EXPECT_EQ(ASCIIToUTF16(whitespace_test_cases[i].expected_title),
255 new_node->GetTitle()); 255 new_node->GetTitle());
256 EXPECT_EQ(BookmarkNode::URL, new_node->type()); 256 EXPECT_EQ(BookmarkNode::URL, new_node->type());
257 } 257 }
258 } 258 }
259 259
260 TEST_F(BookmarkModelTest, AddURLToSyncedBookmarks) { 260 TEST_F(BookmarkModelTest, AddURLToMobileBookmarks) {
261 const BookmarkNode* root = model_.synced_node(); 261 const BookmarkNode* root = model_.mobile_node();
262 const string16 title(ASCIIToUTF16("foo")); 262 const string16 title(ASCIIToUTF16("foo"));
263 const GURL url("http://foo.com"); 263 const GURL url("http://foo.com");
264 264
265 const BookmarkNode* new_node = model_.AddURL(root, 0, title, url); 265 const BookmarkNode* new_node = model_.AddURL(root, 0, title, url);
266 AssertObserverCount(1, 0, 0, 0, 0); 266 AssertObserverCount(1, 0, 0, 0, 0);
267 observer_details_.ExpectEquals(root, NULL, 0, -1); 267 observer_details_.ExpectEquals(root, NULL, 0, -1);
268 268
269 ASSERT_EQ(1, root->child_count()); 269 ASSERT_EQ(1, root->child_count());
270 ASSERT_EQ(title, new_node->GetTitle()); 270 ASSERT_EQ(title, new_node->GetTitle());
271 ASSERT_TRUE(url == new_node->url()); 271 ASSERT_TRUE(url == new_node->url());
272 ASSERT_EQ(BookmarkNode::URL, new_node->type()); 272 ASSERT_EQ(BookmarkNode::URL, new_node->type());
273 ASSERT_TRUE(new_node == model_.GetMostRecentlyAddedNodeForURL(url)); 273 ASSERT_TRUE(new_node == model_.GetMostRecentlyAddedNodeForURL(url));
274 274
275 EXPECT_TRUE(new_node->id() != root->id() && 275 EXPECT_TRUE(new_node->id() != root->id() &&
276 new_node->id() != model_.other_node()->id() && 276 new_node->id() != model_.other_node()->id() &&
277 new_node->id() != model_.synced_node()->id()); 277 new_node->id() != model_.mobile_node()->id());
278 } 278 }
279 279
280 TEST_F(BookmarkModelTest, AddFolder) { 280 TEST_F(BookmarkModelTest, AddFolder) {
281 const BookmarkNode* root = model_.bookmark_bar_node(); 281 const BookmarkNode* root = model_.bookmark_bar_node();
282 const string16 title(ASCIIToUTF16("foo")); 282 const string16 title(ASCIIToUTF16("foo"));
283 283
284 const BookmarkNode* new_node = model_.AddFolder(root, 0, title); 284 const BookmarkNode* new_node = model_.AddFolder(root, 0, title);
285 AssertObserverCount(1, 0, 0, 0, 0); 285 AssertObserverCount(1, 0, 0, 0, 0);
286 observer_details_.ExpectEquals(root, NULL, 0, -1); 286 observer_details_.ExpectEquals(root, NULL, 0, -1);
287 287
288 ASSERT_EQ(1, root->child_count()); 288 ASSERT_EQ(1, root->child_count());
289 ASSERT_EQ(title, new_node->GetTitle()); 289 ASSERT_EQ(title, new_node->GetTitle());
290 ASSERT_EQ(BookmarkNode::FOLDER, new_node->type()); 290 ASSERT_EQ(BookmarkNode::FOLDER, new_node->type());
291 291
292 EXPECT_TRUE(new_node->id() != root->id() && 292 EXPECT_TRUE(new_node->id() != root->id() &&
293 new_node->id() != model_.other_node()->id() && 293 new_node->id() != model_.other_node()->id() &&
294 new_node->id() != model_.synced_node()->id()); 294 new_node->id() != model_.mobile_node()->id());
295 295
296 // Add another folder, just to make sure folder_ids are incremented correctly. 296 // Add another folder, just to make sure folder_ids are incremented correctly.
297 ClearCounts(); 297 ClearCounts();
298 model_.AddFolder(root, 0, title); 298 model_.AddFolder(root, 0, title);
299 AssertObserverCount(1, 0, 0, 0, 0); 299 AssertObserverCount(1, 0, 0, 0, 0);
300 observer_details_.ExpectEquals(root, NULL, 0, -1); 300 observer_details_.ExpectEquals(root, NULL, 0, -1);
301 } 301 }
302 302
303 TEST_F(BookmarkModelTest, AddFolderWithWhitespaceTitle) { 303 TEST_F(BookmarkModelTest, AddFolderWithWhitespaceTitle) {
304 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(whitespace_test_cases); ++i) { 304 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(whitespace_test_cases); ++i) {
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after
484 ASSERT_EQ(model_.bookmark_bar_node(), model_.GetParentForNewNodes()); 484 ASSERT_EQ(model_.bookmark_bar_node(), model_.GetParentForNewNodes());
485 485
486 const string16 title(ASCIIToUTF16("foo")); 486 const string16 title(ASCIIToUTF16("foo"));
487 const GURL url("http://foo.com"); 487 const GURL url("http://foo.com");
488 488
489 model_.AddURL(model_.other_node(), 0, title, url); 489 model_.AddURL(model_.other_node(), 0, title, url);
490 ASSERT_EQ(model_.other_node(), model_.GetParentForNewNodes()); 490 ASSERT_EQ(model_.other_node(), model_.GetParentForNewNodes());
491 } 491 }
492 492
493 // Tests that adding a URL to a folder updates the last modified time. 493 // Tests that adding a URL to a folder updates the last modified time.
494 TEST_F(BookmarkModelTest, ParentForNewSyncedNodes) { 494 TEST_F(BookmarkModelTest, ParentForNewMobileNodes) {
495 ASSERT_EQ(model_.bookmark_bar_node(), model_.GetParentForNewNodes()); 495 ASSERT_EQ(model_.bookmark_bar_node(), model_.GetParentForNewNodes());
496 496
497 const string16 title(ASCIIToUTF16("foo")); 497 const string16 title(ASCIIToUTF16("foo"));
498 const GURL url("http://foo.com"); 498 const GURL url("http://foo.com");
499 499
500 model_.AddURL(model_.synced_node(), 0, title, url); 500 model_.AddURL(model_.mobile_node(), 0, title, url);
501 ASSERT_EQ(model_.synced_node(), model_.GetParentForNewNodes()); 501 ASSERT_EQ(model_.mobile_node(), model_.GetParentForNewNodes());
502 } 502 }
503 503
504 // Make sure recently modified stays in sync when adding a URL. 504 // Make sure recently modified stays in sync when adding a URL.
505 TEST_F(BookmarkModelTest, MostRecentlyModifiedFolders) { 505 TEST_F(BookmarkModelTest, MostRecentlyModifiedFolders) {
506 // Add a folder. 506 // Add a folder.
507 const BookmarkNode* folder = model_.AddFolder(model_.other_node(), 0, 507 const BookmarkNode* folder = model_.AddFolder(model_.other_node(), 0,
508 ASCIIToUTF16("foo")); 508 ASCIIToUTF16("foo"));
509 // Add a URL to it. 509 // Add a URL to it.
510 model_.AddURL(folder, 0, ASCIIToUTF16("blah"), GURL("http://foo.com")); 510 model_.AddURL(folder, 0, ASCIIToUTF16("blah"), GURL("http://foo.com"));
511 511
(...skipping 317 matching lines...) Expand 10 before | Expand all | Expand 10 after
829 // Creates a set of nodes in the bookmark bar model, then recreates the 829 // Creates a set of nodes in the bookmark bar model, then recreates the
830 // bookmark bar model which triggers loading from the db and checks the loaded 830 // bookmark bar model which triggers loading from the db and checks the loaded
831 // structure to make sure it is what we first created. 831 // structure to make sure it is what we first created.
832 TEST_F(BookmarkModelTestWithProfile, CreateAndRestore) { 832 TEST_F(BookmarkModelTestWithProfile, CreateAndRestore) {
833 struct TestData { 833 struct TestData {
834 // Structure of the children of the bookmark bar model node. 834 // Structure of the children of the bookmark bar model node.
835 const std::string bbn_contents; 835 const std::string bbn_contents;
836 // Structure of the children of the other node. 836 // Structure of the children of the other node.
837 const std::string other_contents; 837 const std::string other_contents;
838 // Structure of the children of the synced node. 838 // Structure of the children of the synced node.
839 const std::string synced_contents; 839 const std::string mobile_contents;
840 } data[] = { 840 } data[] = {
841 // See PopulateNodeFromString for a description of these strings. 841 // See PopulateNodeFromString for a description of these strings.
842 { "", "" }, 842 { "", "" },
843 { "a", "b" }, 843 { "a", "b" },
844 { "a [ b ]", "" }, 844 { "a [ b ]", "" },
845 { "", "[ b ] a [ c [ d e [ f ] ] ]" }, 845 { "", "[ b ] a [ c [ d e [ f ] ] ]" },
846 { "a [ b ]", "" }, 846 { "a [ b ]", "" },
847 { "a b c [ d e [ f ] ]", "g h i [ j k [ l ] ]"}, 847 { "a b c [ d e [ f ] ]", "g h i [ j k [ l ] ]"},
848 }; 848 };
849 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { 849 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) {
850 // Recreate the profile. We need to reset with NULL first so that the last 850 // Recreate the profile. We need to reset with NULL first so that the last
851 // HistoryService releases the locks on the files it creates and we can 851 // HistoryService releases the locks on the files it creates and we can
852 // delete them. 852 // delete them.
853 profile_.reset(NULL); 853 profile_.reset(NULL);
854 profile_.reset(new TestingProfile()); 854 profile_.reset(new TestingProfile());
855 profile_->CreateBookmarkModel(true); 855 profile_->CreateBookmarkModel(true);
856 profile_->CreateHistoryService(true, false); 856 profile_->CreateHistoryService(true, false);
857 BlockTillBookmarkModelLoaded(); 857 BlockTillBookmarkModelLoaded();
858 858
859 TestNode bbn; 859 TestNode bbn;
860 PopulateNodeFromString(data[i].bbn_contents, &bbn); 860 PopulateNodeFromString(data[i].bbn_contents, &bbn);
861 PopulateBookmarkNode(&bbn, bb_model_, bb_model_->bookmark_bar_node()); 861 PopulateBookmarkNode(&bbn, bb_model_, bb_model_->bookmark_bar_node());
862 862
863 TestNode other; 863 TestNode other;
864 PopulateNodeFromString(data[i].other_contents, &other); 864 PopulateNodeFromString(data[i].other_contents, &other);
865 PopulateBookmarkNode(&other, bb_model_, bb_model_->other_node()); 865 PopulateBookmarkNode(&other, bb_model_, bb_model_->other_node());
866 866
867 TestNode synced; 867 TestNode mobile;
868 PopulateNodeFromString(data[i].synced_contents, &synced); 868 PopulateNodeFromString(data[i].mobile_contents, &mobile);
869 PopulateBookmarkNode(&synced, bb_model_, bb_model_->synced_node()); 869 PopulateBookmarkNode(&mobile, bb_model_, bb_model_->mobile_node());
870 870
871 profile_->CreateBookmarkModel(false); 871 profile_->CreateBookmarkModel(false);
872 BlockTillBookmarkModelLoaded(); 872 BlockTillBookmarkModelLoaded();
873 873
874 VerifyModelMatchesNode(&bbn, bb_model_->bookmark_bar_node()); 874 VerifyModelMatchesNode(&bbn, bb_model_->bookmark_bar_node());
875 VerifyModelMatchesNode(&other, bb_model_->other_node()); 875 VerifyModelMatchesNode(&other, bb_model_->other_node());
876 VerifyModelMatchesNode(&synced, bb_model_->synced_node()); 876 VerifyModelMatchesNode(&mobile, bb_model_->mobile_node());
877 VerifyNoDuplicateIDs(bb_model_); 877 VerifyNoDuplicateIDs(bb_model_);
878 } 878 }
879 } 879 }
880 880
881 // Test class that creates a BookmarkModel with a real history backend. 881 // Test class that creates a BookmarkModel with a real history backend.
882 class BookmarkModelTestWithProfile2 : public BookmarkModelTestWithProfile { 882 class BookmarkModelTestWithProfile2 : public BookmarkModelTestWithProfile {
883 public: 883 public:
884 virtual void SetUp() { 884 virtual void SetUp() {
885 profile_.reset(new TestingProfile()); 885 profile_.reset(new TestingProfile());
886 } 886 }
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
936 ASSERT_TRUE(child->is_folder()); 936 ASSERT_TRUE(child->is_folder());
937 ASSERT_EQ(ASCIIToUTF16("OF1"), child->GetTitle()); 937 ASSERT_EQ(ASCIIToUTF16("OF1"), child->GetTitle());
938 ASSERT_EQ(0, child->child_count()); 938 ASSERT_EQ(0, child->child_count());
939 939
940 child = parent->GetChild(1); 940 child = parent->GetChild(1);
941 ASSERT_EQ(BookmarkNode::URL, child->type()); 941 ASSERT_EQ(BookmarkNode::URL, child->type());
942 ASSERT_EQ(ASCIIToUTF16("About Google"), child->GetTitle()); 942 ASSERT_EQ(ASCIIToUTF16("About Google"), child->GetTitle());
943 ASSERT_TRUE(child->url() == 943 ASSERT_TRUE(child->url() ==
944 GURL("http://www.google.com/intl/en/about.html")); 944 GURL("http://www.google.com/intl/en/about.html"));
945 945
946 parent = bb_model_->synced_node(); 946 parent = bb_model_->mobile_node();
947 ASSERT_EQ(0, parent->child_count()); 947 ASSERT_EQ(0, parent->child_count());
948 948
949 ASSERT_TRUE(bb_model_->IsBookmarked(GURL("http://www.google.com"))); 949 ASSERT_TRUE(bb_model_->IsBookmarked(GURL("http://www.google.com")));
950 } 950 }
951 951
952 void VerifyUniqueIDs() { 952 void VerifyUniqueIDs() {
953 std::set<int64> ids; 953 std::set<int64> ids;
954 bool has_unique = true; 954 bool has_unique = true;
955 VerifyUniqueIDImpl(bb_model_->bookmark_bar_node(), &ids, &has_unique); 955 VerifyUniqueIDImpl(bb_model_->bookmark_bar_node(), &ids, &has_unique);
956 VerifyUniqueIDImpl(bb_model_->other_node(), &ids, &has_unique); 956 VerifyUniqueIDImpl(bb_model_->other_node(), &ids, &has_unique);
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
1074 AssertObserverCount(0, 0, 0, 0, 1); 1074 AssertObserverCount(0, 0, 0, 0, 1);
1075 1075
1076 // Make sure the order matches (remember, 'a' and 'C' are folders and 1076 // Make sure the order matches (remember, 'a' and 'C' are folders and
1077 // come first). 1077 // come first).
1078 EXPECT_EQ(parent->GetChild(0)->GetTitle(), ASCIIToUTF16("a")); 1078 EXPECT_EQ(parent->GetChild(0)->GetTitle(), ASCIIToUTF16("a"));
1079 EXPECT_EQ(parent->GetChild(1)->GetTitle(), ASCIIToUTF16("C")); 1079 EXPECT_EQ(parent->GetChild(1)->GetTitle(), ASCIIToUTF16("C"));
1080 EXPECT_EQ(parent->GetChild(2)->GetTitle(), ASCIIToUTF16("B")); 1080 EXPECT_EQ(parent->GetChild(2)->GetTitle(), ASCIIToUTF16("B"));
1081 EXPECT_EQ(parent->GetChild(3)->GetTitle(), ASCIIToUTF16("d")); 1081 EXPECT_EQ(parent->GetChild(3)->GetTitle(), ASCIIToUTF16("d"));
1082 } 1082 }
1083 1083
1084 TEST_F(BookmarkModelTest, NodeVisibility) {
1085 EXPECT_TRUE(model_.bookmark_bar_node()->IsVisible());
1086 EXPECT_TRUE(model_.other_node()->IsVisible());
1087 // Sync node invisible by default
1088 EXPECT_FALSE(model_.synced_node()->IsVisible());
1089
1090 // Arbitrary node should be visible
1091 TestNode bbn;
1092 PopulateNodeFromString("B", &bbn);
1093 const BookmarkNode* parent = model_.bookmark_bar_node();
1094 PopulateBookmarkNode(&bbn, &model_, parent);
1095 EXPECT_TRUE(parent->GetChild(0)->IsVisible());
1096 }
1097
1098 TEST_F(BookmarkModelTest, SyncNodeVisibileIfFlagSet) {
1099 CommandLine::ForCurrentProcess()->AppendSwitch(
1100 switches::kEnableSyncedBookmarksFolder);
1101 EXPECT_TRUE(model_.synced_node()->IsVisible());
1102 }
1103
1104 TEST_F(BookmarkModelTest, SyncNodeVisibileWithChildren) {
1105 const BookmarkNode* root = model_.synced_node();
1106 const string16 title(ASCIIToUTF16("foo"));
1107 const GURL url("http://foo.com");
1108
1109 model_.AddURL(root, 0, title, url);
1110 EXPECT_TRUE(model_.synced_node()->IsVisible());
1111 }
1112
1113 } // namespace 1084 } // namespace
OLDNEW
« no previous file with comments | « chrome/browser/bookmarks/bookmark_model_test_utils.cc ('k') | chrome/browser/bookmarks/bookmark_storage.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698