blob: d6d7cd3756edc667887e3feb2f88017e1a7d81d8 [file] [log] [blame]
#ifndef __HAYAI_MAIN
#define __HAYAI_MAIN
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <errno.h>
#include <fstream>
#include <set>
#include <vector>
#include "hayai.hpp"
#if defined(_WIN32)
# define PATH_SEPARATOR '\\'
#else
# define PATH_SEPARATOR '/'
#endif
#define HAYAI_MAIN_FORMAT_FLAG(_desc) \
::hayai::Console::TextGreen << _desc << ::hayai::Console::TextDefault
#define HAYAI_MAIN_FORMAT_ARGUMENT(_desc) \
::hayai::Console::TextYellow << _desc << ::hayai::Console::TextDefault
#define HAYAI_MAIN_FORMAT_ERROR(_desc) \
::hayai::Console::TextRed << "Error:" << \
::hayai::Console::TextDefault << " " << _desc
#define HAYAI_MAIN_USAGE_ERROR(_desc) \
{ \
std::cerr << HAYAI_MAIN_FORMAT_ERROR(_desc) << std::endl \
<< std::endl; \
ShowUsage(argv[0]); \
return EXIT_FAILURE; \
}
namespace hayai
{
/// Execution mode.
enum MainExecutionMode
{
/// Run benchmarks.
MainRunBenchmarks,
/// List benchmarks but do not execute them.
MainListBenchmarks
};
/// File outputter.
class FileOutputter
{
public:
/// File outputter.
/// @param path Output path. Expected to be available during the life
/// time of the outputter.
FileOutputter(const char* path)
: _path(path),
_outputter(NULL)
{
}
virtual ~FileOutputter()
{
if (_outputter)
delete _outputter;
_stream.close();
}
/// Set up.
/// Opens the output file for writing and initializes the outputter.
virtual void SetUp()
{
_stream.open(_path,
std::ios_base::out |
std::ios_base::trunc |
std::ios_base::binary);
if (_stream.bad())
{
std::stringstream error;
error << "failed to open " << _path << " for writing: "
<< strerror(errno);
throw std::runtime_error(error.str());
}
_outputter = CreateOutputter(_stream);
}
/// Outputter.
virtual ::hayai::Outputter& Outputter()
{
if (!_outputter)
throw std::runtime_error("outputter has not been set up");
return *_outputter;
}
protected:
/// Create outputter from output stream.
/// @param stream Output stream for the outputter.
/// @returns the outputter for the given format.
virtual ::hayai::Outputter* CreateOutputter(std::ostream& stream) = 0;
private:
const char* _path;
std::ofstream _stream;
::hayai::Outputter* _outputter;
};
#define FILE_OUTPUTTER_IMPLEMENTATION(_prefix) \
class _prefix ## FileOutputter \
: public FileOutputter \
{ \
public: \
_prefix ## FileOutputter(const char* path) \
: FileOutputter(path) \
{} \
protected: \
virtual ::hayai::Outputter* CreateOutputter(std::ostream& stream) \
{ \
return new ::hayai::_prefix ## Outputter(stream); \
} \
}
FILE_OUTPUTTER_IMPLEMENTATION(Json);
FILE_OUTPUTTER_IMPLEMENTATION(Console);
FILE_OUTPUTTER_IMPLEMENTATION(JUnitXml);
#undef FILE_OUTPUTTER_IMPLEMENTATION
/// Default main executable runner for Hayai.
class MainRunner
{
public:
MainRunner()
: ExecutionMode(MainRunBenchmarks),
ShuffleBenchmarks(false),
StdoutOutputter(NULL)
{
}
~MainRunner()
{
// Clean up the outputters.
for (std::vector<FileOutputter*>::iterator it =
FileOutputters.begin();
it != FileOutputters.end();
++it)
delete *it;
if (StdoutOutputter)
delete StdoutOutputter;
}
/// Execution mode.
MainExecutionMode ExecutionMode;
/// Shuffle benchmarks.
bool ShuffleBenchmarks;
/// File outputters.
///
/// Outputter will be freed by the class on destruction.
std::vector<FileOutputter*> FileOutputters;
/// Standard output outputter.
///
/// Will be freed by the class on destruction.
Outputter* StdoutOutputter;
/// Parse arguments.
/// @param argc Argument count including the executable name.
/// @param argv Arguments.
/// @param residualArgs Pointer to vector to hold residual arguments
/// after parsing. If not NULL, the parser will not fail upon
/// encounting an unknown argument but will instead add it to the list
/// of residual arguments and return a success code. Note: the parser
/// will still fail if an invalid value is provided to a known
/// argument.
/// @returns 0 on success, otherwise the exit status code to be
/// returned from the executable.
int ParseArgs(int argc,
char** argv,
std::vector<char*>* residualArgs = NULL)
{
int argI = 1;
while (argI < argc)
{
char* arg = argv[argI++];
bool argLast = (argI == argc);
std::size_t argLen = strlen(arg);
if (argLen == 0)
continue;
// List flag.
if ((!strcmp(arg, "-l")) || (!strcmp(arg, "--list")))
ExecutionMode = ::hayai::MainListBenchmarks;
// Shuffle flag.
else if ((!strcmp(arg, "-s")) || (!strcmp(arg, "--shuffle")))
ShuffleBenchmarks = true;
// Filter flag.
else if ((!strcmp(arg, "-f")) || (!strcmp(arg, "--filter")))
{
if ((argLast) || (*argv[argI] == 0))
HAYAI_MAIN_USAGE_ERROR(HAYAI_MAIN_FORMAT_FLAG(arg) <<
" requires a pattern to be specified");
char* pattern = argv[argI++];
::hayai::Benchmarker::ApplyPatternFilter(pattern);
}
// Output flag.
else if ((!strcmp(arg, "-o")) || (!strcmp(arg, "--output")))
{
if (argLast)
HAYAI_MAIN_USAGE_ERROR(HAYAI_MAIN_FORMAT_FLAG(arg) <<
" requires a format to be specified");
char* formatSpecifier = argv[argI++];
char* format = formatSpecifier;
char* path = strchr(formatSpecifier, ':');
if (path)
{
*(path++) = 0;
if (!strlen(path))
path = NULL;
}
#define ADD_OUTPUTTER(_prefix) \
{ \
if (path) \
FileOutputters.push_back( \
new ::hayai::_prefix ## FileOutputter(path) \
); \
else \
{ \
if (StdoutOutputter) \
delete StdoutOutputter; \
StdoutOutputter = \
new ::hayai::_prefix ## Outputter(std::cout); \
} \
}
if (!strcmp(format, "console"))
ADD_OUTPUTTER(Console)
else if (!strcmp(format, "json"))
ADD_OUTPUTTER(Json)
else if (!strcmp(format, "junit"))
ADD_OUTPUTTER(JUnitXml)
else
HAYAI_MAIN_USAGE_ERROR("invalid format: " << format);
#undef ADD_OUTPUTTER
}
// Console coloring flag.
else if ((!strcmp(arg, "-c")) || (!strcmp(arg, "--color")))
{
if (argLast)
HAYAI_MAIN_USAGE_ERROR(
HAYAI_MAIN_FORMAT_FLAG(arg) <<
" requires an argument " <<
"of either " << HAYAI_MAIN_FORMAT_FLAG("yes") <<
" or " << HAYAI_MAIN_FORMAT_FLAG("no")
);
char* choice = argv[argI++];
bool enabled;
if ((!strcmp(choice, "yes")) ||
(!strcmp(choice, "true")) ||
(!strcmp(choice, "on")) ||
(!strcmp(choice, "1")))
enabled = true;
else if ((!strcmp(choice, "no")) ||
(!strcmp(choice, "false")) ||
(!strcmp(choice, "off")) ||
(!strcmp(choice, "0")))
enabled = false;
else
HAYAI_MAIN_USAGE_ERROR(
"invalid argument to " <<
HAYAI_MAIN_FORMAT_FLAG(arg) <<
": " << choice
);
::hayai::Console::SetFormattingEnabled(enabled);
}
// Help.
else if ((!strcmp(arg, "-?")) ||
(!strcmp(arg, "-h")) ||
(!strcmp(arg, "--help")))
{
ShowUsage(argv[0]);
return EXIT_FAILURE;
}
else if (residualArgs)
{
residualArgs->push_back(arg);
}
else
{
HAYAI_MAIN_USAGE_ERROR("unknown option: " << arg);
}
}
return EXIT_SUCCESS;
}
/// Run the selected execution mode.
/// @returns the exit status code to be returned from the executable.
int Run()
{
// Execute based on the selected mode.
switch (ExecutionMode)
{
case ::hayai::MainRunBenchmarks:
return RunBenchmarks();
case ::hayai::MainListBenchmarks:
return ListBenchmarks();
default:
std::cerr << HAYAI_MAIN_FORMAT_ERROR(
"invalid execution mode: " << ExecutionMode
) << std::endl;
return EXIT_FAILURE;
}
}
private:
/// Run benchmarks.
/// @returns the exit status code to be returned from the executable.
int RunBenchmarks()
{
// Hook up the outputs.
if (StdoutOutputter)
::hayai::Benchmarker::AddOutputter(*StdoutOutputter);
for (std::vector< ::hayai::FileOutputter*>::iterator it =
FileOutputters.begin();
it < FileOutputters.end();
++it)
{
::hayai::FileOutputter& fileOutputter = **it;
try
{
fileOutputter.SetUp();
}
catch (std::exception& e)
{
std::cerr << HAYAI_MAIN_FORMAT_ERROR(e.what()) << std::endl;
return EXIT_FAILURE;
}
::hayai::Benchmarker::AddOutputter(fileOutputter.Outputter());
}
// Run the benchmarks.
if (ShuffleBenchmarks)
{
std::srand(static_cast<unsigned>(std::time(0)));
::hayai::Benchmarker::ShuffleTests();
}
::hayai::Benchmarker::RunAllTests();
return EXIT_SUCCESS;
}
/// List benchmarks.
/// @returns the exit status code to be returned from the executable.
int ListBenchmarks()
{
// List out the unique benchmark names.
std::vector<const ::hayai::TestDescriptor*> tests =
::hayai::Benchmarker::ListTests();
std::vector<std::string> testNames;
std::set<std::string> uniqueTestNames;
for (std::vector<const ::hayai::TestDescriptor*>::iterator it =
tests.begin();
it < tests.end();
++it)
{
if (uniqueTestNames.find((*it)->CanonicalName) !=
uniqueTestNames.end())
continue;
testNames.push_back((*it)->CanonicalName);
uniqueTestNames.insert((*it)->CanonicalName);
}
// Sort the benchmark names.
std::sort(testNames.begin(), testNames.end());
// Dump the list.
for (std::vector<std::string>::iterator it = testNames.begin();
it < testNames.end();
++it)
std::cout << *it << std::endl;
return EXIT_SUCCESS;
}
/// Show usage.
/// @param execName Executable name.
void ShowUsage(const char* execName)
{
const char* baseName = strrchr(execName, PATH_SEPARATOR);
std::cerr << "Usage: " << (baseName ? baseName + 1 : execName)
<< " " << HAYAI_MAIN_FORMAT_FLAG("[OPTIONS]") << std::endl
<< std::endl
<< " Runs the benchmarks for this project." << std::endl
<< std::endl
<< "Benchmark selection options:" << std::endl
<< " " << HAYAI_MAIN_FORMAT_FLAG("-l") << ", "
<< HAYAI_MAIN_FORMAT_FLAG("--list")
<< std::endl
<< " List the names of all benchmarks instead of "
<< "running them." << std::endl
<< " " << HAYAI_MAIN_FORMAT_FLAG("-f") << ", "
<< HAYAI_MAIN_FORMAT_FLAG("--filter")
<< " <" << HAYAI_MAIN_FORMAT_ARGUMENT("pattern") << ">"
<< std::endl
<< " Run only the tests whose name matches one of the "
<< "positive patterns but" << std::endl
<< " none of the negative patterns. '?' matches any "
<< "single character; '*'" << std::endl
<< " matches any substring; ':' separates two "
<< "patterns."
<< std::endl
<< "Benchmark execution options:" << std::endl
<< " " << HAYAI_MAIN_FORMAT_FLAG("-s") << ", "
<< HAYAI_MAIN_FORMAT_FLAG("--shuffle")
<< std::endl
<< " Randomize benchmark execution order."
<< std::endl
<< std::endl
<< "Benchmark output options:" << std::endl
<< " " << HAYAI_MAIN_FORMAT_FLAG("-o") << ", "
<< HAYAI_MAIN_FORMAT_FLAG("--output")
<< " <" << HAYAI_MAIN_FORMAT_ARGUMENT("format") << ">[:"
<< HAYAI_MAIN_FORMAT_ARGUMENT("<path>") << "]"
<< std::endl
<< " Output results in a specific format. If no "
<< "path is specified, the output" << std::endl
<< " will be presented on stdout. Can be specified "
<< "multiple times to get output" << std::endl
<< " in different formats. The supported formats are:"
<< std::endl
<< std::endl
<< " " << HAYAI_MAIN_FORMAT_ARGUMENT("console")
<< std::endl
<< " Standard console output." << std::endl
<< " " << HAYAI_MAIN_FORMAT_ARGUMENT("json")
<< std::endl
<< " JSON." << std::endl
<< " " << HAYAI_MAIN_FORMAT_ARGUMENT("junit")
<< std::endl
<< " JUnit-compatible XML (very restrictive.)"
<< std::endl
<< std::endl
<< " If multiple output formats are provided without "
<< "a path, only the last" << std::endl
<< " provided format will be output to stdout."
<< std::endl
<< " " << HAYAI_MAIN_FORMAT_FLAG("--c") << ", "
<< HAYAI_MAIN_FORMAT_FLAG("--color") << " ("
<< ::hayai::Console::TextGreen << "yes"
<< ::hayai::Console::TextDefault << "|"
<< ::hayai::Console::TextGreen << "no"
<< ::hayai::Console::TextDefault << ")" << std::endl
<< " Enable colored output when available. Default "
<< ::hayai::Console::TextGreen << "yes"
<< ::hayai::Console::TextDefault << "." << std::endl
<< std::endl
<< "Miscellaneous options:" << std::endl
<< " " << HAYAI_MAIN_FORMAT_FLAG("-?") << ", "
<< HAYAI_MAIN_FORMAT_FLAG("-h") << ", "
<< HAYAI_MAIN_FORMAT_FLAG("--help") << std::endl
<< " Show this help information." << std::endl
<< std::endl
<< "hayai version: " << HAYAI_VERSION << std::endl
<< "Clock implementation: "
<< ::hayai::Clock::Description()
<< std::endl;
}
};
}
#undef HAYAI_MAIN_FORMAT_FLAG
#undef HAYAI_MAIN_FORMAT_ARGUMENT
#undef HAYAI_MAIN_FORMAT_ERROR
#undef HAYAI_MAIN_USAGE_ERROR
#endif