1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/command_line.h" 6 #include "base/file_util.h" 7 #include "base/files/file_path.h" 8 #include "base/path_service.h" 9 #include "base/strings/string_split.h" 10 #include "base/strings/string_util.h" 11 #include "base/threading/sequenced_worker_pool.h" 12 #include "chrome/browser/chrome_notification_types.h" 13 #include "chrome/browser/browser_process.h" 14 #include "chrome/browser/page_cycler/page_cycler.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/profiles/profile_manager.h" 17 #include "chrome/browser/ui/browser_list.h" 18 #include "chrome/common/chrome_paths.h" 19 #include "chrome/common/chrome_switches.h" 20 #include "chrome/test/base/in_process_browser_test.h" 21 #include "chrome/test/base/testing_profile.h" 22 #include "chrome/test/base/ui_test_utils.h" 23 #include "content/public/browser/notification_observer.h" 24 #include "content/public/browser/notification_registrar.h" 25 #include "content/public/browser/notification_service.h" 26 #include "content/public/common/content_switches.h" 27 #include "content/public/common/url_constants.h" 28 #include "url/gurl.h" 29 30 // TODO(kbr): remove: http://crbug.com/222296 31 #if defined(OS_MACOSX) 32 #import "base/mac/mac_util.h" 33 #endif 34 35 // Basic PageCyclerBrowserTest structure; used in testing most of PageCycler's 36 // functionality. 37 class PageCyclerBrowserTest : public content::NotificationObserver, 38 public InProcessBrowserTest { 39 public: 40 PageCyclerBrowserTest() : page_cycler_(NULL) { 41 } 42 43 virtual ~PageCyclerBrowserTest() { 44 } 45 46 // Initialize file paths within a temporary directory; this should be 47 // empty and nonexistent. 48 virtual void InitFilePaths(base::FilePath temp_path) { 49 temp_path_ = temp_path; 50 urls_file_ = temp_path.AppendASCII("urls_file"); 51 errors_file_ = temp_path.AppendASCII("errors"); 52 stats_file_ = temp_path.AppendASCII("stats"); 53 54 ASSERT_FALSE(base::PathExists(urls_file_)); 55 ASSERT_FALSE(base::PathExists(errors_file_)); 56 ASSERT_FALSE(base::PathExists(stats_file_)); 57 } 58 59 // Initialize a PageCycler using either the base fields, or using provided 60 // ones. 61 void InitPageCycler() { 62 page_cycler_ = new PageCycler(browser(), urls_file()); 63 page_cycler_->set_errors_file(errors_file()); 64 page_cycler_->set_stats_file(stats_file()); 65 } 66 67 void InitPageCycler(base::FilePath urls_file, 68 base::FilePath errors_file, 69 base::FilePath stats_file) { 70 page_cycler_ = new PageCycler(browser(), urls_file); 71 page_cycler_->set_errors_file(errors_file); 72 page_cycler_->set_stats_file(stats_file); 73 } 74 75 void RegisterForNotifications() { 76 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSED, 77 content::NotificationService::AllSources()); 78 } 79 80 // Get a collection of basic urls which are stored in the test directory. 81 // NOTE: |test_server| must be started first! 82 std::vector<GURL> GetURLs() { 83 std::vector<GURL> urls; 84 urls.push_back(test_server()->GetURL("files/page_cycler/basic_html.html")); 85 urls.push_back(test_server()->GetURL("files/page_cycler/basic_js.html")); 86 urls.push_back(test_server()->GetURL("files/page_cycler/basic_css.html")); 87 return urls; 88 } 89 90 // Read the errors file, and generate a vector of error strings. 91 std::vector<std::string> GetErrorsFromFile() { 92 std::string error_file_contents; 93 CHECK(file_util::ReadFileToString(errors_file_, 94 &error_file_contents)); 95 if (error_file_contents[error_file_contents.size() - 1] == '\n') 96 error_file_contents.resize(error_file_contents.size() - 1); 97 98 std::vector<std::string> errors; 99 base::SplitString(error_file_contents, '\n', &errors); 100 101 return errors; 102 } 103 104 // Convert a vector of GURLs into a newline-separated string, ready to be 105 // written to the urls file for PageCycler to use. 106 std::string GetStringFromURLs(std::vector<GURL> urls) { 107 std::string urls_string; 108 for (std::vector<GURL>::const_iterator iter = urls.begin(); 109 iter != urls.end(); ++iter) 110 urls_string.append(iter->spec() + "\n"); 111 return urls_string; 112 } 113 114 // content::NotificationObserver. 115 virtual void Observe(int type, 116 const content::NotificationSource& source, 117 const content::NotificationDetails& details) OVERRIDE { 118 switch (type) { 119 case chrome::NOTIFICATION_BROWSER_CLOSED: 120 base::MessageLoop::current()->PostTask( 121 FROM_HERE, base::MessageLoop::QuitClosure()); 122 break; 123 default: 124 NOTREACHED(); 125 break; 126 } 127 } 128 129 base::FilePath urls_file() { return urls_file_; } 130 base::FilePath errors_file() { return errors_file_; } 131 base::FilePath stats_file() { return stats_file_; } 132 PageCycler* page_cycler() { return page_cycler_; } 133 134 protected: 135 base::FilePath temp_path_; 136 base::FilePath urls_file_; 137 base::FilePath errors_file_; 138 base::FilePath stats_file_; 139 PageCycler* page_cycler_; 140 content::NotificationRegistrar registrar_; 141 }; 142 143 // Structure used for testing PageCycler's ability to playback a series of 144 // URLs given a cache directory. 145 class PageCyclerCachedBrowserTest : public PageCyclerBrowserTest { 146 public: 147 // For a cached test, we use the provided user data directory from the test 148 // directory. 149 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 150 base::FilePath test_dir; 151 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir)); 152 test_dir = test_dir.AppendASCII("page_cycler"); 153 154 base::FilePath source_data_dir = test_dir.AppendASCII("cached_data_dir"); 155 CHECK(base::PathExists(source_data_dir)); 156 157 CHECK(user_data_dir_.CreateUniqueTempDir()); 158 159 base::FilePath dest_data_dir = 160 user_data_dir_.path().AppendASCII("cached_data_dir"); 161 CHECK(!base::PathExists(dest_data_dir)); 162 163 CHECK(base::CopyDirectory(source_data_dir, user_data_dir_.path(), 164 true)); // recursive. 165 CHECK(base::PathExists(dest_data_dir)); 166 167 command_line->AppendSwitchPath(switches::kUserDataDir, 168 dest_data_dir); 169 command_line->AppendSwitch(switches::kPlaybackMode); 170 } 171 172 // Initialize the file paths to use the UserDataDir's urls file, instead 173 // of one to be written. 174 virtual void InitFilePaths(base::FilePath temp_path) OVERRIDE { 175 urls_file_ = user_data_dir_.path().AppendASCII("cached_data_dir") 176 .AppendASCII("urls"); 177 errors_file_ = temp_path.AppendASCII("errors"); 178 stats_file_ = temp_path.AppendASCII("stats"); 179 180 ASSERT_TRUE(base::PathExists(urls_file_)); 181 ASSERT_FALSE(base::PathExists(errors_file_)); 182 ASSERT_FALSE(base::PathExists(stats_file_)); 183 } 184 185 private: 186 // The directory storing the copy of the UserDataDir. 187 base::ScopedTempDir user_data_dir_; 188 }; 189 190 // Sanity check; iterate through a series of URLs and make sure there are no 191 // errors. 192 IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, BasicTest) { 193 base::ScopedTempDir temp; 194 ASSERT_TRUE(temp.CreateUniqueTempDir()); 195 196 RegisterForNotifications(); 197 InitFilePaths(temp.path()); 198 199 ASSERT_TRUE(test_server()->Start()); 200 201 std::string urls_string = GetStringFromURLs(GetURLs());; 202 203 ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(), 204 urls_string.size())); 205 206 InitPageCycler(); 207 page_cycler()->Run(); 208 209 content::RunMessageLoop(); 210 ASSERT_FALSE(base::PathExists(errors_file())); 211 ASSERT_TRUE(base::PathExists(stats_file())); 212 } 213 214 // Test to make sure that PageCycler will recognize unvisitable URLs, and will 215 // handle them appropriately. 216 IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, UnvisitableURL) { 217 const size_t kNumErrors = 1; 218 const char kFakeURL[] = "http://www.pleasenoonehavethisurlanytimeinthenext" 219 "century.com/gibberish"; 220 base::ScopedTempDir temp; 221 ASSERT_TRUE(temp.CreateUniqueTempDir()); 222 223 RegisterForNotifications(); 224 InitFilePaths(temp.path()); 225 226 ASSERT_TRUE(test_server()->Start()); 227 228 std::vector<GURL> urls = GetURLs(); 229 urls.push_back(GURL(kFakeURL)); 230 std::string urls_string = GetStringFromURLs(urls); 231 232 ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(), 233 urls_string.size())); 234 235 InitPageCycler(); 236 page_cycler()->Run(); 237 238 content::RunMessageLoop(); 239 ASSERT_TRUE(base::PathExists(errors_file())); 240 ASSERT_TRUE(base::PathExists(stats_file())); 241 242 std::vector<std::string> errors = GetErrorsFromFile(); 243 244 ASSERT_EQ(kNumErrors, errors.size()); 245 246 // Check that each error message contains the fake URL (i.e., that it wasn't 247 // from a valid URL, and that the fake URL was caught each time). 248 ASSERT_NE(std::string::npos, errors[0].find(kFakeURL)); 249 } 250 251 // Test that PageCycler will remove an invalid URL prior to running. 252 IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, InvalidURL) { 253 const char kBadURL[] = "notarealurl"; 254 255 base::ScopedTempDir temp; 256 ASSERT_TRUE(temp.CreateUniqueTempDir()); 257 258 RegisterForNotifications(); 259 InitFilePaths(temp.path()); 260 261 ASSERT_TRUE(test_server()->Start()); 262 263 std::string urls_string = GetStringFromURLs(GetURLs()); 264 urls_string.append(kBadURL).append("\n"); 265 266 ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(), 267 urls_string.size())); 268 269 InitPageCycler(); 270 page_cycler()->Run(); 271 272 content::RunMessageLoop(); 273 ASSERT_TRUE(base::PathExists(errors_file())); 274 ASSERT_TRUE(base::PathExists(stats_file())); 275 276 std::vector<std::string> errors = GetErrorsFromFile(); 277 ASSERT_EQ(1u, errors.size()); 278 279 std::string expected_error = "Omitting invalid URL: "; 280 expected_error.append(kBadURL).append("."); 281 282 ASSERT_FALSE(errors[0].compare(expected_error)); 283 } 284 285 // Test that PageCycler will remove a Chrome Error URL prior to running. 286 IN_PROC_BROWSER_TEST_F(PageCyclerBrowserTest, ChromeErrorURL) { 287 base::ScopedTempDir temp; 288 ASSERT_TRUE(temp.CreateUniqueTempDir()); 289 290 RegisterForNotifications(); 291 InitFilePaths(temp.path()); 292 293 ASSERT_TRUE(test_server()->Start()); 294 295 std::vector<GURL> urls = GetURLs(); 296 urls.push_back(GURL(content::kUnreachableWebDataURL)); 297 std::string urls_string = GetStringFromURLs(urls); 298 299 ASSERT_TRUE(file_util::WriteFile(urls_file(), urls_string.c_str(), 300 urls_string.size())); 301 302 InitPageCycler(); 303 page_cycler()->Run(); 304 305 content::RunMessageLoop(); 306 ASSERT_TRUE(base::PathExists(errors_file())); 307 ASSERT_TRUE(base::PathExists(stats_file())); 308 309 std::vector<std::string> errors = GetErrorsFromFile(); 310 ASSERT_EQ(1u, errors.size()); 311 312 std::string expected_error = "Chrome error pages are not allowed as urls. " 313 "Omitting url: "; 314 expected_error.append(content::kUnreachableWebDataURL).append("."); 315 316 ASSERT_FALSE(errors[0].compare(expected_error)); 317 } 318 319 #if !defined(OS_CHROMEOS) 320 // TODO(rdevlin.cronin): Perhaps page cycler isn't completely implemented on 321 // ChromeOS? 322 323 // Test that PageCycler will visit all the urls from a cache directory 324 // successfully while in playback mode. 325 // Disabled due to flaky timeouts. Tracking bugs include 326 // [ http://crbug.com/159026 ], [ http://crbug.com/131333 ], and 327 // [ http://crbug.com/222296 ]. 328 IN_PROC_BROWSER_TEST_F(PageCyclerCachedBrowserTest, DISABLED_PlaybackMode) { 329 #if defined(OS_MACOSX) 330 // TODO(kbr): re-enable: http://crbug.com/222296 331 if (base::mac::IsOSMountainLionOrLater()) 332 return; 333 #endif 334 335 base::ScopedTempDir temp; 336 ASSERT_TRUE(temp.CreateUniqueTempDir()); 337 338 RegisterForNotifications(); 339 InitFilePaths(temp.path()); 340 341 InitPageCycler(); 342 343 page_cycler()->Run(); 344 345 content::RunMessageLoop(); 346 ASSERT_TRUE(base::PathExists(stats_file())); 347 ASSERT_FALSE(base::PathExists(errors_file())); 348 } 349 #endif // !defined(OS_CHROMEOS) 350 351 #if !defined(OS_CHROMEOS) 352 // TODO(rdevlin.cronin): Perhaps page cycler isn't completely implemented on 353 // ChromeOS? 354 355 // Test that PageCycler will have a cache miss if a URL is missing from the 356 // cache directory while in playback mode. 357 // Bug 131333: This test fails on a XP debug bot since Build 17609. 358 #if (defined(OS_WIN) || defined(OS_MACOSX)) && !defined(NDEBUG) 359 #define MAYBE_URLNotInCache DISABLED_URLNotInCache 360 #else 361 #define MAYBE_URLNotInCache URLNotInCache 362 #endif 363 IN_PROC_BROWSER_TEST_F(PageCyclerCachedBrowserTest, MAYBE_URLNotInCache) { 364 const char kCacheMissURL[] = "http://www.images.google.com/"; 365 366 base::ScopedTempDir temp; 367 ASSERT_TRUE(temp.CreateUniqueTempDir()); 368 369 RegisterForNotifications(); 370 InitFilePaths(temp.path()); 371 372 // Only use a single URL that is not in cache. That's sufficient for the test 373 // scenario, and makes things faster than needlessly cycling through all the 374 // other URLs. 375 376 base::FilePath new_urls_file = temp.path().AppendASCII("urls"); 377 ASSERT_FALSE(base::PathExists(new_urls_file)); 378 379 ASSERT_TRUE(file_util::WriteFile(new_urls_file, kCacheMissURL, 380 sizeof(kCacheMissURL))); 381 382 InitPageCycler(new_urls_file, errors_file(), stats_file()); 383 page_cycler()->Run(); 384 385 content::RunMessageLoop(); 386 ASSERT_TRUE(base::PathExists(errors_file())); 387 ASSERT_TRUE(base::PathExists(stats_file())); 388 389 std::vector<std::string> errors = GetErrorsFromFile(); 390 ASSERT_EQ(1u, errors.size()); 391 392 std::string expected_error; 393 expected_error.append("Failed to load the page at: ") 394 .append(kCacheMissURL) 395 .append(": The requested entry was not found in the cache."); 396 397 ASSERT_FALSE(errors[0].compare(expected_error)); 398 } 399 #endif // !defined(OS_CHROMEOS) 400