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 Test log compare utility.
22 *//*--------------------------------------------------------------------*/
23
24 #include "xeTestLogParser.hpp"
25 #include "xeTestResultParser.hpp"
26 #include "deFilePath.hpp"
27 #include "deString.h"
28 #include "deThread.hpp"
29 #include "deCommandLine.hpp"
30
31 #include <vector>
32 #include <string>
33 #include <cstdio>
34 #include <cstdlib>
35 #include <fstream>
36 #include <iostream>
37 #include <set>
38 #include <map>
39
40 using std::map;
41 using std::set;
42 using std::string;
43 using std::vector;
44
45 enum OutputMode
46 {
47 OUTPUTMODE_ALL = 0,
48 OUTPUTMODE_DIFF,
49
50 OUTPUTMODE_LAST
51 };
52
53 enum OutputFormat
54 {
55 OUTPUTFORMAT_TEXT = 0,
56 OUTPUTFORMAT_CSV,
57
58 OUTPUTFORMAT_LAST
59 };
60
61 enum OutputValue
62 {
63 OUTPUTVALUE_STATUS_CODE = 0,
64 OUTPUTVALUE_STATUS_DETAILS,
65
66 OUTPUTVALUE_LAST
67 };
68
69 namespace opt
70 {
71
72 DE_DECLARE_COMMAND_LINE_OPT(OutMode, OutputMode);
73 DE_DECLARE_COMMAND_LINE_OPT(OutFormat, OutputFormat);
74 DE_DECLARE_COMMAND_LINE_OPT(OutValue, OutputValue);
75
registerOptions(de::cmdline::Parser & parser)76 static void registerOptions(de::cmdline::Parser &parser)
77 {
78 using de::cmdline::NamedValue;
79 using de::cmdline::Option;
80
81 static const NamedValue<OutputMode> s_outputModes[] = {{"all", OUTPUTMODE_ALL}, {"diff", OUTPUTMODE_DIFF}};
82 static const NamedValue<OutputFormat> s_outputFormats[] = {{"text", OUTPUTFORMAT_TEXT}, {"csv", OUTPUTFORMAT_CSV}};
83 static const NamedValue<OutputValue> s_outputValues[] = {{"code", OUTPUTVALUE_STATUS_CODE},
84 {"details", OUTPUTVALUE_STATUS_DETAILS}};
85
86 parser << Option<OutFormat>("f", "format", "Output format", s_outputFormats, "csv")
87 << Option<OutMode>("m", "mode", "Output mode", s_outputModes, "all")
88 << Option<OutValue>("v", "value", "Value to extract", s_outputValues, "code");
89 }
90
91 } // namespace opt
92
93 struct CommandLine
94 {
CommandLineCommandLine95 CommandLine(void) : outMode(OUTPUTMODE_ALL), outFormat(OUTPUTFORMAT_CSV), outValue(OUTPUTVALUE_STATUS_CODE)
96 {
97 }
98
99 OutputMode outMode;
100 OutputFormat outFormat;
101 OutputValue outValue;
102 vector<string> filenames;
103 };
104
105 struct ShortBatchResult
106 {
107 vector<xe::TestCaseResultHeader> resultHeaders;
108 map<string, int> resultMap;
109 };
110
111 class ShortResultHandler : public xe::TestLogHandler
112 {
113 public:
ShortResultHandler(ShortBatchResult & result)114 ShortResultHandler(ShortBatchResult &result) : m_result(result)
115 {
116 }
117
setSessionInfo(const xe::SessionInfo &)118 void setSessionInfo(const xe::SessionInfo &)
119 {
120 // Ignored.
121 }
122
startTestCaseResult(const char * casePath)123 xe::TestCaseResultPtr startTestCaseResult(const char *casePath)
124 {
125 return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
126 }
127
testCaseResultUpdated(const xe::TestCaseResultPtr &)128 void testCaseResultUpdated(const xe::TestCaseResultPtr &)
129 {
130 // Ignored.
131 }
132
testCaseResultComplete(const xe::TestCaseResultPtr & caseData)133 void testCaseResultComplete(const xe::TestCaseResultPtr &caseData)
134 {
135 xe::TestCaseResultHeader header;
136 int caseNdx = (int)m_result.resultHeaders.size();
137
138 header.casePath = caseData->getTestCasePath();
139 header.caseType = xe::TESTCASETYPE_SELF_VALIDATE;
140 header.statusCode = caseData->getStatusCode();
141 header.statusDetails = caseData->getStatusDetails();
142
143 if (header.statusCode == xe::TESTSTATUSCODE_LAST)
144 {
145 xe::TestCaseResult fullResult;
146
147 xe::parseTestCaseResultFromData(&m_testResultParser, &fullResult, *caseData.get());
148
149 header = xe::TestCaseResultHeader(fullResult);
150 }
151
152 // Insert into result list & map.
153 m_result.resultHeaders.push_back(header);
154 m_result.resultMap[header.casePath] = caseNdx;
155 }
156
157 private:
158 ShortBatchResult &m_result;
159 xe::TestResultParser m_testResultParser;
160 };
161
readLogFile(ShortBatchResult & batchResult,const char * filename)162 static void readLogFile(ShortBatchResult &batchResult, const char *filename)
163 {
164 std::ifstream in(filename, std::ifstream::binary | std::ifstream::in);
165 ShortResultHandler resultHandler(batchResult);
166 xe::TestLogParser parser(&resultHandler);
167 uint8_t buf[1024];
168 int numRead = 0;
169
170 for (;;)
171 {
172 in.read((char *)&buf[0], DE_LENGTH_OF_ARRAY(buf));
173 numRead = (int)in.gcount();
174
175 if (numRead <= 0)
176 break;
177
178 parser.parse(&buf[0], numRead);
179 }
180
181 in.close();
182 }
183
184 class LogFileReader : public de::Thread
185 {
186 public:
LogFileReader(ShortBatchResult & batchResult,const char * filename)187 LogFileReader(ShortBatchResult &batchResult, const char *filename)
188 : m_batchResult(batchResult)
189 , m_filename(filename)
190 {
191 }
192
run(void)193 void run(void)
194 {
195 readLogFile(m_batchResult, m_filename.c_str());
196 }
197
198 private:
199 ShortBatchResult &m_batchResult;
200 std::string m_filename;
201 };
202
computeCaseList(vector<string> & cases,const vector<ShortBatchResult> & batchResults)203 static void computeCaseList(vector<string> &cases, const vector<ShortBatchResult> &batchResults)
204 {
205 // \todo [2012-07-10 pyry] Do proper case ordering (eg. handle missing cases nicely).
206 set<string> addedCases;
207
208 for (vector<ShortBatchResult>::const_iterator batchIter = batchResults.begin(); batchIter != batchResults.end();
209 batchIter++)
210 {
211 for (vector<xe::TestCaseResultHeader>::const_iterator caseIter = batchIter->resultHeaders.begin();
212 caseIter != batchIter->resultHeaders.end(); caseIter++)
213 {
214 if (addedCases.find(caseIter->casePath) == addedCases.end())
215 {
216 cases.push_back(caseIter->casePath);
217 addedCases.insert(caseIter->casePath);
218 }
219 }
220 }
221 }
222
getTestResultHeaders(vector<xe::TestCaseResultHeader> & headers,const vector<ShortBatchResult> & batchResults,const char * casePath)223 static void getTestResultHeaders(vector<xe::TestCaseResultHeader> &headers,
224 const vector<ShortBatchResult> &batchResults, const char *casePath)
225 {
226 headers.resize(batchResults.size());
227
228 for (int ndx = 0; ndx < (int)batchResults.size(); ndx++)
229 {
230 const ShortBatchResult &batchResult = batchResults[ndx];
231 map<string, int>::const_iterator resultPos = batchResult.resultMap.find(casePath);
232
233 if (resultPos != batchResult.resultMap.end())
234 headers[ndx] = batchResult.resultHeaders[resultPos->second];
235 else
236 {
237 headers[ndx].casePath = casePath;
238 headers[ndx].caseType = xe::TESTCASETYPE_SELF_VALIDATE;
239 headers[ndx].statusCode = xe::TESTSTATUSCODE_LAST;
240 }
241 }
242 }
243
getStatusCodeName(xe::TestStatusCode code)244 static const char *getStatusCodeName(xe::TestStatusCode code)
245 {
246 if (code == xe::TESTSTATUSCODE_LAST)
247 return "Missing";
248 else
249 return xe::getTestStatusCodeName(code);
250 }
251
runCompare(const CommandLine & cmdLine,std::ostream & dst)252 static bool runCompare(const CommandLine &cmdLine, std::ostream &dst)
253 {
254 vector<ShortBatchResult> results;
255 vector<string> batchNames;
256 bool compareOk = true;
257
258 XE_CHECK(!cmdLine.filenames.empty());
259
260 try
261 {
262 // Read in batch results
263 results.resize(cmdLine.filenames.size());
264 {
265 std::vector<de::SharedPtr<LogFileReader>> readers;
266
267 for (int ndx = 0; ndx < (int)cmdLine.filenames.size(); ndx++)
268 {
269 readers.push_back(
270 de::SharedPtr<LogFileReader>(new LogFileReader(results[ndx], cmdLine.filenames[ndx].c_str())));
271 readers.back()->start();
272 }
273
274 for (int ndx = 0; ndx < (int)cmdLine.filenames.size(); ndx++)
275 {
276 readers[ndx]->join();
277
278 // Use file name as batch name.
279 batchNames.push_back(de::FilePath(cmdLine.filenames[ndx].c_str()).getBaseName());
280 }
281 }
282
283 // Compute unified case list.
284 vector<string> caseList;
285 computeCaseList(caseList, results);
286
287 // Stats.
288 int numCases = (int)caseList.size();
289 int numEqual = 0;
290
291 if (cmdLine.outFormat == OUTPUTFORMAT_CSV)
292 {
293 dst << "TestCasePath";
294 for (vector<string>::const_iterator nameIter = batchNames.begin(); nameIter != batchNames.end(); nameIter++)
295 dst << "," << *nameIter;
296 dst << "\n";
297 }
298
299 // Compare cases.
300 for (vector<string>::const_iterator caseIter = caseList.begin(); caseIter != caseList.end(); caseIter++)
301 {
302 const string &caseName = *caseIter;
303 vector<xe::TestCaseResultHeader> headers;
304 bool allEqual = true;
305
306 getTestResultHeaders(headers, results, caseName.c_str());
307
308 for (vector<xe::TestCaseResultHeader>::const_iterator iter = headers.begin() + 1; iter != headers.end();
309 iter++)
310 {
311 if (iter->statusCode != headers[0].statusCode)
312 {
313 allEqual = false;
314 break;
315 }
316 }
317
318 if (allEqual)
319 numEqual += 1;
320
321 if (cmdLine.outMode == OUTPUTMODE_ALL || !allEqual)
322 {
323 if (cmdLine.outFormat == OUTPUTFORMAT_TEXT)
324 {
325 dst << caseName << "\n";
326 for (int ndx = 0; ndx < (int)headers.size(); ndx++)
327 dst << " " << batchNames[ndx] << ": " << getStatusCodeName(headers[ndx].statusCode) << " ("
328 << headers[ndx].statusDetails << ")\n";
329 dst << "\n";
330 }
331 else if (cmdLine.outFormat == OUTPUTFORMAT_CSV)
332 {
333 dst << caseName;
334 for (vector<xe::TestCaseResultHeader>::const_iterator iter = headers.begin(); iter != headers.end();
335 iter++)
336 dst << ","
337 << (cmdLine.outValue == OUTPUTVALUE_STATUS_CODE ? getStatusCodeName(iter->statusCode) :
338 iter->statusDetails.c_str());
339 dst << "\n";
340 }
341 }
342 }
343
344 compareOk = numEqual == numCases;
345
346 if (cmdLine.outFormat == OUTPUTFORMAT_TEXT)
347 {
348 dst << " " << numEqual << " / " << numCases << " test case results match.\n";
349 dst << " Comparison " << (compareOk ? "passed" : "FAILED") << "!\n";
350 }
351 }
352 catch (const std::exception &e)
353 {
354 printf("%s\n", e.what());
355 compareOk = false;
356 }
357
358 return compareOk;
359 }
360
parseCommandLine(CommandLine & cmdLine,int argc,const char * const * argv)361 static bool parseCommandLine(CommandLine &cmdLine, int argc, const char *const *argv)
362 {
363 de::cmdline::Parser parser;
364 de::cmdline::CommandLine opts;
365
366 XE_CHECK(argc >= 1);
367
368 opt::registerOptions(parser);
369
370 if (!parser.parse(argc - 1, &argv[1], &opts, std::cerr) || opts.getArgs().empty())
371 {
372 std::cout << argv[0] << ": [options] [filenames]\n";
373 parser.help(std::cout);
374 return false;
375 }
376
377 cmdLine.outFormat = opts.getOption<opt::OutFormat>();
378 cmdLine.outMode = opts.getOption<opt::OutMode>();
379 cmdLine.outValue = opts.getOption<opt::OutValue>();
380 cmdLine.filenames = opts.getArgs();
381
382 return true;
383 }
384
main(int argc,const char * const * argv)385 int main(int argc, const char *const *argv)
386 {
387 CommandLine cmdLine;
388
389 if (!parseCommandLine(cmdLine, argc, argv))
390 return -1;
391
392 try
393 {
394 bool compareOk = runCompare(cmdLine, std::cout);
395 return compareOk ? 0 : -1;
396 }
397 catch (const std::exception &e)
398 {
399 printf("FATAL ERROR: %s\n", e.what());
400 return -1;
401 }
402 }
403