diff options
author | jcampan@chromium.org <jcampan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-10 16:43:27 +0000 |
---|---|---|
committer | jcampan@chromium.org <jcampan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-10 16:43:27 +0000 |
commit | 42a116127704f97231765ff599cc1267747e2a70 (patch) | |
tree | 13f05cd5a09b75a2dd7649f12ddd0399b837beb4 /chrome/test/test_launcher | |
parent | 02528ba2437c46f8ced345431f8ee9ec25625577 (diff) | |
download | chromium_src-42a116127704f97231765ff599cc1267747e2a70.zip chromium_src-42a116127704f97231765ff599cc1267747e2a70.tar.gz chromium_src-42a116127704f97231765ff599cc1267747e2a70.tar.bz2 |
This CL makes a more generic browser test launcher and a DLL of the
interactive UI tests, so the interactive UI tests can be run isolated.
BUG=None
TEST=Run the the interactive ui tests with the new launcher:
test_launcher --lib=interactive_ui_tests_dll
Review URL: http://codereview.chromium.org/197045
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@25863 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test/test_launcher')
-rw-r--r-- | chrome/test/test_launcher/in_proc_test_runner.cc | 135 | ||||
-rw-r--r-- | chrome/test/test_launcher/out_of_proc_test_runner.cc | 86 | ||||
-rw-r--r-- | chrome/test/test_launcher/run_all_unittests.cc | 46 | ||||
-rw-r--r-- | chrome/test/test_launcher/test_runner.cc | 132 | ||||
-rw-r--r-- | chrome/test/test_launcher/test_runner.h | 52 |
5 files changed, 451 insertions, 0 deletions
diff --git a/chrome/test/test_launcher/in_proc_test_runner.cc b/chrome/test/test_launcher/in_proc_test_runner.cc new file mode 100644 index 0000000..379d4e5 --- /dev/null +++ b/chrome/test/test_launcher/in_proc_test_runner.cc @@ -0,0 +1,135 @@ +// Copyright (c) 2009 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 <string> + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/native_library.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/string_util.h" + +#include "chrome/test/test_launcher/test_runner.h" + +// This version of the test launcher loads a dynamic library containing the +// tests and executes the them in that library. When the test has been run the +// library is unloaded, to ensure atexit handlers are run and static +// initializers will be run again for the next test. + +namespace { + +const wchar_t* const kLibNameFlag = L"lib"; +const wchar_t* const kGTestListTestsFlag = L"gtest_list_tests"; + +class InProcTestRunner : public tests::TestRunner { + public: + explicit InProcTestRunner(const std::wstring& lib_name) + : lib_name_(lib_name), + dynamic_lib_(NULL), + run_test_proc_(NULL) { + } + + ~InProcTestRunner() { + if (!dynamic_lib_) + return; + base::UnloadNativeLibrary(dynamic_lib_); + LOG(INFO) << "Unloaded " << base::GetNativeLibraryName(lib_name_); + } + + bool Init() { + FilePath lib_path; + CHECK(PathService::Get(base::FILE_EXE, &lib_path)); + lib_path = lib_path.DirName().Append(base::GetNativeLibraryName(lib_name_)); + + LOG(INFO) << "Loading '" << lib_path.value() << "'"; + + dynamic_lib_ = base::LoadNativeLibrary(lib_path); + if (!dynamic_lib_) { + LOG(ERROR) << "Failed to load " << lib_path.value(); + return false; + } + + run_test_proc_ = reinterpret_cast<RunTestProc>( + base::GetFunctionPointerFromNativeLibrary(dynamic_lib_, "RunTests")); + if (!run_test_proc_) { + LOG(ERROR) << + "Failed to find RunTest function in " << lib_path.value(); + return false; + } + + return true; + } + + // Returns true if the test succeeded, false if it failed. + bool RunTest(const std::string& test_name) { + std::string filter_flag = StringPrintf("--gtest_filter=%s", + test_name.c_str()); + char* argv[3]; + argv[0] = const_cast<char*>(""); + argv[1] = const_cast<char*>(filter_flag.c_str()); + // Always enable disabled tests. This method is not called with disabled + // tests unless this flag was specified to the test launcher. + argv[2] = "--gtest_also_run_disabled_tests"; + return RunAsIs(3, argv) == 0; + } + + // Calls-in to GTest with the arguments we were started with. + int RunAsIs(int argc, char** argv) { + return (run_test_proc_)(argc, argv); + } + + private: + typedef int (CDECL *RunTestProc)(int, char**); + + std::wstring lib_name_; + base::NativeLibrary dynamic_lib_; + RunTestProc run_test_proc_; + + DISALLOW_COPY_AND_ASSIGN(InProcTestRunner); +}; + +class InProcTestRunnerFactory : public tests::TestRunnerFactory { + public: + explicit InProcTestRunnerFactory(const std::wstring& lib_name) + : lib_name_(lib_name) { + } + + virtual tests::TestRunner* CreateTestRunner() const { + return new InProcTestRunner(lib_name_); + } + + private: + std::wstring lib_name_; + + DISALLOW_COPY_AND_ASSIGN(InProcTestRunnerFactory); +}; + +} // namespace + +int main(int argc, char** argv) { + base::AtExitManager at_exit_manager; + + CommandLine::Init(argc, argv); + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + std::wstring lib_name = command_line->GetSwitchValue(kLibNameFlag); + if (lib_name.empty()) { + LOG(ERROR) << "No dynamic library name specified. You must specify one with" + " the --lib=<lib_name> option."; + return 1; + } + + if (command_line->HasSwitch(kGTestListTestsFlag)) { + InProcTestRunner test_runner(lib_name); + if (!test_runner.Init()) + return 1; + return test_runner.RunAsIs(argc, argv); + } + + InProcTestRunnerFactory test_runner_factory(lib_name); + return tests::RunTests(test_runner_factory) ? 0 : 1; +} diff --git a/chrome/test/test_launcher/out_of_proc_test_runner.cc b/chrome/test/test_launcher/out_of_proc_test_runner.cc new file mode 100644 index 0000000..3d02bcb --- /dev/null +++ b/chrome/test/test_launcher/out_of_proc_test_runner.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2009 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 <string> + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/process_util.h" + +#include "chrome/test/test_launcher/test_runner.h" +#include "chrome/test/unit/chrome_test_suite.h" + +// This version of the test launcher forks a new process for each test it runs. + +namespace { + +const wchar_t* const kGTestListTestsFlag = L"gtest_list_tests"; +const wchar_t* const kChildProcessFlag = L"child"; + +class OutOfProcTestRunner : public tests::TestRunner { + public: + OutOfProcTestRunner() { + } + + virtual ~OutOfProcTestRunner() { + } + + bool Init() { + return true; + } + + // Returns true if the test succeeded, false if it failed. + bool RunTest(const std::string& test_name) { + const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); + CommandLine new_cmd_line(cmd_line->argv()); + // Always enable disabled tests. This method is not called with disabled + // tests unless this flag was specified to the browser test executable. + new_cmd_line.AppendSwitch(L"gtest_also_run_disabled_tests"); + new_cmd_line.AppendSwitchWithValue(L"gtest_filter", ASCIIToWide(test_name)); + new_cmd_line.AppendSwitch(kChildProcessFlag); + + base::ProcessHandle process_handle; + bool r = base::LaunchApp(new_cmd_line, false, false, &process_handle); + if (!r) + return false; + + int exit_code = 0; + r = base::WaitForExitCode(process_handle, &exit_code); + if (!r) + return false; + + return exit_code == 0; + } + + private: + DISALLOW_COPY_AND_ASSIGN(OutOfProcTestRunner); +}; + +class OutOfProcTestRunnerFactory : public tests::TestRunnerFactory { + public: + OutOfProcTestRunnerFactory() { } + + virtual tests::TestRunner* CreateTestRunner() const { + return new OutOfProcTestRunner(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(OutOfProcTestRunnerFactory); +}; + +} // namespace + +int main(int argc, char** argv) { + CommandLine::Init(argc, argv); + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + + if (command_line->HasSwitch(kChildProcessFlag)) + return ChromeTestSuite(argc, argv).Run(); + + if (command_line->HasSwitch(kGTestListTestsFlag)) + return ChromeTestSuite(argc, argv).Run(); + + OutOfProcTestRunnerFactory test_runner_factory; + return tests::RunTests(test_runner_factory) ? 0 : 1; +} diff --git a/chrome/test/test_launcher/run_all_unittests.cc b/chrome/test/test_launcher/run_all_unittests.cc new file mode 100644 index 0000000..e4eb431 --- /dev/null +++ b/chrome/test/test_launcher/run_all_unittests.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2009 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/string_util.h" +#include "chrome/test/unit/chrome_test_suite.h" + +#if defined(OS_WIN) +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT +#define CDECL +#endif + +// We use extern C for the prototype DLLEXPORT to avoid C++ name mangling. +extern "C" { +DLLEXPORT int CDECL RunTests(int argc, char **argv) { + return ChromeTestSuite(argc, argv).Run(); +} +} + +#if defined(OS_WIN) +BOOL WINAPI DllMain(HINSTANCE dll_module, DWORD reason, LPVOID reserved) { + if (reason == DLL_PROCESS_DETACH) { + // The CRichEditCtrl (used by the omnibox) calls OleInitialize, but somehow + // does not always calls OleUninitialize, causing an unbalanced Ole + // initialization that triggers a DCHECK in ScopedOleInitializer the next + // time we run a test. + // This behavior has been seen on some Vista boxes, but not all of them. + // There is a flag to prevent Ole initialization in CRichEditCtrl (see + // http://support.microsoft.com/kb/238989), but it is set to 0 in recent + // Windows versions. + // This is a dirty hack to make sure the OleCount is back to 0 in all cases, + // so the next test will have Ole unitialized, as expected. + + if (OleInitialize(NULL) == S_FALSE) { + // We were already initialized, balance that extra-initialization. + OleUninitialize(); + } + // Balance the OleInitialize from the above test. + OleUninitialize(); + } + return TRUE; +} + +#endif diff --git a/chrome/test/test_launcher/test_runner.cc b/chrome/test/test_launcher/test_runner.cc new file mode 100644 index 0000000..4fb34cd --- /dev/null +++ b/chrome/test/test_launcher/test_runner.cc @@ -0,0 +1,132 @@ +// Copyright (c) 2009 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 "chrome/test/test_launcher/test_runner.h" + +#include <vector> + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" + +namespace { + +const wchar_t* const kGTestListTestsFlag = L"gtest_list_tests"; +const wchar_t* const kGTestRunDisabledTestsFlag = + L"gtest_also_run_disabled_tests"; + +// Retrieves the list of tests to run by running gtest with the +// --gtest_list_tests flag in a forked process and parsing its output. +// |command_line| should contain the command line used to start the browser +// test launcher, it is expected that it does not contain the +// --gtest_list_tests flag already. +// Note: we cannot implement this in-process for InProcessTestRunner as GTest +// prints to the stdout and there are no good way of temporarily redirecting +// outputs. +bool GetTestList(const CommandLine& command_line, + std::vector<std::string>* test_list) { + DCHECK(!command_line.HasSwitch(kGTestListTestsFlag)); + + // Run ourselves with the --gtest_list_tests option and read the output. + CommandLine new_command_line(command_line); + new_command_line.AppendSwitch(kGTestListTestsFlag); + std::string output; + if (!base::GetAppOutput(new_command_line, &output)) + return false; + + // The output looks like: + // TestCase. + // Test1 + // Test2 + // OtherTestCase. + // FooTest + // ... + std::vector<std::string> lines; + SplitString(output, '\n', &lines); + + std::string test_case; + for (std::vector<std::string>::const_iterator iter = lines.begin(); + iter != lines.end(); ++iter) { + std::string line = *iter; + if (line.empty()) + continue; // Just ignore empty lines if any. + + if (line[line.size() - 1] == '.') { + // This is a new test case. + test_case = line; + continue; + } + + if (!command_line.HasSwitch(kGTestRunDisabledTestsFlag) && + line.find("DISABLED") != std::string::npos) + continue; // Skip disabled tests. + + // We are dealing with a test. + test_list->push_back(test_case + line); + } + return true; +} + +} // namespace + +namespace tests { + +TestRunner::TestRunner() { +} + +TestRunner::~TestRunner() { +} + +bool RunTests(const TestRunnerFactory& test_runner_factory) { + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + + DCHECK(!command_line->HasSwitch(kGTestListTestsFlag)); + + // First let's get the list of tests we need to run. + std::vector<std::string> test_list; + if (!GetTestList(*command_line, &test_list)) { + printf("Failed to retrieve the tests to run.\n"); + return false; + } + + if (test_list.empty()) { + printf("No tests to run.\n"); + return false; + } + + int test_run_count = 0; + std::vector<std::string> failed_tests; + for (std::vector<std::string>::const_iterator iter = test_list.begin(); + iter != test_list.end(); ++iter) { + std::string test_name = *iter; + scoped_ptr<TestRunner> test_runner(test_runner_factory.CreateTestRunner()); + if (!test_runner.get() || !test_runner->Init()) + return false; + test_run_count++; + if (!test_runner->RunTest(test_name.c_str())) { + if (std::find(failed_tests.begin(), failed_tests.end(), test_name) == + failed_tests.end()) { + failed_tests.push_back(*iter); + } + } + } + + printf("%d test%s run\n", test_run_count, test_run_count > 1 ? "s" : ""); + printf("%d test%s failed\n", static_cast<int>(failed_tests.size()), + failed_tests.size() > 1 ? "s" : ""); + if (failed_tests.empty()) + return true; + + printf("Failing tests:\n"); + for (std::vector<std::string>::const_iterator iter = failed_tests.begin(); + iter != failed_tests.end(); ++iter) { + printf("%s\n", iter->c_str()); + } + + return false; +} + +} // namespace diff --git a/chrome/test/test_launcher/test_runner.h b/chrome/test/test_launcher/test_runner.h new file mode 100644 index 0000000..6700dde --- /dev/null +++ b/chrome/test/test_launcher/test_runner.h @@ -0,0 +1,52 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_TEST_TEST_LAUNCHER_TEST_RUNNER_ +#define CHROME_TEST_TEST_LAUNCHER_TEST_RUNNER_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" + +namespace tests { + +class TestRunnerFactory; + +// Runs the tests specified by the --gtest_filter flag specified in the command +// line that started this process. +// Returns true if all tests succeeded, false if there were no tests to run, or +// one or more tests failed, or if initialization failed. +// Results are printed to stdout. +bool RunTests(const TestRunnerFactory& test_runner_factory); + +// This class defines a way to run tests in an isolated environment (each test +// having its static variables uninitialized). +// There are 2 implementations, in-process and out-of-process. +class TestRunner { + public: + TestRunner(); + virtual ~TestRunner(); + + // Called once before the TestRunner is used. Gives it an opportunity to + // perform any requried initialization. Should return true if the + // initialization was successful. + virtual bool Init() = 0; + + // Runs the test named |test_name| and returns true if the test succeeded, + // false if it failed. + virtual bool RunTest(const std::string& test_name) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(TestRunner); +}; + +class TestRunnerFactory { + public: + virtual TestRunner* CreateTestRunner() const = 0; +}; + +} // namespace + +#endif // CHROME_TEST_TEST_LAUNCHER_TEST_RUNNER_ |