1 /*------------------------------------------------------------------------- 2 * drawElements Quality Program Test Executor 3 * ------------------------------------------ 4 * 5 * Copyright 2014 The Android Open Source Project 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * 19 *//*! 20 * \file 21 * \brief Batch result to XML export. 22 *//*--------------------------------------------------------------------*/ 23 24 #include "xeTestLogParser.hpp" 25 #include "xeTestResultParser.hpp" 26 #include "xeXMLWriter.hpp" 27 #include "xeTestLogWriter.hpp" 28 #include "deFilePath.hpp" 29 #include "deString.h" 30 #include "deStringUtil.hpp" 31 #include "deCommandLine.hpp" 32 33 #include <vector> 34 #include <string> 35 #include <map> 36 #include <cstdio> 37 #include <fstream> 38 #include <iostream> 39 40 using std::vector; 41 using std::string; 42 using std::map; 43 44 static const char* CASELIST_STYLESHEET = "caselist.xsl"; 45 static const char* TESTCASE_STYLESHEET = "testlog.xsl"; 46 47 enum OutputMode 48 { 49 OUTPUTMODE_SEPARATE = 0, //!< Separate 50 OUTPUTMODE_SINGLE, 51 52 OUTPUTMODE_LAST 53 }; 54 55 namespace opt 56 { 57 58 DE_DECLARE_COMMAND_LINE_OPT(OutMode, OutputMode); 59 60 void registerOptions (de::cmdline::Parser& parser) 61 { 62 using de::cmdline::Option; 63 using de::cmdline::NamedValue; 64 65 static const NamedValue<OutputMode> s_modes[] = 66 { 67 { "single", OUTPUTMODE_SINGLE }, 68 { "separate", OUTPUTMODE_SEPARATE } 69 }; 70 71 parser << Option<OutMode>("m", "mode", "Output mode", s_modes, "single"); 72 } 73 74 } // opt 75 76 struct CommandLine 77 { 78 CommandLine (void) 79 : outputMode(OUTPUTMODE_SINGLE) 80 { 81 } 82 83 std::string batchResultFile; 84 std::string outputPath; 85 OutputMode outputMode; 86 }; 87 88 static bool parseCommandLine (CommandLine& cmdLine, int argc, const char* const* argv) 89 { 90 de::cmdline::Parser parser; 91 de::cmdline::CommandLine opts; 92 93 opt::registerOptions(parser); 94 95 if (!parser.parse(argc-1, argv+1, &opts, std::cerr) || 96 opts.getArgs().size() != 2) 97 { 98 printf("%s: [options] [testlog] [destination path]\n", argv[0]); 99 parser.help(std::cout); 100 return false; 101 } 102 103 cmdLine.outputMode = opts.getOption<opt::OutMode>(); 104 cmdLine.batchResultFile = opts.getArgs()[0]; 105 cmdLine.outputPath = opts.getArgs()[1]; 106 107 return true; 108 } 109 110 static void parseBatchResult (xe::TestLogParser& parser, const char* filename) 111 { 112 std::ifstream in (filename, std::ios_base::binary); 113 deUint8 buf[2048]; 114 115 for (;;) 116 { 117 in.read((char*)&buf[0], sizeof(buf)); 118 int numRead = (int)in.gcount(); 119 120 if (numRead > 0) 121 parser.parse(&buf[0], numRead); 122 123 if (numRead < (int)sizeof(buf)) 124 break; 125 } 126 } 127 128 // Export to single file 129 130 struct BatchResultTotals 131 { 132 BatchResultTotals (void) 133 { 134 for (int i = 0;i < xe::TESTSTATUSCODE_LAST; i++) 135 countByCode[i] = 0; 136 } 137 138 int countByCode[xe::TESTSTATUSCODE_LAST]; 139 }; 140 141 class ResultToSingleXmlLogHandler : public xe::TestLogHandler 142 { 143 public: 144 ResultToSingleXmlLogHandler (xe::xml::Writer& writer, BatchResultTotals& totals) 145 : m_writer (writer) 146 , m_totals (totals) 147 { 148 } 149 150 void setSessionInfo (const xe::SessionInfo&) 151 { 152 } 153 154 xe::TestCaseResultPtr startTestCaseResult (const char* casePath) 155 { 156 return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath)); 157 } 158 159 void testCaseResultUpdated (const xe::TestCaseResultPtr&) 160 { 161 } 162 163 void testCaseResultComplete (const xe::TestCaseResultPtr& resultData) 164 { 165 xe::TestCaseResult result; 166 167 xe::parseTestCaseResultFromData(&m_resultParser, &result, *resultData.get()); 168 169 // Write result. 170 xe::writeTestResult(result, m_writer); 171 172 // Record total 173 XE_CHECK(de::inBounds<int>(result.statusCode, 0, xe::TESTSTATUSCODE_LAST)); 174 m_totals.countByCode[result.statusCode] += 1; 175 } 176 177 private: 178 xe::xml::Writer& m_writer; 179 BatchResultTotals& m_totals; 180 xe::TestResultParser m_resultParser; 181 }; 182 183 static void writeTotals (xe::xml::Writer& writer, const BatchResultTotals& totals) 184 { 185 using xe::xml::Writer; 186 187 int totalCases = 0; 188 189 writer << Writer::BeginElement("ResultTotals"); 190 191 for (int code = 0; code < xe::TESTSTATUSCODE_LAST; code++) 192 { 193 writer << Writer::Attribute(xe::getTestStatusCodeName((xe::TestStatusCode)code), de::toString(totals.countByCode[code]).c_str()); 194 totalCases += totals.countByCode[code]; 195 } 196 197 writer << Writer::Attribute("All", de::toString(totalCases).c_str()) 198 << Writer::EndElement; 199 } 200 201 static void batchResultToSingleXmlFile (const char* batchResultFilename, const char* dstFileName) 202 { 203 std::ofstream out (dstFileName, std::ios_base::binary); 204 xe::xml::Writer writer (out); 205 BatchResultTotals totals; 206 ResultToSingleXmlLogHandler handler (writer, totals); 207 xe::TestLogParser parser (&handler); 208 209 XE_CHECK(out.good()); 210 211 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" 212 << "<?xml-stylesheet href=\"" << TESTCASE_STYLESHEET << "\" type=\"text/xsl\"?>\n"; 213 214 writer << xe::xml::Writer::BeginElement("BatchResult") 215 << xe::xml::Writer::Attribute("FileName", de::FilePath(batchResultFilename).getBaseName()); 216 217 // Parse and write individual cases 218 parseBatchResult(parser, batchResultFilename); 219 220 // Write ResultTotals 221 writeTotals(writer, totals); 222 223 writer << xe::xml::Writer::EndElement; 224 out << "\n"; 225 } 226 227 // Export to separate files 228 229 class ResultToXmlFilesLogHandler : public xe::TestLogHandler 230 { 231 public: 232 ResultToXmlFilesLogHandler (vector<xe::TestCaseResultHeader>& resultHeaders, const char* dstPath) 233 : m_resultHeaders (resultHeaders) 234 , m_dstPath (dstPath) 235 { 236 } 237 238 void setSessionInfo (const xe::SessionInfo&) 239 { 240 } 241 242 xe::TestCaseResultPtr startTestCaseResult (const char* casePath) 243 { 244 return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath)); 245 } 246 247 void testCaseResultUpdated (const xe::TestCaseResultPtr&) 248 { 249 } 250 251 void testCaseResultComplete (const xe::TestCaseResultPtr& resultData) 252 { 253 xe::TestCaseResult result; 254 255 xe::parseTestCaseResultFromData(&m_resultParser, &result, *resultData.get()); 256 257 // Write result. 258 { 259 de::FilePath casePath = de::FilePath::join(m_dstPath, (result.casePath + ".xml").c_str()); 260 std::ofstream out (casePath.getPath(), std::ofstream::binary|std::ofstream::trunc); 261 xe::xml::Writer xmlWriter (out); 262 263 if (!out.good()) 264 throw xe::Error(string("Failed to open ") + casePath.getPath()); 265 266 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" 267 << "<?xml-stylesheet href=\"" << TESTCASE_STYLESHEET << "\" type=\"text/xsl\"?>\n"; 268 xe::writeTestResult(result, xmlWriter); 269 out << "\n"; 270 } 271 272 m_resultHeaders.push_back(xe::TestCaseResultHeader(result)); 273 } 274 275 private: 276 vector<xe::TestCaseResultHeader>& m_resultHeaders; 277 std::string m_dstPath; 278 xe::TestResultParser m_resultParser; 279 }; 280 281 typedef std::map<const xe::TestCase*, const xe::TestCaseResultHeader*> ShortTestResultMap; 282 283 static void writeTestCaseListNode (const xe::TestNode* testNode, const ShortTestResultMap& resultMap, xe::xml::Writer& dst) 284 { 285 using xe::xml::Writer; 286 287 bool isGroup = testNode->getNodeType() == xe::TESTNODETYPE_GROUP; 288 string fullPath; 289 testNode->getFullPath(fullPath); 290 291 if (isGroup) 292 { 293 const xe::TestGroup* group = static_cast<const xe::TestGroup*>(testNode); 294 295 dst << Writer::BeginElement("TestGroup") 296 << Writer::Attribute("Name", testNode->getName()); 297 298 for (int childNdx = 0; childNdx < group->getNumChildren(); childNdx++) 299 writeTestCaseListNode(group->getChild(childNdx), resultMap, dst); 300 301 dst << Writer::EndElement; 302 } 303 else 304 { 305 DE_ASSERT(testNode->getNodeType() == xe::TESTNODETYPE_TEST_CASE); 306 307 const xe::TestCase* testCase = static_cast<const xe::TestCase*>(testNode); 308 ShortTestResultMap::const_iterator resultPos = resultMap.find(testCase); 309 const xe::TestCaseResultHeader* result = resultPos != resultMap.end() ? resultPos->second : DE_NULL; 310 311 DE_ASSERT(result); 312 313 dst << Writer::BeginElement("TestCase") 314 << Writer::Attribute("Name", testNode->getName()) 315 << Writer::Attribute("Type", xe::getTestCaseTypeName(result->caseType)) 316 << Writer::Attribute("StatusCode", xe::getTestStatusCodeName(result->statusCode)) 317 << Writer::Attribute("StatusDetails", result->statusDetails.c_str()) 318 << Writer::EndElement; 319 } 320 } 321 322 static void writeTestCaseList (const xe::TestRoot& root, const ShortTestResultMap& resultMap, xe::xml::Writer& dst) 323 { 324 using xe::xml::Writer; 325 326 dst << Writer::BeginElement("TestRoot"); 327 328 for (int childNdx = 0; childNdx < root.getNumChildren(); childNdx++) 329 writeTestCaseListNode(root.getChild(childNdx), resultMap, dst); 330 331 dst << Writer::EndElement; 332 } 333 334 static void batchResultToSeparateXmlFiles (const char* batchResultFilename, const char* dstPath) 335 { 336 xe::TestRoot testRoot; 337 vector<xe::TestCaseResultHeader> shortResults; 338 ShortTestResultMap resultMap; 339 340 // Initialize destination directory. 341 if (!de::FilePath(dstPath).exists()) 342 de::createDirectoryAndParents(dstPath); 343 else 344 XE_CHECK_MSG(de::FilePath(dstPath).getType() == de::FilePath::TYPE_DIRECTORY, "Destination is not directory"); 345 346 // Parse batch result and write out test cases. 347 { 348 ResultToXmlFilesLogHandler handler (shortResults, dstPath); 349 xe::TestLogParser parser (&handler); 350 351 parseBatchResult(parser, batchResultFilename); 352 } 353 354 // Build case hierarchy & short result map. 355 { 356 xe::TestHierarchyBuilder hierarchyBuilder(&testRoot); 357 358 for (vector<xe::TestCaseResultHeader>::const_iterator result = shortResults.begin(); result != shortResults.end(); result++) 359 { 360 xe::TestCase* testCase = hierarchyBuilder.createCase(result->casePath.c_str(), result->caseType); 361 resultMap.insert(std::make_pair(testCase, &(*result))); 362 } 363 } 364 365 // Create caselist. 366 { 367 de::FilePath indexPath = de::FilePath::join(dstPath, "caselist.xml"); 368 std::ofstream out (indexPath.getPath(), std::ofstream::binary|std::ofstream::trunc); 369 xe::xml::Writer xmlWriter (out); 370 371 XE_CHECK_MSG(out.good(), "Failed to open caselist.xml"); 372 373 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" 374 << "<?xml-stylesheet href=\"" << CASELIST_STYLESHEET << "\" type=\"text/xsl\"?>\n"; 375 writeTestCaseList(testRoot, resultMap, xmlWriter); 376 out << "\n"; 377 } 378 } 379 380 int main (int argc, const char* const* argv) 381 { 382 try 383 { 384 CommandLine cmdLine; 385 if (!parseCommandLine(cmdLine, argc, argv)) 386 return -1; 387 388 if (cmdLine.outputMode == OUTPUTMODE_SINGLE) 389 batchResultToSingleXmlFile(cmdLine.batchResultFile.c_str(), cmdLine.outputPath.c_str()); 390 else 391 batchResultToSeparateXmlFiles(cmdLine.batchResultFile.c_str(), cmdLine.outputPath.c_str()); 392 } 393 catch (const std::exception& e) 394 { 395 printf("%s\n", e.what()); 396 return -1; 397 } 398 399 return 0; 400 } 401