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::map;
41 using std::string;
42 using std::vector;
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
registerOptions(de::cmdline::Parser & parser)60 void registerOptions(de::cmdline::Parser &parser)
61 {
62 using de::cmdline::NamedValue;
63 using de::cmdline::Option;
64
65 static const NamedValue<OutputMode> s_modes[] = {{"single", OUTPUTMODE_SINGLE}, {"separate", OUTPUTMODE_SEPARATE}};
66
67 parser << Option<OutMode>("m", "mode", "Output mode", s_modes, "single");
68 }
69
70 } // namespace opt
71
72 struct CommandLine
73 {
CommandLineCommandLine74 CommandLine(void) : outputMode(OUTPUTMODE_SINGLE)
75 {
76 }
77
78 std::string batchResultFile;
79 std::string outputPath;
80 OutputMode outputMode;
81 };
82
parseCommandLine(CommandLine & cmdLine,int argc,const char * const * argv)83 static bool parseCommandLine(CommandLine &cmdLine, int argc, const char *const *argv)
84 {
85 de::cmdline::Parser parser;
86 de::cmdline::CommandLine opts;
87
88 opt::registerOptions(parser);
89
90 if (!parser.parse(argc - 1, argv + 1, &opts, std::cerr) || opts.getArgs().size() != 2)
91 {
92 printf("%s: [options] [testlog] [destination path]\n", argv[0]);
93 parser.help(std::cout);
94 return false;
95 }
96
97 cmdLine.outputMode = opts.getOption<opt::OutMode>();
98 cmdLine.batchResultFile = opts.getArgs()[0];
99 cmdLine.outputPath = opts.getArgs()[1];
100
101 return true;
102 }
103
parseBatchResult(xe::TestLogParser & parser,const char * filename)104 static void parseBatchResult(xe::TestLogParser &parser, const char *filename)
105 {
106 std::ifstream in(filename, std::ios_base::binary);
107 uint8_t buf[2048];
108
109 for (;;)
110 {
111 in.read((char *)&buf[0], sizeof(buf));
112 int numRead = (int)in.gcount();
113
114 if (numRead > 0)
115 parser.parse(&buf[0], numRead);
116
117 if (numRead < (int)sizeof(buf))
118 break;
119 }
120 }
121
122 // Export to single file
123
124 struct BatchResultTotals
125 {
BatchResultTotalsBatchResultTotals126 BatchResultTotals(void)
127 {
128 for (int i = 0; i < xe::TESTSTATUSCODE_LAST; i++)
129 countByCode[i] = 0;
130 }
131
132 int countByCode[xe::TESTSTATUSCODE_LAST];
133 };
134
135 class ResultToSingleXmlLogHandler : public xe::TestLogHandler
136 {
137 public:
ResultToSingleXmlLogHandler(xe::xml::Writer & writer,BatchResultTotals & totals)138 ResultToSingleXmlLogHandler(xe::xml::Writer &writer, BatchResultTotals &totals) : m_writer(writer), m_totals(totals)
139 {
140 }
141
setSessionInfo(const xe::SessionInfo &)142 void setSessionInfo(const xe::SessionInfo &)
143 {
144 }
145
startTestCaseResult(const char * casePath)146 xe::TestCaseResultPtr startTestCaseResult(const char *casePath)
147 {
148 return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
149 }
150
testCaseResultUpdated(const xe::TestCaseResultPtr &)151 void testCaseResultUpdated(const xe::TestCaseResultPtr &)
152 {
153 }
154
testCaseResultComplete(const xe::TestCaseResultPtr & resultData)155 void testCaseResultComplete(const xe::TestCaseResultPtr &resultData)
156 {
157 xe::TestCaseResult result;
158
159 xe::parseTestCaseResultFromData(&m_resultParser, &result, *resultData.get());
160
161 // Write result.
162 xe::writeTestResult(result, m_writer);
163
164 // Record total
165 XE_CHECK(de::inBounds<int>(result.statusCode, 0, xe::TESTSTATUSCODE_LAST));
166 m_totals.countByCode[result.statusCode] += 1;
167 }
168
169 private:
170 xe::xml::Writer &m_writer;
171 BatchResultTotals &m_totals;
172 xe::TestResultParser m_resultParser;
173 };
174
writeTotals(xe::xml::Writer & writer,const BatchResultTotals & totals)175 static void writeTotals(xe::xml::Writer &writer, const BatchResultTotals &totals)
176 {
177 using xe::xml::Writer;
178
179 int totalCases = 0;
180
181 writer << Writer::BeginElement("ResultTotals");
182
183 for (int code = 0; code < xe::TESTSTATUSCODE_LAST; code++)
184 {
185 writer << Writer::Attribute(xe::getTestStatusCodeName((xe::TestStatusCode)code),
186 de::toString(totals.countByCode[code]).c_str());
187 totalCases += totals.countByCode[code];
188 }
189
190 writer << Writer::Attribute("All", de::toString(totalCases).c_str()) << Writer::EndElement;
191 }
192
batchResultToSingleXmlFile(const char * batchResultFilename,const char * dstFileName)193 static void batchResultToSingleXmlFile(const char *batchResultFilename, const char *dstFileName)
194 {
195 std::ofstream out(dstFileName, std::ios_base::binary);
196 xe::xml::Writer writer(out);
197 BatchResultTotals totals;
198 ResultToSingleXmlLogHandler handler(writer, totals);
199 xe::TestLogParser parser(&handler);
200
201 XE_CHECK(out.good());
202
203 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
204 << "<?xml-stylesheet href=\"" << TESTCASE_STYLESHEET << "\" type=\"text/xsl\"?>\n";
205
206 writer << xe::xml::Writer::BeginElement("BatchResult")
207 << xe::xml::Writer::Attribute("FileName", de::FilePath(batchResultFilename).getBaseName());
208
209 // Parse and write individual cases
210 parseBatchResult(parser, batchResultFilename);
211
212 // Write ResultTotals
213 writeTotals(writer, totals);
214
215 writer << xe::xml::Writer::EndElement;
216 out << "\n";
217 }
218
219 // Export to separate files
220
221 class ResultToXmlFilesLogHandler : public xe::TestLogHandler
222 {
223 public:
ResultToXmlFilesLogHandler(vector<xe::TestCaseResultHeader> & resultHeaders,const char * dstPath)224 ResultToXmlFilesLogHandler(vector<xe::TestCaseResultHeader> &resultHeaders, const char *dstPath)
225 : m_resultHeaders(resultHeaders)
226 , m_dstPath(dstPath)
227 {
228 }
229
setSessionInfo(const xe::SessionInfo &)230 void setSessionInfo(const xe::SessionInfo &)
231 {
232 }
233
startTestCaseResult(const char * casePath)234 xe::TestCaseResultPtr startTestCaseResult(const char *casePath)
235 {
236 return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
237 }
238
testCaseResultUpdated(const xe::TestCaseResultPtr &)239 void testCaseResultUpdated(const xe::TestCaseResultPtr &)
240 {
241 }
242
testCaseResultComplete(const xe::TestCaseResultPtr & resultData)243 void testCaseResultComplete(const xe::TestCaseResultPtr &resultData)
244 {
245 xe::TestCaseResult result;
246
247 xe::parseTestCaseResultFromData(&m_resultParser, &result, *resultData.get());
248
249 // Write result.
250 {
251 de::FilePath casePath = de::FilePath::join(m_dstPath, (result.casePath + ".xml").c_str());
252 std::ofstream out(casePath.getPath(), std::ofstream::binary | std::ofstream::trunc);
253 xe::xml::Writer xmlWriter(out);
254
255 if (!out.good())
256 throw xe::Error(string("Failed to open ") + casePath.getPath());
257
258 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
259 << "<?xml-stylesheet href=\"" << TESTCASE_STYLESHEET << "\" type=\"text/xsl\"?>\n";
260 xe::writeTestResult(result, xmlWriter);
261 out << "\n";
262 }
263
264 m_resultHeaders.push_back(xe::TestCaseResultHeader(result));
265 }
266
267 private:
268 vector<xe::TestCaseResultHeader> &m_resultHeaders;
269 std::string m_dstPath;
270 xe::TestResultParser m_resultParser;
271 };
272
273 typedef std::map<const xe::TestCase *, const xe::TestCaseResultHeader *> ShortTestResultMap;
274
writeTestCaseListNode(const xe::TestNode * testNode,const ShortTestResultMap & resultMap,xe::xml::Writer & dst)275 static void writeTestCaseListNode(const xe::TestNode *testNode, const ShortTestResultMap &resultMap,
276 xe::xml::Writer &dst)
277 {
278 using xe::xml::Writer;
279
280 bool isGroup = testNode->getNodeType() == xe::TESTNODETYPE_GROUP;
281 string fullPath;
282 testNode->getFullPath(fullPath);
283
284 if (isGroup)
285 {
286 const xe::TestGroup *group = static_cast<const xe::TestGroup *>(testNode);
287
288 dst << Writer::BeginElement("TestGroup") << Writer::Attribute("Name", testNode->getName());
289
290 for (int childNdx = 0; childNdx < group->getNumChildren(); childNdx++)
291 writeTestCaseListNode(group->getChild(childNdx), resultMap, dst);
292
293 dst << Writer::EndElement;
294 }
295 else
296 {
297 DE_ASSERT(testNode->getNodeType() == xe::TESTNODETYPE_TEST_CASE);
298
299 const xe::TestCase *testCase = static_cast<const xe::TestCase *>(testNode);
300 ShortTestResultMap::const_iterator resultPos = resultMap.find(testCase);
301 const xe::TestCaseResultHeader *result = resultPos != resultMap.end() ? resultPos->second : DE_NULL;
302
303 DE_ASSERT(result);
304
305 dst << Writer::BeginElement("TestCase") << Writer::Attribute("Name", testNode->getName())
306 << Writer::Attribute("Type", xe::getTestCaseTypeName(result->caseType))
307 << Writer::Attribute("StatusCode", xe::getTestStatusCodeName(result->statusCode))
308 << Writer::Attribute("StatusDetails", result->statusDetails.c_str()) << Writer::EndElement;
309 }
310 }
311
writeTestCaseList(const xe::TestRoot & root,const ShortTestResultMap & resultMap,xe::xml::Writer & dst)312 static void writeTestCaseList(const xe::TestRoot &root, const ShortTestResultMap &resultMap, xe::xml::Writer &dst)
313 {
314 using xe::xml::Writer;
315
316 dst << Writer::BeginElement("TestRoot");
317
318 for (int childNdx = 0; childNdx < root.getNumChildren(); childNdx++)
319 writeTestCaseListNode(root.getChild(childNdx), resultMap, dst);
320
321 dst << Writer::EndElement;
322 }
323
batchResultToSeparateXmlFiles(const char * batchResultFilename,const char * dstPath)324 static void batchResultToSeparateXmlFiles(const char *batchResultFilename, const char *dstPath)
325 {
326 xe::TestRoot testRoot;
327 vector<xe::TestCaseResultHeader> shortResults;
328 ShortTestResultMap resultMap;
329
330 // Initialize destination directory.
331 if (!de::FilePath(dstPath).exists())
332 de::createDirectoryAndParents(dstPath);
333 else
334 XE_CHECK_MSG(de::FilePath(dstPath).getType() == de::FilePath::TYPE_DIRECTORY, "Destination is not directory");
335
336 // Parse batch result and write out test cases.
337 {
338 ResultToXmlFilesLogHandler handler(shortResults, dstPath);
339 xe::TestLogParser parser(&handler);
340
341 parseBatchResult(parser, batchResultFilename);
342 }
343
344 // Build case hierarchy & short result map.
345 {
346 xe::TestHierarchyBuilder hierarchyBuilder(&testRoot);
347
348 for (vector<xe::TestCaseResultHeader>::const_iterator result = shortResults.begin();
349 result != shortResults.end(); result++)
350 {
351 xe::TestCase *testCase = hierarchyBuilder.createCase(result->casePath.c_str(), result->caseType);
352 resultMap.insert(std::make_pair(testCase, &(*result)));
353 }
354 }
355
356 // Create caselist.
357 {
358 de::FilePath indexPath = de::FilePath::join(dstPath, "caselist.xml");
359 std::ofstream out(indexPath.getPath(), std::ofstream::binary | std::ofstream::trunc);
360 xe::xml::Writer xmlWriter(out);
361
362 XE_CHECK_MSG(out.good(), "Failed to open caselist.xml");
363
364 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
365 << "<?xml-stylesheet href=\"" << CASELIST_STYLESHEET << "\" type=\"text/xsl\"?>\n";
366 writeTestCaseList(testRoot, resultMap, xmlWriter);
367 out << "\n";
368 }
369 }
370
main(int argc,const char * const * argv)371 int main(int argc, const char *const *argv)
372 {
373 try
374 {
375 CommandLine cmdLine;
376 if (!parseCommandLine(cmdLine, argc, argv))
377 return -1;
378
379 if (cmdLine.outputMode == OUTPUTMODE_SINGLE)
380 batchResultToSingleXmlFile(cmdLine.batchResultFile.c_str(), cmdLine.outputPath.c_str());
381 else
382 batchResultToSeparateXmlFiles(cmdLine.batchResultFile.c_str(), cmdLine.outputPath.c_str());
383 }
384 catch (const std::exception &e)
385 {
386 printf("%s\n", e.what());
387 return -1;
388 }
389
390 return 0;
391 }
392