blob: f5f150f51ff986ccf33f5467616ff6d13fd1e13d [file] [log] [blame]
/*-------------------------------------------------------------------------
* drawElements Quality Program Test Executor
* ------------------------------------------
*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*//*!
* \file
* \brief Test log compare utility.
*//*--------------------------------------------------------------------*/
#include "xeTestLogParser.hpp"
#include "xeTestResultParser.hpp"
#include "deFilePath.hpp"
#include "deString.h"
#include "deThread.hpp"
#include "deCommandLine.hpp"
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <set>
#include <map>
using std::map;
using std::set;
using std::string;
using std::vector;
enum OutputMode
{
OUTPUTMODE_ALL = 0,
OUTPUTMODE_DIFF,
OUTPUTMODE_LAST
};
enum OutputFormat
{
OUTPUTFORMAT_TEXT = 0,
OUTPUTFORMAT_CSV,
OUTPUTFORMAT_LAST
};
enum OutputValue
{
OUTPUTVALUE_STATUS_CODE = 0,
OUTPUTVALUE_STATUS_DETAILS,
OUTPUTVALUE_LAST
};
namespace opt
{
DE_DECLARE_COMMAND_LINE_OPT(OutMode, OutputMode);
DE_DECLARE_COMMAND_LINE_OPT(OutFormat, OutputFormat);
DE_DECLARE_COMMAND_LINE_OPT(OutValue, OutputValue);
static void registerOptions(de::cmdline::Parser &parser)
{
using de::cmdline::NamedValue;
using de::cmdline::Option;
static const NamedValue<OutputMode> s_outputModes[] = {{"all", OUTPUTMODE_ALL}, {"diff", OUTPUTMODE_DIFF}};
static const NamedValue<OutputFormat> s_outputFormats[] = {{"text", OUTPUTFORMAT_TEXT}, {"csv", OUTPUTFORMAT_CSV}};
static const NamedValue<OutputValue> s_outputValues[] = {{"code", OUTPUTVALUE_STATUS_CODE},
{"details", OUTPUTVALUE_STATUS_DETAILS}};
parser << Option<OutFormat>("f", "format", "Output format", s_outputFormats, "csv")
<< Option<OutMode>("m", "mode", "Output mode", s_outputModes, "all")
<< Option<OutValue>("v", "value", "Value to extract", s_outputValues, "code");
}
} // namespace opt
struct CommandLine
{
CommandLine(void) : outMode(OUTPUTMODE_ALL), outFormat(OUTPUTFORMAT_CSV), outValue(OUTPUTVALUE_STATUS_CODE)
{
}
OutputMode outMode;
OutputFormat outFormat;
OutputValue outValue;
vector<string> filenames;
};
struct ShortBatchResult
{
vector<xe::TestCaseResultHeader> resultHeaders;
map<string, int> resultMap;
};
class ShortResultHandler : public xe::TestLogHandler
{
public:
ShortResultHandler(ShortBatchResult &result) : m_result(result)
{
}
void setSessionInfo(const xe::SessionInfo &)
{
// Ignored.
}
xe::TestCaseResultPtr startTestCaseResult(const char *casePath)
{
return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
}
void testCaseResultUpdated(const xe::TestCaseResultPtr &)
{
// Ignored.
}
void testCaseResultComplete(const xe::TestCaseResultPtr &caseData)
{
xe::TestCaseResultHeader header;
int caseNdx = (int)m_result.resultHeaders.size();
header.casePath = caseData->getTestCasePath();
header.caseType = xe::TESTCASETYPE_SELF_VALIDATE;
header.statusCode = caseData->getStatusCode();
header.statusDetails = caseData->getStatusDetails();
if (header.statusCode == xe::TESTSTATUSCODE_LAST)
{
xe::TestCaseResult fullResult;
xe::parseTestCaseResultFromData(&m_testResultParser, &fullResult, *caseData.get());
header = xe::TestCaseResultHeader(fullResult);
}
// Insert into result list & map.
m_result.resultHeaders.push_back(header);
m_result.resultMap[header.casePath] = caseNdx;
}
private:
ShortBatchResult &m_result;
xe::TestResultParser m_testResultParser;
};
static void readLogFile(ShortBatchResult &batchResult, const char *filename)
{
std::ifstream in(filename, std::ifstream::binary | std::ifstream::in);
ShortResultHandler resultHandler(batchResult);
xe::TestLogParser parser(&resultHandler);
uint8_t buf[1024];
int numRead = 0;
for (;;)
{
in.read((char *)&buf[0], DE_LENGTH_OF_ARRAY(buf));
numRead = (int)in.gcount();
if (numRead <= 0)
break;
parser.parse(&buf[0], numRead);
}
in.close();
}
class LogFileReader : public de::Thread
{
public:
LogFileReader(ShortBatchResult &batchResult, const char *filename)
: m_batchResult(batchResult)
, m_filename(filename)
{
}
void run(void)
{
readLogFile(m_batchResult, m_filename.c_str());
}
private:
ShortBatchResult &m_batchResult;
std::string m_filename;
};
static void computeCaseList(vector<string> &cases, const vector<ShortBatchResult> &batchResults)
{
// \todo [2012-07-10 pyry] Do proper case ordering (eg. handle missing cases nicely).
set<string> addedCases;
for (vector<ShortBatchResult>::const_iterator batchIter = batchResults.begin(); batchIter != batchResults.end();
batchIter++)
{
for (vector<xe::TestCaseResultHeader>::const_iterator caseIter = batchIter->resultHeaders.begin();
caseIter != batchIter->resultHeaders.end(); caseIter++)
{
if (addedCases.find(caseIter->casePath) == addedCases.end())
{
cases.push_back(caseIter->casePath);
addedCases.insert(caseIter->casePath);
}
}
}
}
static void getTestResultHeaders(vector<xe::TestCaseResultHeader> &headers,
const vector<ShortBatchResult> &batchResults, const char *casePath)
{
headers.resize(batchResults.size());
for (int ndx = 0; ndx < (int)batchResults.size(); ndx++)
{
const ShortBatchResult &batchResult = batchResults[ndx];
map<string, int>::const_iterator resultPos = batchResult.resultMap.find(casePath);
if (resultPos != batchResult.resultMap.end())
headers[ndx] = batchResult.resultHeaders[resultPos->second];
else
{
headers[ndx].casePath = casePath;
headers[ndx].caseType = xe::TESTCASETYPE_SELF_VALIDATE;
headers[ndx].statusCode = xe::TESTSTATUSCODE_LAST;
}
}
}
static const char *getStatusCodeName(xe::TestStatusCode code)
{
if (code == xe::TESTSTATUSCODE_LAST)
return "Missing";
else
return xe::getTestStatusCodeName(code);
}
static bool runCompare(const CommandLine &cmdLine, std::ostream &dst)
{
vector<ShortBatchResult> results;
vector<string> batchNames;
bool compareOk = true;
XE_CHECK(!cmdLine.filenames.empty());
try
{
// Read in batch results
results.resize(cmdLine.filenames.size());
{
std::vector<de::SharedPtr<LogFileReader>> readers;
for (int ndx = 0; ndx < (int)cmdLine.filenames.size(); ndx++)
{
readers.push_back(
de::SharedPtr<LogFileReader>(new LogFileReader(results[ndx], cmdLine.filenames[ndx].c_str())));
readers.back()->start();
}
for (int ndx = 0; ndx < (int)cmdLine.filenames.size(); ndx++)
{
readers[ndx]->join();
// Use file name as batch name.
batchNames.push_back(de::FilePath(cmdLine.filenames[ndx].c_str()).getBaseName());
}
}
// Compute unified case list.
vector<string> caseList;
computeCaseList(caseList, results);
// Stats.
int numCases = (int)caseList.size();
int numEqual = 0;
if (cmdLine.outFormat == OUTPUTFORMAT_CSV)
{
dst << "TestCasePath";
for (vector<string>::const_iterator nameIter = batchNames.begin(); nameIter != batchNames.end(); nameIter++)
dst << "," << *nameIter;
dst << "\n";
}
// Compare cases.
for (vector<string>::const_iterator caseIter = caseList.begin(); caseIter != caseList.end(); caseIter++)
{
const string &caseName = *caseIter;
vector<xe::TestCaseResultHeader> headers;
bool allEqual = true;
getTestResultHeaders(headers, results, caseName.c_str());
for (vector<xe::TestCaseResultHeader>::const_iterator iter = headers.begin() + 1; iter != headers.end();
iter++)
{
if (iter->statusCode != headers[0].statusCode)
{
allEqual = false;
break;
}
}
if (allEqual)
numEqual += 1;
if (cmdLine.outMode == OUTPUTMODE_ALL || !allEqual)
{
if (cmdLine.outFormat == OUTPUTFORMAT_TEXT)
{
dst << caseName << "\n";
for (int ndx = 0; ndx < (int)headers.size(); ndx++)
dst << " " << batchNames[ndx] << ": " << getStatusCodeName(headers[ndx].statusCode) << " ("
<< headers[ndx].statusDetails << ")\n";
dst << "\n";
}
else if (cmdLine.outFormat == OUTPUTFORMAT_CSV)
{
dst << caseName;
for (vector<xe::TestCaseResultHeader>::const_iterator iter = headers.begin(); iter != headers.end();
iter++)
dst << ","
<< (cmdLine.outValue == OUTPUTVALUE_STATUS_CODE ? getStatusCodeName(iter->statusCode) :
iter->statusDetails.c_str());
dst << "\n";
}
}
}
compareOk = numEqual == numCases;
if (cmdLine.outFormat == OUTPUTFORMAT_TEXT)
{
dst << " " << numEqual << " / " << numCases << " test case results match.\n";
dst << " Comparison " << (compareOk ? "passed" : "FAILED") << "!\n";
}
}
catch (const std::exception &e)
{
printf("%s\n", e.what());
compareOk = false;
}
return compareOk;
}
static bool parseCommandLine(CommandLine &cmdLine, int argc, const char *const *argv)
{
de::cmdline::Parser parser;
de::cmdline::CommandLine opts;
XE_CHECK(argc >= 1);
opt::registerOptions(parser);
if (!parser.parse(argc - 1, &argv[1], &opts, std::cerr) || opts.getArgs().empty())
{
std::cout << argv[0] << ": [options] [filenames]\n";
parser.help(std::cout);
return false;
}
cmdLine.outFormat = opts.getOption<opt::OutFormat>();
cmdLine.outMode = opts.getOption<opt::OutMode>();
cmdLine.outValue = opts.getOption<opt::OutValue>();
cmdLine.filenames = opts.getArgs();
return true;
}
int main(int argc, const char *const *argv)
{
CommandLine cmdLine;
if (!parseCommandLine(cmdLine, argc, argv))
return -1;
try
{
bool compareOk = runCompare(cmdLine, std::cout);
return compareOk ? 0 : -1;
}
catch (const std::exception &e)
{
printf("FATAL ERROR: %s\n", e.what());
return -1;
}
}