test_results_tracker.cc revision 0f1bc08d4cfcc34181b0b5cbf065c40f687bf740
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. 4 5#include "base/test/launcher/test_results_tracker.h" 6 7#include "base/command_line.h" 8#include "base/file_util.h" 9#include "base/files/file_path.h" 10#include "base/format_macros.h" 11#include "base/json/json_file_value_serializer.h" 12#include "base/logging.h" 13#include "base/strings/stringprintf.h" 14#include "base/test/launcher/test_launcher.h" 15#include "base/values.h" 16 17namespace base { 18 19// See https://groups.google.com/a/chromium.org/d/msg/chromium-dev/nkdTP7sstSc/uT3FaE_sgkAJ . 20using ::operator<<; 21 22namespace { 23 24// The default output file for XML output. 25const FilePath::CharType kDefaultOutputFile[] = FILE_PATH_LITERAL( 26 "test_detail.xml"); 27 28// Utility function to print a list of test names. Uses iterator to be 29// compatible with different containers, like vector and set. 30template<typename InputIterator> 31void PrintTests(InputIterator first, 32 InputIterator last, 33 const std::string& description) { 34 size_t count = std::distance(first, last); 35 if (count == 0) 36 return; 37 38 fprintf(stdout, 39 "%" PRIuS " test%s %s:\n", 40 count, 41 count != 1 ? "s" : "", 42 description.c_str()); 43 for (InputIterator i = first; i != last; ++i) 44 fprintf(stdout, " %s\n", (*i).c_str()); 45 fflush(stdout); 46} 47 48} // namespace 49 50TestResultsTracker::TestResultsTracker() : iteration_(-1), out_(NULL) { 51} 52 53TestResultsTracker::~TestResultsTracker() { 54 DCHECK(thread_checker_.CalledOnValidThread()); 55 56 if (!out_) 57 return; 58 fprintf(out_, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 59 fprintf(out_, "<testsuites name=\"AllTests\" tests=\"\" failures=\"\"" 60 " disabled=\"\" errors=\"\" time=\"\">\n"); 61 62 // Maps test case names to test results. 63 typedef std::map<std::string, std::vector<TestResult> > TestCaseMap; 64 TestCaseMap test_case_map; 65 66 for (PerIterationData::ResultsMap::iterator i = 67 per_iteration_data_[iteration_].results.begin(); 68 i != per_iteration_data_[iteration_].results.end(); 69 ++i) { 70 // Use the last test result as the final one. 71 TestResult result = i->second.test_results.back(); 72 test_case_map[result.GetTestCaseName()].push_back(result); 73 } 74 for (TestCaseMap::iterator i = test_case_map.begin(); 75 i != test_case_map.end(); 76 ++i) { 77 fprintf(out_, " <testsuite name=\"%s\" tests=\"%" PRIuS "\" failures=\"\"" 78 " disabled=\"\" errors=\"\" time=\"\">\n", 79 i->first.c_str(), i->second.size()); 80 for (size_t j = 0; j < i->second.size(); ++j) { 81 const TestResult& result = i->second[j]; 82 fprintf(out_, " <testcase name=\"%s\" status=\"run\" time=\"%.3f\"" 83 " classname=\"%s\">\n", 84 result.GetTestName().c_str(), 85 result.elapsed_time.InSecondsF(), 86 result.GetTestCaseName().c_str()); 87 if (result.status != TestResult::TEST_SUCCESS) 88 fprintf(out_, " <failure message=\"\" type=\"\"></failure>\n"); 89 fprintf(out_, " </testcase>\n"); 90 } 91 fprintf(out_, " </testsuite>\n"); 92 } 93 fprintf(out_, "</testsuites>\n"); 94 fclose(out_); 95} 96 97bool TestResultsTracker::Init(const CommandLine& command_line) { 98 DCHECK(thread_checker_.CalledOnValidThread()); 99 100 // Prevent initializing twice. 101 if (out_) { 102 NOTREACHED(); 103 return false; 104 } 105 106 if (!command_line.HasSwitch(kGTestOutputFlag)) 107 return true; 108 109 std::string flag = command_line.GetSwitchValueASCII(kGTestOutputFlag); 110 size_t colon_pos = flag.find(':'); 111 FilePath path; 112 if (colon_pos != std::string::npos) { 113 FilePath flag_path = 114 command_line.GetSwitchValuePath(kGTestOutputFlag); 115 FilePath::StringType path_string = flag_path.value(); 116 path = FilePath(path_string.substr(colon_pos + 1)); 117 // If the given path ends with '/', consider it is a directory. 118 // Note: This does NOT check that a directory (or file) actually exists 119 // (the behavior is same as what gtest does). 120 if (path.EndsWithSeparator()) { 121 FilePath executable = command_line.GetProgram().BaseName(); 122 path = path.Append(executable.ReplaceExtension( 123 FilePath::StringType(FILE_PATH_LITERAL("xml")))); 124 } 125 } 126 if (path.value().empty()) 127 path = FilePath(kDefaultOutputFile); 128 FilePath dir_name = path.DirName(); 129 if (!DirectoryExists(dir_name)) { 130 LOG(WARNING) << "The output directory does not exist. " 131 << "Creating the directory: " << dir_name.value(); 132 // Create the directory if necessary (because the gtest does the same). 133 if (!file_util::CreateDirectory(dir_name)) { 134 LOG(ERROR) << "Failed to created directory " << dir_name.value(); 135 return false; 136 } 137 } 138 out_ = file_util::OpenFile(path, "w"); 139 if (!out_) { 140 LOG(ERROR) << "Cannot open output file: " 141 << path.value() << "."; 142 return false; 143 } 144 145 return true; 146} 147 148void TestResultsTracker::OnTestIterationStarting() { 149 DCHECK(thread_checker_.CalledOnValidThread()); 150 151 // Start with a fresh state for new iteration. 152 iteration_++; 153 per_iteration_data_.push_back(PerIterationData()); 154} 155 156void TestResultsTracker::AddTestResult(const TestResult& result) { 157 DCHECK(thread_checker_.CalledOnValidThread()); 158 159 per_iteration_data_[iteration_].results[ 160 result.full_name].test_results.push_back(result); 161} 162 163void TestResultsTracker::PrintSummaryOfCurrentIteration() const { 164 std::map<TestResult::Status, std::set<std::string> > tests_by_status; 165 166 for (PerIterationData::ResultsMap::const_iterator j = 167 per_iteration_data_[iteration_].results.begin(); 168 j != per_iteration_data_[iteration_].results.end(); 169 ++j) { 170 // Use the last test result as the final one. 171 TestResult result = j->second.test_results.back(); 172 tests_by_status[result.status].insert(result.full_name); 173 } 174 175 PrintTests(tests_by_status[TestResult::TEST_FAILURE].begin(), 176 tests_by_status[TestResult::TEST_FAILURE].end(), 177 "failed"); 178 PrintTests(tests_by_status[TestResult::TEST_TIMEOUT].begin(), 179 tests_by_status[TestResult::TEST_TIMEOUT].end(), 180 "timed out"); 181 PrintTests(tests_by_status[TestResult::TEST_CRASH].begin(), 182 tests_by_status[TestResult::TEST_CRASH].end(), 183 "crashed"); 184 PrintTests(tests_by_status[TestResult::TEST_SKIPPED].begin(), 185 tests_by_status[TestResult::TEST_SKIPPED].end(), 186 "skipped"); 187 PrintTests(tests_by_status[TestResult::TEST_UNKNOWN].begin(), 188 tests_by_status[TestResult::TEST_UNKNOWN].end(), 189 "had unknown result"); 190} 191 192void TestResultsTracker::PrintSummaryOfAllIterations() const { 193 DCHECK(thread_checker_.CalledOnValidThread()); 194 195 std::map<TestResult::Status, std::set<std::string> > tests_by_status; 196 197 for (int i = 0; i <= iteration_; i++) { 198 for (PerIterationData::ResultsMap::const_iterator j = 199 per_iteration_data_[i].results.begin(); 200 j != per_iteration_data_[i].results.end(); 201 ++j) { 202 // Use the last test result as the final one. 203 TestResult result = j->second.test_results.back(); 204 tests_by_status[result.status].insert(result.full_name); 205 } 206 } 207 208 fprintf(stdout, "Summary of all itest iterations:\n"); 209 fflush(stdout); 210 211 PrintTests(tests_by_status[TestResult::TEST_FAILURE].begin(), 212 tests_by_status[TestResult::TEST_FAILURE].end(), 213 "failed"); 214 PrintTests(tests_by_status[TestResult::TEST_TIMEOUT].begin(), 215 tests_by_status[TestResult::TEST_TIMEOUT].end(), 216 "timed out"); 217 PrintTests(tests_by_status[TestResult::TEST_CRASH].begin(), 218 tests_by_status[TestResult::TEST_CRASH].end(), 219 "crashed"); 220 PrintTests(tests_by_status[TestResult::TEST_SKIPPED].begin(), 221 tests_by_status[TestResult::TEST_SKIPPED].end(), 222 "skipped"); 223 PrintTests(tests_by_status[TestResult::TEST_UNKNOWN].begin(), 224 tests_by_status[TestResult::TEST_UNKNOWN].end(), 225 "had unknown result"); 226 227 fprintf(stdout, "End of the summary.\n"); 228 fflush(stdout); 229} 230 231void TestResultsTracker::AddGlobalTag(const std::string& tag) { 232 global_tags_.insert(tag); 233} 234 235bool TestResultsTracker::SaveSummaryAsJSON(const FilePath& path) const { 236 scoped_ptr<DictionaryValue> summary_root(new DictionaryValue); 237 238 ListValue* global_tags = new ListValue; 239 summary_root->Set("global_tags", global_tags); 240 241 for (std::set<std::string>::const_iterator i = global_tags_.begin(); 242 i != global_tags_.end(); 243 ++i) { 244 global_tags->AppendString(*i); 245 } 246 247 ListValue* per_iteration_data = new ListValue; 248 summary_root->Set("per_iteration_data", per_iteration_data); 249 250 for (int i = 0; i <= iteration_; i++) { 251 DictionaryValue* current_iteration_data = new DictionaryValue; 252 per_iteration_data->Append(current_iteration_data); 253 254 for (PerIterationData::ResultsMap::const_iterator j = 255 per_iteration_data_[i].results.begin(); 256 j != per_iteration_data_[i].results.end(); 257 ++j) { 258 ListValue* test_results = new ListValue; 259 current_iteration_data->SetWithoutPathExpansion(j->first, test_results); 260 261 for (size_t k = 0; k < j->second.test_results.size(); k++) { 262 const TestResult& test_result = j->second.test_results[k]; 263 264 DictionaryValue* test_result_value = new DictionaryValue; 265 test_results->Append(test_result_value); 266 267 test_result_value->SetString("status", test_result.StatusAsString()); 268 test_result_value->SetInteger( 269 "elapsed_time_ms", test_result.elapsed_time.InMilliseconds()); 270 test_result_value->SetString("output_snippet", 271 test_result.output_snippet); 272 } 273 } 274 } 275 276 JSONFileValueSerializer serializer(path); 277 return serializer.Serialize(*summary_root); 278} 279 280TestResultsTracker::AggregateTestResult::AggregateTestResult() { 281} 282 283TestResultsTracker::AggregateTestResult::~AggregateTestResult() { 284} 285 286TestResultsTracker::PerIterationData::PerIterationData() { 287} 288 289TestResultsTracker::PerIterationData::~PerIterationData() { 290} 291 292} // namespace base 293