| #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 |