mirror of
https://github.com/pantor/inja.git
synced 2026-02-17 09:03:58 +00:00
531 lines
19 KiB
C++
Executable File
531 lines
19 KiB
C++
Executable File
#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
|