blob: 8eb549edda03e13347de06be86247c393c779c5f [file] [log] [blame]
// 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;
}