diff options
-rw-r--r-- | base/base.gyp | 10 | ||||
-rw-r--r-- | base/test/DEPS | 3 | ||||
-rw-r--r-- | base/test/gtest_xml_util.cc | 113 | ||||
-rw-r--r-- | base/test/gtest_xml_util.h | 24 | ||||
-rw-r--r-- | base/test/run_all_unittests.cc | 25 | ||||
-rw-r--r-- | base/test/test_launcher.cc | 4 | ||||
-rw-r--r-- | base/test/test_launcher.h | 2 | ||||
-rw-r--r-- | base/test/unit_test_launcher.cc | 209 | ||||
-rw-r--r-- | base/test/unit_test_launcher.h | 22 | ||||
-rw-r--r-- | base/test/unit_test_launcher_ios.cc | 17 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_common.gypi | 2 | ||||
-rw-r--r-- | content/content_browser.gypi | 2 | ||||
-rw-r--r-- | content/public/test/test_launcher.cc | 12 | ||||
-rw-r--r-- | third_party/libxml/chromium/libxml_utils.cc | 4 | ||||
-rw-r--r-- | third_party/libxml/chromium/libxml_utils.h | 3 | ||||
-rw-r--r-- | third_party/zlib/google/zip.gyp | 27 | ||||
-rw-r--r-- | third_party/zlib/zlib.gyp | 19 |
18 files changed, 472 insertions, 28 deletions
diff --git a/base/base.gyp b/base/base.gyp index 60c9586..2600754 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -838,6 +838,7 @@ 'base_i18n', '../testing/gmock.gyp:gmock', '../testing/gtest.gyp:gtest', + '../third_party/libxml/libxml.gyp:libxml', 'third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', ], 'export_dependent_settings': [ @@ -868,6 +869,8 @@ 'test/expectations/expectation.h', 'test/expectations/parser.cc', 'test/expectations/parser.h', + 'test/gtest_xml_util.cc', + 'test/gtest_xml_util.h', 'test/mock_chrome_application_mac.h', 'test/mock_chrome_application_mac.mm', 'test/mock_devices_changed_observer.cc', @@ -931,6 +934,9 @@ 'test/thread_test_helper.h', 'test/trace_event_analyzer.cc', 'test/trace_event_analyzer.h', + 'test/unit_test_launcher_ios.cc', + 'test/unit_test_launcher.cc', + 'test/unit_test_launcher.h', 'test/values_test_util.cc', 'test/values_test_util.h', ], @@ -941,6 +947,10 @@ # by file name rules). ['include', '^test/test_file_util_mac\\.cc$'], ], + 'sources!': [ + # iOS uses its own unit test launcher. + 'test/unit_test_launcher.cc', + ], }], ], # target_conditions }, diff --git a/base/test/DEPS b/base/test/DEPS new file mode 100644 index 0000000..5827c26 --- /dev/null +++ b/base/test/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+third_party/libxml", +] diff --git a/base/test/gtest_xml_util.cc b/base/test/gtest_xml_util.cc new file mode 100644 index 0000000..6187d9f --- /dev/null +++ b/base/test/gtest_xml_util.cc @@ -0,0 +1,113 @@ +// Copyright 2013 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/test/gtest_xml_util.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/test/test_launcher.h" +#include "third_party/libxml/chromium/libxml_utils.h" + +namespace base { + +bool ProcessGTestOutput(const base::FilePath& output_file, + std::vector<TestResult>* results) { + DCHECK(results); + + std::string xml_contents; + if (!file_util::ReadFileToString(output_file, &xml_contents)) + return false; + + XmlReader xml_reader; + if (!xml_reader.Load(xml_contents)) + return false; + + enum { + STATE_INIT, + STATE_TESTSUITE, + STATE_TESTCASE, + STATE_FAILURE, + STATE_END, + } state = STATE_INIT; + + while (xml_reader.Read()) { + xml_reader.SkipToElement(); + std::string node_name(xml_reader.NodeName()); + + switch (state) { + case STATE_INIT: + if (node_name == "testsuites" && !xml_reader.IsClosingElement()) + state = STATE_TESTSUITE; + else + return false; + break; + case STATE_TESTSUITE: + if (node_name == "testsuites" && xml_reader.IsClosingElement()) + state = STATE_END; + else if (node_name == "testsuite" && !xml_reader.IsClosingElement()) + state = STATE_TESTCASE; + else + return false; + break; + case STATE_TESTCASE: + if (node_name == "testsuite" && xml_reader.IsClosingElement()) { + state = STATE_TESTSUITE; + } else if (node_name == "testcase" && !xml_reader.IsClosingElement()) { + std::string test_status; + if (!xml_reader.NodeAttribute("status", &test_status)) + return false; + + if (test_status != "run" && test_status != "notrun") + return false; + if (test_status != "run") + break; + + TestResult result; + if (!xml_reader.NodeAttribute("classname", &result.test_case_name)) + return false; + if (!xml_reader.NodeAttribute("name", &result.test_name)) + return false; + + std::string test_time_str; + if (!xml_reader.NodeAttribute("time", &test_time_str)) + return false; + result.elapsed_time = + TimeDelta::FromMicroseconds(strtod(test_time_str.c_str(), NULL) * + Time::kMicrosecondsPerSecond); + + result.success = true; + + results->push_back(result); + } else if (node_name == "failure" && !xml_reader.IsClosingElement()) { + std::string failure_message; + if (!xml_reader.NodeAttribute("message", &failure_message)) + return false; + + DCHECK(!results->empty()); + results->at(results->size() - 1).success = false; + + state = STATE_FAILURE; + } else if (node_name == "testcase" && xml_reader.IsClosingElement()) { + // Deliberately empty. + } else { + return false; + } + break; + case STATE_FAILURE: + if (node_name == "failure" && xml_reader.IsClosingElement()) + state = STATE_TESTCASE; + else + return false; + break; + case STATE_END: + // If we are here and there are still XML elements, the file has wrong + // format. + return false; + } + } + + return true; +} + +} // namespace base
\ No newline at end of file diff --git a/base/test/gtest_xml_util.h b/base/test/gtest_xml_util.h new file mode 100644 index 0000000..0aecfb1 --- /dev/null +++ b/base/test/gtest_xml_util.h @@ -0,0 +1,24 @@ +// Copyright 2013 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 BASE_TEST_GTEST_XML_UTIL_H_ +#define BASE_TEST_GTEST_XML_UTIL_H_ + +#include <vector> + +#include "base/compiler_specific.h" + +namespace base { + +class FilePath; +struct TestResult; + +// Produces a vector of test results based on GTest output file. +// Returns true iff the output file exists and has been successfully parsed. +bool ProcessGTestOutput(const base::FilePath& output_file, + std::vector<TestResult>* results) WARN_UNUSED_RESULT; + +} // namespace base + +#endif // BASE_TEST_GTEST_XML_UTIL_H_
\ No newline at end of file diff --git a/base/test/run_all_unittests.cc b/base/test/run_all_unittests.cc index 2c8d29c..51f504d 100644 --- a/base/test/run_all_unittests.cc +++ b/base/test/run_all_unittests.cc @@ -2,8 +2,31 @@ // 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/bind.h" #include "base/test/test_suite.h" +#include "base/test/unit_test_launcher.h" + +namespace { + +class NoAtExitBaseTestSuite : public base::TestSuite { + public: + NoAtExitBaseTestSuite(int argc, char** argv) + : base::TestSuite(argc, argv, false) { + } +}; + +int RunTestSuite(int argc, char** argv) { + return NoAtExitBaseTestSuite(argc, argv).Run(); +} + +} // namespace int main(int argc, char** argv) { - return base::TestSuite(argc, argv).Run(); +#if !defined(OS_ANDROID) + base::AtExitManager at_exit; +#endif + return base::LaunchUnitTests(argc, + argv, + base::Bind(&RunTestSuite, argc, argv)); } diff --git a/base/test/test_launcher.cc b/base/test/test_launcher.cc index b217d63..a70570c 100644 --- a/base/test/test_launcher.cc +++ b/base/test/test_launcher.cc @@ -358,10 +358,6 @@ int LaunchChildGTestProcess(const CommandLine& command_line, CommandLine new_command_line(command_line.GetProgram()); CommandLine::SwitchMap switches = command_line.GetSwitches(); - // Strip out gtest_output flag because otherwise we would overwrite results - // of the other tests. - switches.erase(kGTestOutputFlag); - // Strip out gtest_repeat flag - this is handled by the launcher process. switches.erase(kGTestRepeatFlag); diff --git a/base/test/test_launcher.h b/base/test/test_launcher.h index 9b4413d..264454f2 100644 --- a/base/test/test_launcher.h +++ b/base/test/test_launcher.h @@ -30,6 +30,8 @@ extern const char kGTestOutputFlag[]; // Structure containing result of a single test. struct TestResult { + std::string GetFullName() const { return test_case_name + "." + test_name; } + TestResult(); // Name of the test case (before the dot, e.g. "A" for test "A.B"). diff --git a/base/test/unit_test_launcher.cc b/base/test/unit_test_launcher.cc new file mode 100644 index 0000000..ebf3d40 --- /dev/null +++ b/base/test/unit_test_launcher.cc @@ -0,0 +1,209 @@ +// Copyright 2013 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/test/unit_test_launcher.h" + +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/files/scoped_temp_dir.h" +#include "base/file_util.h" +#include "base/format_macros.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/test/gtest_xml_util.h" +#include "base/test/test_launcher.h" +#include "base/test/test_timeouts.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +// This constant controls how many tests are run in a single batch. +const size_t kTestBatchLimit = 10; + +// Flag to enable the new launcher logic. +// TODO(phajdan.jr): Remove it, http://crbug.com/236893 . +const char kBraveNewTestLauncherFlag[] = "brave-new-test-launcher"; + +// Flag to run all tests in a single process. +const char kSingleProcessTestsFlag[] = "single-process-tests"; + +// Returns command line for child GTest process based on the command line +// of current process. |test_names| is a vector of test full names +// (e.g. "A.B"), |output_file| is path to the GTest XML output file. +CommandLine GetCommandLineForChildGTestProcess( + const std::vector<std::string>& test_names, + const base::FilePath& output_file) { + CommandLine new_cmd_line(CommandLine::ForCurrentProcess()->GetProgram()); + CommandLine::SwitchMap switches = + CommandLine::ForCurrentProcess()->GetSwitches(); + + switches.erase(kGTestOutputFlag); + new_cmd_line.AppendSwitchPath( + kGTestOutputFlag, + base::FilePath(FILE_PATH_LITERAL("xml:") + output_file.value())); + + for (CommandLine::SwitchMap::const_iterator iter = switches.begin(); + iter != switches.end(); ++iter) { + new_cmd_line.AppendSwitchNative(iter->first, iter->second); + } + + new_cmd_line.AppendSwitchASCII(kGTestFilterFlag, + JoinString(test_names, ":")); + new_cmd_line.AppendSwitch(kSingleProcessTestsFlag); + new_cmd_line.AppendSwitch(kBraveNewTestLauncherFlag); + + return new_cmd_line; +} + +class UnitTestLauncherDelegate : public TestLauncherDelegate { + private: + struct TestLaunchInfo { + std::string GetFullName() const { + return test_case_name + "." + test_name; + } + + std::string test_case_name; + std::string test_name; + TestResultCallback callback; + }; + + virtual bool ShouldRunTest(const testing::TestCase* test_case, + const testing::TestInfo* test_info) OVERRIDE { + // There is no additional logic to disable specific tests. + return true; + } + + virtual void RunTest(const testing::TestCase* test_case, + const testing::TestInfo* test_info, + const TestResultCallback& callback) OVERRIDE { + TestLaunchInfo launch_info; + launch_info.test_case_name = test_case->name(); + launch_info.test_name = test_info->name(); + launch_info.callback = callback; + tests_.push_back(launch_info); + + // Run tests in batches no larger than the limit. + if (tests_.size() >= kTestBatchLimit) + RunRemainingTests(); + } + + virtual void RunRemainingTests() OVERRIDE { + if (tests_.empty()) + return; + + // Create a dedicated temporary directory to store the xml result data + // per run to ensure clean state and make it possible to launch multiple + // processes in parallel. + base::FilePath output_file; + base::ScopedTempDir temp_dir; + CHECK(temp_dir.CreateUniqueTempDir()); + output_file = temp_dir.path().AppendASCII("test_results.xml"); + + std::vector<std::string> test_names; + for (size_t i = 0; i < tests_.size(); i++) + test_names.push_back(tests_[i].GetFullName()); + + CommandLine cmd_line( + GetCommandLineForChildGTestProcess(test_names, output_file)); + + // Adjust the timeout depending on how many tests we're running + // (note that e.g. the last batch of tests will be smaller). + // TODO(phajdan.jr): Consider an adaptive timeout, which can change + // depending on how many tests ran and how many remain. + // Note: do NOT parse child's stdout to do that, it's known to be + // unreliable (e.g. buffering issues can mix up the output). + base::TimeDelta timeout = + test_names.size() * TestTimeouts::action_timeout(); + + // TODO(phajdan.jr): Distinguish between test failures and crashes. + bool was_timeout = false; + int exit_code = LaunchChildGTestProcess(cmd_line, + std::string(), + timeout, + &was_timeout); + + ProcessTestResults(output_file, exit_code); + + tests_.clear(); + } + + void ProcessTestResults(const base::FilePath& output_file, int exit_code) { + std::vector<TestResult> test_results; + bool have_test_results = ProcessGTestOutput(output_file, &test_results); + + if (have_test_results) { + // TODO(phajdan.jr): Check for duplicates and mismatches between + // the results we got from XML file and tests we intended to run. + std::map<std::string, bool> results_map; + for (size_t i = 0; i < test_results.size(); i++) + results_map[test_results[i].GetFullName()] = test_results[i].success; + + for (size_t i = 0; i < tests_.size(); i++) { + TestResult test_result; + test_result.test_case_name = tests_[i].test_case_name; + test_result.test_name = tests_[i].test_name; + test_result.success = results_map[tests_[i].GetFullName()]; + tests_[i].callback.Run(test_result); + } + + // TODO(phajdan.jr): Handle the case where the exit code is non-zero + // but results file indicates that all tests passed (e.g. crash during + // shutdown). + } else { + // We do not have reliable details about test results (parsing test + // stdout is known to be unreliable), apply the executable exit code + // to all tests. + // TODO(phajdan.jr): Be smarter about this, e.g. retry each test + // individually. + for (size_t i = 0; i < tests_.size(); i++) { + TestResult test_result; + test_result.test_case_name = tests_[i].test_case_name; + test_result.test_name = tests_[i].test_name; + test_result.success = (exit_code == 0); + tests_[i].callback.Run(test_result); + } + } + } + + std::vector<TestLaunchInfo> tests_; +}; + +} // namespace + +int LaunchUnitTests(int argc, + char** argv, + const RunTestSuiteCallback& run_test_suite) { + CommandLine::Init(argc, argv); + if (CommandLine::ForCurrentProcess()->HasSwitch(kSingleProcessTestsFlag) || + !CommandLine::ForCurrentProcess()->HasSwitch(kBraveNewTestLauncherFlag)) { + return run_test_suite.Run(); + } + + base::TimeTicks start_time(base::TimeTicks::Now()); + + fprintf(stdout, + "Starting tests...\n" + "IMPORTANT DEBUGGING NOTE: batches of tests are run inside their own \n" + "process. For debugging a test inside a debugger, use the\n" + "--gtest_filter=<your_test_name> flag along with \n" + "--single-process-tests.\n"); + fflush(stdout); + + testing::InitGoogleTest(&argc, argv); + TestTimeouts::Initialize(); + + base::UnitTestLauncherDelegate delegate; + int exit_code = base::LaunchTests(&delegate, argc, argv); + + fprintf(stdout, + "Tests took %" PRId64 " seconds.\n", + (base::TimeTicks::Now() - start_time).InSeconds()); + fflush(stdout); + + return exit_code; +} + +} // namespace base
\ No newline at end of file diff --git a/base/test/unit_test_launcher.h b/base/test/unit_test_launcher.h new file mode 100644 index 0000000..ceac298 --- /dev/null +++ b/base/test/unit_test_launcher.h @@ -0,0 +1,22 @@ +// Copyright 2013 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 BASE_TEST_UNIT_TEST_LAUNCHER_H_ +#define BASE_TEST_UNIT_TEST_LAUNCHER_H_ + +#include "base/callback.h" + +namespace base { + +// Callback that runs a test suite and returns exit code. +typedef base::Callback<int(void)> RunTestSuiteCallback; + +// Launches unit tests in given test suite. Returns exit code. +int LaunchUnitTests(int argc, + char** argv, + const RunTestSuiteCallback& run_test_suite); + +} // namespace base + +#endif // BASE_TEST_UNIT_TEST_LAUNCHER_H_
\ No newline at end of file diff --git a/base/test/unit_test_launcher_ios.cc b/base/test/unit_test_launcher_ios.cc new file mode 100644 index 0000000..9709a70 --- /dev/null +++ b/base/test/unit_test_launcher_ios.cc @@ -0,0 +1,17 @@ +// Copyright 2013 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/test/unit_test_launcher.h" + +namespace base { + +int LaunchUnitTests(int argc, + char** argv, + const RunTestSuiteCallback& run_test_suite) { + // Stub implementation - iOS doesn't support features we need for + // the full test launcher (e.g. process_util). + return run_test_suite.Run(); +} + +} // namespace base
\ No newline at end of file diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index db2da4b..9585ce1 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -51,8 +51,8 @@ '../third_party/libjingle/libjingle.gyp:libjingle', '../third_party/libxml/libxml.gyp:libxml', '../third_party/widevine/cdm/widevine_cdm.gyp:widevine_cdm_version_h', + '../third_party/zlib/google/zip.gyp:zip', '../third_party/zlib/zlib.gyp:minizip', - '../third_party/zlib/zlib.gyp:zip', '../third_party/zlib/zlib.gyp:zlib', '../ui/base/strings/ui_strings.gyp:ui_strings', '../ui/message_center/message_center.gyp:message_center', diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index 7457bcf..a6117b5 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -48,7 +48,7 @@ '<(DEPTH)/third_party/libxml/libxml.gyp:libxml', '<(DEPTH)/third_party/mt19937ar/mt19937ar.gyp:mt19937ar', '<(DEPTH)/third_party/sqlite/sqlite.gyp:sqlite', - '<(DEPTH)/third_party/zlib/zlib.gyp:zip', + '<(DEPTH)/third_party/zlib/google/zip.gyp:zip', '<(DEPTH)/ui/ui.gyp:ui_resources', '<(DEPTH)/url/url.gyp:url_lib', '<(DEPTH)/webkit/common/user_agent/webkit_user_agent.gyp:user_agent', diff --git a/content/content_browser.gypi b/content/content_browser.gypi index d1afb07..49e7987 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -12,7 +12,7 @@ '../skia/skia.gyp:skia', '../sql/sql.gyp:sql', '../third_party/re2/re2.gyp:re2', - '../third_party/zlib/zlib.gyp:zip', + '../third_party/zlib/google/zip.gyp:zip', '../third_party/zlib/zlib.gyp:zlib', '../ui/snapshot/snapshot.gyp:snapshot', '../ui/ui.gyp:ui', diff --git a/content/public/test/test_launcher.cc b/content/public/test/test_launcher.cc index 0c3e355..c29a239 100644 --- a/content/public/test/test_launcher.cc +++ b/content/public/test/test_launcher.cc @@ -86,7 +86,17 @@ int DoRunTestInternal(const testing::TestCase* test_case, } } - CommandLine new_cmd_line(command_line); + CommandLine new_cmd_line(command_line.GetProgram()); + CommandLine::SwitchMap switches = command_line.GetSwitches(); + + // Strip out gtest_output flag because otherwise we would overwrite results + // of the other tests. + switches.erase(base::kGTestOutputFlag); + + for (CommandLine::SwitchMap::const_iterator iter = switches.begin(); + iter != switches.end(); ++iter) { + new_cmd_line.AppendSwitchNative(iter->first, iter->second); + } // Always enable disabled tests. This method is not called with disabled // tests unless this flag was specified to the browser test executable. diff --git a/third_party/libxml/chromium/libxml_utils.cc b/third_party/libxml/chromium/libxml_utils.cc index 63cbb6b..a237be5 100644 --- a/third_party/libxml/chromium/libxml_utils.cc +++ b/third_party/libxml/chromium/libxml_utils.cc @@ -48,6 +48,10 @@ bool XmlReader::NodeAttribute(const char* name, std::string* out) { return true; } +bool XmlReader::IsClosingElement() { + return NodeType() == XML_READER_TYPE_END_ELEMENT; +} + bool XmlReader::ReadElementContent(std::string* content) { const int start_depth = Depth(); diff --git a/third_party/libxml/chromium/libxml_utils.h b/third_party/libxml/chromium/libxml_utils.h index 80d05f6..9091f49 100644 --- a/third_party/libxml/chromium/libxml_utils.h +++ b/third_party/libxml/chromium/libxml_utils.h @@ -73,6 +73,9 @@ class XmlReader { // returns true and |value| is set to "a". bool NodeAttribute(const char* name, std::string* value); + // Returns true if the node is a closing element (e.g. </foo>). + bool IsClosingElement(); + // Helper functions not provided by libxml ---------------------------------- // Return the string content within an element. diff --git a/third_party/zlib/google/zip.gyp b/third_party/zlib/google/zip.gyp new file mode 100644 index 0000000..9542f9c --- /dev/null +++ b/third_party/zlib/google/zip.gyp @@ -0,0 +1,27 @@ +# Copyright 2013 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. + +{ + 'targets': [ + { + 'target_name': 'zip', + 'type': 'static_library', + 'dependencies': [ + '../zlib.gyp:minizip', + '../../../base/base.gyp:base', + ], + 'include_dirs': [ + '../../..', + ], + 'sources': [ + 'zip.cc', + 'zip.h', + 'zip_internal.cc', + 'zip_internal.h', + 'zip_reader.cc', + 'zip_reader.h', + ], + }, + ], +} diff --git a/third_party/zlib/zlib.gyp b/third_party/zlib/zlib.gyp index ebdad42..279a6e8 100644 --- a/third_party/zlib/zlib.gyp +++ b/third_party/zlib/zlib.gyp @@ -107,24 +107,5 @@ }], ], }, - { - 'target_name': 'zip', - 'type': 'static_library', - 'dependencies': [ - 'minizip', - '../../base/base.gyp:base', - ], - 'include_dirs': [ - '../..', - ], - 'sources': [ - 'google/zip.cc', - 'google/zip.h', - 'google/zip_internal.cc', - 'google/zip_internal.h', - 'google/zip_reader.cc', - 'google/zip_reader.h', - ], - }, ], } |