| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/at_exit.h" |
| #include "base/basictypes.h" |
| #include "base/command_line.h" |
| #include "base/env_var.h" |
| #include "base/event_recorder.h" |
| #include "base/file_path.h" |
| #include "base/file_util.h" |
| #include "base/i18n/icu_util.h" |
| #include "base/memory_debug.h" |
| #include "base/message_loop.h" |
| #include "base/path_service.h" |
| #include "base/process_util.h" |
| #include "base/rand_util.h" |
| #include "base/stats_table.h" |
| #include "base/string_number_conversions.h" |
| #include "base/sys_info.h" |
| #include "base/trace_event.h" |
| #include "base/utf_string_conversions.h" |
| #include "net/base/cookie_monster.h" |
| #include "net/base/net_module.h" |
| #include "net/base/net_util.h" |
| #include "net/http/http_cache.h" |
| #include "net/test/test_server.h" |
| #include "net/url_request/url_request_context.h" |
| #include "third_party/WebKit/WebKit/chromium/public/WebKit.h" |
| #include "third_party/WebKit/WebKit/chromium/public/WebScriptController.h" |
| #include "webkit/glue/webkit_glue.h" |
| #include "webkit/glue/window_open_disposition.h" |
| #include "webkit/extensions/v8/gc_extension.h" |
| #include "webkit/extensions/v8/heap_profiler_extension.h" |
| #include "webkit/extensions/v8/playback_extension.h" |
| #include "webkit/extensions/v8/profiler_extension.h" |
| #include "webkit/tools/test_shell/simple_resource_loader_bridge.h" |
| #include "webkit/tools/test_shell/test_shell.h" |
| #include "webkit/tools/test_shell/test_shell_platform_delegate.h" |
| #include "webkit/tools/test_shell/test_shell_request_context.h" |
| #include "webkit/tools/test_shell/test_shell_switches.h" |
| #include "webkit/tools/test_shell/test_shell_webkit_init.h" |
| |
| #if defined(OS_WIN) |
| #pragma warning(disable: 4996) |
| #endif |
| |
| static const size_t kPathBufSize = 2048; |
| |
| using WebKit::WebScriptController; |
| |
| namespace { |
| |
| // StatsTable initialization parameters. |
| const char* const kStatsFilePrefix = "testshell_"; |
| int kStatsFileThreads = 20; |
| int kStatsFileCounters = 200; |
| |
| void RemoveSharedMemoryFile(std::string& filename) { |
| // Stats uses SharedMemory under the hood. On posix, this results in a file |
| // on disk. |
| #if defined(OS_POSIX) |
| base::SharedMemory memory; |
| memory.Delete(UTF8ToWide(filename)); |
| #endif |
| } |
| |
| } // namespace |
| |
| int main(int argc, char* argv[]) { |
| base::EnableInProcessStackDumping(); |
| base::EnableTerminationOnHeapCorruption(); |
| |
| // Some tests may use base::Singleton<>, thus we need to instanciate |
| // the AtExitManager or else we will leak objects. |
| base::AtExitManager at_exit_manager; |
| |
| TestShellPlatformDelegate::PreflightArgs(&argc, &argv); |
| CommandLine::Init(argc, argv); |
| const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); |
| |
| TestShellPlatformDelegate platform(parsed_command_line); |
| |
| if (parsed_command_line.HasSwitch(test_shell::kStartupDialog)) |
| TestShell::ShowStartupDebuggingDialog(); |
| |
| if (parsed_command_line.HasSwitch(test_shell::kCheckLayoutTestSystemDeps)) { |
| exit(platform.CheckLayoutTestSystemDependencies() ? 0 : 1); |
| } |
| |
| // Allocate a message loop for this thread. Although it is not used |
| // directly, its constructor sets up some necessary state. |
| MessageLoopForUI main_message_loop; |
| |
| scoped_ptr<base::EnvVarGetter> env(base::EnvVarGetter::Create()); |
| bool suppress_error_dialogs = ( |
| env->HasEnv("CHROME_HEADLESS") || |
| parsed_command_line.HasSwitch(test_shell::kNoErrorDialogs) || |
| parsed_command_line.HasSwitch(test_shell::kLayoutTests)); |
| bool layout_test_mode = |
| parsed_command_line.HasSwitch(test_shell::kLayoutTests); |
| bool ux_theme = parsed_command_line.HasSwitch(test_shell::kUxTheme); |
| bool classic_theme = |
| parsed_command_line.HasSwitch(test_shell::kClassicTheme); |
| #if defined(OS_WIN) |
| bool generic_theme = (layout_test_mode && !ux_theme && !classic_theme) || |
| parsed_command_line.HasSwitch(test_shell::kGenericTheme); |
| #else |
| // Stop compiler warnings about unused variables. |
| ux_theme = ux_theme; |
| #endif |
| |
| bool enable_gp_fault_error_box = false; |
| enable_gp_fault_error_box = |
| parsed_command_line.HasSwitch(test_shell::kGPFaultErrorBox); |
| |
| bool allow_external_pages = |
| parsed_command_line.HasSwitch(test_shell::kAllowExternalPages); |
| |
| TestShell::InitLogging(suppress_error_dialogs, |
| layout_test_mode, |
| enable_gp_fault_error_box); |
| |
| // Initialize WebKit for this scope. |
| TestShellWebKitInit test_shell_webkit_init(layout_test_mode); |
| |
| // Suppress abort message in v8 library in debugging mode (but not |
| // actually under a debugger). V8 calls abort() when it hits |
| // assertion errors. |
| if (suppress_error_dialogs) { |
| platform.SuppressErrorReporting(); |
| } |
| |
| if (parsed_command_line.HasSwitch(test_shell::kEnableTracing)) |
| base::TraceLog::StartTracing(); |
| |
| net::HttpCache::Mode cache_mode = net::HttpCache::NORMAL; |
| |
| // This is a special mode where JS helps the browser implement |
| // playback/record mode. Generally, in this mode, some functions |
| // of client-side randomness are removed. For example, in |
| // this mode Math.random() and Date.getTime() may not return |
| // values which vary. |
| bool playback_mode = |
| parsed_command_line.HasSwitch(test_shell::kPlaybackMode); |
| bool record_mode = |
| parsed_command_line.HasSwitch(test_shell::kRecordMode); |
| |
| if (playback_mode) |
| cache_mode = net::HttpCache::PLAYBACK; |
| else if (record_mode) |
| cache_mode = net::HttpCache::RECORD; |
| |
| if (layout_test_mode || |
| parsed_command_line.HasSwitch(test_shell::kEnableFileCookies)) |
| net::CookieMonster::EnableFileScheme(); |
| |
| FilePath cache_path = |
| parsed_command_line.GetSwitchValuePath(test_shell::kCacheDir); |
| // If the cache_path is empty and it's layout_test_mode, leave it empty |
| // so we use an in-memory cache. This makes running multiple test_shells |
| // in parallel less flaky. |
| if (cache_path.empty() && !layout_test_mode) { |
| PathService::Get(base::DIR_EXE, &cache_path); |
| cache_path = cache_path.AppendASCII("cache"); |
| } |
| |
| // Initializing with a default context, which means no on-disk cookie DB, |
| // and no support for directory listings. |
| SimpleResourceLoaderBridge::Init(cache_path, cache_mode, layout_test_mode); |
| |
| // Load ICU data tables |
| icu_util::Initialize(); |
| |
| // Config the network module so it has access to a limited set of resources. |
| net::NetModule::SetResourceProvider(TestShell::NetResourceProvider); |
| |
| // On Linux and Mac, load the test root certificate. |
| net::TestServerLauncher ssl_util; |
| if (!ssl_util.LoadTestRootCert()) { |
| LOG(ERROR) << "Failed to load test root certificate."; |
| } |
| |
| platform.InitializeGUI(); |
| |
| if (parsed_command_line.HasSwitch(test_shell::kEnableLegacyParser)) { |
| TestShell::disable_html5_parser(); |
| } |
| |
| TestShell::InitializeTestShell(layout_test_mode, allow_external_pages); |
| |
| if (parsed_command_line.HasSwitch(test_shell::kAllowScriptsToCloseWindows)) |
| TestShell::SetAllowScriptsToCloseWindows(); |
| |
| // Disable user themes for layout tests so pixel tests are consistent. |
| #if defined(OS_WIN) |
| TestShellWebTheme::Engine engine; |
| #endif |
| if (classic_theme) |
| platform.SelectUnifiedTheme(); |
| #if defined(OS_WIN) |
| if (generic_theme) |
| test_shell_webkit_init.SetThemeEngine(&engine); |
| #endif |
| |
| if (parsed_command_line.HasSwitch(test_shell::kTestShellTimeOut)) { |
| const std::wstring timeout_str = parsed_command_line.GetSwitchValue( |
| test_shell::kTestShellTimeOut); |
| int timeout_ms = |
| static_cast<int>(StringToInt64(WideToUTF16Hack(timeout_str.c_str()))); |
| if (timeout_ms > 0) |
| TestShell::SetFileTestTimeout(timeout_ms); |
| } |
| |
| // Treat the first argument as the initial URL to open. |
| GURL starting_url; |
| |
| // Default to a homepage if we're interactive. |
| if (!layout_test_mode) { |
| FilePath path; |
| PathService::Get(base::DIR_SOURCE_ROOT, &path); |
| path = path.AppendASCII("webkit"); |
| path = path.AppendASCII("data"); |
| path = path.AppendASCII("test_shell"); |
| path = path.AppendASCII("index.html"); |
| starting_url = net::FilePathToFileURL(path); |
| } |
| |
| const std::vector<CommandLine::StringType>& args = parsed_command_line.args(); |
| if (args.size() > 0) { |
| GURL url(args[0]); |
| if (url.is_valid()) { |
| starting_url = url; |
| } else { |
| // Treat as a relative file path. |
| FilePath path = FilePath(args[0]); |
| file_util::AbsolutePath(&path); |
| starting_url = net::FilePathToFileURL(path); |
| } |
| } |
| |
| std::wstring js_flags = |
| parsed_command_line.GetSwitchValue(test_shell::kJavaScriptFlags); |
| // Test shell always exposes the GC. |
| js_flags += L" --expose-gc"; |
| webkit_glue::SetJavaScriptFlags(js_flags); |
| // Expose GCController to JavaScript. |
| WebScriptController::registerExtension(extensions_v8::GCExtension::Get()); |
| |
| if (parsed_command_line.HasSwitch(test_shell::kProfiler)) { |
| WebScriptController::registerExtension( |
| extensions_v8::ProfilerExtension::Get()); |
| } |
| |
| if (parsed_command_line.HasSwitch(test_shell::kHeapProfiler)) { |
| WebScriptController::registerExtension( |
| extensions_v8::HeapProfilerExtension::Get()); |
| } |
| |
| // Load and initialize the stats table. Attempt to construct a somewhat |
| // unique name to isolate separate instances from each other. |
| |
| // truncate the random # to 32 bits for the benefit of Mac OS X, to |
| // avoid tripping over its maximum shared memory segment name length |
| std::string stats_filename = kStatsFilePrefix + |
| base::Uint64ToString(base::RandUint64() & 0xFFFFFFFFL); |
| RemoveSharedMemoryFile(stats_filename); |
| StatsTable *table = new StatsTable(stats_filename, |
| kStatsFileThreads, |
| kStatsFileCounters); |
| StatsTable::set_current(table); |
| |
| TestShell* shell; |
| if (TestShell::CreateNewWindow(starting_url, &shell)) { |
| if (record_mode || playback_mode) { |
| platform.SetWindowPositionForRecording(shell); |
| WebScriptController::registerExtension( |
| extensions_v8::PlaybackExtension::Get()); |
| } |
| |
| shell->Show(WebKit::WebNavigationPolicyNewWindow); |
| |
| if (parsed_command_line.HasSwitch(test_shell::kDumpStatsTable)) |
| shell->DumpStatsTableOnExit(); |
| |
| bool no_events = parsed_command_line.HasSwitch(test_shell::kNoEvents); |
| if ((record_mode || playback_mode) && !no_events) { |
| FilePath script_path = cache_path; |
| // Create the cache directory in case it doesn't exist. |
| file_util::CreateDirectory(cache_path); |
| script_path = script_path.AppendASCII("script.log"); |
| if (record_mode) |
| base::EventRecorder::current()->StartRecording(script_path); |
| if (playback_mode) |
| base::EventRecorder::current()->StartPlayback(script_path); |
| } |
| |
| if (parsed_command_line.HasSwitch(test_shell::kDebugMemoryInUse)) { |
| base::MemoryDebug::SetMemoryInUseEnabled(true); |
| // Dump all in use memory at startup |
| base::MemoryDebug::DumpAllMemoryInUse(); |
| } |
| |
| // See if we need to run the tests. |
| if (layout_test_mode) { |
| // Set up for the kind of test requested. |
| TestShell::TestParams params; |
| if (parsed_command_line.HasSwitch(test_shell::kDumpPixels)) { |
| // The pixel test flag also gives the image file name to use. |
| params.dump_pixels = true; |
| params.pixel_file_name = parsed_command_line.GetSwitchValuePath( |
| test_shell::kDumpPixels); |
| if (params.pixel_file_name.empty()) { |
| fprintf(stderr, "No file specified for pixel tests"); |
| exit(1); |
| } |
| } |
| if (parsed_command_line.HasSwitch(test_shell::kNoTree)) { |
| params.dump_tree = false; |
| } |
| |
| if (!starting_url.is_valid()) { |
| // Watch stdin for URLs. |
| char filenameBuffer[kPathBufSize]; |
| while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) { |
| // When running layout tests we pass new line separated |
| // tests to TestShell. Each line is a space separated list |
| // of filename, timeout and expected pixel hash. The timeout |
| // and the pixel hash are optional. |
| char* newLine = strchr(filenameBuffer, '\n'); |
| if (newLine) |
| *newLine = '\0'; |
| if (!*filenameBuffer) |
| continue; |
| |
| params.test_url = strtok(filenameBuffer, " "); |
| |
| // Set the current path to the directory that contains the test |
| // files. This is because certain test file may use the relative |
| // path. |
| GURL test_url(params.test_url); |
| FilePath test_file_path; |
| net::FileURLToFilePath(test_url, &test_file_path); |
| file_util::SetCurrentDirectory(test_file_path.DirName()); |
| |
| int old_timeout_ms = TestShell::GetLayoutTestTimeout(); |
| |
| char* timeout = strtok(NULL, " "); |
| if (timeout) { |
| TestShell::SetFileTestTimeout(atoi(timeout)); |
| char* pixel_hash = strtok(NULL, " "); |
| if (pixel_hash) |
| params.pixel_hash = pixel_hash; |
| } |
| |
| if (!TestShell::RunFileTest(params)) |
| break; |
| |
| TestShell::SetFileTestTimeout(old_timeout_ms); |
| } |
| } else { |
| // TODO(ojan): Provide a way for run-singly tests to pass |
| // in a hash and then set params.pixel_hash here. |
| params.test_url = starting_url.spec(); |
| TestShell::RunFileTest(params); |
| } |
| |
| shell->CallJSGC(); |
| shell->CallJSGC(); |
| |
| // When we finish the last test, cleanup the LayoutTestController. |
| // It may have references to not-yet-cleaned up windows. By |
| // cleaning up here we help purify reports. |
| shell->ResetTestController(); |
| |
| // Flush any remaining messages before we kill ourselves. |
| // http://code.google.com/p/chromium/issues/detail?id=9500 |
| MessageLoop::current()->RunAllPending(); |
| } else { |
| MessageLoop::current()->Run(); |
| } |
| |
| if (record_mode) |
| base::EventRecorder::current()->StopRecording(); |
| if (playback_mode) |
| base::EventRecorder::current()->StopPlayback(); |
| } |
| |
| TestShell::ShutdownTestShell(); |
| TestShell::CleanupLogging(); |
| |
| // Tear down shared StatsTable; prevents unit_tests from leaking it. |
| StatsTable::set_current(NULL); |
| delete table; |
| RemoveSharedMemoryFile(stats_filename); |
| |
| return 0; |
| } |