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 tests which run for ChromeFrame. 6 // 7 // Usage: 8 // <reliability test exe> --list=file --startline=start --endline=end [...] 9 // Upon invocation, it visits each of the URLs on line numbers between start 10 // and end, inclusive, stored in the input file. The line number starts from 1. 11 // 12 // Optional Switches: 13 // --iterations=num: goes through the list of URLs constructed in usage 2 or 3 14 // num times. 15 // --memoryusage: prints out memory usage when visiting each page. 16 // --logfile=filepath: saves the visit log to the specified path. 17 // --timeout=seconds: time out as specified in seconds during each 18 // page load. 19 // --nopagedown: won't simulate page down key presses after page load. 20 // --noclearprofile: do not clear profile dir before firing up each time. 21 // --savedebuglog: save Chrome, V8, and test debug log for each page loaded. 22 #include <fstream> 23 #include <ostream> 24 25 #include "base/command_line.h" 26 #include "base/file_util.h" 27 #include "base/file_version_info.h" 28 #include "base/files/file_enumerator.h" 29 #include "base/files/file_path.h" 30 #include "base/i18n/time_formatting.h" 31 #include "base/path_service.h" 32 #include "base/prefs/json_pref_store.h" 33 #include "base/prefs/pref_registry_simple.h" 34 #include "base/prefs/pref_service.h" 35 #include "base/prefs/pref_value_store.h" 36 #include "base/strings/string_number_conversions.h" 37 #include "base/strings/string_util.h" 38 #include "base/strings/utf_string_conversions.h" 39 #include "base/test/test_file_util.h" 40 #include "base/time/time.h" 41 #include "chrome/browser/prefs/pref_service_mock_factory.h" 42 #include "chrome/common/automation_messages.h" 43 #include "chrome/common/chrome_constants.h" 44 #include "chrome/common/chrome_paths.h" 45 #include "chrome/common/chrome_paths_internal.h" 46 #include "chrome/common/chrome_switches.h" 47 #include "chrome/common/logging_chrome.h" 48 #include "chrome/common/net/url_fixer_upper.h" 49 #include "chrome/common/pref_names.h" 50 #include "chrome/test/automation/automation_proxy.h" 51 #include "chrome/test/automation/browser_proxy.h" 52 #include "chrome/test/automation/tab_proxy.h" 53 #include "chrome/test/automation/window_proxy.h" 54 #include "chrome/test/ui/ui_test.h" 55 #include "chrome_frame/test/chrome_frame_test_utils.h" 56 #include "chrome_frame/test/ie_event_sink.h" 57 #include "chrome_frame/test/reliability/page_load_test.h" 58 #include "chrome_frame/utils.h" 59 #include "content/public/browser/browser_thread.h" 60 #include "content/public/test/test_browser_thread.h" 61 #include "net/base/net_util.h" 62 #include "testing/gmock/include/gmock/gmock.h" 63 #include "testing/gtest/include/gtest/gtest.h" 64 65 using testing::StrCaseEq; 66 using testing::StrCaseNe; 67 68 namespace { 69 70 // See comments at the beginning of the file for the definition of switches. 71 const char kListSwitch[] = "list"; 72 const char kStartIndexSwitch[] = "startline"; 73 const char kEndIndexSwitch[] = "endline"; 74 const char kIterationSwitch[] = "iterations"; 75 const char kContinuousLoadSwitch[] = "continuousload"; 76 const char kMemoryUsageSwitch[] = "memoryusage"; 77 const char kLogFileSwitch[] = "logfile"; 78 const char kTimeoutSwitch[] = "timeout"; 79 const char kNoPageDownSwitch[] = "nopagedown"; 80 const char kNoClearProfileSwitch[] = "noclearprofile"; 81 const char kSaveDebugLogSwitch[] = "savedebuglog"; 82 83 // These are copied from v8 definitions as we cannot include them. 84 const char kV8LogFileSwitch[] = "logfile"; 85 const char kV8LogFileDefaultName[] = "v8.log"; 86 87 // String name of local chrome dll for looking up file information. 88 const wchar_t kChromeDll[] = L"chrome.dll"; 89 90 base::FilePath g_url_file_path; 91 int32 g_start_index = 1; 92 int32 g_end_index = kint32max; 93 int32 g_iterations = 1; 94 bool g_memory_usage = false; 95 bool g_page_down = true; 96 bool g_clear_profile = true; 97 std::string g_end_url; 98 base::FilePath g_log_file_path; 99 bool g_save_debug_log = false; 100 base::FilePath g_chrome_log_path; 101 base::FilePath g_v8_log_path; 102 base::FilePath g_test_log_path; 103 bool g_stand_alone = false; 104 105 const int kUrlNavigationTimeoutSeconds = 20; 106 int g_timeout_seconds = kUrlNavigationTimeoutSeconds; 107 108 // Mocks document complete and load events. 109 class MockLoadListener : public chrome_frame_test::IEEventListener { 110 public: 111 MOCK_METHOD1(OnDocumentComplete, void (const wchar_t* url)); // NOLINT 112 MOCK_METHOD1(OnLoad, void (const wchar_t* url)); // NOLINT 113 MOCK_METHOD0(OnQuit, void ()); // NOLINT 114 115 private: 116 virtual void OnDocumentComplete(IDispatch* dispatch, VARIANT* url) { 117 if (url->bstrVal) 118 OnDocumentComplete(url->bstrVal); 119 } 120 }; 121 122 ACTION_P(QuitIE, event_sink) { 123 EXPECT_HRESULT_SUCCEEDED(event_sink->CloseWebBrowser()); 124 } 125 126 class PageLoadTest : public testing::Test { 127 public: 128 enum NavigationResult { 129 NAVIGATION_ERROR = 0, 130 NAVIGATION_SUCCESS, 131 }; 132 133 typedef struct { 134 // These are results from the test automation that drives Chrome 135 NavigationResult result; 136 int crash_dump_count; 137 // These are stability metrics recorded by Chrome itself 138 bool browser_clean_exit; 139 int browser_execution_phase; 140 int browser_launch_count; 141 int page_load_count; 142 int browser_crash_count; 143 int renderer_crash_count; 144 int plugin_crash_count; 145 } NavigationMetrics; 146 147 PageLoadTest() {} 148 149 // Accept URL as std::string here because the url may also act as a test id 150 // and needs to be logged in its original format even if invalid. 151 void NavigateToURLLogResult(const std::string& url_string, 152 std::ofstream& log_file, 153 NavigationMetrics* metrics_output) { 154 GURL url(url_string); 155 NavigationMetrics metrics = {NAVIGATION_ERROR}; 156 std::ofstream test_log; 157 158 // Create a test log. 159 g_test_log_path = base::FilePath(FILE_PATH_LITERAL("test_log.log")); 160 test_log.open(g_test_log_path.value().c_str()); 161 162 // Check file version info for chrome dll. 163 scoped_ptr<FileVersionInfo> file_info; 164 #if defined(OS_WIN) 165 file_info.reset( 166 FileVersionInfo::CreateFileVersionInfo(base::FilePath(kChromeDll))); 167 #elif defined(OS_LINUX) || defined(OS_MACOSX) 168 // TODO(fmeawad): the version retrieved here belongs to the test module and 169 // not the chrome binary, need to be changed to chrome binary instead. 170 file_info.reset(FileVersionInfo::CreateFileVersionInfoForCurrentModule()); 171 #endif // !defined(OS_WIN) 172 std::wstring last_change = file_info->last_change(); 173 test_log << "Last Change: "; 174 test_log << last_change << std::endl; 175 176 177 // Log timestamp for test start. 178 base::Time time_now = base::Time::Now(); 179 double time_start = time_now.ToDoubleT(); 180 test_log << "Test Start: "; 181 test_log << base::TimeFormatFriendlyDateAndTime(time_now) << std::endl; 182 183 HRESULT hr = E_FAIL; 184 185 chrome_frame_test::TimedMsgLoop message_loop; 186 187 // Launch IE. 188 base::win::ScopedComPtr<IWebBrowser2> web_browser2; 189 hr = chrome_frame_test::LaunchIEAsComServer(web_browser2.Receive()); 190 EXPECT_HRESULT_SUCCEEDED(hr); 191 EXPECT_TRUE(web_browser2.get() != NULL); 192 web_browser2->put_Visible(VARIANT_TRUE); 193 194 // Log Browser Launched time. 195 time_now = base::Time::Now(); 196 test_log << "browser_launched_seconds="; 197 test_log << (time_now.ToDoubleT() - time_start) << std::endl; 198 199 bool is_chrome_frame_navigation = 200 StartsWith(UTF8ToWide(url.spec()), kChromeProtocolPrefix, true); 201 202 CComObjectStack<chrome_frame_test::IEEventSink> ie_event_sink; 203 MockLoadListener load_listener; 204 // Disregard any interstitial about:blank loads. 205 EXPECT_CALL(load_listener, OnDocumentComplete(StrCaseEq(L"about:blank"))) 206 .Times(testing::AnyNumber()); 207 208 // Note that we can't compare the loaded url directly with the given url 209 // because the page may have redirected us to a different page, e.g. 210 // www.google.com -> www.google.ca. 211 if (is_chrome_frame_navigation) { 212 EXPECT_CALL(load_listener, OnDocumentComplete(testing::_)); 213 EXPECT_CALL(load_listener, OnLoad(testing::_)) 214 .WillOnce(QuitIE(&ie_event_sink)); 215 } else { 216 EXPECT_CALL(load_listener, OnDocumentComplete(StrCaseNe(L"about:blank"))) 217 .WillOnce(QuitIE(&ie_event_sink)); 218 } 219 EXPECT_CALL(load_listener, OnQuit()).WillOnce(QUIT_LOOP(message_loop)); 220 221 // Attach the sink and navigate. 222 ie_event_sink.set_listener(&load_listener); 223 ie_event_sink.Attach(web_browser2); 224 hr = ie_event_sink.Navigate(UTF8ToWide(url.spec())); 225 if (SUCCEEDED(hr)) { 226 message_loop.RunFor(base::TimeDelta::FromSeconds(g_timeout_seconds)); 227 if (!message_loop.WasTimedOut()) 228 metrics.result = NAVIGATION_SUCCESS; 229 } 230 231 // Log navigate complete time. 232 time_now = base::Time::Now(); 233 test_log << "navigate_complete_seconds="; 234 test_log << (time_now.ToDoubleT() - time_start) << std::endl; 235 236 // Close IE. 237 ie_event_sink.set_listener(NULL); 238 ie_event_sink.Uninitialize(); 239 chrome_frame_test::CloseAllIEWindows(); 240 241 // Log end of test time. 242 time_now = base::Time::Now(); 243 test_log << "total_duration_seconds="; 244 test_log << (time_now.ToDoubleT() - time_start) << std::endl; 245 246 // Get navigation result and metrics, and optionally write to the log file 247 // provided. The log format is: 248 // <url> <navigation_result> <browser_crash_count> <renderer_crash_count> 249 // <plugin_crash_count> <crash_dump_count> [chrome_log=<path> 250 // v8_log=<path>] crash_dump=<path> 251 if (log_file.is_open()) { 252 log_file << url_string; 253 switch (metrics.result) { 254 case NAVIGATION_ERROR: 255 log_file << " error"; 256 break; 257 case NAVIGATION_SUCCESS: 258 log_file << " success"; 259 break; 260 default: 261 break; 262 } 263 } 264 265 // Get stability metrics recorded by Chrome itself. 266 if (is_chrome_frame_navigation) { 267 GetStabilityMetrics(&metrics); 268 } 269 270 if (log_file.is_open()) { 271 log_file << " " << metrics.browser_crash_count \ 272 // The renderer crash count is flaky due to 1183283. 273 // Ignore the count since we also catch crash by 274 // crash_dump_count. 275 << " " << 0 \ 276 << " " << metrics.plugin_crash_count \ 277 << " " << metrics.crash_dump_count; 278 } 279 280 // Close test log. 281 test_log.close(); 282 283 if (log_file.is_open() && g_save_debug_log) 284 SaveDebugLogs(log_file); 285 286 // Log revision information for Chrome build under test. 287 log_file << " " << "revision=" << last_change; 288 289 // Get crash dumps. 290 LogOrDeleteNewCrashDumps(log_file, &metrics); 291 292 if (log_file.is_open()) { 293 log_file << std::endl; 294 } 295 296 if (metrics_output) { 297 *metrics_output = metrics; 298 } 299 } 300 301 void NavigateThroughURLList(std::ofstream& log_file) { 302 std::ifstream file(g_url_file_path.value().c_str()); 303 ASSERT_TRUE(file.is_open()); 304 305 // We navigate to URLs in the following order. 306 // CF -> CF -> host -> CF -> CF -> host. 307 for (int line_index = 1; 308 line_index <= g_end_index && !file.eof(); 309 ++line_index) { 310 std::string url_str; 311 std::getline(file, url_str); 312 313 if (file.fail()) { 314 break; 315 } 316 317 // Every 3rd URL goes into the host browser. 318 if (line_index % 3 != 0) { 319 std::string actual_url; 320 actual_url = WideToUTF8(kChromeProtocolPrefix); 321 actual_url += url_str; 322 url_str = actual_url; 323 } 324 325 if (g_start_index <= line_index) { 326 NavigateToURLLogResult(url_str, log_file, NULL); 327 } 328 } 329 330 file.close(); 331 } 332 333 protected: 334 virtual void SetUp() { 335 // Initialize crash_dumps_dir_path_. 336 PathService::Get(chrome::DIR_CRASH_DUMPS, &crash_dumps_dir_path_); 337 base::FileEnumerator enumerator(crash_dumps_dir_path_, 338 false, // not recursive 339 base::FileEnumerator::FILES); 340 for (base::FilePath path = enumerator.Next(); !path.value().empty(); 341 path = enumerator.Next()) { 342 if (path.MatchesExtension(FILE_PATH_LITERAL(".dmp"))) 343 crash_dumps_[path.BaseName()] = true; 344 } 345 346 if (g_clear_profile) { 347 base::FilePath user_data_dir; 348 chrome::GetChromeFrameUserDataDirectory(&user_data_dir); 349 ASSERT_TRUE(file_util::DieFileDie(user_data_dir, true)); 350 } 351 352 SetConfigBool(kChromeFrameHeadlessMode, true); 353 SetConfigBool(kAllowUnsafeURLs, true); 354 } 355 356 virtual void TearDown() { 357 DeleteConfigValue(kChromeFrameHeadlessMode); 358 DeleteConfigValue(kAllowUnsafeURLs); 359 } 360 361 base::FilePath ConstructSavedDebugLogPath(const base::FilePath& debug_log_path, 362 int index) { 363 std::string suffix("_"); 364 suffix.append(base::IntToString(index)); 365 return debug_log_path.InsertBeforeExtensionASCII(suffix); 366 } 367 368 void SaveDebugLog(const base::FilePath& log_path, const std::wstring& log_id, 369 std::ofstream& log_file, int index) { 370 if (!log_path.empty()) { 371 base::FilePath saved_log_file_path = 372 ConstructSavedDebugLogPath(log_path, index); 373 if (base::Move(log_path, saved_log_file_path)) { 374 log_file << " " << log_id << "=" << saved_log_file_path.value(); 375 } 376 } 377 } 378 379 // Rename the chrome and v8 debug log files if existing, and save the file 380 // paths in the log_file provided. 381 void SaveDebugLogs(std::ofstream& log_file) { 382 static int url_count = 1; 383 SaveDebugLog(g_chrome_log_path, L"chrome_log", log_file, url_count); 384 SaveDebugLog(g_v8_log_path, L"v8_log", log_file, url_count); 385 SaveDebugLog(g_test_log_path, L"test_log", log_file, url_count); 386 url_count++; 387 } 388 389 // If a log_file is provided, log the crash dump with the given path; 390 // otherwise, delete the crash dump file. 391 void LogOrDeleteCrashDump(std::ofstream& log_file, 392 base::FilePath crash_dump_file_name) { 393 base::FilePath crash_dump_file_path(crash_dumps_dir_path_); 394 crash_dump_file_path = crash_dump_file_path.Append(crash_dump_file_name); 395 base::FilePath crash_text_file_path = 396 crash_dump_file_path.ReplaceExtension(FILE_PATH_LITERAL("txt")); 397 398 if (log_file.is_open()) { 399 crash_dumps_[crash_dump_file_name] = true; 400 log_file << " crash_dump=" << crash_dump_file_path.value().c_str(); 401 } else { 402 ASSERT_TRUE(file_util::DieFileDie( 403 crash_dump_file_path, false)); 404 ASSERT_TRUE(file_util::DieFileDie( 405 crash_text_file_path, false)); 406 } 407 } 408 409 // Check whether there are new .dmp files. Additionally, write 410 // " crash_dump=<full path name of the .dmp file>" 411 // to log_file. 412 void LogOrDeleteNewCrashDumps(std::ofstream& log_file, 413 NavigationMetrics* metrics) { 414 int num_dumps = 0; 415 416 base::FileEnumerator enumerator(crash_dumps_dir_path_, 417 false, // not recursive 418 base::FileEnumerator::FILES); 419 for (base::FilePath path = enumerator.Next(); !path.value().empty(); 420 path = enumerator.Next()) { 421 if (path.MatchesExtension(FILE_PATH_LITERAL(".dmp")) && 422 !crash_dumps_[path.BaseName()]) { 423 LogOrDeleteCrashDump(log_file, path.BaseName()); 424 num_dumps++; 425 } 426 } 427 if (metrics) 428 metrics->crash_dump_count = num_dumps; 429 } 430 431 // Get a PrefService whose contents correspond to the Local State file 432 // that was saved by the app as it closed. The caller takes ownership of the 433 // returned PrefService object. 434 PrefService* GetLocalState(PrefRegistry* registry) { 435 base::FilePath path; 436 chrome::GetChromeFrameUserDataDirectory(&path); 437 PrefServiceMockFactory factory; 438 factory.SetUserPrefsFile( 439 path, 440 JsonPrefStore::GetTaskRunnerForFile( 441 path, content::BrowserThread::GetBlockingPool())); 442 return factory.Create(registry).release(); 443 } 444 445 void GetStabilityMetrics(NavigationMetrics* metrics) { 446 if (!metrics) 447 return; 448 scoped_refptr<PrefRegistrySimple> registry = new PrefRegistrySimple(); 449 registry->RegisterBooleanPref(prefs::kStabilityExitedCleanly, false); 450 registry->RegisterIntegerPref(prefs::kStabilityExecutionPhase, 0); 451 registry->RegisterIntegerPref(prefs::kStabilityLaunchCount, -1); 452 registry->RegisterIntegerPref(prefs::kStabilityPageLoadCount, -1); 453 registry->RegisterIntegerPref(prefs::kStabilityCrashCount, 0); 454 registry->RegisterIntegerPref(prefs::kStabilityRendererCrashCount, 0); 455 456 scoped_ptr<PrefService> local_state(GetLocalState(registry)); 457 if (!local_state.get()) 458 return; 459 460 metrics->browser_clean_exit = 461 local_state->GetBoolean(prefs::kStabilityExitedCleanly); 462 metrics->browser_execution_phase = 463 local_state->GetInteger(prefs::kStabilityExecutionPhase); 464 metrics->browser_launch_count = 465 local_state->GetInteger(prefs::kStabilityLaunchCount); 466 metrics->page_load_count = 467 local_state->GetInteger(prefs::kStabilityPageLoadCount); 468 metrics->browser_crash_count = 469 local_state->GetInteger(prefs::kStabilityCrashCount); 470 metrics->renderer_crash_count = 471 local_state->GetInteger(prefs::kStabilityRendererCrashCount); 472 // TODO(huanr) 473 metrics->plugin_crash_count = 0; 474 475 if (!metrics->browser_clean_exit) 476 metrics->browser_crash_count++; 477 } 478 479 base::FilePath GetSampleDataDir() { 480 base::FilePath test_dir; 481 PathService::Get(chrome::DIR_TEST_DATA, &test_dir); 482 test_dir = test_dir.AppendASCII("reliability"); 483 test_dir = test_dir.AppendASCII("sample_pages"); 484 return test_dir; 485 } 486 487 // The pathname of Chrome's crash dumps directory. 488 base::FilePath crash_dumps_dir_path_; 489 490 // The set of all the crash dumps we have seen. Each crash generates a 491 // .dmp and a .txt file in the crash dumps directory. We only store the 492 // .dmp files in this set. 493 // 494 // The set is implemented as a std::map. The key is the file name, and 495 // the value is false (the file is not in the set) or true (the file is 496 // in the set). The initial value for any key in std::map is 0 (false), 497 // which in this case means a new file is not in the set initially, 498 // exactly the semantics we want. 499 std::map<base::FilePath, bool> crash_dumps_; 500 }; 501 502 TEST_F(PageLoadTest, IEFullTabMode_Reliability) { 503 std::ofstream log_file; 504 505 if (!g_log_file_path.empty()) { 506 log_file.open(g_log_file_path.value().c_str()); 507 } 508 509 EXPECT_FALSE(g_url_file_path.empty()); 510 511 for (int k = 0; k < g_iterations; ++k) { 512 NavigateThroughURLList(log_file); 513 } 514 515 log_file.close(); 516 } 517 518 } // namespace 519 520 namespace { 521 void ReportHandler(const std::string& str) { 522 // Ignore report events. 523 } 524 } 525 526 void SetPageRange(const CommandLine& parsed_command_line) { 527 // If calling into this function, we are running as a standalone program. 528 g_stand_alone = true; 529 530 // Since we use --enable-dcheck for reliability tests, suppress the error 531 // dialog in the test process. 532 logging::SetLogReportHandler(ReportHandler); 533 534 if (parsed_command_line.HasSwitch(kStartIndexSwitch)) { 535 ASSERT_TRUE( 536 base::StringToInt(parsed_command_line.GetSwitchValueASCII( 537 kStartIndexSwitch), 538 &g_start_index)); 539 ASSERT_GT(g_start_index, 0); 540 } 541 542 if (parsed_command_line.HasSwitch(kEndIndexSwitch)) { 543 ASSERT_TRUE( 544 base::StringToInt(parsed_command_line.GetSwitchValueASCII( 545 kEndIndexSwitch), 546 &g_end_index)); 547 ASSERT_GT(g_end_index, 0); 548 } 549 550 ASSERT_TRUE(g_end_index >= g_start_index); 551 552 if (parsed_command_line.HasSwitch(kListSwitch)) 553 g_url_file_path = parsed_command_line.GetSwitchValuePath(kListSwitch); 554 555 if (parsed_command_line.HasSwitch(kIterationSwitch)) { 556 ASSERT_TRUE( 557 base::StringToInt(parsed_command_line.GetSwitchValueASCII( 558 kIterationSwitch), 559 &g_iterations)); 560 ASSERT_GT(g_iterations, 0); 561 } 562 563 if (parsed_command_line.HasSwitch(kMemoryUsageSwitch)) 564 g_memory_usage = true; 565 566 if (parsed_command_line.HasSwitch(kLogFileSwitch)) 567 g_log_file_path = parsed_command_line.GetSwitchValuePath(kLogFileSwitch); 568 569 if (parsed_command_line.HasSwitch(kTimeoutSwitch)) { 570 ASSERT_TRUE( 571 base::StringToInt(parsed_command_line.GetSwitchValueASCII( 572 kTimeoutSwitch), 573 &g_timeout_seconds)); 574 ASSERT_GT(g_timeout_seconds, 0); 575 } 576 577 if (parsed_command_line.HasSwitch(kNoPageDownSwitch)) 578 g_page_down = false; 579 580 if (parsed_command_line.HasSwitch(kNoClearProfileSwitch)) 581 g_clear_profile = false; 582 583 if (parsed_command_line.HasSwitch(kSaveDebugLogSwitch)) { 584 g_save_debug_log = true; 585 g_chrome_log_path = logging::GetLogFileName(); 586 // We won't get v8 log unless --no-sandbox is specified. 587 if (parsed_command_line.HasSwitch(switches::kNoSandbox)) { 588 PathService::Get(base::DIR_CURRENT, &g_v8_log_path); 589 g_v8_log_path = g_v8_log_path.AppendASCII(kV8LogFileDefaultName); 590 // The command line switch may override the default v8 log path. 591 if (parsed_command_line.HasSwitch(switches::kJavaScriptFlags)) { 592 CommandLine v8_command_line( 593 parsed_command_line.GetSwitchValuePath(switches::kJavaScriptFlags)); 594 if (v8_command_line.HasSwitch(kV8LogFileSwitch)) { 595 g_v8_log_path = base::MakeAbsoluteFilePath( 596 v8_command_line.GetSwitchValuePath(kV8LogFileSwitch)); 597 } 598 } 599 } 600 } 601 } 602