1 // Copyright 2013 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.
5 #include "base/test/launcher/test_results_tracker.h"
7 #include "base/base64.h"
8 #include "base/command_line.h"
9 #include "base/files/file_path.h"
10 #include "base/files/file_util.h"
11 #include "base/format_macros.h"
12 #include "base/json/json_file_value_serializer.h"
13 #include "base/json/string_escape.h"
14 #include "base/logging.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/test/launcher/test_launcher.h"
18 #include "base/values.h"
24 // The default output file for XML output.
25 const FilePath::CharType kDefaultOutputFile
[] = FILE_PATH_LITERAL(
28 std::string
TestNameWithoutDisabledPrefix(const std::string
& test_name
) {
29 std::string
test_name_no_disabled(test_name
);
30 ReplaceSubstringsAfterOffset(&test_name_no_disabled
, 0, "DISABLED_", "");
31 return test_name_no_disabled
;
36 TestResultsTracker::TestResultsTracker() : iteration_(-1), out_(NULL
) {
39 TestResultsTracker::~TestResultsTracker() {
40 DCHECK(thread_checker_
.CalledOnValidThread());
44 fprintf(out_
, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
45 fprintf(out_
, "<testsuites name=\"AllTests\" tests=\"\" failures=\"\""
46 " disabled=\"\" errors=\"\" time=\"\">\n");
48 // Maps test case names to test results.
49 typedef std::map
<std::string
, std::vector
<TestResult
> > TestCaseMap
;
50 TestCaseMap test_case_map
;
52 for (PerIterationData::ResultsMap::iterator i
=
53 per_iteration_data_
[iteration_
].results
.begin();
54 i
!= per_iteration_data_
[iteration_
].results
.end();
56 // Use the last test result as the final one.
57 TestResult result
= i
->second
.test_results
.back();
58 test_case_map
[result
.GetTestCaseName()].push_back(result
);
60 for (TestCaseMap::iterator i
= test_case_map
.begin();
61 i
!= test_case_map
.end();
63 fprintf(out_
, " <testsuite name=\"%s\" tests=\"%" PRIuS
"\" failures=\"\""
64 " disabled=\"\" errors=\"\" time=\"\">\n",
65 i
->first
.c_str(), i
->second
.size());
66 for (size_t j
= 0; j
< i
->second
.size(); ++j
) {
67 const TestResult
& result
= i
->second
[j
];
68 fprintf(out_
, " <testcase name=\"%s\" status=\"run\" time=\"%.3f\""
69 " classname=\"%s\">\n",
70 result
.GetTestName().c_str(),
71 result
.elapsed_time
.InSecondsF(),
72 result
.GetTestCaseName().c_str());
73 if (result
.status
!= TestResult::TEST_SUCCESS
)
74 fprintf(out_
, " <failure message=\"\" type=\"\"></failure>\n");
75 fprintf(out_
, " </testcase>\n");
77 fprintf(out_
, " </testsuite>\n");
79 fprintf(out_
, "</testsuites>\n");
83 bool TestResultsTracker::Init(const CommandLine
& command_line
) {
84 DCHECK(thread_checker_
.CalledOnValidThread());
86 // Prevent initializing twice.
92 if (!command_line
.HasSwitch(kGTestOutputFlag
))
95 std::string flag
= command_line
.GetSwitchValueASCII(kGTestOutputFlag
);
96 size_t colon_pos
= flag
.find(':');
98 if (colon_pos
!= std::string::npos
) {
100 command_line
.GetSwitchValuePath(kGTestOutputFlag
);
101 FilePath::StringType path_string
= flag_path
.value();
102 path
= FilePath(path_string
.substr(colon_pos
+ 1));
103 // If the given path ends with '/', consider it is a directory.
104 // Note: This does NOT check that a directory (or file) actually exists
105 // (the behavior is same as what gtest does).
106 if (path
.EndsWithSeparator()) {
107 FilePath executable
= command_line
.GetProgram().BaseName();
108 path
= path
.Append(executable
.ReplaceExtension(
109 FilePath::StringType(FILE_PATH_LITERAL("xml"))));
112 if (path
.value().empty())
113 path
= FilePath(kDefaultOutputFile
);
114 FilePath dir_name
= path
.DirName();
115 if (!DirectoryExists(dir_name
)) {
116 LOG(WARNING
) << "The output directory does not exist. "
117 << "Creating the directory: " << dir_name
.value();
118 // Create the directory if necessary (because the gtest does the same).
119 if (!base::CreateDirectory(dir_name
)) {
120 LOG(ERROR
) << "Failed to created directory " << dir_name
.value();
124 out_
= OpenFile(path
, "w");
126 LOG(ERROR
) << "Cannot open output file: "
127 << path
.value() << ".";
134 void TestResultsTracker::OnTestIterationStarting() {
135 DCHECK(thread_checker_
.CalledOnValidThread());
137 // Start with a fresh state for new iteration.
139 per_iteration_data_
.push_back(PerIterationData());
142 void TestResultsTracker::AddTest(
143 const std::string
& test_name
, const std::string
& file
, int line
) {
144 // Record disabled test names without DISABLED_ prefix so that they are easy
145 // to compare with regular test names, e.g. before or after disabling.
146 all_tests_
.insert(TestNameWithoutDisabledPrefix(test_name
));
148 test_locations_
.insert(std::make_pair(
149 TestNameWithoutDisabledPrefix(test_name
), CodeLocation(file
, line
)));
152 void TestResultsTracker::AddDisabledTest(const std::string
& test_name
) {
153 // Record disabled test names without DISABLED_ prefix so that they are easy
154 // to compare with regular test names, e.g. before or after disabling.
155 disabled_tests_
.insert(TestNameWithoutDisabledPrefix(test_name
));
158 void TestResultsTracker::AddTestResult(const TestResult
& result
) {
159 DCHECK(thread_checker_
.CalledOnValidThread());
161 per_iteration_data_
[iteration_
].results
[
162 result
.full_name
].test_results
.push_back(result
);
165 void TestResultsTracker::PrintSummaryOfCurrentIteration() const {
166 TestStatusMap
tests_by_status(GetTestStatusMapForCurrentIteration());
168 PrintTests(tests_by_status
[TestResult::TEST_FAILURE
].begin(),
169 tests_by_status
[TestResult::TEST_FAILURE
].end(),
171 PrintTests(tests_by_status
[TestResult::TEST_FAILURE_ON_EXIT
].begin(),
172 tests_by_status
[TestResult::TEST_FAILURE_ON_EXIT
].end(),
174 PrintTests(tests_by_status
[TestResult::TEST_TIMEOUT
].begin(),
175 tests_by_status
[TestResult::TEST_TIMEOUT
].end(),
177 PrintTests(tests_by_status
[TestResult::TEST_CRASH
].begin(),
178 tests_by_status
[TestResult::TEST_CRASH
].end(),
180 PrintTests(tests_by_status
[TestResult::TEST_SKIPPED
].begin(),
181 tests_by_status
[TestResult::TEST_SKIPPED
].end(),
183 PrintTests(tests_by_status
[TestResult::TEST_UNKNOWN
].begin(),
184 tests_by_status
[TestResult::TEST_UNKNOWN
].end(),
185 "had unknown result");
188 void TestResultsTracker::PrintSummaryOfAllIterations() const {
189 DCHECK(thread_checker_
.CalledOnValidThread());
191 TestStatusMap
tests_by_status(GetTestStatusMapForAllIterations());
193 fprintf(stdout
, "Summary of all test iterations:\n");
196 PrintTests(tests_by_status
[TestResult::TEST_FAILURE
].begin(),
197 tests_by_status
[TestResult::TEST_FAILURE
].end(),
199 PrintTests(tests_by_status
[TestResult::TEST_FAILURE_ON_EXIT
].begin(),
200 tests_by_status
[TestResult::TEST_FAILURE_ON_EXIT
].end(),
202 PrintTests(tests_by_status
[TestResult::TEST_TIMEOUT
].begin(),
203 tests_by_status
[TestResult::TEST_TIMEOUT
].end(),
205 PrintTests(tests_by_status
[TestResult::TEST_CRASH
].begin(),
206 tests_by_status
[TestResult::TEST_CRASH
].end(),
208 PrintTests(tests_by_status
[TestResult::TEST_SKIPPED
].begin(),
209 tests_by_status
[TestResult::TEST_SKIPPED
].end(),
211 PrintTests(tests_by_status
[TestResult::TEST_UNKNOWN
].begin(),
212 tests_by_status
[TestResult::TEST_UNKNOWN
].end(),
213 "had unknown result");
215 fprintf(stdout
, "End of the summary.\n");
219 void TestResultsTracker::AddGlobalTag(const std::string
& tag
) {
220 global_tags_
.insert(tag
);
223 bool TestResultsTracker::SaveSummaryAsJSON(const FilePath
& path
) const {
224 scoped_ptr
<DictionaryValue
> summary_root(new DictionaryValue
);
226 scoped_ptr
<ListValue
> global_tags(new ListValue
);
227 for (const auto& global_tag
: global_tags_
) {
228 global_tags
->AppendString(global_tag
);
230 summary_root
->Set("global_tags", global_tags
.Pass());
232 scoped_ptr
<ListValue
> all_tests(new ListValue
);
233 for (const auto& test
: all_tests_
) {
234 all_tests
->AppendString(test
);
236 summary_root
->Set("all_tests", all_tests
.Pass());
238 scoped_ptr
<ListValue
> disabled_tests(new ListValue
);
239 for (const auto& disabled_test
: disabled_tests_
) {
240 disabled_tests
->AppendString(disabled_test
);
242 summary_root
->Set("disabled_tests", disabled_tests
.Pass());
244 scoped_ptr
<ListValue
> per_iteration_data(new ListValue
);
246 for (int i
= 0; i
<= iteration_
; i
++) {
247 scoped_ptr
<DictionaryValue
> current_iteration_data(new DictionaryValue
);
249 for (PerIterationData::ResultsMap::const_iterator j
=
250 per_iteration_data_
[i
].results
.begin();
251 j
!= per_iteration_data_
[i
].results
.end();
253 scoped_ptr
<ListValue
> test_results(new ListValue
);
255 for (size_t k
= 0; k
< j
->second
.test_results
.size(); k
++) {
256 const TestResult
& test_result
= j
->second
.test_results
[k
];
258 scoped_ptr
<DictionaryValue
> test_result_value(new DictionaryValue
);
260 test_result_value
->SetString("status", test_result
.StatusAsString());
261 test_result_value
->SetInteger(
263 static_cast<int>(test_result
.elapsed_time
.InMilliseconds()));
265 bool lossless_snippet
= false;
266 if (IsStringUTF8(test_result
.output_snippet
)) {
267 test_result_value
->SetString(
268 "output_snippet", test_result
.output_snippet
);
269 lossless_snippet
= true;
271 test_result_value
->SetString(
273 "<non-UTF-8 snippet, see output_snippet_base64>");
276 // TODO(phajdan.jr): Fix typo in JSON key (losless -> lossless)
277 // making sure not to break any consumers of this data.
278 test_result_value
->SetBoolean("losless_snippet", lossless_snippet
);
280 // Also include the raw version (base64-encoded so that it can be safely
281 // JSON-serialized - there are no guarantees about character encoding
282 // of the snippet). This can be very useful piece of information when
283 // debugging a test failure related to character encoding.
284 std::string base64_output_snippet
;
285 Base64Encode(test_result
.output_snippet
, &base64_output_snippet
);
286 test_result_value
->SetString("output_snippet_base64",
287 base64_output_snippet
);
288 test_results
->Append(test_result_value
.Pass());
291 current_iteration_data
->SetWithoutPathExpansion(j
->first
,
292 test_results
.Pass());
294 per_iteration_data
->Append(current_iteration_data
.Pass());
295 summary_root
->Set("per_iteration_data", per_iteration_data
.Pass());
298 JSONFileValueSerializer
serializer(path
);
299 return serializer
.Serialize(*summary_root
);
302 TestResultsTracker::TestStatusMap
303 TestResultsTracker::GetTestStatusMapForCurrentIteration() const {
304 TestStatusMap tests_by_status
;
305 GetTestStatusForIteration(iteration_
, &tests_by_status
);
306 return tests_by_status
;
309 TestResultsTracker::TestStatusMap
310 TestResultsTracker::GetTestStatusMapForAllIterations() const {
311 TestStatusMap tests_by_status
;
312 for (int i
= 0; i
<= iteration_
; i
++)
313 GetTestStatusForIteration(i
, &tests_by_status
);
314 return tests_by_status
;
317 void TestResultsTracker::GetTestStatusForIteration(
318 int iteration
, TestStatusMap
* map
) const {
319 for (PerIterationData::ResultsMap::const_iterator j
=
320 per_iteration_data_
[iteration
].results
.begin();
321 j
!= per_iteration_data_
[iteration
].results
.end();
323 // Use the last test result as the final one.
324 const TestResult
& result
= j
->second
.test_results
.back();
325 (*map
)[result
.status
].insert(result
.full_name
);
329 // Utility function to print a list of test names. Uses iterator to be
330 // compatible with different containers, like vector and set.
331 template<typename InputIterator
>
332 void TestResultsTracker::PrintTests(InputIterator first
,
334 const std::string
& description
) const {
335 size_t count
= std::distance(first
, last
);
340 "%" PRIuS
" test%s %s:\n",
342 count
!= 1 ? "s" : "",
343 description
.c_str());
344 for (InputIterator i
= first
; i
!= last
; ++i
) {
348 test_locations_
.at(*i
).file
.c_str(),
349 test_locations_
.at(*i
).line
);
355 TestResultsTracker::AggregateTestResult::AggregateTestResult() {
358 TestResultsTracker::AggregateTestResult::~AggregateTestResult() {
361 TestResultsTracker::PerIterationData::PerIterationData() {
364 TestResultsTracker::PerIterationData::~PerIterationData() {