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// This file provides reliablity test which runs under UI test framework. The
6// test is intended to run within QEMU environment.
7//
8// Usage 1: reliability_test
9// Upon invocation, it visits a hard coded list of sample URLs. This is mainly
10// used by buildbot, to verify reliability_test itself runs ok.
11//
12// Usage 2: reliability_test --site=url --startpage=start --endpage=end [...]
13// Upon invocation, it visits a list of URLs constructed as
14// "http://url/page?id=k". (start <= k <= end).
15//
16// Usage 3: reliability_test --list=file --startline=start --endline=end [...]
17// Upon invocation, it visits each of the URLs on line numbers between start
18// and end, inclusive, stored in the input file. The line number starts from 1.
19//
20// If both "--site" and "--list" are provided, the "--site" set of arguments
21// are ignored.
22//
23// Optional Switches:
24// --iterations=num: goes through the list of URLs constructed in usage 2 or 3
25//                   num times.
26// --continuousload: continuously visits the list of URLs without restarting
27//                    browser for each page load.
28// --memoryusage: prints out memory usage when visiting each page.
29// --endurl=url: visits the specified url in the end.
30// --logfile=filepath: saves the visit log to the specified path.
31// --noclearprofile: do not clear profile dir before firing up each time.
32// --savedebuglog: save Chrome, V8, and test debug log for each page loaded.
33// --searchdumpsbypid: Look for crash dumps by browser process id.
34//                     "crash_dir/pid/"
35
36#include <fstream>
37#include <vector>
38
39#include "base/command_line.h"
40#include "base/environment.h"
41#include "base/file_util.h"
42#include "base/file_version_info.h"
43#include "base/files/file_enumerator.h"
44#include "base/files/file_path.h"
45#include "base/i18n/time_formatting.h"
46#include "base/memory/scoped_ptr.h"
47#include "base/path_service.h"
48#include "base/prefs/json_pref_store.h"
49#include "base/prefs/pref_registry_simple.h"
50#include "base/prefs/pref_service.h"
51#include "base/process/kill.h"
52#include "base/strings/string_number_conversions.h"
53#include "base/strings/string_util.h"
54#include "base/test/test_file_util.h"
55#include "base/threading/platform_thread.h"
56#include "base/time/time.h"
57#include "chrome/browser/prefs/pref_service_mock_builder.h"
58#include "chrome/common/automation_messages.h"
59#include "chrome/common/chrome_constants.h"
60#include "chrome/common/chrome_paths.h"
61#include "chrome/common/chrome_switches.h"
62#include "chrome/common/chrome_version_info.h"
63#include "chrome/common/logging_chrome.h"
64#include "chrome/common/net/url_fixer_upper.h"
65#include "chrome/common/pref_names.h"
66#include "chrome/common/render_messages.h"
67#include "chrome/common/url_constants.h"
68#include "chrome/test/automation/automation_proxy.h"
69#include "chrome/test/automation/browser_proxy.h"
70#include "chrome/test/automation/tab_proxy.h"
71#include "chrome/test/automation/window_proxy.h"
72#include "chrome/test/ui/ui_test.h"
73#include "net/base/net_util.h"
74#include "v8/include/v8-testing.h"
75
76namespace {
77
78// See comments at the beginning of the file for the definition of switches.
79const char kSiteSwitch[] = "site";
80const char kStartPageSwitch[] = "startpage";
81const char kEndPageSwitch[] = "endpage";
82const char kListSwitch[] = "list";
83const char kStartIndexSwitch[] = "startline";
84const char kEndIndexSwitch[] = "endline";
85const char kIterationSwitch[] = "iterations";
86const char kContinuousLoadSwitch[] = "continuousload";
87const char kMemoryUsageSwitch[] = "memoryusage";
88const char kEndURLSwitch[] = "endurl";
89const char kLogFileSwitch[] = "logfile";
90const char kNoPageDownSwitch[] = "nopagedown";
91const char kNoClearProfileSwitch[] = "noclearprofile";
92const char kSaveDebugLogSwitch[] = "savedebuglog";
93const char kStressOptSwitch[] = "stress-opt";
94const char kStressDeoptSwitch[] = "stress-deopt";
95
96const char kDefaultServerUrl[] = "http://urllist.com";
97std::string g_server_url;
98const char kTestPage1[] = "page1.html";
99const char kTestPage2[] = "page2.html";
100
101// These are copied from v8 definitions as we cannot include them.
102const char kV8LogFileSwitch[] = "logfile";
103const char kV8LogFileDefaultName[] = "v8.log";
104
105// String name of local chrome dll for looking up file information.
106const wchar_t kChromeDll[] = L"chrome.dll";
107
108bool g_append_page_id = false;
109int32 g_start_page;
110int32 g_end_page;
111base::FilePath g_url_file_path;
112int32 g_start_index = 1;
113int32 g_end_index = kint32max;
114int32 g_iterations = 1;
115bool g_memory_usage = false;
116bool g_continuous_load = false;
117bool g_browser_existing = false;
118bool g_clear_profile = true;
119std::string g_end_url;
120base::FilePath g_log_file_path;
121bool g_save_debug_log = false;
122base::FilePath g_chrome_log_path;
123base::FilePath g_v8_log_path;
124base::FilePath g_test_log_path;
125bool g_stand_alone = false;
126bool g_stress_opt = false;
127bool g_stress_deopt = false;
128
129void ReportHandler(const std::string& str) {
130  // Ignore report events.
131}
132
133void SetPageRange(const CommandLine& parsed_command_line) {
134  // If calling into this function, we are running as a standalone program.
135  g_stand_alone = true;
136
137  // Since we use --enable-dcheck for reliability tests, suppress the error
138  // dialog in the test process.
139  logging::SetLogReportHandler(ReportHandler);
140
141  if (parsed_command_line.HasSwitch(kStartPageSwitch)) {
142    ASSERT_TRUE(parsed_command_line.HasSwitch(kEndPageSwitch));
143    ASSERT_TRUE(
144        base::StringToInt(parsed_command_line.GetSwitchValueASCII(
145                              kStartPageSwitch),
146                          &g_start_page));
147    ASSERT_TRUE(
148        base::StringToInt(parsed_command_line.GetSwitchValueASCII(
149                              kEndPageSwitch),
150                          &g_end_page));
151    ASSERT_TRUE(g_start_page > 0 && g_end_page > 0);
152    ASSERT_TRUE(g_start_page < g_end_page);
153    g_append_page_id = true;
154  } else {
155    ASSERT_FALSE(parsed_command_line.HasSwitch(kEndPageSwitch));
156  }
157
158  if (parsed_command_line.HasSwitch(kSiteSwitch)) {
159    g_server_url = parsed_command_line.GetSwitchValueASCII(kSiteSwitch);
160  }
161
162  if (parsed_command_line.HasSwitch(kStartIndexSwitch)) {
163    ASSERT_TRUE(
164        base::StringToInt(parsed_command_line.GetSwitchValueASCII(
165                              kStartIndexSwitch),
166                          &g_start_index));
167    ASSERT_GT(g_start_index, 0);
168  }
169
170  if (parsed_command_line.HasSwitch(kEndIndexSwitch)) {
171    ASSERT_TRUE(
172        base::StringToInt(parsed_command_line.GetSwitchValueASCII(
173                              kEndIndexSwitch),
174                          &g_end_index));
175    ASSERT_GT(g_end_index, 0);
176  }
177
178  ASSERT_TRUE(g_end_index >= g_start_index);
179
180  if (parsed_command_line.HasSwitch(kListSwitch))
181    g_url_file_path = parsed_command_line.GetSwitchValuePath(kListSwitch);
182
183  if (parsed_command_line.HasSwitch(kIterationSwitch)) {
184    ASSERT_TRUE(
185        base::StringToInt(parsed_command_line.GetSwitchValueASCII(
186                              kIterationSwitch),
187                          &g_iterations));
188    ASSERT_GT(g_iterations, 0);
189  }
190
191  if (parsed_command_line.HasSwitch(kMemoryUsageSwitch))
192    g_memory_usage = true;
193
194  if (parsed_command_line.HasSwitch(kContinuousLoadSwitch))
195    g_continuous_load = true;
196
197  if (parsed_command_line.HasSwitch(kEndURLSwitch))
198    g_end_url = parsed_command_line.GetSwitchValueASCII(kEndURLSwitch);
199
200  if (parsed_command_line.HasSwitch(kLogFileSwitch))
201    g_log_file_path = parsed_command_line.GetSwitchValuePath(kLogFileSwitch);
202
203  if (parsed_command_line.HasSwitch(kNoClearProfileSwitch))
204    g_clear_profile = false;
205
206  if (parsed_command_line.HasSwitch(kSaveDebugLogSwitch)) {
207    g_save_debug_log = true;
208    g_chrome_log_path = logging::GetLogFileName();
209    // We won't get v8 log unless --no-sandbox is specified.
210    if (parsed_command_line.HasSwitch(switches::kNoSandbox)) {
211      PathService::Get(base::DIR_CURRENT, &g_v8_log_path);
212      g_v8_log_path = g_v8_log_path.AppendASCII(kV8LogFileDefaultName);
213      // The command line switch may override the default v8 log path.
214      if (parsed_command_line.HasSwitch(switches::kJavaScriptFlags)) {
215        CommandLine v8_command_line(
216            parsed_command_line.GetSwitchValuePath(switches::kJavaScriptFlags));
217        if (v8_command_line.HasSwitch(kV8LogFileSwitch)) {
218          g_v8_log_path = base::MakeAbsoluteFilePath(
219              v8_command_line.GetSwitchValuePath(kV8LogFileSwitch));
220        }
221      }
222    }
223  }
224
225  if (parsed_command_line.HasSwitch(kStressOptSwitch)) {
226    g_stress_opt = true;
227  }
228  if (parsed_command_line.HasSwitch(kStressDeoptSwitch)) {
229    g_stress_deopt = true;
230  }
231}
232
233class PageLoadTest : public UITest {
234 public:
235  enum NavigationResult {
236    NAVIGATION_ERROR = 0,
237    NAVIGATION_SUCCESS,
238    NAVIGATION_AUTH_NEEDED,
239    NAVIGATION_TIME_OUT,
240  };
241
242  typedef struct {
243    // These are results from the test automation that drives Chrome
244    NavigationResult result;
245    int crash_dump_count;
246    // These are stability metrics recorded by Chrome itself
247    bool browser_clean_exit;
248    int browser_launch_count;
249    int page_load_count;
250    int browser_crash_count;
251    int renderer_crash_count;
252    int plugin_crash_count;
253  } NavigationMetrics;
254
255  PageLoadTest() {
256    show_window_ = true;
257    SetPageRange(*CommandLine::ForCurrentProcess());
258  }
259
260  void EnsureBrowserAndServer() {
261    if (!g_browser_existing) {
262      LaunchBrowserAndServer();
263      g_browser_existing = true;
264    }
265  }
266
267  // Load a URL in a browser tab and perform a couple of page down events.
268  //   url_string:      The URL to navigate to. Accept URL as std::string here
269  //                    because the url may also act as a test id and needs to
270  //                    be logged in its original format even if invalid.
271  //   log_file:        Log file for test results and possible crash dump
272  //                    files. This file does not need to be opened in which
273  //                    case nothing is logged.
274  //   metrics_output:  Return metrics for the page load.
275  //   keep_browser:    Set to true if the browser should be kept open after
276  //                    loading the page.
277  //   log_only_errors: Set to true if only errors should be logged otherwise
278  //                    successful navigations will also be logged.
279  bool NavigateToURLLogResult(const std::string& url_string,
280                              std::ofstream& log_file,
281                              NavigationMetrics* metrics_output,
282                              bool keep_browser,
283                              bool log_only_error) {
284    GURL url(url_string);
285    NavigationMetrics metrics = {NAVIGATION_ERROR};
286    std::ofstream test_log;
287
288    // Create a test log.
289    g_test_log_path = base::FilePath(FILE_PATH_LITERAL("test_log.log"));
290    test_log.open(g_test_log_path.value().c_str());
291
292    // Get the version of Chrome we're running.
293    std::string last_change;
294#if defined(OS_WIN)
295    // Check file version info for chrome dll.
296    scoped_ptr<FileVersionInfo> file_info;
297    file_info.reset(
298        FileVersionInfo::CreateFileVersionInfo(base::FilePath(kChromeDll)));
299    last_change = WideToASCII(file_info->last_change());
300#elif defined(OS_POSIX)
301    // TODO(fmeawad): On Mac, the version retrieved here belongs to the test
302    // module and not the chrome binary, need to be changed to chrome binary
303    // instead.
304    chrome::VersionInfo version_info;
305    last_change = version_info.LastChange();
306#endif  // !defined(OS_WIN)
307    test_log << "Last Change: ";
308    test_log << last_change << std::endl;
309
310    // Log timestamp for test start.
311    base::Time time_now = base::Time::Now();
312    double time_start = time_now.ToDoubleT();
313    test_log << "Test Start: ";
314    test_log << base::TimeFormatFriendlyDateAndTime(time_now) << std::endl;
315
316    // Make sure the browser is running.
317    EnsureBrowserAndServer();
318
319    // Log Browser Launched time.
320    time_now = base::Time::Now();
321    test_log << "browser_launched_seconds=";
322    test_log << (time_now.ToDoubleT() - time_start) << std::endl;
323
324    int result = AUTOMATION_MSG_NAVIGATION_ERROR;
325    // This is essentially what NavigateToURL does except we don't fire
326    // assertion when page loading fails. We log the result instead.
327    {
328      // TabProxy should be released before Browser is closed.
329      scoped_refptr<TabProxy> tab_proxy(GetActiveTab());
330      if (tab_proxy.get())
331        result = tab_proxy->NavigateToURL(url);
332    }
333
334    // Log navigate complete time.
335    time_now = base::Time::Now();
336    test_log << "navigate_complete_seconds=";
337    test_log << (time_now.ToDoubleT() - time_start) << std::endl;
338
339    if (!keep_browser) {
340      CloseBrowserAndServer();
341      g_browser_existing = false;
342    }
343
344    // Log end of test time.
345    time_now = base::Time::Now();
346    test_log << "total_duration_seconds=";
347    test_log << (time_now.ToDoubleT() - time_start) << std::endl;
348
349    // Get navigation result and metrics, and optionally write to the log file
350    // provided.  The log format is:
351    // <url> <navigation_result> <browser_crash_count> <renderer_crash_count>
352    // <plugin_crash_count> <crash_dump_count> [chrome_log=<path>
353    // v8_log=<path>] crash_dump=<path>
354    switch (result) {
355      case AUTOMATION_MSG_NAVIGATION_ERROR:
356        metrics.result = NAVIGATION_ERROR;
357        break;
358      case AUTOMATION_MSG_NAVIGATION_SUCCESS:
359        metrics.result = NAVIGATION_SUCCESS;
360        break;
361      case AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED:
362        metrics.result = NAVIGATION_AUTH_NEEDED;
363        break;
364      default:
365        metrics.result = NAVIGATION_ERROR;
366        break;
367    }
368
369    // Get crash dumps - don't delete them if logging.
370    std::vector<base::FilePath> new_crash_dumps;
371    CollectNewCrashDumps(new_crash_dumps, &metrics, !log_file.is_open());
372
373    bool do_log = log_file.is_open() &&
374                  (!log_only_error ||
375                   metrics.result != NAVIGATION_SUCCESS ||
376                   !new_crash_dumps.empty());
377    if (do_log) {
378      log_file << url_string;
379      switch (metrics.result) {
380        case NAVIGATION_ERROR:
381          log_file << " error";
382          break;
383        case NAVIGATION_SUCCESS:
384          log_file << " success";
385          break;
386        case NAVIGATION_AUTH_NEEDED:
387          log_file << " auth_needed";
388          break;
389        case NAVIGATION_TIME_OUT:
390          log_file << " timeout";
391          break;
392        default:
393          break;
394      }
395    }
396
397#if !defined(OS_MACOSX)  // Not used by mac chromebot.
398    // Get stability metrics recorded by Chrome itself.
399    GetStabilityMetrics(&metrics);
400#endif
401
402    if (do_log) {
403      log_file << " " << metrics.browser_crash_count \
404               // The renderer crash count is flaky due to 1183283.
405               // Ignore the count since we also catch crash by
406               // crash_dump_count.
407               << " " << 0 \
408               << " " << metrics.plugin_crash_count \
409               << " " << metrics.crash_dump_count;
410    }
411
412    // Close test log.
413    test_log.close();
414
415    if (do_log && g_save_debug_log && !g_continuous_load)
416      SaveDebugLogs(log_file);
417
418    // Log revision information for Chrome build under test.
419    if (do_log)
420      log_file << " " << "revision=" << last_change;
421
422    if (do_log) {
423      for (size_t i = 0; i < new_crash_dumps.size(); i++)
424        log_file << " crash_dump=" << new_crash_dumps[i].value().c_str();
425    }
426
427    if (do_log)
428      log_file << std::endl;
429
430    if (metrics_output)
431      *metrics_output = metrics;
432
433    return do_log;
434  }
435
436  void NavigateThroughPageID(std::ofstream& log_file) {
437    if (g_append_page_id) {
438      // For usage 2
439      for (int i = g_start_page; i <= g_end_page; ++i) {
440        const char* server = g_server_url.empty() ? kDefaultServerUrl :
441            g_server_url.c_str();
442        std::string test_page_url(
443            base::StringPrintf("%s/page?id=%d", server, i));
444        NavigateToURLLogResult(
445            test_page_url, log_file, NULL, g_continuous_load, false);
446      }
447    } else {
448      // Don't run if running as a standalone program which is for distributed
449      // testing, to avoid mistakenly hitting web sites with many instances.
450      if (g_stand_alone)
451        return;
452      // For usage 1
453      NavigationMetrics metrics;
454
455      base::FilePath sample_data_dir = GetSampleDataDir();
456      base::FilePath test_page_1 = sample_data_dir.AppendASCII(kTestPage1);
457      base::FilePath test_page_2 = sample_data_dir.AppendASCII(kTestPage2);
458
459      GURL test_url_1 = net::FilePathToFileURL(test_page_1);
460      GURL test_url_2 = net::FilePathToFileURL(test_page_2);
461
462      // Convert back to string so that all calls to navigate are the same.
463      const std::string test_url_1_string = test_url_1.spec();
464      const std::string test_url_2_string = test_url_2.spec();
465
466      NavigateToURLLogResult(
467          test_url_1_string, log_file, &metrics, g_continuous_load, false);
468      // Verify everything is fine
469      EXPECT_EQ(NAVIGATION_SUCCESS, metrics.result);
470      EXPECT_EQ(0, metrics.crash_dump_count);
471      EXPECT_TRUE(metrics.browser_clean_exit);
472      EXPECT_EQ(1, metrics.browser_launch_count);
473      // Both starting page and test_url_1 are loaded.
474      EXPECT_EQ(2, metrics.page_load_count);
475      EXPECT_EQ(0, metrics.browser_crash_count);
476      EXPECT_EQ(0, metrics.renderer_crash_count);
477      EXPECT_EQ(0, metrics.plugin_crash_count);
478
479      // Go to "about:crash"
480      NavigateToURLLogResult(content::kChromeUICrashURL,
481                             log_file,
482                             &metrics,
483                             g_continuous_load,
484                             false);
485      // Found a crash dump
486      EXPECT_EQ(1, metrics.crash_dump_count) << kFailedNoCrashService;
487      // Browser did not crash, and exited cleanly.
488      EXPECT_TRUE(metrics.browser_clean_exit);
489      EXPECT_EQ(1, metrics.browser_launch_count);
490      // Only the renderer should have crashed.
491      EXPECT_EQ(0, metrics.browser_crash_count);
492      EXPECT_EQ(1, metrics.renderer_crash_count);
493      EXPECT_EQ(0, metrics.plugin_crash_count);
494
495      NavigateToURLLogResult(
496          test_url_2_string, log_file, &metrics, g_continuous_load, false);
497      // The data on previous crash should be cleared and we should get
498      // metrics for a successful page load.
499      EXPECT_EQ(NAVIGATION_SUCCESS, metrics.result);
500      EXPECT_EQ(0, metrics.crash_dump_count);
501      EXPECT_TRUE(metrics.browser_clean_exit);
502      EXPECT_EQ(1, metrics.browser_launch_count);
503      EXPECT_EQ(0, metrics.browser_crash_count);
504      EXPECT_EQ(0, metrics.renderer_crash_count);
505      EXPECT_EQ(0, metrics.plugin_crash_count);
506
507      // Verify metrics service does what we need when browser process crashes.
508      LaunchBrowserAndServer();
509      {
510        // TabProxy should be released before Browser is closed.
511        scoped_refptr<TabProxy> tab_proxy(GetActiveTab());
512        EXPECT_TRUE(tab_proxy.get());
513        if (tab_proxy.get()) {
514          EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS,
515                    tab_proxy->NavigateToURL(GURL(test_url_1)));
516        }
517      }
518      // Kill browser process.
519      base::ProcessHandle browser_process = process();
520      base::KillProcess(browser_process, 0, false);
521
522      GetStabilityMetrics(&metrics);
523      // This is not a clean shutdown.
524      EXPECT_FALSE(metrics.browser_clean_exit);
525      EXPECT_EQ(1, metrics.browser_crash_count);
526      EXPECT_EQ(0, metrics.renderer_crash_count);
527      EXPECT_EQ(0, metrics.plugin_crash_count);
528      // Relaunch browser so UITest does not fire assertion during TearDown.
529      LaunchBrowserAndServer();
530    }
531  }
532
533  // For usage 3
534  void NavigateThroughURLList(std::ofstream& log_file) {
535    std::ifstream file(g_url_file_path.value().c_str());
536    ASSERT_TRUE(file.is_open());
537
538    for (int line_index = 1;
539         line_index <= g_end_index && !file.eof();
540         ++line_index) {
541      std::string url_str;
542      std::getline(file, url_str);
543
544      if (file.fail())
545        break;
546
547      if (g_start_index <= line_index) {
548        if (g_stress_opt || g_stress_deopt) {
549          // Make sure the browser is running to communicate the stress
550          // setting.
551          EnsureBrowserAndServer();
552          v8::Testing::StressType stress_type =
553              g_stress_opt
554                  ? v8::Testing::kStressTypeOpt
555                  : v8::Testing::kStressTypeDeopt;
556          scoped_refptr<TabProxy> tab_proxy(GetActiveTab());
557          if (tab_proxy.get()) {
558            tab_proxy->JavaScriptStressTestControl(
559                kJavaScriptStressTestSetStressRunType, stress_type);
560          }
561
562          bool success = true;
563          // Load each page a number of times and keep the same browser open
564          // for these loads. This loop will end if an error is encountered
565          // during one of the loads and the error logged.
566          for (int i = 0;
567               i < v8::Testing::GetStressRuns() && success;
568               i++) {
569            bool last_load = (i == (v8::Testing::GetStressRuns() - 1));
570            bool keep_browser = !last_load || g_continuous_load;
571            bool log_only_error = !last_load;
572            NavigationMetrics metrics;
573
574            scoped_refptr<TabProxy> tab_proxy(GetActiveTab());
575            if (tab_proxy.get()) {
576              tab_proxy->JavaScriptStressTestControl(
577                  kJavaScriptStressTestPrepareStressRun, i);
578            }
579            bool did_log_error;
580            did_log_error = NavigateToURLLogResult(url_str,
581                                                   log_file,
582                                                   &metrics,
583                                                   keep_browser,
584                                                   log_only_error);
585            success = metrics.result == NAVIGATION_SUCCESS && !did_log_error;
586          }
587        } else {
588          NavigateToURLLogResult(
589              url_str, log_file, NULL, g_continuous_load, false);
590        }
591      }
592    }
593
594    file.close();
595  }
596
597 protected:
598  // Call the base class's SetUp method and initialize our own class members.
599  virtual void SetUp() {
600    // Set UI Test members before setting up browser.
601    clear_profile_ = g_clear_profile;
602
603    UITest::SetUp();
604    g_browser_existing = true;
605
606    // If 'BREAKPAD_DUMP_LOCATION' environment variable is set, use it instead.
607    scoped_ptr<base::Environment> env(base::Environment::Create());
608    std::string alternate_minidump_location;
609    if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_minidump_location)) {
610      crash_dumps_dir_path_ = base::FilePath::FromUTF8Unsafe(
611          alternate_minidump_location);
612    } else {
613      PathService::Get(chrome::DIR_CRASH_DUMPS, &crash_dumps_dir_path_);
614    }
615
616    base::FileEnumerator enumerator(crash_dumps_dir_path_,
617                                    false,  // not recursive
618                                    base::FileEnumerator::FILES);
619    for (base::FilePath path = enumerator.Next(); !path.value().empty();
620         path = enumerator.Next()) {
621      if (path.MatchesExtension(FILE_PATH_LITERAL(".dmp")))
622        crash_dumps_[path.BaseName()] = true;
623    }
624  }
625
626  base::FilePath ConstructSavedDebugLogPath(
627      const base::FilePath& debug_log_path,
628      int index) {
629    std::string suffix("_");
630    suffix.append(base::IntToString(index));
631    return debug_log_path.InsertBeforeExtensionASCII(suffix);
632  }
633
634  void SaveDebugLog(const base::FilePath& log_path, const std::wstring& log_id,
635                    std::ofstream& log_file, int index) {
636    if (!log_path.empty()) {
637      base::FilePath saved_log_file_path =
638          ConstructSavedDebugLogPath(log_path, index);
639      if (base::Move(log_path, saved_log_file_path)) {
640        log_file << " " << log_id << "=" << saved_log_file_path.value();
641      }
642    }
643  }
644
645  // Rename the chrome and v8 debug log files if existing, and save the file
646  // paths in the log_file provided.
647  void SaveDebugLogs(std::ofstream& log_file) {
648    static int url_count = 1;
649    SaveDebugLog(g_chrome_log_path, L"chrome_log", log_file, url_count);
650    SaveDebugLog(g_v8_log_path, L"v8_log", log_file, url_count);
651    SaveDebugLog(g_test_log_path, L"test_log", log_file, url_count);
652    url_count++;
653  }
654
655  // Delete a crash dump file.
656  void DeleteCrashDump(base::FilePath crash_dump_file_name) {
657    base::FilePath crash_dump_file_path(crash_dumps_dir_path_);
658    crash_dump_file_path = crash_dump_file_path.Append(crash_dump_file_name);
659    base::FilePath crash_text_file_path =
660        crash_dump_file_path.ReplaceExtension(FILE_PATH_LITERAL("txt"));
661
662    ASSERT_TRUE(file_util::DieFileDie(crash_dump_file_path, false));
663    ASSERT_TRUE(file_util::DieFileDie(crash_text_file_path, false));
664  }
665
666  bool HasNewCrashDumps() {
667    base::FileEnumerator enumerator(crash_dumps_dir_path_,
668                                    false,  // not recursive
669                                    base::FileEnumerator::FILES);
670    for (base::FilePath path = enumerator.Next(); !path.value().empty();
671         path = enumerator.Next()) {
672      if (path.MatchesExtension(FILE_PATH_LITERAL(".dmp")) &&
673          !crash_dumps_[path.BaseName()]) {
674        return true;
675      }
676    }
677
678    return false;
679  }
680
681  // Check whether there are new .dmp files. Return the list and optionally
682  // delete them afterwards.
683  void CollectNewCrashDumps(std::vector<base::FilePath>& new_crash_dumps,
684                            NavigationMetrics* metrics,
685                            bool delete_dumps) {
686    int num_dumps = 0;
687    base::FileEnumerator enumerator(crash_dumps_dir_path_,
688                                    false,  // not recursive
689                                    base::FileEnumerator::FILES);
690    for (base::FilePath path = enumerator.Next(); !path.value().empty();
691         path = enumerator.Next()) {
692      if (path.MatchesExtension(FILE_PATH_LITERAL(".dmp")) &&
693          !crash_dumps_[path.BaseName()]) {
694        crash_dumps_[path.BaseName()] = true;
695        base::FilePath crash_dump_file_path(crash_dumps_dir_path_);
696        crash_dump_file_path = crash_dump_file_path.Append(path.BaseName());
697        new_crash_dumps.push_back(crash_dump_file_path);
698        if (delete_dumps)
699          DeleteCrashDump(path.BaseName());
700        num_dumps++;
701      }
702    }
703    if (metrics)
704      metrics->crash_dump_count = num_dumps;
705  }
706
707  // Get a PrefService whose contents correspond to the Local State file
708  // that was saved by the app as it closed.  The caller takes ownership of the
709  // returned PrefService object.
710  PrefService* GetLocalState(PrefRegistry* registry) {
711    base::FilePath path = user_data_dir().Append(chrome::kLocalStateFilename);
712    PrefServiceMockBuilder builder;
713    builder.WithUserFilePrefs(
714        path, base::MessageLoop::current()->message_loop_proxy().get());
715    return builder.Create(registry);
716  }
717
718  void GetStabilityMetrics(NavigationMetrics* metrics) {
719    if (!metrics)
720      return;
721    scoped_refptr<PrefRegistrySimple> registry = new PrefRegistrySimple();
722    registry->RegisterBooleanPref(prefs::kStabilityExitedCleanly, false);
723    registry->RegisterIntegerPref(prefs::kStabilityLaunchCount, -1);
724    registry->RegisterIntegerPref(prefs::kStabilityPageLoadCount, -1);
725    registry->RegisterIntegerPref(prefs::kStabilityCrashCount, 0);
726    registry->RegisterIntegerPref(prefs::kStabilityRendererCrashCount, 0);
727
728    scoped_ptr<PrefService> local_state(GetLocalState(registry.get()));
729    if (!local_state.get())
730      return;
731
732    metrics->browser_clean_exit =
733        local_state->GetBoolean(prefs::kStabilityExitedCleanly);
734    metrics->browser_launch_count =
735        local_state->GetInteger(prefs::kStabilityLaunchCount);
736    metrics->page_load_count =
737        local_state->GetInteger(prefs::kStabilityPageLoadCount);
738    metrics->browser_crash_count =
739        local_state->GetInteger(prefs::kStabilityCrashCount);
740    metrics->renderer_crash_count =
741        local_state->GetInteger(prefs::kStabilityRendererCrashCount);
742    // TODO(huanr)
743    metrics->plugin_crash_count = 0;
744
745    if (!metrics->browser_clean_exit)
746      metrics->browser_crash_count++;
747  }
748
749  base::FilePath GetSampleDataDir() {
750    base::FilePath test_dir;
751    PathService::Get(chrome::DIR_TEST_DATA, &test_dir);
752    test_dir = test_dir.AppendASCII("reliability");
753    test_dir = test_dir.AppendASCII("sample_pages");
754    return test_dir;
755  }
756
757  // The pathname of Chrome's crash dumps directory.
758  base::FilePath crash_dumps_dir_path_;
759
760  // The set of all the crash dumps we have seen.  Each crash generates a
761  // .dmp and a .txt file in the crash dumps directory.  We only store the
762  // .dmp files in this set.
763  //
764  // The set is implemented as a std::map.  The key is the file name, and
765  // the value is false (the file is not in the set) or true (the file is
766  // in the set).  The initial value for any key in std::map is 0 (false),
767  // which in this case means a new file is not in the set initially,
768  // exactly the semantics we want.
769  std::map<base::FilePath, bool> crash_dumps_;
770};
771
772TEST_F(PageLoadTest, Reliability) {
773  std::ofstream log_file;
774
775  if (!g_log_file_path.empty()) {
776    log_file.open(g_log_file_path.value().c_str());
777  }
778
779  for (int k = 0; k < g_iterations; ++k) {
780    if (g_url_file_path.empty()) {
781      NavigateThroughPageID(log_file);
782    } else {
783      NavigateThroughURLList(log_file);
784    }
785  }
786
787  if (!g_end_url.empty()) {
788    NavigateToURLLogResult(
789        g_end_url, log_file, NULL, g_continuous_load, false);
790  }
791
792  log_file.close();
793}
794
795}  // namespace
796
797