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.
37class 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.
145class 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.
192IN_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.
216IN_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.
252IN_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.
286IN_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 ].
328IN_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
363IN_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