From 915b8ef3235cc7cdd44918131c703db340e9d9dc Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Sat, 31 Oct 2015 21:27:49 -0600 Subject: [PATCH 01/28] Fixed dir listing utility function --- src/util.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/util.cpp b/src/util.cpp index 28e5fd3..e93f5d8 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -56,18 +56,30 @@ namespace Util if(dirptr == nullptr) throw CException("opendir"); + std::string filepath; while((entry = readdir(dirptr))) { - lstat(entry->d_name, &st); + filepath = path; + filepath += '/'; + filepath += entry->d_name; + //printf("FD! - %s\n", filepath.c_str()); + if(strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) + continue; + if(lstat(filepath.c_str(), &st) != 0) + perror("lstat"); if(ft == DIRECTORY && S_ISDIR(st.st_mode)) { + //printf("DIR! - %s, %d\n", entry->d_name, S_ISDIR(st.st_mode)); std::string str(entry->d_name); ret.push_back(str); } else if(ft == FILETYPE && !S_ISDIR(st.st_mode)) { + //printf("FILE! - %s\n", entry->d_name); std::string str(entry->d_name); ret.push_back(str); } else if(ft == ALL) { + //printf("ALL! - %s\n", entry->d_name); std::string str(entry->d_name); ret.push_back(str); } From 255ec90ce1120ebab683dacca5b97a176bbd8960 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Sat, 31 Oct 2015 21:52:55 -0600 Subject: [PATCH 02/28] Switched from yaml config to libconfig Switched renzoku from using yaml based configs to libconfig instead. libconfig is much more in line with the type of config that I was hoping to have renzoku use in the first place. Now that I have finally decided on an official config format, I can finally start actually recommending it's use to others since renzoku should be able to build on other OSs. It used to be that only Linux Distros with the latest and greatest packages could do so. This fixes #2 and gets a closer to completing the tasks needed for #1. Signed-off-by: Colton Wolkins (Ogre) --- Makefile.am | 2 +- configure.ac | 56 ++++- renzoku.conf | 34 +-- src/Makefile.am | 2 +- src/config.cpp | 114 +++++----- src/config.hpp | 8 +- src/config/InputFile.tar.gz | Bin 0 -> 5926 bytes src/config/Makefile.am | 21 ++ src/config/config.cpp | 1 + src/config/config.hpp | 10 + src/config/librenconf.a | Bin 0 -> 1388 bytes src/config/main.cpp | 47 ++++ src/config/renconf | Bin 0 -> 39296 bytes src/config/renzoku.conf | 20 ++ src/config/test.conf | 7 + src/main.cpp | 13 +- src/watcher.cpp | 8 +- tests/catch.hpp | 422 +++++++++++++++++------------------- 18 files changed, 460 insertions(+), 305 deletions(-) create mode 100644 src/config/InputFile.tar.gz create mode 100644 src/config/Makefile.am create mode 100644 src/config/config.cpp create mode 100644 src/config/config.hpp create mode 100644 src/config/librenconf.a create mode 100644 src/config/main.cpp create mode 100755 src/config/renconf create mode 100644 src/config/renzoku.conf create mode 100644 src/config/test.conf diff --git a/Makefile.am b/Makefile.am index d3b0f59..5a3918a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,2 +1,2 @@ AUTOMAKE_OPTIONS = foreign -SUBDIRS = src doc examples man tests +SUBDIRS = src src/config doc examples man tests diff --git a/configure.ac b/configure.ac index f7c854c..7942383 100644 --- a/configure.ac +++ b/configure.ac @@ -16,9 +16,59 @@ AC_LANG(C++) # Checks for header files. AC_CHECK_HEADERS([stdint.h stdlib.h sys/time.h unistd.h]) # http://lists.gnu.org/archive/html/autoconf/2003-11/msg00155.html -#AC_LANG_PUSH([C++]) +AC_LANG_PUSH([C++]) AC_CHECK_HEADERS([yaml-cpp/yaml.h boost/shared_ptr.hpp]) -#AC_LANG_POP([C++]) + +# debug? daemon mode? +AC_ARG_ENABLE(recursion, + [AS_HELP_STRING([--disable-recursion], + [Disable recursive directory searching])], + [feature_recursion=no], #replace with ac_define + [feature_recursion=yes]) +AC_ARG_ENABLE(log-level, + [AS_HELP_STRING([--enable-log-level[=level]], + [Specify the logging level:\ + debug: The most verbose level\ + info: A bit less verbose\ + warn: Warnings only\ + error: Errors only])], + [ if test "x$enableval" = "xdebug" ; then + AC_DEFINE([LOGLEVEL], [debug], + [Logging set to debug level]) + elif test "x$enableval" = "xinfo" ; then + AC_DEFINE([LOGLEVEL], [info], + [Logging set to info level]) + elif test "x$enableval" = "xwarn" ; then + AC_DEFINE([LOGLEVEL], [warn], + [Logging set to warning level]) + elif test "x$enableval" = "xerror" ; then + AC_DEFINE([LOGLEVEL], [error], + [Logging set to error level]) + else + AC_MSG_FAILURE( + [Invalid log level] + ) + fi + ]) + +AC_ARG_WITH([yaml], + [AS_HELP_STRING([--without-yaml], + [Disable yaml config support])], + [], + [with_yaml=yes]) +LIBYAMLCPP= +AS_IF([test "x$with_yaml" != xno], + [AC_CHECK_LIB([yaml-cpp], [main], + [AC_SUBST([LIBYAMLCPP], ["-lyaml-cpp"]) + AC_DEFINE([HAVE_YAML], [1], + [Define if you have libyaml-cpp]) + ], + [AC_MSG_FAILURE( + [yaml-cpp test failed (--without-yaml to disable)] + )], + [] + )]) +AC_LANG_POP([C++]) # Checks for typedefs, structures, and compiler characteristics. AC_CHECK_HEADER_STDBOOL @@ -39,5 +89,5 @@ AC_PROG_RANLIB #AC_CONFIG_FILES([Makefile]) AC_OUTPUT(Makefile src/Makefile doc/Makefile examples/Makefile man/Makefile - tests/Makefile) + tests/Makefile src/config/Makefile) AC_OUTPUT diff --git a/renzoku.conf b/renzoku.conf index 2f06810..ba40b8b 100644 --- a/renzoku.conf +++ b/renzoku.conf @@ -1,14 +1,20 @@ -watch: - Filter: ['*.cpp', '*.hpp', '*.c', '*.h', 'include/*.hxx'] - Include: Makefile - Exclude: src/Config.cpp -compile: - Command: make - Enabled: yes -Test: - command: make test - IgnoreStatus: [2,3,4] - Enabled: yes -program: - command: /usr/bin/echo Hello World - Enabled: yes +watch = { + Filter = ["*.cpp", "*.hpp", "*.c", "*.h", "include/*.hxx", "*.conf"] + Include = "Makefile" + Exclude = "src/Config.cpp" +} +compile = { + Command = "make" + Enabled = true +} +Test = { + command = "make check" + IgnoreStatus = [2,3,4] + Enabled = true +} +program = { + command = "./src/config/renconf" +# command: /usr/bin/echo Hello World + Enabled = true +} + diff --git a/src/Makefile.am b/src/Makefile.am index 16796e2..ada15d6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ #CFLAGS = --pedantic -Wall -std=c99 -O2 #CXXFLAGS = -Wall -g -Isrc/ -static -lyaml-cpp -std=c++11 AM_CXXFLAGS = --pedantic -Wall -O2 -g -std=c++11 -AM_LDFLAGS = -Bstatic -lyaml-cpp -Bdynamic +AM_LDFLAGS = -Bstatic -lconfig++ -Bdynamic # this lists the binaries to produce, the (non-PHONY, binary) targets in # the previous manual Makefile diff --git a/src/config.cpp b/src/config.cpp index dc4626b..c46916d 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -98,25 +98,36 @@ void Config::findConfig(std::ifstream& is) void Config::parseConfig(std::istream& file) { - config = YAML::Load(file); - assert(config); - assert(config.Type() == YAML::NodeType::Map); - assert(config.IsMap()); - assert(config["watch"]); - assert(config["program"]); - // Compiler - // Tester - for (YAML::const_iterator it=config.begin();it!=config.end();++it) { - assert(it->second.Type() == YAML::NodeType::Map); + int err; + size_t file_length; + char* file_contents = NULL; + file.seekg(0, file.end); + file_length = file.tellg(); + file.seekg(0, file.beg); + file_contents = new char[file_length]; + if(file_contents == NULL) + throw std::runtime_error("Failed to read config [Allocation error]"); + file.read(file_contents, file_length); + err = ( (size_t)file.gcount() != file_length ); + if(err) + throw std::runtime_error("Failed to read config [Failed to read all " + "characters]"); + config.readString(file_contents); + libconfig::Setting &root = config.getRoot(); + + assert(config.exists("watch")); + assert(config.exists("program")); + for (auto it = root.begin(); it != root.end(); ++it) { + assert(it->isGroup()); //Logger::getLogger()->LOG(logger, DEBUG, "%s - %s\n", it->first.as().c_str(), it->second.as().c_str()); - std::string key = Util::lowercase_r(it->first.as()); + std::string key = Util::lowercase_r(it->getName()); if(key == "watch") { - this->parseWatcher(it->second); + this->parseWatcher(*it); } else if(key == "program") { - this->parseCommand(it->second, mProgram); + this->parseCommand(*it, mProgram); if(procman->getProgram()) { throw std::runtime_error("Program already set!"); } @@ -125,13 +136,13 @@ void Config::parseConfig(std::istream& file) } else if(key == "test") { - this->parseCommand(it->second, mTest); + this->parseCommand(*it, mTest); if(mTest.command && mTest.enabled) procman->addProcess(new Process(logger, mTest.command, true)); } else if(key == "compile") { - this->parseCommand(it->second, mCompile); + this->parseCommand(*it, mCompile); if(procman->getBuildStep()) { throw std::runtime_error("BuildStage already set!"); } @@ -146,22 +157,23 @@ void Config::parseConfig(std::istream& file) } } -void Config::parseCommand(const YAML::Node& node, iCommandConfig& config) +void Config::parseCommand(const libconfig::Setting& node, iCommandConfig& config) { //std::cerr << "Under active development!" << '\n'; //return; - if(node.size() >= 1 && node.IsMap()) - for (YAML::const_iterator iter=node.begin();iter!=node.end();++iter) { - std::string key = iter->first.as(); - YAML::Node value = iter->second; + printf("%d %d %s\n", node.getLength(), node.isArray(), node.getName()); + if(node.getLength() > 0 && node.isGroup()) + for (auto iter = node.begin(); iter != node.end(); ++iter) { + std::string key = iter->getName(); + const libconfig::Setting &value = *iter; Util::lowercase(key); //std::cout << key << std::endl; if(key == "command") { - if(value.IsScalar()) + if(value.isScalar()) { - LOG(logger, DEBUG, "Proc: %s", value.as().c_str()); - config.command = Util::parseCommand(value.as()); + LOG(logger, DEBUG, "Proc: %s", (const char*)value); + config.command = Util::parseCommand(value); #if 0 // TODO: We need to take quotes from the user and send them all as // a single argument instead of multiple arguments. @@ -192,7 +204,7 @@ void Config::parseCommand(const YAML::Node& node, iCommandConfig& config) } } else if( key == "enabled" ) { - config.enabled = value.as(); + config.enabled = value; } } //for(size_t i=0;config.command[i]; i++) @@ -206,24 +218,24 @@ void Config::restartProcesses() procman->restartAll(); } -void Config::parseWatcher(const YAML::Node& node) +void Config::parseWatcher(const libconfig::Setting& node) { size_t asterisk_count; - if(node.size() >= 1 && node.IsMap()) - for (YAML::const_iterator iter=node.begin();iter!=node.end();++iter) { - std::string key = iter->first.as(); - YAML::Node value = iter->second; + if(node.getLength() > 0 && node.isGroup()) + for (auto iter = node.begin(); iter != node.end(); ++iter) { + std::string key = iter->getName(); + const libconfig::Setting &value = *iter; Util::lowercase(key); if(key == "filter") { - if(!value.IsSequence()) - std::cerr << "ERROR!\n"; - for(YAML::const_iterator filter_iter=value.begin(); - filter_iter!=value.end(); + if(!value.isArray()) + std::cerr << "ERROR!\n" << key; + for(auto filter_iter=value.begin(); + filter_iter != value.end(); ++filter_iter) { asterisk_count = 0; - std::string val = filter_iter->as(); + std::string val = *filter_iter; for(size_t i = 0; i < val.length(); i++) if(val[i] == '*') asterisk_count++; @@ -235,46 +247,46 @@ void Config::parseWatcher(const YAML::Node& node) } else if(key == "include") { - if(!value.IsSequence() && !value.IsScalar()) + if(!value.isArray() && !value.isScalar()) std::cerr << "ERROR!\n"; - if(value.IsSequence()) + if(value.isArray()) { - for(YAML::const_iterator filter_iter=value.begin(); + for(auto filter_iter=value.begin(); filter_iter!=value.end(); ++filter_iter) { - LOG(logger, DEBUG, "Include: %s", filter_iter->as().c_str()); - mWatch.include.push_back(filter_iter->as()); + LOG(logger, DEBUG, "Include: %s", (const char*)*filter_iter); + mWatch.include.push_back(*filter_iter); } } - else if(value.IsScalar()) + else if(value.isScalar()) { - LOG(logger, DEBUG, "Include: %s", value.as().c_str()); - mWatch.include.push_back(value.as()); + LOG(logger, DEBUG, "Include: %s", (const char*)value); + mWatch.include.push_back(value); } } else if(key == "exclude") { - if(!value.IsSequence() && !value.IsScalar()) + if(!value.isArray() && !value.isScalar()) std::cerr << "ERROR!\n"; - if(value.IsSequence()) + if(value.isArray()) { - for(YAML::const_iterator filter_iter=value.begin(); + for(auto filter_iter=value.begin(); filter_iter!=value.end(); ++filter_iter) { - LOG(logger, DEBUG, "Exclude: %s", filter_iter->as().c_str()); - mWatch.exclude.push_back(filter_iter->as()); + LOG(logger, DEBUG, "Exclude: %s", (const char*)*filter_iter); + mWatch.exclude.push_back(*filter_iter); } } - else if(value.IsScalar()) + else if(value.isScalar()) { - LOG(logger, DEBUG, "Exclude: %s", value.as().c_str()); - mWatch.exclude.push_back(value.as()); + LOG(logger, DEBUG, "Exclude: %s", (const char*)value); + mWatch.exclude.push_back(value); } } else - LOG(logger, DEBUG, "Value: %s\n", value.as().c_str()); + LOG(logger, DEBUG, "Value: %s\n", (const char*)value); } } diff --git a/src/config.hpp b/src/config.hpp index f42502f..ddaa82d 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include "process.hpp" #include "log.hpp" #include "procman.hpp" @@ -74,14 +74,14 @@ class Config { * #vim: set tw=8 sw=8 ai noet: */ private: - YAML::Node config; + libconfig::Config config; #define CONFIG_SECTION(X) X##Config m##X; #include "config_sections.hpp" #undef CONFIG_SECTION ProcessManager* procman; iLogger* logger; - void parseWatcher(const YAML::Node&); - void parseCommand(const YAML::Node&, iCommandConfig&); + void parseWatcher(const libconfig::Setting&); + void parseCommand(const libconfig::Setting&, iCommandConfig&); void parseConfig(std::istream&); void findConfig(std::ifstream&); public: diff --git a/src/config/InputFile.tar.gz b/src/config/InputFile.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..413b09a8f7a2c583f0ce368cdbed5b0fdb697bbe GIT binary patch literal 5926 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 O>tB#X*JPejdjCJT9eXbT literal 0 HcmV?d00001 diff --git a/src/config/main.cpp b/src/config/main.cpp new file mode 100644 index 0000000..ecf6b76 --- /dev/null +++ b/src/config/main.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +using namespace libconfig; + +int main(int argc, char *argv[]) +{ + printf("test\n"); + /*std::ifstream infile("test.conf"); + // http://stackoverflow.com/questions/7868936/read-file-line-by-line + std::string line; + while(std::getline(infile, line)) + printf("%s\n", line.c_str());*/ + // http://stackoverflow.com/questions/4823177/reading-a-file-character-by-character-in-c + FILE* file = fopen("src/config/test.conf", "r"); + char* code; + size_t n = 0; + int c; + if (file == NULL) + return 1; + while((c = fgetc(file)) != EOF) + printf("%c", (char)c); + fclose(file); + Config cfg; + try { + cfg.readFile("src/config/test.conf"); + } catch(const FileIOException &fioex) { + std::cerr << "I/O error while reading file." << std::endl; + return(EXIT_FAILURE); + } catch(const ParseException &pex) + { + std::cerr << "Parse error at " << pex.getFile() << ":" << pex.getLine() + << " - " << pex.getError() << std::endl; + return(EXIT_FAILURE); + } + if (cfg.exists("watcher") && cfg.lookup("watcher").isGroup()) { + printf("Watcher Exists! ^.^\n"); + const Setting &watcher = cfg.lookup("watcher"); + for (auto it = watcher.begin(); it != watcher.end(); ++it) { + printf("%s\n", it->getName()); + + } + } + return 0; +} diff --git a/src/config/renconf b/src/config/renconf new file mode 100755 index 0000000000000000000000000000000000000000..ec1bfb8f08cf1098a3dbbeaf678cbe2c9b808b22 GIT binary patch literal 39296 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 literal 0 HcmV?d00001 diff --git a/src/config/renzoku.conf b/src/config/renzoku.conf new file mode 100644 index 0000000..6716c3b --- /dev/null +++ b/src/config/renzoku.conf @@ -0,0 +1,20 @@ +watch = { + Filter = ["*.cpp", "*.hpp", "*.c", "*.h", "include/*.hxx", "*.conf"] + Include = "Makefile" + Exclude = "src/Config.cpp" + } +compile = { + Command = "make" + Enabled = true +} +Test = { + command = "make check" + IgnoreStatus = [2,3,4] + Enabled = true + } +program = { + command = "./src/config/renconf" +# command: /usr/bin/echo Hello World + Enabled = true + } + diff --git a/src/config/test.conf b/src/config/test.conf new file mode 100644 index 0000000..502da23 --- /dev/null +++ b/src/config/test.conf @@ -0,0 +1,7 @@ +line1 = 31; +line2 = { +} +watcher = { +cmd = "eep? I'm a multi-line \"string\""; +test = 1; +} diff --git a/src/main.cpp b/src/main.cpp index 822aadb..12dc0ee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,6 +41,7 @@ #include "log.hpp" #include "watcher.hpp" #include "procman.hpp" +#include volatile sig_atomic_t gRunning = 1; @@ -52,12 +53,20 @@ int main(int argc, char** argv) { //watcher(); StdoutLogger logger; ProcessManager procman(&logger); - Config config(&logger, &procman); - Watcher w(&logger, "./", config, &procman, true); + Config* config; + try { + config = new Config(&logger, &procman); + } catch ( const libconfig::ParseException &pex ) { + fprintf(stderr, "Parse error at %s:%d - %s\n", pex.getFile(), + pex.getLine(), pex.getError()); + return EXIT_FAILURE; + } + Watcher w(&logger, "./", *config, &procman, true); while(gRunning) w.listen(); + delete config; return EXIT_SUCCESS; } diff --git a/src/watcher.cpp b/src/watcher.cpp index 57734e9..9099c0d 100644 --- a/src/watcher.cpp +++ b/src/watcher.cpp @@ -68,10 +68,12 @@ void Watcher::recursiveWatch(std::string maindir) { for(std::string dir : dirs) { - if(dir == "." || dir == "..") + if(dir.compare(".") == 0 || dir.compare("..") == 0) continue; + //LOG(logger, DEBUG, "Watching %s", (maindir + '/' + dir).c_str()); mFDs.push_back(inotify_add_watch(mINotify, (maindir + '/' + dir).c_str(), IN_MODIFY)); + //LOG(logger, DEBUG, "%s => %s", dir.c_str(), "."); this->recursiveWatch(maindir + '/' + dir); } } @@ -99,10 +101,10 @@ void Watcher::listen() if( length <= 0 ) LOG_ERROR(logger, "read"); - LOG(logger, DEBUG, "Event buffer size: %d", /*mBuffer,*/length); + //LOG(logger, DEBUG, "Event buffer size: %d", /*mBuffer,*/length); while( i < length ) { struct inotify_event *event = ( struct inotify_event * ) &mBuffer[i]; - LOG(logger, DEBUG, "File %s -> 0x%x!", event->name, event->mask); + //LOG(logger, DEBUG, "File %s -> 0x%x!", event->name, event->mask); if( event->len && (event->mask & IN_MODIFY) && (! (event->mask & IN_ISDIR)) diff --git a/tests/catch.hpp b/tests/catch.hpp index 15621f9..de61226 100644 --- a/tests/catch.hpp +++ b/tests/catch.hpp @@ -1,25 +1,6 @@ /* - * Renzoku - Re-build, re-test, and re-run a program whenever the code changes - * Copyright (C) 2015 Colton Wolkins - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ -/* - * CATCH v1.1 build 14 (develop branch) - * Generated: 2015-03-04 18:32:24.627737 + * Catch v1.2.1 + * Generated: 2015-06-30 18:23:27.961086 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. @@ -32,6 +13,12 @@ #define TWOBLUECUBES_CATCH_HPP_INCLUDED +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + // #included from: internal/catch_suppress_warnings.h #define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED @@ -49,6 +36,7 @@ # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wc++98-compat" # pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# pragma clang diagnostic ignored "-Wswitch-enum" # endif #elif defined __GNUC__ # pragma GCC diagnostic ignored "-Wvariadic-macros" @@ -88,16 +76,34 @@ // #included from: catch_compiler_capabilities.h #define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED -// Much of the following code is based on Boost (1.53) +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? +// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported + +// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 #ifdef __clang__ # if __has_feature(cxx_nullptr) -# define CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # if __has_feature(cxx_noexcept) -# define CATCH_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif #endif // __clang__ @@ -106,51 +112,26 @@ // Borland #ifdef __BORLANDC__ -#if (__BORLANDC__ > 0x582 ) -//#define CATCH_CONFIG_SFINAE // Not confirmed -#endif - #endif // __BORLANDC__ //////////////////////////////////////////////////////////////////////////////// // EDG #ifdef __EDG_VERSION__ -#if (__EDG_VERSION__ > 238 ) -//#define CATCH_CONFIG_SFINAE // Not confirmed -#endif - #endif // __EDG_VERSION__ //////////////////////////////////////////////////////////////////////////////// // Digital Mars #ifdef __DMC__ -#if (__DMC__ > 0x840 ) -//#define CATCH_CONFIG_SFINAE // Not confirmed -#endif - #endif // __DMC__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ -#if __GNUC__ < 3 - -#if (__GNUC_MINOR__ >= 96 ) -//#define CATCH_CONFIG_SFINAE -#endif - -#elif __GNUC__ >= 3 - -// #define CATCH_CONFIG_SFINAE // Taking this out completely for now - -#endif // __GNUC__ < 3 - #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) - -#define CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR #endif #endif // __GNUC__ @@ -160,11 +141,12 @@ #ifdef _MSC_VER #if (_MSC_VER >= 1600) -#define CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR #endif -#if (_MSC_VER >= 1310 ) // (VC++ 7.0+) -//#define CATCH_CONFIG_SFINAE // Not confirmed +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #endif #endif // _MSC_VER @@ -175,21 +157,62 @@ ( defined __GNUC__ && __GNUC__ >= 3 ) || \ ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) -#ifndef CATCH_CONFIG_NO_VARIADIC_MACROS -#define CATCH_CONFIG_VARIADIC_MACROS -#endif +#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS #endif //////////////////////////////////////////////////////////////////////////////// // C++ language feature support -// detect language version: -#if (__cplusplus == 201103L) -# define CATCH_CPP11 -# define CATCH_CPP11_OR_GREATER -#elif (__cplusplus >= 201103L) +// catch all support for C++11 +#if (__cplusplus >= 201103L) + # define CATCH_CPP11_OR_GREATER + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# endif + +# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NULLPTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_IS_ENUM +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TUPLE +#endif +#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) +#define CATCH_CONFIG_VARIADIC_MACROS #endif // noexcept support: @@ -204,7 +227,7 @@ namespace Catch { class NonCopyable { -#ifdef CATCH_CPP11_OR_GREATER +#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; @@ -267,7 +290,7 @@ namespace Catch { SourceLineInfo(); SourceLineInfo( char const* _file, std::size_t _line ); SourceLineInfo( SourceLineInfo const& other ); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SourceLineInfo( SourceLineInfo && ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo& operator = ( SourceLineInfo && ) = default; @@ -656,11 +679,11 @@ namespace Catch { // ResultDisposition::Flags enum struct ResultDisposition { enum Flags { - Normal = 0x00, + Normal = 0x01, - ContinueOnFailure = 0x01, // Failures fail test, but execution continues - FalseTest = 0x02, // Prefix expression with ! - SuppressFail = 0x04 // Failures are reported but do not fail the test + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test }; }; inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { @@ -708,7 +731,7 @@ namespace Catch { AssertionResult(); AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); ~AssertionResult(); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionResult( AssertionResult const& ) = default; AssertionResult( AssertionResult && ) = default; AssertionResult& operator = ( AssertionResult const& ) = default; @@ -764,8 +787,8 @@ namespace Catch { ResultDisposition::Flags resultDisposition ); template - ExpressionLhs operator->* ( T const& operand ); - ExpressionLhs operator->* ( bool value ); + ExpressionLhs operator <= ( T const& operand ); + ExpressionLhs operator <= ( bool value ); template ResultBuilder& operator << ( T const& value ) { @@ -988,40 +1011,6 @@ namespace Internal { // #included from: catch_tostring.h #define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED -// #included from: catch_sfinae.hpp -#define TWOBLUECUBES_CATCH_SFINAE_HPP_INCLUDED - -// Try to detect if the current compiler supports SFINAE - -namespace Catch { - - struct TrueType { - static const bool value = true; - typedef void Enable; - char sizer[1]; - }; - struct FalseType { - static const bool value = false; - typedef void Disable; - char sizer[2]; - }; - -#ifdef CATCH_CONFIG_SFINAE - - template struct NotABooleanExpression; - - template struct If : NotABooleanExpression {}; - template<> struct If : TrueType {}; - template<> struct If : FalseType {}; - - template struct SizedIf; - template<> struct SizedIf : TrueType {}; - template<> struct SizedIf : FalseType {}; - -#endif // CATCH_CONFIG_SFINAE - -} // end namespace Catch - #include #include #include @@ -1074,8 +1063,11 @@ inline id performOptionalSelector( id obj, SEL sel ) { #endif -#ifdef CATCH_CPP11_OR_GREATER +#ifdef CATCH_CONFIG_CPP11_TUPLE #include +#endif + +#ifdef CATCH_CONFIG_CPP11_IS_ENUM #include #endif @@ -1117,32 +1109,13 @@ namespace Detail { extern std::string unprintableString; -// SFINAE is currently disabled by default for all compilers. -// If the non SFINAE version of IsStreamInsertable is ambiguous for you -// and your compiler supports SFINAE, try #defining CATCH_CONFIG_SFINAE -#ifdef CATCH_CONFIG_SFINAE - - template - class IsStreamInsertableHelper { - template struct TrueIfSizeable : TrueType {}; - - template - static TrueIfSizeable dummy(T2*); - static FalseType dummy(...); - - public: - typedef SizedIf type; - }; - - template - struct IsStreamInsertable : IsStreamInsertableHelper::type {}; - -#else - struct BorgType { template BorgType( T const& ); }; + struct TrueType { char sizer[1]; }; + struct FalseType { char sizer[2]; }; + TrueType& testStreamable( std::ostream& ); FalseType testStreamable( FalseType ); @@ -1155,9 +1128,7 @@ namespace Detail { enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; }; -#endif - -#if defined(CATCH_CPP11_OR_GREATER) +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) template::value > @@ -1179,7 +1150,7 @@ namespace Detail { #endif template struct StringMakerBase { -#if defined(CATCH_CPP11_OR_GREATER) +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) template static std::string convert( T const& v ) { @@ -1252,7 +1223,7 @@ std::string toString( std::vector const& v ) { return Detail::rangeToString( v.begin(), v.end() ); } -#ifdef CATCH_CPP11_OR_GREATER +#ifdef CATCH_CONFIG_CPP11_TUPLE // toString for tuples namespace TupleDetail { @@ -1292,7 +1263,7 @@ struct StringMaker> { return os.str(); } }; -#endif +#endif // CATCH_CONFIG_CPP11_TUPLE namespace Detail { template @@ -1337,13 +1308,13 @@ namespace Catch { template class ExpressionLhs { ExpressionLhs& operator = ( ExpressionLhs const& ); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS ExpressionLhs& operator = ( ExpressionLhs && ) = delete; # endif public: ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS ExpressionLhs( ExpressionLhs const& ) = default; ExpressionLhs( ExpressionLhs && ) = default; # endif @@ -1424,11 +1395,11 @@ class ExpressionLhs { namespace Catch { template - inline ExpressionLhs ResultBuilder::operator->* ( T const& operand ) { + inline ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { return ExpressionLhs( *this, operand ); } - inline ExpressionLhs ResultBuilder::operator->* ( bool value ) { + inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { return ExpressionLhs( *this, value ); } @@ -1600,7 +1571,7 @@ namespace Catch { do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ - ( __catchResult->*expr ).endExpression(); \ + ( __catchResult <= expr ).endExpression(); \ } \ catch( ... ) { \ __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ @@ -3533,7 +3504,7 @@ namespace Clara { template struct IArgFunction { virtual ~IArgFunction() {} -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS IArgFunction() = default; IArgFunction( IArgFunction const& ) = default; # endif @@ -4624,7 +4595,7 @@ namespace Catch } virtual ~AssertionStats(); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionStats( AssertionStats const& ) = default; AssertionStats( AssertionStats && ) = default; AssertionStats& operator = ( AssertionStats const& ) = default; @@ -4647,7 +4618,7 @@ namespace Catch missingAssertions( _missingAssertions ) {} virtual ~SectionStats(); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SectionStats( SectionStats const& ) = default; SectionStats( SectionStats && ) = default; SectionStats& operator = ( SectionStats const& ) = default; @@ -4674,7 +4645,7 @@ namespace Catch {} virtual ~TestCaseStats(); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestCaseStats( TestCaseStats const& ) = default; TestCaseStats( TestCaseStats && ) = default; TestCaseStats& operator = ( TestCaseStats const& ) = default; @@ -4702,7 +4673,7 @@ namespace Catch {} virtual ~TestGroupStats(); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestGroupStats( TestGroupStats const& ) = default; TestGroupStats( TestGroupStats && ) = default; TestGroupStats& operator = ( TestGroupStats const& ) = default; @@ -4724,7 +4695,7 @@ namespace Catch {} virtual ~TestRunStats(); -# ifndef CATCH_CPP11_OR_GREATER +# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS TestRunStats( TestRunStats const& _other ) : runInfo( _other.runInfo ), totals( _other.totals ), @@ -4979,32 +4950,15 @@ namespace SectionTracking { RunState runState() const { return m_runState; } - TrackedSection* findChild( std::string const& childName ) { - TrackedSections::iterator it = m_children.find( childName ); - return it != m_children.end() - ? &it->second - : NULL; - } - TrackedSection* acquireChild( std::string const& childName ) { - if( TrackedSection* child = findChild( childName ) ) - return child; - m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); - return findChild( childName ); - } + TrackedSection* findChild( std::string const& childName ); + TrackedSection* acquireChild( std::string const& childName ); + void enter() { if( m_runState == NotStarted ) m_runState = Executing; } - void leave() { - for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); - it != itEnd; - ++it ) - if( it->second.runState() != Completed ) { - m_runState = ExecutingChildren; - return; - } - m_runState = Completed; - } + void leave(); + TrackedSection* getParent() { return m_parent; } @@ -5017,9 +4971,31 @@ namespace SectionTracking { RunState m_runState; TrackedSections m_children; TrackedSection* m_parent; - }; + inline TrackedSection* TrackedSection::findChild( std::string const& childName ) { + TrackedSections::iterator it = m_children.find( childName ); + return it != m_children.end() + ? &it->second + : NULL; + } + inline TrackedSection* TrackedSection::acquireChild( std::string const& childName ) { + if( TrackedSection* child = findChild( childName ) ) + return child; + m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); + return findChild( childName ); + } + inline void TrackedSection::leave() { + for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); + it != itEnd; + ++it ) + if( it->second.runState() != Completed ) { + m_runState = ExecutingChildren; + return; + } + m_runState = Completed; + } + class TestCaseTracker { public: TestCaseTracker( std::string const& testCaseName ) @@ -5502,18 +5478,19 @@ namespace Catch { struct Version { Version( unsigned int _majorVersion, unsigned int _minorVersion, - unsigned int _buildNumber, - char const* const _branchName ) - : majorVersion( _majorVersion ), - minorVersion( _minorVersion ), - buildNumber( _buildNumber ), - branchName( _branchName ) - {} + unsigned int _patchNumber, + std::string const& _branchName, + unsigned int _buildNumber ); unsigned int const majorVersion; unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + std::string const branchName; unsigned int const buildNumber; - char const* const branchName; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); private: void operator=( Version const& ); @@ -5633,12 +5610,7 @@ namespace Catch { } void showHelp( std::string const& processName ) { - Catch::cout() << "\nCatch v" << libraryVersion.majorVersion << "." - << libraryVersion.minorVersion << " build " - << libraryVersion.buildNumber; - if( libraryVersion.branchName != std::string( "master" ) ) - Catch::cout() << " (" << libraryVersion.branchName << " branch)"; - Catch::cout() << "\n"; + Catch::cout() << "\nCatch v" << libraryVersion << "\n"; m_cli.usage( Catch::cout(), processName ); Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; @@ -5655,9 +5627,10 @@ namespace Catch { catch( std::exception& ex ) { { Colour colourGuard( Colour::Red ); - Catch::cerr() << "\nError(s) in input:\n" - << Text( ex.what(), TextAttributes().setIndent(2) ) - << "\n\n"; + Catch::cerr() + << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; } m_cli.usage( Catch::cout(), m_configData.processName ); return (std::numeric_limits::max)(); @@ -6604,7 +6577,7 @@ namespace Catch { return TestCaseInfo::None; } inline bool isReservedTag( std::string const& tag ) { - return TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); } inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { if( isReservedTag( tag ) ) { @@ -6769,8 +6742,33 @@ namespace Catch { namespace Catch { - // These numbers are maintained by a script - Version libraryVersion( 1, 1, 14, "develop" ); + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + std::string const& _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << "." + << version.minorVersion << "." + << version.patchNumber; + + if( !version.branchName.empty() ) { + os << "-" << version.branchName + << "." << version.buildNumber; + } + return os; + } + + Version libraryVersion( 1, 2, 1, "", 0 ); + } // #included from: catch_message.hpp @@ -6952,11 +6950,11 @@ namespace Catch { uint64_t getCurrentTicks() { static uint64_t hz=0, hzo=0; if (!hz) { - QueryPerformanceFrequency((LARGE_INTEGER*)&hz); - QueryPerformanceCounter((LARGE_INTEGER*)&hzo); + QueryPerformanceFrequency( reinterpret_cast( &hz ) ); + QueryPerformanceCounter( reinterpret_cast( &hzo ) ); } uint64_t t; - QueryPerformanceCounter((LARGE_INTEGER*)&t); + QueryPerformanceCounter( reinterpret_cast( &t ) ); return ((t-hzo)*1000000)/hz; } #else @@ -7290,19 +7288,17 @@ std::string toString( wchar_t* const value ) std::string toString( int value ) { std::ostringstream oss; - if( value > 8192 ) - oss << "0x" << std::hex << value; - else - oss << value; + oss << value; + if( value >= 255 ) + oss << " (0x" << std::hex << value << ")"; return oss.str(); } std::string toString( unsigned long value ) { std::ostringstream oss; - if( value > 8192 ) - oss << "0x" << std::hex << value; - else - oss << value; + oss << value; + if( value >= 255 ) + oss << " (0x" << std::hex << value << ")"; return oss.str(); } @@ -7433,7 +7429,7 @@ namespace Catch { if( !result.isOk() ) { if( getCurrentContext().getConfig()->shouldDebugBreak() ) m_shouldDebugBreak = true; - if( getCurrentContext().getRunner()->aborting() || m_assertionInfo.resultDisposition == ResultDisposition::Normal ) + if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) m_shouldThrow = true; } } @@ -7916,27 +7912,6 @@ namespace Catch { endElement(); } -//# ifndef CATCH_CPP11_OR_GREATER -// XmlWriter& operator = ( XmlWriter const& other ) { -// XmlWriter temp( other ); -// swap( temp ); -// return *this; -// } -//# else -// XmlWriter( XmlWriter const& ) = default; -// XmlWriter( XmlWriter && ) = default; -// XmlWriter& operator = ( XmlWriter const& ) = default; -// XmlWriter& operator = ( XmlWriter && ) = default; -//# endif -// -// void swap( XmlWriter& other ) { -// std::swap( m_tagIsOpen, other.m_tagIsOpen ); -// std::swap( m_needsNewline, other.m_needsNewline ); -// std::swap( m_tags, other.m_tags ); -// std::swap( m_indent, other.m_indent ); -// std::swap( m_os, other.m_os ); -// } - XmlWriter& startElement( std::string const& name ) { ensureTagClosed(); newlineIfNecessary(); @@ -8719,12 +8694,7 @@ namespace Catch { stream << "\n" << getLineOfChars<'~'>() << "\n"; Colour colour( Colour::SecondaryText ); stream << currentTestRunInfo->name - << " is a Catch v" << libraryVersion.majorVersion << "." - << libraryVersion.minorVersion << " b" - << libraryVersion.buildNumber; - if( libraryVersion.branchName != std::string( "master" ) ) - stream << " (" << libraryVersion.branchName << ")"; - stream << " host application.\n" + << " is a Catch v" << libraryVersion << " host application.\n" << "Run with -? for options\n\n"; if( m_config->rngSeed() != 0 ) From c8195adfcdb822612573d348ec485aba964e9046 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Sun, 29 Nov 2015 18:56:57 -0700 Subject: [PATCH 03/28] Switched to libconfig version checks --- configure.ac | 10 ++++++++++ src/config.cpp | 10 ++++++++++ src/util.cpp | 1 + src/watcher.cpp | 5 +++-- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 7942383..e349196 100644 --- a/configure.ac +++ b/configure.ac @@ -68,6 +68,16 @@ AS_IF([test "x$with_yaml" != xno], )], [] )]) +AC_ARG_ENABLE(debug, + AS_HELP_STRING([--enable-debug], + [build with additional debugging code]), + [case "${enableval}" in + yes) debug=true; AC_DEFINE([__DEBUG__]) ;; + no) debug=false ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) + ;; esac], + [debug=false]) +AM_CONDITIONAL(DEBUG, test x"$debug" = x"true") AC_LANG_POP([C++]) # Checks for typedefs, structures, and compiler characteristics. diff --git a/src/config.cpp b/src/config.cpp index c46916d..d7359c2 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -163,6 +163,9 @@ void Config::parseCommand(const libconfig::Setting& node, iCommandConfig& config //return; printf("%d %d %s\n", node.getLength(), node.isArray(), node.getName()); if(node.getLength() > 0 && node.isGroup()) + { +#if (((LIBCONFIGXX_VER_MAJOR == 1) && (LIBCONFIGXX_VER_MINOR >= 5)) \ + || (LIBCONFIGXX_VER_MAJOR > 1)) for (auto iter = node.begin(); iter != node.end(); ++iter) { std::string key = iter->getName(); const libconfig::Setting &value = *iter; @@ -207,6 +210,8 @@ void Config::parseCommand(const libconfig::Setting& node, iCommandConfig& config config.enabled = value; } } +#endif + } //for(size_t i=0;config.command[i]; i++) //{ // std::cout << "==" << config.command[i] << '\n'; @@ -222,6 +227,9 @@ void Config::parseWatcher(const libconfig::Setting& node) { size_t asterisk_count; if(node.getLength() > 0 && node.isGroup()) + { +#if (((LIBCONFIGXX_VER_MAJOR == 1) && (LIBCONFIGXX_VER_MINOR >= 5)) \ + || (LIBCONFIGXX_VER_MAJOR > 1)) for (auto iter = node.begin(); iter != node.end(); ++iter) { std::string key = iter->getName(); const libconfig::Setting &value = *iter; @@ -288,5 +296,7 @@ void Config::parseWatcher(const libconfig::Setting& node) else LOG(logger, DEBUG, "Value: %s\n", (const char*)value); } +#endif + } } diff --git a/src/util.cpp b/src/util.cpp index e93f5d8..66e7056 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -131,6 +131,7 @@ namespace Util newCommand[i] = NULL; return newCommand; } + bool strMatch(const std::string& expression, const std::string& str) { size_t lenstr, lensuffix, asterisk, i; diff --git a/src/watcher.cpp b/src/watcher.cpp index 9099c0d..2bc1de3 100644 --- a/src/watcher.cpp +++ b/src/watcher.cpp @@ -101,10 +101,11 @@ void Watcher::listen() if( length <= 0 ) LOG_ERROR(logger, "read"); - //LOG(logger, DEBUG, "Event buffer size: %d", /*mBuffer,*/length); + LOG(logger, DEBUG, "Event buffer size: %d", /*mBuffer,*/length); while( i < length ) { struct inotify_event *event = ( struct inotify_event * ) &mBuffer[i]; - //LOG(logger, DEBUG, "File %s -> 0x%x!", event->name, event->mask); + LOG(logger, DEBUG, "File %s -> 0x%x!", event->name, event->mask); + LOG(logger, DEBUG, "%d && %X & %X && ! %X & %X", event->len, event->mask, IN_MODIFY, event->mask, IN_ISDIR); if( event->len && (event->mask & IN_MODIFY) && (! (event->mask & IN_ISDIR)) From bba6c8e4d887e4bd96582916b226c50d36f28fc0 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Sun, 29 Nov 2015 18:59:49 -0700 Subject: [PATCH 04/28] Stopped programs from running all the time When a process' command was NULL (Not set), or when it was disabled, we were still trying to for and run the command. Now we return false. --- src/process.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/process.cpp b/src/process.cpp index e95ee6b..073285f 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -76,6 +76,8 @@ Process::~Process() bool Process::run() { + if(mCommand == NULL || !mEnabled) + return false; mProc = fork(); if(mProc == -1) { From a917807a506b483f43ff2ccb298bdfb26a1de3b4 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Sun, 29 Nov 2015 19:02:41 -0700 Subject: [PATCH 05/28] Remove a couple of cruft files --- src/config/librenconf.a | Bin 1388 -> 0 bytes src/config/renconf | Bin 39296 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/config/librenconf.a delete mode 100755 src/config/renconf diff --git a/src/config/librenconf.a b/src/config/librenconf.a deleted file mode 100644 index 6bd6caa1b45f06341a70508cc540246522e94e58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1388 zcmb_c!A{#i5S@?$A#j0HBrcm1R1iCfk|;t1S&CXNs6F*o37lm~0@#l1jRagsI3oe# z-f!aAGyZ~pK|5>j*2%^ZRmI5bnRzpBW<9%dao_f7?;vlIX(>;*u|4@Fr1m#{k_Qy( zxo7>Z;i)?F_>Ef3tW@ibxWq~R_S4blg-k}2i6jFDka!9O*j`9%D>5xug`Db52Lm|# z@{P22b}AK8H>yUNlpH>ES*y!j=JzSO;%i%1%|@wFBc*Qie65m_SOGt6QNabX=Q+$g z^Syvyn*-lFXOssf);E3T-gp>gkCU8ZbI1%rBy!(2>tJC{Q5l;Kp>ksVS!H!d4ib5NSb7^Lz-k^F%8Bl%X9FhSzKN!aj@?pn^;85 zMDJwHDX!naqU7t7_*ecG@^iTUm%l6fqnZ+H%1a!5|4j@Dtem7omL>g14Oe`n+Za#d zzewK38~h{5*V5os2c)n1s_;4*`s_qZ@xQA?sdc{a({8sV6Mssc_@S-f5@#i(5UU7F O>tB#X*JPejdjCJT9eXbT diff --git a/src/config/renconf b/src/config/renconf deleted file mode 100755 index ec1bfb8f08cf1098a3dbbeaf678cbe2c9b808b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39296 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 From f4aebf201cda7ea73bc71d668eaa5a36adf7dc71 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Sun, 29 Nov 2015 19:03:35 -0700 Subject: [PATCH 06/28] Added first pass of config parser Detailed Reasoning ================== Fed Up ------ I'm fed up with the configuration parsers out there. The versions that are out there that I've used in this project so far (libconfig++ and libyaml-cpp) have changed their API in some way or another that made it impossible to compile on older systems, without shipping the libraries along with the package or statically linking them into renzoku. Birth of a Parser ----------------- I have started to write my own parser so that I A) achieve better cross-platformify renzoku and B) finally learn how to write a parser from the ground up. The code within this commit is still in-development and will be until it can successfully parse the config file outlined in GH-3. Problems with the previous config format ---------------------------------------- Quite frankly, the main problem with the previous config file format was that I was unable to step away from the project and think of other use cases. The only main use cases that I had were my own. Speaking about Renzoku at PLUG really helped me understand how renzoku could be used by other people. GH-3 came out of PLUG and has become more of a guide for where I'm heading next with my code. :D --- configure.ac | 2 +- renzoku.conf | 4 +- src/Makefile.am | 3 +- src/parser/Makefile.am | 23 +++++ src/parser/lexicon.cpp | 53 ++++++++++++ src/parser/lexicon.hpp | 21 +++++ src/parser/main.cpp | 40 +++++++++ src/parser/parser.cpp | 0 src/parser/parser.hpp | 55 ++++++++++++ src/parser/test.conf | 32 +++++++ src/parser/tokenizer.cpp | 154 +++++++++++++++++++++++++++++++++ src/{ => parser}/tokenizer.hpp | 89 ++++++++++--------- src/tokenizer.cpp | 68 --------------- test.conf | 33 +++++++ 14 files changed, 463 insertions(+), 114 deletions(-) create mode 100644 src/parser/Makefile.am create mode 100644 src/parser/lexicon.cpp create mode 100644 src/parser/lexicon.hpp create mode 100644 src/parser/main.cpp create mode 100644 src/parser/parser.cpp create mode 100644 src/parser/parser.hpp create mode 100644 src/parser/test.conf create mode 100644 src/parser/tokenizer.cpp rename src/{ => parser}/tokenizer.hpp (56%) delete mode 100644 src/tokenizer.cpp create mode 100644 test.conf diff --git a/configure.ac b/configure.ac index e349196..54f8a03 100644 --- a/configure.ac +++ b/configure.ac @@ -99,5 +99,5 @@ AC_PROG_RANLIB #AC_CONFIG_FILES([Makefile]) AC_OUTPUT(Makefile src/Makefile doc/Makefile examples/Makefile man/Makefile - tests/Makefile src/config/Makefile) + tests/Makefile src/config/Makefile src/parser/Makefile) AC_OUTPUT diff --git a/renzoku.conf b/renzoku.conf index ba40b8b..d1eb302 100644 --- a/renzoku.conf +++ b/renzoku.conf @@ -10,10 +10,10 @@ compile = { Test = { command = "make check" IgnoreStatus = [2,3,4] - Enabled = true + Enabled = false } program = { - command = "./src/config/renconf" + command = "./src/parser/parser" # command: /usr/bin/echo Hello World Enabled = true } diff --git a/src/Makefile.am b/src/Makefile.am index ada15d6..2982ffe 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,6 +2,7 @@ #CXXFLAGS = -Wall -g -Isrc/ -static -lyaml-cpp -std=c++11 AM_CXXFLAGS = --pedantic -Wall -O2 -g -std=c++11 AM_LDFLAGS = -Bstatic -lconfig++ -Bdynamic +SUBDIRS = parser # this lists the binaries to produce, the (non-PHONY, binary) targets in # the previous manual Makefile @@ -33,8 +34,6 @@ librenzoku_a_SOURCES = \ util.hpp \ watcher.cpp \ watcher.hpp -# tokenizer.cpp -# tokenizer.hpp bin_PROGRAMS = renzoku diff --git a/src/parser/Makefile.am b/src/parser/Makefile.am new file mode 100644 index 0000000..0796439 --- /dev/null +++ b/src/parser/Makefile.am @@ -0,0 +1,23 @@ +if DEBUG +AM_CFLAGS = -g3 -O0 -std=c++11 +AM_CXXFLAGS = -g3 -O0 -std=c++11 +else +AM_CFLAGS = -O2 -std=c++11 +AM_CXXFLAGS = -O2 -std=c++11 +endif +noinst_LIBRARIES = libparser.a +libparser_a_SOURCES = \ + main.cpp \ + lexicon.cpp \ + lexicon.hpp \ + tokenizer.cpp \ + tokenizer.hpp \ + parser.cpp \ + parser.hpp + +bin_PROGRAMS = parser +parserdir = $(pkglibdir)/@PACKAGE@ +parser_LDADD = libparser.a +parser_SOURCES = \ + main.cpp + diff --git a/src/parser/lexicon.cpp b/src/parser/lexicon.cpp new file mode 100644 index 0000000..e599885 --- /dev/null +++ b/src/parser/lexicon.cpp @@ -0,0 +1,53 @@ +#include "lexicon.hpp" +#include +#include + +BLexicon::BLexicon() { + memset(chars, SPECIAL, sizeof(chars)); + chars[0] = INVALID; + for (int i = 1; i <= 31; i++) { + chars[i] = CTRL; + } + chars[9] = WS; + chars[10] = WS; // NL + chars[13] = WS; // CR + chars[32] = WS; + + // Special Characters + char specials[] = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; + for (int i = 0; i < strlen(specials); i++) { + chars[(unsigned)specials[i]] = SPECIAL; + } + + for (int i = 48; i < 58; i++) { + chars[i] = NUMERIC; + } + for (int i = 65; i < 91; i++) { + chars[i] = ALPHA; + } + chars['_'] = ALPHA; + for (int i = 97; i < 122; i++) { + chars[i] = ALPHA; + } +} +BLexicon::lexicon_t BLexicon::getLexicon(char c) { + //fprintf(stderr, "%u\n", (unsigned char)c); + if((unsigned char)c > 255) + return INVALID; + return chars[(unsigned char)c]; +} +BLexicon::~BLexicon() { +} +// enum lexicon_t { CTRL, WS, ALPHA, NUMBERIC, SPECIAL, INVALID }; +// lexicon_t chars[256]; +// UTF8 = just mark 128-255 as alpha +// http://zaemis.blogspot.com/2011/06/reading-unicode-utf-8-by-hand-in-c.html + +UTF8Lexicon::UTF8Lexicon() : + BLexicon() +{ + for (int i = 128; i < 256; i++) { + chars[i] = ALPHA; + } +} +UTF8Lexicon::~UTF8Lexicon() { } diff --git a/src/parser/lexicon.hpp b/src/parser/lexicon.hpp new file mode 100644 index 0000000..b9015a9 --- /dev/null +++ b/src/parser/lexicon.hpp @@ -0,0 +1,21 @@ +#pragma once + +class BLexicon +{ +public: + BLexicon(); + virtual ~BLexicon(); + enum lexicon_t { CTRL, WS, ALPHA, NUMERIC, SPECIAL, INVALID }; + lexicon_t getLexicon(char c); + +protected: + lexicon_t chars[256]; + +}; + +class UTF8Lexicon : public BLexicon +{ +public: + UTF8Lexicon(); + virtual ~UTF8Lexicon(); +}; diff --git a/src/parser/main.cpp b/src/parser/main.cpp new file mode 100644 index 0000000..3e391a7 --- /dev/null +++ b/src/parser/main.cpp @@ -0,0 +1,40 @@ +#include +#include +#include "lexicon.hpp" +#include "tokenizer.hpp" +int main(int argc, char *argv[]) +{ + char str[] = "Rawr! I'm a monster!\nれんぞく。 日本語。\n1人二人2人\n"; + printf("Original String: < +#include +#include + +#define THROWPARSE(MESSAGE) throw ParserException(MESSAGE, mFilename, \ + mCurrentLine, mCurrentColumn) + +Tokenizer::Tokenizer(std::string filename) : + mCurrentChar(0), + mCurrentLine(0), + mCurrentColumn(0) +{ + std::ifstream* file = new std::ifstream(); + //file->exceptions( std::ifstream::failbit | std::ifstream::badbit ); + mFilename = filename; + file->open(filename.c_str(), std::fstream::in); + /* This code section is not needed due to the fact that we turned exceptions + * on for the file stream. + if(!file.good()) { + //fprintf(stderr, "Unable to open %s: %s\n", filename.c_str(), perror(errno)); + std::string error = "Unable to open config file '" + filename + "'"; + perror(error.c_str()); + return; + }// */ + + // Code for dumping file + //char c = file.get(); + //while (file.good()) { + // std::cout << c; + // c = file.get(); + //} + mFile = file; +} +const Tokenizer::TokenType Tokenizer::getToken() const +{ + return mToken; +} +char Tokenizer::nextChar() +{ + mCurrentChar++; + mCurrentColumn++; + char c = mFile->get(); + if( c == '\n' || c == '\r' ) { + mCurrentLine++; + mCurrentColumn = 0; + } + return c; +} +void Tokenizer::next() +{ + char c; + BLexicon::lexicon_t lexicon; + BLexicon::lexicon_t oldlexicon; + mValue = ""; + mToken = INVALID; + + c = mFile->peek(); + if( c == EOF ) {// EOF reached + mToken = EOFTOK; + return; + } + lexicon = mLexer->getLexicon(c); + if( lexicon == BLexicon::WS && c == ' ' ) { + do { + nextChar(); // Eat the space + c = mFile->peek(); + } while( c == ' ' ); + mToken = WS; + } + else if( lexicon == BLexicon::WS ) { + do { + nextChar(); // Eat the space + c = mFile->peek(); + } while( c != EOF && mLexer->getLexicon(c) == BLexicon::WS ); + mToken = NL; + } + else if( lexicon == BLexicon::ALPHA || lexicon == BLexicon::NUMERIC ) { + oldlexicon = lexicon; + do { + mValue += nextChar(); + c = mFile->peek(); + lexicon = mLexer->getLexicon(c); + } while( c != EOF && lexicon == oldlexicon ); + mToken = WORD; + } + else if( lexicon == BLexicon::SPECIAL ) { + if( c == '"' || c == '\'' ) { + //mFile->exceptions( std::ifstream::failbit & std::ifstream::badbit ); + mToken = STRING; + char quotemarker = nextChar(); + do { + mValue += nextChar(); + c = mFile->peek(); + } while( mFile->good() && c != EOF && c != quotemarker ); + if( c == quotemarker && mFile->good()) + nextChar(); + if( !mFile->good() ) + THROWPARSE("Failed to find end of string"); + //mFile->exceptions( std::ifstream::failbit | std::ifstream::badbit ); + } else { + mValue = nextChar(); + mToken = SPECIAL; + } + } + else { + return; + } +} + +void Tokenizer::setLexer(BLexicon& lexer) { + mLexer = &lexer; +} + +const std::string& Tokenizer::getValue() const +{ + return mValue; +} diff --git a/src/tokenizer.hpp b/src/parser/tokenizer.hpp similarity index 56% rename from src/tokenizer.hpp rename to src/parser/tokenizer.hpp index d2b6f72..f62c33f 100644 --- a/src/tokenizer.hpp +++ b/src/parser/tokenizer.hpp @@ -38,54 +38,61 @@ #pragma once #include +#include +#include -enum TokenType { - PROJECT, - INT, - FLOAT, - OPERATOR, - GROUPING_SYMBOL, - STRING, - COMMENT, - SPACE, - ERROR -}; - -class Token { +class ParserException : public std::runtime_error { private: - TokenType mType; - std::string mValue; + std::string buildMessage( const std::string& what, const std::string& + filename, unsigned long linenum, unsigned long column) { + std::string message = ""; + message = what + ". File " + filename + ". Line " + std::to_string(linenum) + + " column " + std::to_string(column); + return message; + } public: - Token(TokenType, std::string); - TokenType getType() const; - std::string getValue() const; + explicit ParserException( const std::string& what, const std::string& + filename, size_t linenum, size_t column) : + std::runtime_error(buildMessage(what, filename, linenum, column)) + { + } }; class Tokenizer { + public: + enum TokenType { + INVALID=0, + WORD, + BOOLEAN, + NUMBER, + LIST, + STRING, + WS, + NL, + EOFTOK, + SPECIAL + }; + private: - char getNextChar(); - unsigned int currentChar; + size_t mCurrentChar; + size_t mCurrentLine; + size_t mCurrentColumn; + std::string mValue; + TokenType mToken; + BLexicon* mLexer; + std::string mFilename; + std::istream* mFile; + char nextChar(); + + public: + Tokenizer(std::string filename); + void setLexer(BLexicon&); + void next(); + const TokenType getToken() const; + const std::string& getValue() const; +}; +class DummyTokenizer : public Tokenizer { public: - Tokenizer(); - Tokenizer(std::istream&); - Token getToken(); - std::string next(); - /* - static const char COMMENTCHAR = '#'; - static const char INDENTATIONCHAR = '\t'; - static const char* CMDBUILD = "buildcmd"; - static const char* CMDRUN = "runcmd"; - static const char* CMDPROJECT = "project"; - const enum item_type = { - project_name, - build_command, - run_command, - file_mask, - }; - static struct { - item_type Type; - char* Buffer; - } Item; - */ + DummyTokenizer(std::string str); }; diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp deleted file mode 100644 index 68057c7..0000000 --- a/src/tokenizer.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Renzoku - Re-build, re-test, and re-run a program whenever the code changes - * Copyright (C) 2015 Colton Wolkins - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ -/* - * ===================================================================================== - * - * Filename: tokenizer.cpp - * - * Description: Token Parser for config files - * - * Version: 1.0 - * Created: 05/22/2015 01:37:03 AM - * Revision: none - * Compiler: gcc - * - * Author: Colton Wolkins (frostyfrog), frostyfrog2@gmail.com - * Company: - * - * ===================================================================================== - */ -#include "tokenizer.hpp" - -Token::Token(TokenType type, std::string value) : - mValue(value), - mType(type) -{ -} - -TokenType Token::getType() const -{ - return mType; -} - -std::string Token::getValue() const -{ - return mValue; -} - -Tokenizer::Tokenizer() -{ -} -Tokenizer::Tokenizer(std::istream& file) -{ -} -Token Tokenizer::getToken() -{ - return Token(ERROR, "testing!"); -} -std::string Tokenizer::next() -{ - return "RAWR"; -} diff --git a/test.conf b/test.conf new file mode 100644 index 0000000..1c839ab --- /dev/null +++ b/test.conf @@ -0,0 +1,33 @@ +Watch CSS { + files = "*.css", "*.less" + exclude = "/css/compiled_*" + dir = "src/html" + step Compile { + command = "lessc --clean-css=--compatibility=ie8 --advanced \ + less/main.less css/compiled_style.css" + } +} +Watch "C++ Code Base" { + files = "*.cpp", "*.hpp", "test_*.rb", "Makefile" + exclude = "main.*", "test_driver.*" + step Compile { + command = "make me a sandwich" + enabled = true + } + step "Build Tests" { + command = "make test" + enabled = yes + } + step "Run Ruby Tests" { + command = "ruby tests/ruby/test_driver.rb"; enabled = false; + } + step "Run Tests" { + command = "tests/cpp/test_framework" + error_status = 5 + ignore_status = 3-9, 27 + } + step Run { + command = "bin/my_program" + } +} +" From 6c0fae86d81641282074446c41e09acf360c7119 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Mon, 30 Nov 2015 21:14:58 -0700 Subject: [PATCH 07/28] Implemented rudimentary char escapes --- src/parser/test.conf | 2 +- src/parser/tokenizer.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/parser/test.conf b/src/parser/test.conf index 98484e4..cd605e0 100644 --- a/src/parser/test.conf +++ b/src/parser/test.conf @@ -3,7 +3,7 @@ Watch CSS { exclude = "/css/compiled_*" dir = "src/html" step Compile { - command = "lessc --clean-css=\"--compatibility=ie8 --advanced\" \ + command = "lessc --clean-css=\"--compatibility=ie8 \\ --advanced\" \ less/main.less css/compiled_style.css" } } diff --git a/src/parser/tokenizer.cpp b/src/parser/tokenizer.cpp index 0ddec6a..7c1ddc7 100644 --- a/src/parser/tokenizer.cpp +++ b/src/parser/tokenizer.cpp @@ -77,6 +77,11 @@ char Tokenizer::nextChar() mCurrentChar++; mCurrentColumn++; char c = mFile->get(); + if( c == '\\' ) { + mCurrentChar++; + mCurrentColumn++; + c = mFile->get(); + } if( c == '\n' || c == '\r' ) { mCurrentLine++; mCurrentColumn = 0; From 3332db762e1dfe4a979ab11936e365f0b8da225e Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Mon, 30 Nov 2015 21:16:08 -0700 Subject: [PATCH 08/28] Parser driver now spits out token name --- src/parser/main.cpp | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/parser/main.cpp b/src/parser/main.cpp index 3e391a7..7f7e033 100644 --- a/src/parser/main.cpp +++ b/src/parser/main.cpp @@ -2,6 +2,46 @@ #include #include "lexicon.hpp" #include "tokenizer.hpp" + +std::string toktostr(const Tokenizer::TokenType& t) { + enum TokenType { + INVALID=0, + WORD, + BOOLEAN, + NUMBER, + LIST, + STRING, + WS, + NL, + EOFTOK, + SPECIAL + }; + switch (t) { + case INVALID: + return "INVALID"; + case WORD: + return "WORD "; + case BOOLEAN: + return "BOOLEAN"; + case NUMBER: + return "NUMBER "; + case LIST: + return "LIST "; + case STRING: + return "STRING "; + case WS: + return "WS "; + case NL: + return "NL "; + case EOFTOK: + return "EOFTOK "; + case SPECIAL: + return "SPECIAL"; + default: + return "???????"; + } +} + int main(int argc, char *argv[]) { char str[] = "Rawr! I'm a monster!\nれんぞく。 日本語。\n1人二人2人\n"; @@ -33,7 +73,7 @@ int main(int argc, char *argv[]) do { t.next(); if( t.getToken() != Tokenizer::WS && t.getToken() != Tokenizer::NL ) - printf("T:%d V:%s\n", t.getToken(), t.getValue().c_str()); + printf("T:%s | V:%s\n", toktostr(t.getToken()).c_str(), t.getValue().c_str()); } while( t.getToken() != Tokenizer::EOFTOK && t.getToken() != Tokenizer::INVALID ); printf("\nEOF\n"); return 0; From a0733e1307eae95bc64ab9e2cbefe94df0b5fe9e Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Sat, 12 Dec 2015 16:40:11 -0700 Subject: [PATCH 09/28] Added Official Parser This is the first pass at my official parser. It isn't complete, it's not near complete, but it works well so far. I'm creating and pushing this commit as a checkpoint since I'm currently at 801Labs and I want to make sure that I have a backup somewhere. :) Never know what hacktivists might do ;p --- src/parser/parser.cpp | 73 ++++++++++++++++++++++++++++++++++++++++ src/parser/parser.hpp | 22 +++++++++++- src/parser/test.conf | 2 +- src/parser/tokenizer.cpp | 39 ++++++++++++++++++--- src/parser/tokenizer.hpp | 40 ++++++++++++++++++---- test.conf | 1 - 6 files changed, 163 insertions(+), 14 deletions(-) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index e69de29..de4f4d0 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -0,0 +1,73 @@ +#include "parser.hpp" +#include +#include + +#define LOWER(x) std::transform(x.begin(), x.end(), x.begin(),\ + ::tolower); + +CfgWatch* create_CfgWatch() { + CfgWatch* watcher = new CfgWatch; + watcher->name = NULL; + watcher->filesFilter = NULL; + watcher->excludesFilter = NULL; + watcher->workingDir = NULL; + watcher->steps = NULL; + watcher->next = NULL; + return watcher; +} + +Parser::Parser(std::string& filename) : + mFilename(filename), + mTokenizer(mFilename), + mWatchers(NULL), + mLastWatcher(NULL) +{ +} + +Parser::~Parser() { +} + +void Parser::parseWatcher(CfgWatch* watcher) { + std::string name = mTokenizer.getValue(); + watcher->name = new char[name.length()+1]; + // TODO(frostyfrog): Finish this +} +void Parser::Parse() { + BLexicon lexer; + mTokenizer.setLexer(lexer); + do { + mTokenizer.next(); + if( mTokenizer.getToken() != Tokenizer::WS || + mTokenizer.getToken() == Tokenizer::NL || + mTokenizer.getToken() == Tokenizer::EOFTOK ) + continue; + if( mTokenizer.getToken() == Tokenizer::INVALID ) + throw std::runtime_error("Unknown exception while parsing config"); + std::string value = mTokenizer.getValue(); + LOWER(value); + if( value == "watch" ) { + mTokenizer.next(); + if( mTokenizer.getToken() != Tokenizer::WORD && + mTokenizer.getToken() != Tokenizer::STRING ) + throw std::runtime_error("Unknown exception while parsing config"); + CfgWatch* watcher; + if( mLastWatcher == NULL ) { + mWatchers = create_CfgWatch(); + mLastWatcher = mWatchers; + } else { + watcher = create_CfgWatch(); + mLastWatcher = watcher; + mLastWatcher->next = watcher; + } + watcher = mLastWatcher; + this->parseWatcher(watcher); + } + } while( mTokenizer.getToken() != Tokenizer::EOFTOK && + mTokenizer.getToken() != Tokenizer::INVALID ); +} + +const CfgWatch* Parser::begin() const { +} + +const CfgWatch* Parser::end() const { +} diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index bf2d2c3..7b2d914 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -1,4 +1,6 @@ - +#pragma once +#include +#include "tokenizer.hpp" struct CfgStep { char* name; // Required @@ -17,6 +19,23 @@ struct CfgWatch { char** excludesFilter; // Optional, default: NULL char* workingDir; // Optional, default: cwd CfgStep* steps; // Require at least one + CfgWatch* next; // Optional, Can be NULL +}; + +class Parser { + private: + std::string mFilename; + Tokenizer mTokenizer; + CfgWatch* mWatchers; + CfgWatch* mLastWatcher; + + void parseWatcher(CfgWatch*); + public: + virtual ~Parser(); + Parser(std::string& filename); + void Parse(); + const CfgWatch* begin() const; + const CfgWatch* end() const; }; /* Example Config format: @@ -53,3 +72,4 @@ struct CfgWatch { * command = "bin/my_program" * } * } + */ diff --git a/src/parser/test.conf b/src/parser/test.conf index cd605e0..757d6b2 100644 --- a/src/parser/test.conf +++ b/src/parser/test.conf @@ -27,6 +27,6 @@ Watch "C++ Code Base" { ignore_status = 3-9, 27 } step Run { - command = "bin/my_program" + command = "bin/my_program } } diff --git a/src/parser/tokenizer.cpp b/src/parser/tokenizer.cpp index 7c1ddc7..0660b57 100644 --- a/src/parser/tokenizer.cpp +++ b/src/parser/tokenizer.cpp @@ -37,15 +37,19 @@ #include "tokenizer.hpp" #include #include +#include #include #define THROWPARSE(MESSAGE) throw ParserException(MESSAGE, mFilename, \ - mCurrentLine, mCurrentColumn) + parse_start_line ? parse_start_line : mCurrentLine, \ + parse_start_line ? parse_start_column : mCurrentColumn, \ + getCurrentLine(parse_start_pos, parse_start_column)) Tokenizer::Tokenizer(std::string filename) : mCurrentChar(0), - mCurrentLine(0), - mCurrentColumn(0) + mCurrentLine(1), + mCurrentColumn(0), + mFile(NULL) { std::ifstream* file = new std::ifstream(); //file->exceptions( std::ifstream::failbit | std::ifstream::badbit ); @@ -66,8 +70,20 @@ Tokenizer::Tokenizer(std::string filename) : // std::cout << c; // c = file.get(); //} - mFile = file; + if (file->good()) { + mFile = file; + } + else { + throw std::runtime_error("File not found!"); + } +} + +Tokenizer::~Tokenizer() { + if (mFile) { + ((std::ifstream*)mFile)->close(); + } } + const Tokenizer::TokenType Tokenizer::getToken() const { return mToken; @@ -88,9 +104,21 @@ char Tokenizer::nextChar() } return c; } +const std::string Tokenizer::getCurrentLine(size_t& start, size_t& current) { + std::string message; + size_t pos = mFile->tellg(); + mFile->clear(); + mFile->seekg( start - current ); + std::getline(*mFile, message); + mFile->seekg( mCurrentChar ); + return message; +} void Tokenizer::next() { char c; + size_t parse_start_line = 0; + size_t parse_start_column = 0; + size_t parse_start_pos = mCurrentChar; BLexicon::lexicon_t lexicon; BLexicon::lexicon_t oldlexicon; mValue = ""; @@ -130,6 +158,9 @@ void Tokenizer::next() //mFile->exceptions( std::ifstream::failbit & std::ifstream::badbit ); mToken = STRING; char quotemarker = nextChar(); + parse_start_pos = mCurrentChar; + parse_start_line = mCurrentLine; + parse_start_column = mCurrentColumn; do { mValue += nextChar(); c = mFile->peek(); diff --git a/src/parser/tokenizer.hpp b/src/parser/tokenizer.hpp index f62c33f..6cc3cde 100644 --- a/src/parser/tokenizer.hpp +++ b/src/parser/tokenizer.hpp @@ -38,22 +38,43 @@ #pragma once #include +#include +#include +#include #include #include class ParserException : public std::runtime_error { private: - std::string buildMessage( const std::string& what, const std::string& - filename, unsigned long linenum, unsigned long column) { + std::string buildMessage( const std::string& what, + const std::string& filename, + unsigned long linenum, + unsigned long column, + const std::string& line = "") { std::string message = ""; - message = what + ". File " + filename + ". Line " + std::to_string(linenum) - + " column " + std::to_string(column); + message = what; + message += ". File "; + message += filename; + message += ". Line "; + message += std::to_string(linenum); + message += " character "; + message += std::to_string(column); + message += "."; + if(!line.empty()) { + std::string line_replacement = line; + std::replace( line_replacement.begin(), line_replacement.end(), '\t', ' '); + message += "\n\r"; + message += line_replacement; + message += "\n\r"; + message.insert( message.size(), column - 1, ' '); + message += "\e[0;31m^\e[0m Parse error starts from this position"; + } return message; } public: explicit ParserException( const std::string& what, const std::string& - filename, size_t linenum, size_t column) : - std::runtime_error(buildMessage(what, filename, linenum, column)) + filename, size_t linenum, size_t column, const std::string& line ) : + std::runtime_error(buildMessage(what, filename, linenum, column, line)) { } }; @@ -81,15 +102,20 @@ class Tokenizer { TokenType mToken; BLexicon* mLexer; std::string mFilename; - std::istream* mFile; + std::ifstream* mFile; char nextChar(); public: Tokenizer(std::string filename); + virtual ~Tokenizer(); void setLexer(BLexicon&); void next(); const TokenType getToken() const; const std::string& getValue() const; + const std::string getCurrentLine(size_t&, size_t&); + const size_t getLinePosition() const { return mCurrentLine; } + const size_t getColumnPosition() const { return mCurrentColumn; } + const size_t getFilePosition() const { return mCurrentChar; } }; class DummyTokenizer : public Tokenizer { diff --git a/test.conf b/test.conf index 1c839ab..a09279c 100644 --- a/test.conf +++ b/test.conf @@ -30,4 +30,3 @@ Watch "C++ Code Base" { command = "bin/my_program" } } -" From acc4dfd994a2afc7d7a7fd9bf8910c13f510568d Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Thu, 17 Dec 2015 10:16:50 -0700 Subject: [PATCH 10/28] Update .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index e002bd7..1c04ca9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ a.out bin renzoku +src/parser/parser +src/config/renconf +*.a # Generated files *.in @@ -28,3 +31,4 @@ tests/test_runner* *.swp LICENSE.new coverage +ignoreme/ From 044456bb30ee683b52a499cc1fe17d6a605cbe2b Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Thu, 17 Dec 2015 14:42:00 -0700 Subject: [PATCH 11/28] Made progress on parser Now parsing watcher blocks and initial configuration options. Not currently saving options or parsing steps. That is what I'll be working on next. --- src/parser/main.cpp | 43 ++----------- src/parser/parser.cpp | 126 ++++++++++++++++++++++++++++++++++++++- src/parser/parser.hpp | 10 +++- src/parser/tokenizer.cpp | 46 ++++++++++++++ src/parser/tokenizer.hpp | 3 + 5 files changed, 186 insertions(+), 42 deletions(-) diff --git a/src/parser/main.cpp b/src/parser/main.cpp index 7f7e033..684c8e4 100644 --- a/src/parser/main.cpp +++ b/src/parser/main.cpp @@ -2,45 +2,7 @@ #include #include "lexicon.hpp" #include "tokenizer.hpp" - -std::string toktostr(const Tokenizer::TokenType& t) { - enum TokenType { - INVALID=0, - WORD, - BOOLEAN, - NUMBER, - LIST, - STRING, - WS, - NL, - EOFTOK, - SPECIAL - }; - switch (t) { - case INVALID: - return "INVALID"; - case WORD: - return "WORD "; - case BOOLEAN: - return "BOOLEAN"; - case NUMBER: - return "NUMBER "; - case LIST: - return "LIST "; - case STRING: - return "STRING "; - case WS: - return "WS "; - case NL: - return "NL "; - case EOFTOK: - return "EOFTOK "; - case SPECIAL: - return "SPECIAL"; - default: - return "???????"; - } -} +#include "parser.hpp" int main(int argc, char *argv[]) { @@ -75,6 +37,9 @@ int main(int argc, char *argv[]) if( t.getToken() != Tokenizer::WS && t.getToken() != Tokenizer::NL ) printf("T:%s | V:%s\n", toktostr(t.getToken()).c_str(), t.getValue().c_str()); } while( t.getToken() != Tokenizer::EOFTOK && t.getToken() != Tokenizer::INVALID ); + printf("\n"); + Parser p("test.conf"); + p.Parse(); printf("\nEOF\n"); return 0; } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index de4f4d0..bb375b5 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -1,4 +1,5 @@ #include "parser.hpp" +#include "tokenizer.hpp" #include #include @@ -16,7 +17,7 @@ CfgWatch* create_CfgWatch() { return watcher; } -Parser::Parser(std::string& filename) : +Parser::Parser(std::string filename) : mFilename(filename), mTokenizer(mFilename), mWatchers(NULL), @@ -30,23 +31,144 @@ Parser::~Parser() { void Parser::parseWatcher(CfgWatch* watcher) { std::string name = mTokenizer.getValue(); watcher->name = new char[name.length()+1]; + watcher->name[name.length()] = '\0'; // TODO(frostyfrog): Finish this + for (size_t i = 0; i < name.length(); i++) { + watcher->name[i] = name[i]; + } + mTokenizer.next(); + if( mTokenizer.isWhiteSpace() ) + mTokenizer.next(); + assertExp(Tokenizer::SPECIAL, "{"); + mTokenizer.next(); + while( mTokenizer.getToken() != Tokenizer::EOFTOK && + mTokenizer.getToken() != Tokenizer::INVALID && + mTokenizer.getValue() != "}" ) + { + if( mTokenizer.isWhiteSpace() ) { + mTokenizer.next(); + continue; + } + std::string key = parseKey(); + parseWatcherOptions(watcher, "files", key); + parseWatcherOptions(watcher, "exclude", key); + parseWatcherOptions(watcher, "dir", key); + if( key == "step" ) + break; + mTokenizer.next(); + } + printf("Watcher [%s] found!\n", watcher->name); +} + +void Parser::parseWatcherOptions(CfgWatch* watcher, std::string expectKey, + std::string key) { + if( key != expectKey ) + return; + parseEquals(); + std::vector values = parseValue(); + printf("K: %s V: ", key.c_str()); + for (auto value : values) { + printf("%s, ", value.c_str()); + } + printf("\n"); +} + +const std::string& Parser::parseKey() { + //printf("T: %d V: %s\n", mTokenizer.getToken(), + // mTokenizer.getValue().c_str()); + assertExp( Tokenizer::WORD ); + return mTokenizer.getValue(); +} +void Parser::parseEquals() { + bool found_equals = false; + mTokenizer.next(); + while( mTokenizer.getToken() == Tokenizer::WS || + (mTokenizer.getValue() == "=" && + !found_equals)) + { + if( mTokenizer.getValue() == "=" ) + found_equals = true; + mTokenizer.next(); + } +} +const std::vector Parser::parseValue() { + Tokenizer::TokenType t; + std::vector values; + std::string valbuf; + while( mTokenizer.getToken() != Tokenizer::NL && + mTokenizer.getValue() != ";" ) + { + t = mTokenizer.getToken(); + //printf("V: %s\n", mTokenizer.getValue().c_str()); + if( t == Tokenizer::EOFTOK || + t == Tokenizer::INVALID || + t == Tokenizer::SPECIAL && !safeSpecial()) + throw std::runtime_error("Unknown exception while parsing config"); + //printf("V: %s\n", mTokenizer.getValue().c_str()); + if ( t == Tokenizer::SPECIAL && mTokenizer.getValue() == "," ) { + values.push_back(valbuf); + valbuf = ""; + mTokenizer.next(); + if(mTokenizer.getToken() == Tokenizer::WS) + mTokenizer.next(); + continue; + } + valbuf += mTokenizer.getValue(); + mTokenizer.next(); + } + if( values.size() == 0 || values[values.size()-1] != valbuf ) + values.push_back(valbuf); + //printf("V: %s\n", value.c_str()); + return values; +} + +bool Parser::safeSpecial() { + std::string v = mTokenizer.getValue(); + return ( v == "-" || + v == "_" || + v == ";" || + v == "," ); } + +bool Parser::assertExp(const Tokenizer::TokenType& token) { + //printf("T: %d:%d V: %s:%s\n", token, mTokenizer.getToken(), value.c_str(), + // mTokenizer.getValue().c_str()); + if( mTokenizer.getToken() != token ) + throw std::runtime_error("Unknown exception while parsing config"); + return true; +} + +bool Parser::assertExp(const Tokenizer::TokenType& token, std::string value) { + //printf("T: %d:%d V: %s:%s\n", token, mTokenizer.getToken(), value.c_str(), + // mTokenizer.getValue().c_str()); + if( mTokenizer.getToken() != token ) + throw std::runtime_error("Unknown exception while parsing config"); + if( mTokenizer.getValue() != value ) + throw std::runtime_error("Unknown exception while parsing config"); + return true; +} + void Parser::Parse() { BLexicon lexer; mTokenizer.setLexer(lexer); do { mTokenizer.next(); - if( mTokenizer.getToken() != Tokenizer::WS || + if( mTokenizer.getToken() == Tokenizer::WS || mTokenizer.getToken() == Tokenizer::NL || mTokenizer.getToken() == Tokenizer::EOFTOK ) continue; if( mTokenizer.getToken() == Tokenizer::INVALID ) throw std::runtime_error("Unknown exception while parsing config"); + //printf("Token: %s\n", toktostr(mTokenizer.getToken()).c_str()); std::string value = mTokenizer.getValue(); + //printf("Value: %s [%d]\n", value.c_str(), value.size()); LOWER(value); if( value == "watch" ) { mTokenizer.next(); + if( mTokenizer.getToken() != Tokenizer::WS ) + throw std::runtime_error("Unknown exception while parsing config"); + else + mTokenizer.next(); if( mTokenizer.getToken() != Tokenizer::WORD && mTokenizer.getToken() != Tokenizer::STRING ) throw std::runtime_error("Unknown exception while parsing config"); diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 7b2d914..72144bf 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include "tokenizer.hpp" struct CfgStep { @@ -30,9 +31,16 @@ class Parser { CfgWatch* mLastWatcher; void parseWatcher(CfgWatch*); + bool assertExp(const Tokenizer::TokenType&); + bool assertExp(const Tokenizer::TokenType&, std::string); + const std::string& parseKey(); + void parseEquals(); + const std::vector parseValue(); + bool safeSpecial(); + void parseWatcherOptions(CfgWatch*, std::string expectedKey, std::string key); public: virtual ~Parser(); - Parser(std::string& filename); + Parser(std::string filename); void Parse(); const CfgWatch* begin() const; const CfgWatch* end() const; diff --git a/src/parser/tokenizer.cpp b/src/parser/tokenizer.cpp index 0660b57..7d0df06 100644 --- a/src/parser/tokenizer.cpp +++ b/src/parser/tokenizer.cpp @@ -45,6 +45,7 @@ parse_start_line ? parse_start_column : mCurrentColumn, \ getCurrentLine(parse_start_pos, parse_start_column)) + Tokenizer::Tokenizer(std::string filename) : mCurrentChar(0), mCurrentLine(1), @@ -113,6 +114,7 @@ const std::string Tokenizer::getCurrentLine(size_t& start, size_t& current) { mFile->seekg( mCurrentChar ); return message; } + void Tokenizer::next() { char c; @@ -184,7 +186,51 @@ void Tokenizer::setLexer(BLexicon& lexer) { mLexer = &lexer; } +std::string toktostr(const Tokenizer::TokenType& t) { + enum TokenType { + INVALID=0, + WORD, + BOOLEAN, + NUMBER, + LIST, + STRING, + WS, + NL, + EOFTOK, + SPECIAL + }; + switch (t) { + case INVALID: + return "INVALID"; + case WORD: + return "WORD "; + case BOOLEAN: + return "BOOLEAN"; + case NUMBER: + return "NUMBER "; + case LIST: + return "LIST "; + case STRING: + return "STRING "; + case WS: + return "WS "; + case NL: + return "NL "; + case EOFTOK: + return "EOFTOK "; + case SPECIAL: + return "SPECIAL"; + default: + return "???????"; + } +} + const std::string& Tokenizer::getValue() const { + //printf("Token: %s\n", toktostr(mToken).c_str()); return mValue; } + +const bool Tokenizer::isWhiteSpace() const { + return ( mToken == WS || mToken == NL ); +} diff --git a/src/parser/tokenizer.hpp b/src/parser/tokenizer.hpp index 6cc3cde..ec84f9c 100644 --- a/src/parser/tokenizer.hpp +++ b/src/parser/tokenizer.hpp @@ -111,6 +111,7 @@ class Tokenizer { void setLexer(BLexicon&); void next(); const TokenType getToken() const; + const bool isWhiteSpace() const; const std::string& getValue() const; const std::string getCurrentLine(size_t&, size_t&); const size_t getLinePosition() const { return mCurrentLine; } @@ -118,6 +119,8 @@ class Tokenizer { const size_t getFilePosition() const { return mCurrentChar; } }; +std::string toktostr(const Tokenizer::TokenType& t); + class DummyTokenizer : public Tokenizer { public: DummyTokenizer(std::string str); From 5274f3f23ca2b312e4f875b24d2f1d85e9899a77 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Thu, 17 Dec 2015 15:05:45 -0700 Subject: [PATCH 12/28] Fix #4 This is a fix for GH-4. Renzoku no longer segfaults when the build step is disabled. --- src/config.cpp | 15 +++++++++------ src/process.cpp | 13 ++++++++----- src/process.hpp | 4 ++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index d7359c2..447c940 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -131,14 +131,16 @@ void Config::parseConfig(std::istream& file) if(procman->getProgram()) { throw std::runtime_error("Program already set!"); } - if(mProgram.command && mProgram.enabled) - procman->setProgram(new Process(logger, mProgram.command, true)); + if(mProgram.command) + procman->setProgram(new Process(logger, mProgram.command, true, + mProgram.enabled)); } else if(key == "test") { this->parseCommand(*it, mTest); - if(mTest.command && mTest.enabled) - procman->addProcess(new Process(logger, mTest.command, true)); + if(mTest.command) + procman->addProcess(new Process(logger, mTest.command, true, + mTest.enabled)); } else if(key == "compile") { @@ -146,8 +148,9 @@ void Config::parseConfig(std::istream& file) if(procman->getBuildStep()) { throw std::runtime_error("BuildStage already set!"); } - if(mCompile.command && mCompile.enabled) - procman->setBuilder(new Process(logger, mCompile.command, true)); + if(mCompile.command) + procman->setBuilder(new Process(logger, mCompile.command, true, + mCompile.enabled)); } else { diff --git a/src/process.cpp b/src/process.cpp index 073285f..71f46ae 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -43,21 +43,22 @@ #include #include -Process::Process(iLogger* logger, const std::string &command, bool in_path) : +Process::Process(iLogger* logger, const std::string &command, bool in_path, + bool enabled) : mProc(0), mSearchInPath(in_path), mGeneratedCommand(true), - mEnabled(true), + mEnabled(enabled), logger(logger) { mCommand = Util::parseCommand(command); } -Process::Process(iLogger* logger, char** command, bool in_path) : +Process::Process(iLogger* logger, char** command, bool in_path, bool enabled) : mProc(0), mSearchInPath(in_path), mGeneratedCommand(false), - mEnabled(true), + mEnabled(enabled), mCommand(command), logger(logger) { @@ -77,7 +78,7 @@ Process::~Process() bool Process::run() { if(mCommand == NULL || !mEnabled) - return false; + return true; mProc = fork(); if(mProc == -1) { @@ -133,6 +134,8 @@ bool Process::run() } bool Process::runAndWait() { + if(mCommand == NULL || !mEnabled) + return true; if(!this->run()) return false; int status = 0; diff --git a/src/process.hpp b/src/process.hpp index aacee38..d290c2b 100644 --- a/src/process.hpp +++ b/src/process.hpp @@ -50,8 +50,8 @@ class Process iLogger* logger; public: ~Process(); - Process(iLogger*, const std::string &command, bool in_path); - Process(iLogger*, char** command, bool in_path); + Process(iLogger*, const std::string &command, bool in_path, bool enabled); + Process(iLogger*, char** command, bool in_path, bool enabled); inline void enable() { mEnabled = true; }; inline void disable() { mEnabled = false; }; inline bool isEnabled() { return mEnabled; }; From 003c058e0f88143429fb9f44d7cc8edbc491da77 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Thu, 17 Dec 2015 15:42:13 -0700 Subject: [PATCH 13/28] Checkpoint --- src/parser/parser.cpp | 11 +++++++---- src/parser/parser.hpp | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index bb375b5..068dff3 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -50,6 +50,7 @@ void Parser::parseWatcher(CfgWatch* watcher) { continue; } std::string key = parseKey(); + parseWatcherOptions(watcher, "files", key); parseWatcherOptions(watcher, "exclude", key); parseWatcherOptions(watcher, "dir", key); @@ -60,17 +61,19 @@ void Parser::parseWatcher(CfgWatch* watcher) { printf("Watcher [%s] found!\n", watcher->name); } -void Parser::parseWatcherOptions(CfgWatch* watcher, std::string expectKey, - std::string key) { +std::vector Parser::parseWatcherOptions(CfgWatch* watcher, + std::string expectKey, std::string key) { + std::vector values; if( key != expectKey ) - return; + return values; parseEquals(); - std::vector values = parseValue(); + values = parseValue(); printf("K: %s V: ", key.c_str()); for (auto value : values) { printf("%s, ", value.c_str()); } printf("\n"); + return values; } const std::string& Parser::parseKey() { diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 72144bf..0edf70a 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -37,7 +37,7 @@ class Parser { void parseEquals(); const std::vector parseValue(); bool safeSpecial(); - void parseWatcherOptions(CfgWatch*, std::string expectedKey, std::string key); + std::vector parseWatcherOptions(CfgWatch*, std::string expectedKey, std::string key); public: virtual ~Parser(); Parser(std::string filename); From 7d54fcfd0bd68221b62e7b52f6cf7c647c39340e Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Thu, 17 Dec 2015 16:34:30 -0700 Subject: [PATCH 14/28] First rough pass of step parser --- src/parser/parser.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 068dff3..7eafc0e 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -55,7 +55,27 @@ void Parser::parseWatcher(CfgWatch* watcher) { parseWatcherOptions(watcher, "exclude", key); parseWatcherOptions(watcher, "dir", key); if( key == "step" ) - break; + { + mTokenizer.next(); + if( mTokenizer.getToken() != Tokenizer::WS ) + throw std::runtime_error("Unknown exception while parsing config"); + else + mTokenizer.next(); + if( mTokenizer.getToken() != Tokenizer::WORD && + mTokenizer.getToken() != Tokenizer::STRING ) + throw std::runtime_error("Unknown exception while parsing config"); + std::string name_step = mTokenizer.getValue(); + printf("\tStep: %s\n", name_step.c_str()); + mTokenizer.next(); + if( mTokenizer.isWhiteSpace() ) + mTokenizer.next(); + assertExp(Tokenizer::SPECIAL, "{"); + mTokenizer.next(); + while( mTokenizer.getValue() != "}" ) { + mTokenizer.next(); + } + mTokenizer.next(); + } mTokenizer.next(); } printf("Watcher [%s] found!\n", watcher->name); From 485b98a2ec4b62de744f81bf1b26bfc5a180f649 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Thu, 17 Dec 2015 18:40:32 -0700 Subject: [PATCH 15/28] Added some comments --- src/parser/parser.cpp | 81 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 7eafc0e..19b717d 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -29,58 +29,93 @@ Parser::~Parser() { } void Parser::parseWatcher(CfgWatch* watcher) { + // Initialize local variables std::string name = mTokenizer.getValue(); watcher->name = new char[name.length()+1]; watcher->name[name.length()] = '\0'; + // TODO(frostyfrog): Finish this + printf("Watcher [%s] found!\n", watcher->name); + + // Convert string to char array for (size_t i = 0; i < name.length(); i++) { watcher->name[i] = name[i]; } + + // Move off the watch name token mTokenizer.next(); + + // Eat whitespace if( mTokenizer.isWhiteSpace() ) mTokenizer.next(); + + // Parse the section opening assertExp(Tokenizer::SPECIAL, "{"); mTokenizer.next(); + + // Loop through internal values until we hit an invalid token or } while( mTokenizer.getToken() != Tokenizer::EOFTOK && mTokenizer.getToken() != Tokenizer::INVALID && mTokenizer.getValue() != "}" ) { + + // Skip whitespace if( mTokenizer.isWhiteSpace() ) { mTokenizer.next(); continue; } + + // Parse out the key in the key value pairs std::string key = parseKey(); + // Parse each valid section parseWatcherOptions(watcher, "files", key); parseWatcherOptions(watcher, "exclude", key); parseWatcherOptions(watcher, "dir", key); + + // We are now parsing a step if( key == "step" ) { + // Skip the whitespace and move on to the next token mTokenizer.next(); if( mTokenizer.getToken() != Tokenizer::WS ) throw std::runtime_error("Unknown exception while parsing config"); else mTokenizer.next(); + + // Make sure that the token is a word if( mTokenizer.getToken() != Tokenizer::WORD && mTokenizer.getToken() != Tokenizer::STRING ) throw std::runtime_error("Unknown exception while parsing config"); + + // Get the name of the current step std::string name_step = mTokenizer.getValue(); printf("\tStep: %s\n", name_step.c_str()); + + // Skip whitespace mTokenizer.next(); if( mTokenizer.isWhiteSpace() ) mTokenizer.next(); + + // Find the { and skip it assertExp(Tokenizer::SPECIAL, "{"); mTokenizer.next(); + + // TODO(frostyfrog): Parse the insides of the step while( mTokenizer.getValue() != "}" ) { mTokenizer.next(); } + + // Skip the } mTokenizer.next(); } + + // TODO(frostyfrog): Figure out why this was here again... mTokenizer.next(); } - printf("Watcher [%s] found!\n", watcher->name); } +// Parse just the options (key/value) std::vector Parser::parseWatcherOptions(CfgWatch* watcher, std::string expectKey, std::string key) { std::vector values; @@ -96,38 +131,53 @@ std::vector Parser::parseWatcherOptions(CfgWatch* watcher, return values; } +// Get the key for the key/value pair const std::string& Parser::parseKey() { //printf("T: %d V: %s\n", mTokenizer.getToken(), // mTokenizer.getValue().c_str()); assertExp( Tokenizer::WORD ); return mTokenizer.getValue(); } + +// Skip the equals sign void Parser::parseEquals() { bool found_equals = false; mTokenizer.next(); + + // While we hit whitespace AND only encounter a single equals sign while( mTokenizer.getToken() == Tokenizer::WS || (mTokenizer.getValue() == "=" && !found_equals)) { + // We saw an equals sign, bail on next equals if( mTokenizer.getValue() == "=" ) found_equals = true; mTokenizer.next(); } } + const std::vector Parser::parseValue() { + // Initialize the variables Tokenizer::TokenType t; std::vector values; std::string valbuf; + + // Loop until the end of the value while( mTokenizer.getToken() != Tokenizer::NL && mTokenizer.getValue() != ";" ) { + // Cache the token and check it's validity t = mTokenizer.getToken(); //printf("V: %s\n", mTokenizer.getValue().c_str()); if( t == Tokenizer::EOFTOK || t == Tokenizer::INVALID || t == Tokenizer::SPECIAL && !safeSpecial()) throw std::runtime_error("Unknown exception while parsing config"); + + //printf("V: %s\n", mTokenizer.getValue().c_str()); + // If we have a list, this should be handled by the tokenizer, not parser + // TODO(frostyfrog): Implement this in the tokenizer if ( t == Tokenizer::SPECIAL && mTokenizer.getValue() == "," ) { values.push_back(valbuf); valbuf = ""; @@ -145,6 +195,7 @@ const std::vector Parser::parseValue() { return values; } +// Check that the special token should be considered "safe" bool Parser::safeSpecial() { std::string v = mTokenizer.getValue(); return ( v == "-" || @@ -153,6 +204,7 @@ bool Parser::safeSpecial() { v == "," ); } +// Assert that we have a valid token, and not what we aren't expecting bool Parser::assertExp(const Tokenizer::TokenType& token) { //printf("T: %d:%d V: %s:%s\n", token, mTokenizer.getToken(), value.c_str(), // mTokenizer.getValue().c_str()); @@ -161,6 +213,7 @@ bool Parser::assertExp(const Tokenizer::TokenType& token) { return true; } +// Assert that we have a valid token, and not what we aren't expecting bool Parser::assertExp(const Tokenizer::TokenType& token, std::string value) { //printf("T: %d:%d V: %s:%s\n", token, mTokenizer.getToken(), value.c_str(), // mTokenizer.getValue().c_str()); @@ -172,29 +225,53 @@ bool Parser::assertExp(const Tokenizer::TokenType& token, std::string value) { } void Parser::Parse() { + // setup the lexer BLexicon lexer; mTokenizer.setLexer(lexer); + + // Loop until we hit the end of the file do { + + // Grab the next tokens mTokenizer.next(); + + // Skip whitespace if( mTokenizer.getToken() == Tokenizer::WS || mTokenizer.getToken() == Tokenizer::NL || mTokenizer.getToken() == Tokenizer::EOFTOK ) continue; + + // Error on a bad token if( mTokenizer.getToken() == Tokenizer::INVALID ) throw std::runtime_error("Unknown exception while parsing config"); + + // Get the current value //printf("Token: %s\n", toktostr(mTokenizer.getToken()).c_str()); std::string value = mTokenizer.getValue(); //printf("Value: %s [%d]\n", value.c_str(), value.size()); + + // Lowercase the value LOWER(value); + + // Make sure we hit a watch block (only current block) + // TODO(frostyfrog): Implement a config block (websocket reload?) if( value == "watch" ) { + + // Skip over the watch token mTokenizer.next(); + + // Skip whitespace, error if no whitespace if( mTokenizer.getToken() != Tokenizer::WS ) throw std::runtime_error("Unknown exception while parsing config"); else mTokenizer.next(); + + // Make sure that we can grab a word if( mTokenizer.getToken() != Tokenizer::WORD && mTokenizer.getToken() != Tokenizer::STRING ) throw std::runtime_error("Unknown exception while parsing config"); + + // Create a watcher pointer and set the last one to the current one CfgWatch* watcher; if( mLastWatcher == NULL ) { mWatchers = create_CfgWatch(); @@ -205,6 +282,8 @@ void Parser::Parse() { mLastWatcher->next = watcher; } watcher = mLastWatcher; + + // Parse the configuration file this->parseWatcher(watcher); } } while( mTokenizer.getToken() != Tokenizer::EOFTOK && From 7d8ebf78731e84476459d3513edbafeb75c7bed6 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Thu, 5 May 2016 15:08:52 -0600 Subject: [PATCH 16/28] Uncommitted parser changes --- src/parser/parser.cpp | 47 ++++++++++++++++++++++++++++++---------- src/parser/test.conf | 2 +- src/parser/tokenizer.cpp | 8 +++++++ 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 19b717d..e93b00e 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -26,6 +26,7 @@ Parser::Parser(std::string filename) : } Parser::~Parser() { + // TODO(frostyfrog): Cleanup } void Parser::parseWatcher(CfgWatch* watcher) { @@ -35,13 +36,15 @@ void Parser::parseWatcher(CfgWatch* watcher) { watcher->name[name.length()] = '\0'; // TODO(frostyfrog): Finish this - printf("Watcher [%s] found!\n", watcher->name); // Convert string to char array for (size_t i = 0; i < name.length(); i++) { watcher->name[i] = name[i]; } + // Debug: Print out the watcher's name + printf("Watcher [%s] found!\n", watcher->name); + // Move off the watch name token mTokenizer.next(); @@ -67,6 +70,7 @@ void Parser::parseWatcher(CfgWatch* watcher) { // Parse out the key in the key value pairs std::string key = parseKey(); + //LOWER(key) // Should we allow this? // Parse each valid section parseWatcherOptions(watcher, "files", key); @@ -103,6 +107,15 @@ void Parser::parseWatcher(CfgWatch* watcher) { // TODO(frostyfrog): Parse the insides of the step while( mTokenizer.getValue() != "}" ) { + if( mTokenizer.getToken() == Tokenizer::WORD ) { + key = parseKey(); + // Parse each valid section + printf("\t"); + parseWatcherOptions(watcher, "enabled", key); + parseWatcherOptions(watcher, "command", key); + parseWatcherOptions(watcher, "error_status", key); + parseWatcherOptions(watcher, "ignore_status", key); + } mTokenizer.next(); } @@ -118,17 +131,29 @@ void Parser::parseWatcher(CfgWatch* watcher) { // Parse just the options (key/value) std::vector Parser::parseWatcherOptions(CfgWatch* watcher, std::string expectKey, std::string key) { - std::vector values; - if( key != expectKey ) - return values; - parseEquals(); - values = parseValue(); - printf("K: %s V: ", key.c_str()); - for (auto value : values) { - printf("%s, ", value.c_str()); - } - printf("\n"); + + // Setup our vector + std::vector values; + + // Return an empty vector if this is the wrong code block + if( key != expectKey ) return values; + + // Get rid of the equals + parseEquals(); + + // Parse out the value + values = parseValue(); + + // Debug: Print out all values + printf("\t%s: ", key.c_str()); + for (auto value : values) { + printf("[%s]", value.c_str()); + } + printf("\n"); + + // Return the values + return values; } // Get the key for the key/value pair diff --git a/src/parser/test.conf b/src/parser/test.conf index 757d6b2..cd605e0 100644 --- a/src/parser/test.conf +++ b/src/parser/test.conf @@ -27,6 +27,6 @@ Watch "C++ Code Base" { ignore_status = 3-9, 27 } step Run { - command = "bin/my_program + command = "bin/my_program" } } diff --git a/src/parser/tokenizer.cpp b/src/parser/tokenizer.cpp index 7d0df06..527932a 100644 --- a/src/parser/tokenizer.cpp +++ b/src/parser/tokenizer.cpp @@ -98,6 +98,14 @@ char Tokenizer::nextChar() mCurrentChar++; mCurrentColumn++; c = mFile->get(); + if( c == '\n' ) { + mFile->get(); + if( c == '\r' ) + mFile->get(); + while(mFile->peek() == ' ' || mFile->peek() == '\t') + mFile->get(); + c = ' '; + } } if( c == '\n' || c == '\r' ) { mCurrentLine++; From ea81f693ae3dfe34f7fb50cee8c5eefe5df18eb8 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Thu, 28 Jul 2016 15:23:39 -0600 Subject: [PATCH 17/28] Committing backup of work --- .gitmodules | 3 + Makefile.am | 3 + configure.ac | 4 + renzoku.conf | 6 +- src/Makefile.am | 4 +- src/main.cpp | 19 +- src/parser/parser.cpp | 208 +- src/parser/parser.hpp | 32 +- src/parser/tokenizer.cpp | 50 +- src/parser/tokenizer.hpp | 7 +- tap-driver.sh | 651 ++ test.conf | 2 +- test_background.sh | 7 + ...14\343\202\223\343\201\236\343\201\217.sh" | 22 + tests/Makefile.am | 11 +- tests/bash-tap | 1 + tests/catch.hpp | 2314 ++-- tests/catch3.hpp | 9416 +++++++++++++++++ tests/run_tests.sh | 4 + tests/test_config.cpp | 135 +- tests/test_functional.sh | 60 + tests/test_process.cpp | 4 +- 22 files changed, 12111 insertions(+), 852 deletions(-) create mode 100644 .gitmodules create mode 100755 tap-driver.sh create mode 100755 test_background.sh create mode 100755 "test_\343\202\214\343\202\223\343\201\236\343\201\217.sh" create mode 160000 tests/bash-tap create mode 100644 tests/catch3.hpp create mode 100755 tests/run_tests.sh create mode 100755 tests/test_functional.sh diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..caf4a1a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/bash-tap"] + path = tests/bash-tap + url = https://github.com/rwhitworth/bash-tap.git diff --git a/Makefile.am b/Makefile.am index 5a3918a..e91cf2a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,2 +1,5 @@ +sh_TEST_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ + $(top_srcdir)/build-aux/tap-driver.sh + AUTOMAKE_OPTIONS = foreign SUBDIRS = src src/config doc examples man tests diff --git a/configure.ac b/configure.ac index 54f8a03..3b6900a 100644 --- a/configure.ac +++ b/configure.ac @@ -3,6 +3,7 @@ AC_PREREQ([2.69]) AC_INIT(renzoku, 0.1.0, renzoku@frostyfrog.net) +AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE # Checks for programs. @@ -97,6 +98,9 @@ AC_CHECK_FUNCS([getcwd gettimeofday memset select strerror]) # Static Library AC_PROG_RANLIB +AC_REQUIRE_AUX_FILE([tap-driver.sh]) +AC_PROG_AWK + #AC_CONFIG_FILES([Makefile]) AC_OUTPUT(Makefile src/Makefile doc/Makefile examples/Makefile man/Makefile tests/Makefile src/config/Makefile src/parser/Makefile) diff --git a/renzoku.conf b/renzoku.conf index d1eb302..98a68db 100644 --- a/renzoku.conf +++ b/renzoku.conf @@ -1,5 +1,6 @@ watch = { - Filter = ["*.cpp", "*.hpp", "*.c", "*.h", "include/*.hxx", "*.conf"] + Filter = ["*.cpp", "*.hpp", "*.c", "*.h", "include/*.hxx", "*.conf", + "test_*.sh"] Include = "Makefile" Exclude = "src/Config.cpp" } @@ -13,7 +14,8 @@ Test = { Enabled = false } program = { - command = "./src/parser/parser" + command = "make check" + #command = "./test_れんぞく.sh" # command: /usr/bin/echo Hello World Enabled = true } diff --git a/src/Makefile.am b/src/Makefile.am index 2982ffe..fb13d6b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,6 @@ #CFLAGS = --pedantic -Wall -std=c99 -O2 #CXXFLAGS = -Wall -g -Isrc/ -static -lyaml-cpp -std=c++11 -AM_CXXFLAGS = --pedantic -Wall -O2 -g -std=c++11 +AM_CXXFLAGS = --pedantic -Wall -O2 -g -std=c++11 -Iparser/ AM_LDFLAGS = -Bstatic -lconfig++ -Bdynamic SUBDIRS = parser @@ -38,7 +38,7 @@ librenzoku_a_SOURCES = \ bin_PROGRAMS = renzoku renzokudir = $(pkglibdir)/@PACKAGE@ -renzoku_LDADD = librenzoku.a +renzoku_LDADD = librenzoku.a parser/libparser.a renzoku_SOURCES = \ main.hpp \ main.cpp diff --git a/src/main.cpp b/src/main.cpp index 12dc0ee..6766ca5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,6 +42,7 @@ #include "watcher.hpp" #include "procman.hpp" #include +#include "parser/parser.hpp" volatile sig_atomic_t gRunning = 1; @@ -53,20 +54,28 @@ int main(int argc, char** argv) { //watcher(); StdoutLogger logger; ProcessManager procman(&logger); - Config* config; + /*Config* config; try { config = new Config(&logger, &procman); } catch ( const libconfig::ParseException &pex ) { fprintf(stderr, "Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); return EXIT_FAILURE; + }*/ + Parser p("config.conf"); + try { + p.Parse(); + } catch ( const libconfig::ParseException &pex ) { + fprintf(stderr, "Parse error at %s:%d - %s\n", pex.getFile(), + pex.getLine(), pex.getError()); + return EXIT_FAILURE; } - Watcher w(&logger, "./", *config, &procman, true); + //Watcher w(&logger, "./", *config, &procman, true); - while(gRunning) - w.listen(); + //while(gRunning) + // w.listen(); - delete config; + //delete config; return EXIT_SUCCESS; } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index e93b00e..d9f9976 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -6,11 +6,21 @@ #define LOWER(x) std::transform(x.begin(), x.end(), x.begin(),\ ::tolower); +CfgStep* create_CfgStep() { + CfgStep* step = new CfgStep; + step->name = NULL; + step->command = NULL; + step->daemon = false; + step->enabled = false; + step->error_status = NULL; + step->ignore_status = NULL; + step->next = NULL; + return step; +} + CfgWatch* create_CfgWatch() { CfgWatch* watcher = new CfgWatch; watcher->name = NULL; - watcher->filesFilter = NULL; - watcher->excludesFilter = NULL; watcher->workingDir = NULL; watcher->steps = NULL; watcher->next = NULL; @@ -25,23 +35,48 @@ Parser::Parser(std::string filename) : { } +Parser::Parser(std::istream& file) : + mFilename(""), + mTokenizer(mFilename, file), + mWatchers(NULL), + mLastWatcher(NULL) +{ +} + Parser::~Parser() { // TODO(frostyfrog): Cleanup + if( mWatchers != NULL ) { + CfgWatch *watcher = mWatchers; + CfgWatch *tmpwatcher = watcher->next; + while( watcher != mLastWatcher ) { + tmpwatcher = watcher->next; + delete [] watcher->name; + delete watcher; + watcher = tmpwatcher; + } + delete [] watcher->name; + delete watcher; + } +} + +char* Parser::mStrToChar(const std::string& str) { + char* retval = new char[str.length()+1]; + retval[str.length()] = '\0'; + + // Convert string to char array + for (size_t i = 0; i < str.length(); i++) { + retval[i] = str[i]; + } + return retval; } void Parser::parseWatcher(CfgWatch* watcher) { // Initialize local variables std::string name = mTokenizer.getValue(); - watcher->name = new char[name.length()+1]; - watcher->name[name.length()] = '\0'; + watcher->name = mStrToChar(name); // TODO(frostyfrog): Finish this - // Convert string to char array - for (size_t i = 0; i < name.length(); i++) { - watcher->name[i] = name[i]; - } - // Debug: Print out the watcher's name printf("Watcher [%s] found!\n", watcher->name); @@ -73,9 +108,12 @@ void Parser::parseWatcher(CfgWatch* watcher) { //LOWER(key) // Should we allow this? // Parse each valid section - parseWatcherOptions(watcher, "files", key); - parseWatcherOptions(watcher, "exclude", key); - parseWatcherOptions(watcher, "dir", key); + if( mIsKey("files", key) ) + watcher->filesFilter = parseWatcherOptions(watcher, key); + if( mIsKey("exclude", key) ) + watcher->excludesFilter = parseWatcherOptions(watcher, key); + if ( mIsKey("dir", key) ) + parseWatcherOptions(watcher, key); // We are now parsing a step if( key == "step" ) @@ -95,6 +133,8 @@ void Parser::parseWatcher(CfgWatch* watcher) { // Get the name of the current step std::string name_step = mTokenizer.getValue(); printf("\tStep: %s\n", name_step.c_str()); + CfgStep* step = create_CfgStep(); + step->name = mStrToChar(name_step); // Skip whitespace mTokenizer.next(); @@ -111,16 +151,29 @@ void Parser::parseWatcher(CfgWatch* watcher) { key = parseKey(); // Parse each valid section printf("\t"); - parseWatcherOptions(watcher, "enabled", key); - parseWatcherOptions(watcher, "command", key); - parseWatcherOptions(watcher, "error_status", key); - parseWatcherOptions(watcher, "ignore_status", key); + if (mIsKey("enabled", key)) + parseBooleanOption(watcher, key); + if (mIsKey("command", key)) + parseWatcherOption(watcher, key); + if (mIsKey("error_status", key)) + parseWatcherOptions(watcher, key); + if (mIsKey("ignore_status", key)) + parseWatcherOptions(watcher, key); } mTokenizer.next(); } // Skip the } mTokenizer.next(); + if (!watcher->steps) { + watcher->steps = step; + } else { + CfgStep* tmpstep = step; + while(tmpstep->next) { + tmpstep = tmpstep->next; + } + tmpstep->next = step; + } } // TODO(frostyfrog): Figure out why this was here again... @@ -128,17 +181,53 @@ void Parser::parseWatcher(CfgWatch* watcher) { } } +// Determine if we have the right key +bool Parser::mIsKey(const std::string& expectKey, const std::string& key) { + //mTokenizer.next(); + printf("(%s != %s) == %d\n", key.c_str(), expectKey.c_str(), ( key != expectKey )); + return ( key == expectKey ); +} + +// Parse out boolean values +bool Parser::parseBooleanOption(CfgWatch* watcher, std::string key) { + + // Get rid of the equals + parseEquals(); + + // Parse out the value + bool values = parseBoolean(); + + // Debug: Print out all values + printf("\t%s: ", key.c_str()); + printf("\n"); + + // Return the values + return values; +} + +std::string Parser::parseWatcherOption(CfgWatch* watcher, std::string key) { + + // Get rid of the equals + parseEquals(); + + // Parse out the value + char* values = mStrToChar(parseString()); + + // Debug: Print out all values + printf("\t%s: ", key.c_str()); + printf("\n"); + + // Return the values + return values; +} + // Parse just the options (key/value) std::vector Parser::parseWatcherOptions(CfgWatch* watcher, - std::string expectKey, std::string key) { + std::string key) { // Setup our vector std::vector values; - // Return an empty vector if this is the wrong code block - if( key != expectKey ) - return values; - // Get rid of the equals parseEquals(); @@ -193,11 +282,10 @@ const std::vector Parser::parseValue() { { // Cache the token and check it's validity t = mTokenizer.getToken(); - //printf("V: %s\n", mTokenizer.getValue().c_str()); if( t == Tokenizer::EOFTOK || t == Tokenizer::INVALID || t == Tokenizer::SPECIAL && !safeSpecial()) - throw std::runtime_error("Unknown exception while parsing config"); + mTokenizer.invokeError("Unexpected character"); //printf("V: %s\n", mTokenizer.getValue().c_str()); @@ -220,6 +308,48 @@ const std::vector Parser::parseValue() { return values; } +const std::string Parser::parseString() { + // Initialize the variables + Tokenizer::TokenType t; + std::string retval; + + // Loop until the end of the value + if( mTokenizer.getToken() == Tokenizer::WS ) + mTokenizer.next(); + if( mTokenizer.getToken() != Tokenizer::STRING ) + mTokenizer.invokeError("Unexpected character"); + printf("T: %d V: %s\n", mTokenizer.getToken(), + mTokenizer.getValue().c_str()); + retval = mTokenizer.getValue(); + mTokenizer.next(); + if( mTokenizer.getToken() == Tokenizer::EOFTOK || + mTokenizer.getToken() == Tokenizer::INVALID || + mTokenizer.getToken() == Tokenizer::SPECIAL && !safeSpecial()) + mTokenizer.invokeError("Unexpected character"); + return retval; +} + +const bool Parser::parseBoolean() { + // Initialize the variables + Tokenizer::TokenType t; + bool retval; + + // Loop until the end of the value + if( t != Tokenizer::WS ) + mTokenizer.next(); + if( t != Tokenizer::BOOLEAN ) + mTokenizer.invokeError("Unexpected character"); + retval = mTokenizer.getValue() == "y"; + mTokenizer.next(); + if( t == Tokenizer::EOFTOK || + t == Tokenizer::INVALID || + mTokenizer.getValue() != ";" || + t == Tokenizer::SPECIAL && !safeSpecial()) + mTokenizer.invokeError("Unexpected character"); + mTokenizer.next(); + return retval; +} + // Check that the special token should be considered "safe" bool Parser::safeSpecial() { std::string v = mTokenizer.getValue(); @@ -233,8 +363,13 @@ bool Parser::safeSpecial() { bool Parser::assertExp(const Tokenizer::TokenType& token) { //printf("T: %d:%d V: %s:%s\n", token, mTokenizer.getToken(), value.c_str(), // mTokenizer.getValue().c_str()); - if( mTokenizer.getToken() != token ) - throw std::runtime_error("Unknown exception while parsing config"); + if( mTokenizer.getToken() != token ) { + std::string token_str = toktostr(token); + std::string token_str2 = toktostr(mTokenizer.getToken()); + std::string msg = "Expected " + token_str + ", got " + token_str2; + mTokenizer.invokeError(msg); + //throw std::runtime_error("Unknown exception while parsing config"); + } return true; } @@ -242,10 +377,19 @@ bool Parser::assertExp(const Tokenizer::TokenType& token) { bool Parser::assertExp(const Tokenizer::TokenType& token, std::string value) { //printf("T: %d:%d V: %s:%s\n", token, mTokenizer.getToken(), value.c_str(), // mTokenizer.getValue().c_str()); - if( mTokenizer.getToken() != token ) - throw std::runtime_error("Unknown exception while parsing config"); - if( mTokenizer.getValue() != value ) + if( mTokenizer.getToken() != token ) { + std::string token_str = toktostr(token); + std::string token_str2 = toktostr(mTokenizer.getToken()); + std::string msg = "Expected " + token_str + ", got " + token_str2; + mTokenizer.invokeError(msg); + //throw std::runtime_error("Unknown exception while parsing config"); + } + if( mTokenizer.getValue() != value ) { + std::string msg = "Expected \"" + value + "\", got " + + mTokenizer.getValue(); + mTokenizer.invokeError(msg); throw std::runtime_error("Unknown exception while parsing config"); + } return true; } @@ -303,8 +447,8 @@ void Parser::Parse() { mLastWatcher = mWatchers; } else { watcher = create_CfgWatch(); - mLastWatcher = watcher; mLastWatcher->next = watcher; + mLastWatcher = watcher; } watcher = mLastWatcher; @@ -315,8 +459,10 @@ void Parser::Parse() { mTokenizer.getToken() != Tokenizer::INVALID ); } -const CfgWatch* Parser::begin() const { +CfgWatchIter Parser::begin() const { + return CfgWatchIter(mWatchers); } -const CfgWatch* Parser::end() const { +CfgWatchIter Parser::end() const { + return CfgWatchIter(); } diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 0edf70a..ffbf56b 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -16,12 +16,26 @@ struct CfgStep { struct CfgWatch { char* name; // Required - char** filesFilter; // Required - char** excludesFilter; // Optional, default: NULL + std::vector filesFilter; // Required + std::vector excludesFilter; // Optional, default: NULL char* workingDir; // Optional, default: cwd CfgStep* steps; // Require at least one CfgWatch* next; // Optional, Can be NULL }; +class CfgWatchIter { + private: + CfgWatch* node; + public: + inline CfgWatchIter(CfgWatch* node=NULL) { this->node = node; } + inline CfgWatchIter& operator++() { node = node->next; return *this; } + inline const CfgWatch* operator*() { return node; } + inline bool operator==(const CfgWatchIter& x) const { + return node == x.node; + } + inline bool operator!=(const CfgWatchIter& x) const { + return node != x.node; + } +}; class Parser { private: @@ -36,14 +50,22 @@ class Parser { const std::string& parseKey(); void parseEquals(); const std::vector parseValue(); + const bool parseBoolean(); + const std::string parseString(); bool safeSpecial(); - std::vector parseWatcherOptions(CfgWatch*, std::string expectedKey, std::string key); + std::vector parseWatcherOptions(CfgWatch*, std::string key); + std::string parseWatcherOption(CfgWatch*, std::string key); + bool parseBooleanOption(CfgWatch*, std::string key); + bool mIsKey(const std::string& expectKey, const std::string& key); + char* mStrToChar(const std::string&); public: virtual ~Parser(); Parser(std::string filename); + Parser(std::istream& file); void Parse(); - const CfgWatch* begin() const; - const CfgWatch* end() const; + CfgWatchIter begin() const; + CfgWatchIter end() const; + const CfgWatch* getWatchers() const { return mWatchers; } }; /* Example Config format: diff --git a/src/parser/tokenizer.cpp b/src/parser/tokenizer.cpp index 527932a..612b7de 100644 --- a/src/parser/tokenizer.cpp +++ b/src/parser/tokenizer.cpp @@ -50,7 +50,8 @@ Tokenizer::Tokenizer(std::string filename) : mCurrentChar(0), mCurrentLine(1), mCurrentColumn(0), - mFile(NULL) + mFile(NULL), + mFileOwnership(true) { std::ifstream* file = new std::ifstream(); //file->exceptions( std::ifstream::failbit | std::ifstream::badbit ); @@ -79,9 +80,26 @@ Tokenizer::Tokenizer(std::string filename) : } } +Tokenizer::Tokenizer(std::string filename, std::istream& file) : + mCurrentChar(0), + mCurrentLine(1), + mCurrentColumn(0), + mFile(NULL), + mFileOwnership(false) +{ + mFilename = filename; + if (file.good()) { + mFile = &file; + } + else { + throw std::runtime_error("File not found!"); + } +} + Tokenizer::~Tokenizer() { - if (mFile) { + if (mFile && mFileOwnership) { ((std::ifstream*)mFile)->close(); + delete mFile; } } @@ -162,6 +180,14 @@ void Tokenizer::next() lexicon = mLexer->getLexicon(c); } while( c != EOF && lexicon == oldlexicon ); mToken = WORD; + if( mValue == "true" || mValue == "yes") { + mToken = BOOLEAN; + mValue = "y"; + } + else if( mValue == "false" || mValue == "no") { + mToken = BOOLEAN; + mValue = "n"; + } } else if( lexicon == BLexicon::SPECIAL ) { if( c == '"' || c == '\'' ) { @@ -174,6 +200,10 @@ void Tokenizer::next() do { mValue += nextChar(); c = mFile->peek(); + if( c == '\\' ) { + nextChar(); + c = mFile->peek(); + } } while( mFile->good() && c != EOF && c != quotemarker ); if( c == quotemarker && mFile->good()) nextChar(); @@ -190,6 +220,22 @@ void Tokenizer::next() } } +void Tokenizer::invokeError(const std::string& msg) { + size_t parse_start_line = 0; + size_t parse_start_column = 0; + size_t parse_start_pos = mCurrentChar; + parse_start_pos = mCurrentChar; + parse_start_line = mCurrentLine; + parse_start_column = mCurrentColumn; + THROWPARSE(msg); + /* + throw ParserException(msg, mFilename, \ + parse_start_line ? parse_start_line : mCurrentLine, \ + parse_start_line ? parse_start_column-6 : mCurrentColumn, \ + getCurrentLine(parse_start_pos, parse_start_column)); + // */ +} + void Tokenizer::setLexer(BLexicon& lexer) { mLexer = &lexer; } diff --git a/src/parser/tokenizer.hpp b/src/parser/tokenizer.hpp index ec84f9c..2728e56 100644 --- a/src/parser/tokenizer.hpp +++ b/src/parser/tokenizer.hpp @@ -41,7 +41,7 @@ #include #include #include -#include +#include "lexicon.hpp" #include class ParserException : public std::runtime_error { @@ -102,11 +102,13 @@ class Tokenizer { TokenType mToken; BLexicon* mLexer; std::string mFilename; - std::ifstream* mFile; + std::istream* mFile; + bool mFileOwnership; char nextChar(); public: Tokenizer(std::string filename); + Tokenizer(std::string filename, std::istream& file); virtual ~Tokenizer(); void setLexer(BLexicon&); void next(); @@ -117,6 +119,7 @@ class Tokenizer { const size_t getLinePosition() const { return mCurrentLine; } const size_t getColumnPosition() const { return mCurrentColumn; } const size_t getFilePosition() const { return mCurrentChar; } + void invokeError(const std::string& msg); }; std::string toktostr(const Tokenizer::TokenType& t); diff --git a/tap-driver.sh b/tap-driver.sh new file mode 100755 index 0000000..4254e2b --- /dev/null +++ b/tap-driver.sh @@ -0,0 +1,651 @@ +#! /bin/sh +# Copyright (C) 2011-2014 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +scriptversion=2013-12-23.17; # UTC + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +me=tap-driver.sh + +fatal () +{ + echo "$me: fatal: $*" >&2 + exit 1 +} + +usage_error () +{ + echo "$me: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat < + # + trap : 1 3 2 13 15 + if test $merge -gt 0; then + exec 2>&1 + else + exec 2>&3 + fi + "$@" + echo $? + ) | LC_ALL=C ${AM_TAP_AWK-awk} \ + -v me="$me" \ + -v test_script_name="$test_name" \ + -v log_file="$log_file" \ + -v trs_file="$trs_file" \ + -v expect_failure="$expect_failure" \ + -v merge="$merge" \ + -v ignore_exit="$ignore_exit" \ + -v comments="$comments" \ + -v diag_string="$diag_string" \ +' +# TODO: the usages of "cat >&3" below could be optimized when using +# GNU awk, and/on on systems that supports /dev/fd/. + +# Implementation note: in what follows, `result_obj` will be an +# associative array that (partly) simulates a TAP result object +# from the `TAP::Parser` perl module. + +## ----------- ## +## FUNCTIONS ## +## ----------- ## + +function fatal(msg) +{ + print me ": " msg | "cat >&2" + exit 1 +} + +function abort(where) +{ + fatal("internal error " where) +} + +# Convert a boolean to a "yes"/"no" string. +function yn(bool) +{ + return bool ? "yes" : "no"; +} + +function add_test_result(result) +{ + if (!test_results_index) + test_results_index = 0 + test_results_list[test_results_index] = result + test_results_index += 1 + test_results_seen[result] = 1; +} + +# Whether the test script should be re-run by "make recheck". +function must_recheck() +{ + for (k in test_results_seen) + if (k != "XFAIL" && k != "PASS" && k != "SKIP") + return 1 + return 0 +} + +# Whether the content of the log file associated to this test should +# be copied into the "global" test-suite.log. +function copy_in_global_log() +{ + for (k in test_results_seen) + if (k != "PASS") + return 1 + return 0 +} + +function get_global_test_result() +{ + if ("ERROR" in test_results_seen) + return "ERROR" + if ("FAIL" in test_results_seen || "XPASS" in test_results_seen) + return "FAIL" + all_skipped = 1 + for (k in test_results_seen) + if (k != "SKIP") + all_skipped = 0 + if (all_skipped) + return "SKIP" + return "PASS"; +} + +function stringify_result_obj(result_obj) +{ + if (result_obj["is_unplanned"] || result_obj["number"] != testno) + return "ERROR" + + if (plan_seen == LATE_PLAN) + return "ERROR" + + if (result_obj["directive"] == "TODO") + return result_obj["is_ok"] ? "XPASS" : "XFAIL" + + if (result_obj["directive"] == "SKIP") + return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL; + + if (length(result_obj["directive"])) + abort("in function stringify_result_obj()") + + return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL +} + +function decorate_result(result) +{ + color_name = color_for_result[result] + if (color_name) + return color_map[color_name] "" result "" color_map["std"] + # If we are not using colorized output, or if we do not know how + # to colorize the given result, we should return it unchanged. + return result +} + +function report(result, details) +{ + if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/) + { + msg = ": " test_script_name + add_test_result(result) + } + else if (result == "#") + { + msg = " " test_script_name ":" + } + else + { + abort("in function report()") + } + if (length(details)) + msg = msg " " details + # Output on console might be colorized. + print decorate_result(result) msg + # Log the result in the log file too, to help debugging (this is + # especially true when said result is a TAP error or "Bail out!"). + print result msg | "cat >&3"; +} + +function testsuite_error(error_message) +{ + report("ERROR", "- " error_message) +} + +function handle_tap_result() +{ + details = result_obj["number"]; + if (length(result_obj["description"])) + details = details " " result_obj["description"] + + if (plan_seen == LATE_PLAN) + { + details = details " # AFTER LATE PLAN"; + } + else if (result_obj["is_unplanned"]) + { + details = details " # UNPLANNED"; + } + else if (result_obj["number"] != testno) + { + details = sprintf("%s # OUT-OF-ORDER (expecting %d)", + details, testno); + } + else if (result_obj["directive"]) + { + details = details " # " result_obj["directive"]; + if (length(result_obj["explanation"])) + details = details " " result_obj["explanation"] + } + + report(stringify_result_obj(result_obj), details) +} + +# `skip_reason` should be empty whenever planned > 0. +function handle_tap_plan(planned, skip_reason) +{ + planned += 0 # Avoid getting confused if, say, `planned` is "00" + if (length(skip_reason) && planned > 0) + abort("in function handle_tap_plan()") + if (plan_seen) + { + # Error, only one plan per stream is acceptable. + testsuite_error("multiple test plans") + return; + } + planned_tests = planned + # The TAP plan can come before or after *all* the TAP results; we speak + # respectively of an "early" or a "late" plan. If we see the plan line + # after at least one TAP result has been seen, assume we have a late + # plan; in this case, any further test result seen after the plan will + # be flagged as an error. + plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN) + # If testno > 0, we have an error ("too many tests run") that will be + # automatically dealt with later, so do not worry about it here. If + # $plan_seen is true, we have an error due to a repeated plan, and that + # has already been dealt with above. Otherwise, we have a valid "plan + # with SKIP" specification, and should report it as a particular kind + # of SKIP result. + if (planned == 0 && testno == 0) + { + if (length(skip_reason)) + skip_reason = "- " skip_reason; + report("SKIP", skip_reason); + } +} + +function extract_tap_comment(line) +{ + if (index(line, diag_string) == 1) + { + # Strip leading `diag_string` from `line`. + line = substr(line, length(diag_string) + 1) + # And strip any leading and trailing whitespace left. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + # Return what is left (if any). + return line; + } + return ""; +} + +# When this function is called, we know that line is a TAP result line, +# so that it matches the (perl) RE "^(not )?ok\b". +function setup_result_obj(line) +{ + # Get the result, and remove it from the line. + result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0) + sub("^(not )?ok[ \t]*", "", line) + + # If the result has an explicit number, get it and strip it; otherwise, + # automatically assing the next progresive number to it. + if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/) + { + match(line, "^[0-9]+") + # The final `+ 0` is to normalize numbers with leading zeros. + result_obj["number"] = substr(line, 1, RLENGTH) + 0 + line = substr(line, RLENGTH + 1) + } + else + { + result_obj["number"] = testno + } + + if (plan_seen == LATE_PLAN) + # No further test results are acceptable after a "late" TAP plan + # has been seen. + result_obj["is_unplanned"] = 1 + else if (plan_seen && testno > planned_tests) + result_obj["is_unplanned"] = 1 + else + result_obj["is_unplanned"] = 0 + + # Strip trailing and leading whitespace. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + + # This will have to be corrected if we have a "TODO"/"SKIP" directive. + result_obj["description"] = line + result_obj["directive"] = "" + result_obj["explanation"] = "" + + if (index(line, "#") == 0) + return # No possible directive, nothing more to do. + + # Directives are case-insensitive. + rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*" + + # See whether we have the directive, and if yes, where. + pos = match(line, rx "$") + if (!pos) + pos = match(line, rx "[^a-zA-Z0-9_]") + + # If there was no TAP directive, we have nothing more to do. + if (!pos) + return + + # Let`s now see if the TAP directive has been escaped. For example: + # escaped: ok \# SKIP + # not escaped: ok \\# SKIP + # escaped: ok \\\\\# SKIP + # not escaped: ok \ # SKIP + if (substr(line, pos, 1) == "#") + { + bslash_count = 0 + for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--) + bslash_count += 1 + if (bslash_count % 2) + return # Directive was escaped. + } + + # Strip the directive and its explanation (if any) from the test + # description. + result_obj["description"] = substr(line, 1, pos - 1) + # Now remove the test description from the line, that has been dealt + # with already. + line = substr(line, pos) + # Strip the directive, and save its value (normalized to upper case). + sub("^[ \t]*#[ \t]*", "", line) + result_obj["directive"] = toupper(substr(line, 1, 4)) + line = substr(line, 5) + # Now get the explanation for the directive (if any), with leading + # and trailing whitespace removed. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + result_obj["explanation"] = line +} + +function get_test_exit_message(status) +{ + if (status == 0) + return "" + if (status !~ /^[1-9][0-9]*$/) + abort("getting exit status") + if (status < 127) + exit_details = "" + else if (status == 127) + exit_details = " (command not found?)" + else if (status >= 128 && status <= 255) + exit_details = sprintf(" (terminated by signal %d?)", status - 128) + else if (status > 256 && status <= 384) + # We used to report an "abnormal termination" here, but some Korn + # shells, when a child process die due to signal number n, can leave + # in $? an exit status of 256+n instead of the more standard 128+n. + # Apparently, both behaviours are allowed by POSIX (2008), so be + # prepared to handle them both. See also Austing Group report ID + # 0000051 + exit_details = sprintf(" (terminated by signal %d?)", status - 256) + else + # Never seen in practice. + exit_details = " (abnormal termination)" + return sprintf("exited with status %d%s", status, exit_details) +} + +function write_test_results() +{ + print ":global-test-result: " get_global_test_result() > trs_file + print ":recheck: " yn(must_recheck()) > trs_file + print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file + for (i = 0; i < test_results_index; i += 1) + print ":test-result: " test_results_list[i] > trs_file + close(trs_file); +} + +BEGIN { + +## ------- ## +## SETUP ## +## ------- ## + +'"$init_colors"' + +# Properly initialized once the TAP plan is seen. +planned_tests = 0 + +COOKED_PASS = expect_failure ? "XPASS": "PASS"; +COOKED_FAIL = expect_failure ? "XFAIL": "FAIL"; + +# Enumeration-like constants to remember which kind of plan (if any) +# has been seen. It is important that NO_PLAN evaluates "false" as +# a boolean. +NO_PLAN = 0 +EARLY_PLAN = 1 +LATE_PLAN = 2 + +testno = 0 # Number of test results seen so far. +bailed_out = 0 # Whether a "Bail out!" directive has been seen. + +# Whether the TAP plan has been seen or not, and if yes, which kind +# it is ("early" is seen before any test result, "late" otherwise). +plan_seen = NO_PLAN + +## --------- ## +## PARSING ## +## --------- ## + +is_first_read = 1 + +while (1) + { + # Involutions required so that we are able to read the exit status + # from the last input line. + st = getline + if (st < 0) # I/O error. + fatal("I/O error while reading from input stream") + else if (st == 0) # End-of-input + { + if (is_first_read) + abort("in input loop: only one input line") + break + } + if (is_first_read) + { + is_first_read = 0 + nextline = $0 + continue + } + else + { + curline = nextline + nextline = $0 + $0 = curline + } + # Copy any input line verbatim into the log file. + print | "cat >&3" + # Parsing of TAP input should stop after a "Bail out!" directive. + if (bailed_out) + continue + + # TAP test result. + if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/) + { + testno += 1 + setup_result_obj($0) + handle_tap_result() + } + # TAP plan (normal or "SKIP" without explanation). + else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/) + { + # The next two lines will put the number of planned tests in $0. + sub("^1\\.\\.", "") + sub("[^0-9]*$", "") + handle_tap_plan($0, "") + continue + } + # TAP "SKIP" plan, with an explanation. + else if ($0 ~ /^1\.\.0+[ \t]*#/) + { + # The next lines will put the skip explanation in $0, stripping + # any leading and trailing whitespace. This is a little more + # tricky in truth, since we want to also strip a potential leading + # "SKIP" string from the message. + sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "") + sub("[ \t]*$", ""); + handle_tap_plan(0, $0) + } + # "Bail out!" magic. + # Older versions of prove and TAP::Harness (e.g., 3.17) did not + # recognize a "Bail out!" directive when preceded by leading + # whitespace, but more modern versions (e.g., 3.23) do. So we + # emulate the latter, "more modern" behaviour. + else if ($0 ~ /^[ \t]*Bail out!/) + { + bailed_out = 1 + # Get the bailout message (if any), with leading and trailing + # whitespace stripped. The message remains stored in `$0`. + sub("^[ \t]*Bail out![ \t]*", ""); + sub("[ \t]*$", ""); + # Format the error message for the + bailout_message = "Bail out!" + if (length($0)) + bailout_message = bailout_message " " $0 + testsuite_error(bailout_message) + } + # Maybe we have too look for dianogtic comments too. + else if (comments != 0) + { + comment = extract_tap_comment($0); + if (length(comment)) + report("#", comment); + } + } + +## -------- ## +## FINISH ## +## -------- ## + +# A "Bail out!" directive should cause us to ignore any following TAP +# error, as well as a non-zero exit status from the TAP producer. +if (!bailed_out) + { + if (!plan_seen) + { + testsuite_error("missing test plan") + } + else if (planned_tests != testno) + { + bad_amount = testno > planned_tests ? "many" : "few" + testsuite_error(sprintf("too %s tests run (expected %d, got %d)", + bad_amount, planned_tests, testno)) + } + if (!ignore_exit) + { + # Fetch exit status from the last line. + exit_message = get_test_exit_message(nextline) + if (exit_message) + testsuite_error(exit_message) + } + } + +write_test_results() + +exit 0 + +} # End of "BEGIN" block. +' + +# TODO: document that we consume the file descriptor 3 :-( +} 3>"$log_file" + +test $? -eq 0 || fatal "I/O or internal error" + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/test.conf b/test.conf index a09279c..a300ae0 100644 --- a/test.conf +++ b/test.conf @@ -3,7 +3,7 @@ Watch CSS { exclude = "/css/compiled_*" dir = "src/html" step Compile { - command = "lessc --clean-css=--compatibility=ie8 --advanced \ + command = "lessc --clean-css=--compatibility=ie8 --advanced\ less/main.less css/compiled_style.css" } } diff --git a/test_background.sh b/test_background.sh new file mode 100755 index 0000000..8a67578 --- /dev/null +++ b/test_background.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +sleep 2; +echo "Test file 1" > test_1 +echo "Test file 2" > test_2 +echo "Dummy file 1" > test2_1 +echo "Dummy file 2" > test2_2 diff --git "a/test_\343\202\214\343\202\223\343\201\236\343\201\217.sh" "b/test_\343\202\214\343\202\223\343\201\236\343\201\217.sh" new file mode 100755 index 0000000..eecac27 --- /dev/null +++ "b/test_\343\202\214\343\202\223\343\201\236\343\201\217.sh" @@ -0,0 +1,22 @@ +#!/bin/bash +rm -rf test_れんぞく || echo "Failed to remove tmp test directory" +mkdir test_れんぞく +cd test_れんぞく +cat < config.conf +Watch Dummy { + files = "test_*" + step "Cat dummy files" { + command = "bash -c \\"cat test_*\\"" + } +} +Watch "Second Dummy Listener" { + files = "test2_*" + step "Cat dummy files" { + command = "bash -c \\"cat test2_*\\"" + } +} +EOF +echo "Using config:" +cat config.conf +../test_background.sh & +exec ../src/renzoku diff --git a/tests/Makefile.am b/tests/Makefile.am index 43b5332..25523b5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,4 +1,4 @@ -AM_CXXFLAGS = --pedantic -Wall -I. -I../src/ -g -std=c++11 +AM_CXXFLAGS = --pedantic -Wall -I. -I../src/ -g -std=c++11 -I../src/parser/ AM_LDFLAGS = -lyaml-cpp check_PROGRAMS = test_runner test_runner_SOURCES = \ @@ -8,6 +8,11 @@ test_main.cpp \ test_process.cpp \ test_util.cpp #test_tokenizer.cpp -test_runner_LDADD = ../src/librenzoku.a +test_runner_LDADD = ../src/librenzoku.a ../src/parser/libparser.a -TESTS = test_runner +TEST_EXTENSIONS = .sh +SH_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ + $(top_srcdir)/build-aux/tap-driver.sh +#TESTS = test_runner +TESTS = run_tests.sh test_functional.sh +EXTRA_DIST = $(TESTS) diff --git a/tests/bash-tap b/tests/bash-tap new file mode 160000 index 0000000..e8f9227 --- /dev/null +++ b/tests/bash-tap @@ -0,0 +1 @@ +Subproject commit e8f922780356749223dfa77ab5931f18eb502e6a diff --git a/tests/catch.hpp b/tests/catch.hpp index de61226..b2bae97 100644 --- a/tests/catch.hpp +++ b/tests/catch.hpp @@ -1,6 +1,6 @@ /* - * Catch v1.2.1 - * Generated: 2015-06-30 18:23:27.961086 + * Catch v1.2.1-develop.15 + * Generated: 2016-07-01 00:21:46.414928 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. @@ -21,8 +21,6 @@ // #included from: internal/catch_suppress_warnings.h -#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED - #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) @@ -44,7 +42,6 @@ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpadded" #endif - #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL #endif @@ -84,11 +81,19 @@ // CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods // CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? // CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported +// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? +// CATCH_CONFIG_CPP11_OVERRIDE : is override supported? +// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) // CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + // In general each macro has a _NO_ form // (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they @@ -130,10 +135,13 @@ // GCC #ifdef __GNUC__ -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) +#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR #endif +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// @@ -142,6 +150,7 @@ #if (_MSC_VER >= 1600) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) @@ -151,6 +160,8 @@ #endif // _MSC_VER +//////////////////////////////////////////////////////////////////////////////// + // Use variadic macros if the compiler supports them #if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ @@ -165,7 +176,7 @@ // C++ language feature support // catch all support for C++11 -#if (__cplusplus >= 201103L) +#if defined(__cplusplus) && __cplusplus >= 201103L # define CATCH_CPP11_OR_GREATER @@ -193,6 +204,17 @@ # define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS # endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) +# define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) +# define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +# endif + #endif // __cplusplus >= 201103L // Now set the actual defines based on the above + anything the user has configured @@ -212,7 +234,16 @@ # define CATCH_CONFIG_CPP11_TUPLE #endif #if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) -#define CATCH_CONFIG_VARIADIC_MACROS +# define CATCH_CONFIG_VARIADIC_MACROS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_LONG_LONG +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_UNIQUE_PTR #endif // noexcept support: @@ -224,8 +255,36 @@ # define CATCH_NOEXCEPT_IS(x) #endif +// nullptr support +#ifdef CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_NULL nullptr +#else +# define CATCH_NULL NULL +#endif + +// override support +#ifdef CATCH_CONFIG_CPP11_OVERRIDE +# define CATCH_OVERRIDE override +#else +# define CATCH_OVERRIDE +#endif + +// unique_ptr support +#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR +# define CATCH_AUTO_PTR( T ) std::unique_ptr +#else +# define CATCH_AUTO_PTR( T ) std::auto_ptr +#endif + namespace Catch { + struct IConfig; + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + class NonCopyable { #ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS NonCopyable( NonCopyable const& ) = delete; @@ -312,6 +371,9 @@ namespace Catch { void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as @@ -397,7 +459,7 @@ namespace Catch { template class Ptr { public: - Ptr() : m_p( NULL ){} + Ptr() : m_p( CATCH_NULL ){} Ptr( T* p ) : m_p( p ){ if( m_p ) m_p->addRef(); @@ -413,7 +475,7 @@ namespace Catch { void reset() { if( m_p ) m_p->release(); - m_p = NULL; + m_p = CATCH_NULL; } Ptr& operator = ( T* p ){ Ptr temp( p ); @@ -426,12 +488,11 @@ namespace Catch { return *this; } void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } - T* get() { return m_p; } - const T* get() const{ return m_p; } + T* get() const{ return m_p; } T& operator*() const { return *m_p; } T* operator->() const { return m_p; } - bool operator !() const { return m_p == NULL; } - operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } + bool operator !() const { return m_p == CATCH_NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } private: T* m_p; @@ -528,9 +589,13 @@ namespace Catch { struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; - virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const = 0; - + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + } namespace Catch { @@ -758,6 +823,278 @@ namespace Catch { } // end namespace Catch +// #included from: catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template + struct Matcher : SharedImpl + { + typedef ExpressionT ExpressionType; + + virtual ~Matcher() {} + virtual Ptr clone() const = 0; + virtual bool match( ExpressionT const& expr ) const = 0; + virtual std::string toString() const = 0; + }; + + template + struct MatcherImpl : Matcher { + + virtual Ptr > clone() const { + return Ptr >( new DerivedT( static_cast( *this ) ) ); + } + }; + + namespace Generic { + template + struct Not : public MatcherImpl, ExpressionT> { + Not( Matcher const& matcher ) : m_matcher(matcher.clone()) {} + Not( Not const& other ) : m_matcher( other.m_matcher ) {} + + virtual bool match( ExpressionT const& expr ) const CATCH_OVERRIDE { + return !m_matcher->match( expr ); + } + + virtual std::string toString() const CATCH_OVERRIDE { + return "not " + m_matcher->toString(); + } + + Ptr< Matcher > m_matcher; + }; + + template + class AllOf : public MatcherImpl, ExpressionT> { + public: + + AllOf() {} + AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + + AllOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( !m_matchers[i]->match( expr ) ) + return false; + return true; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " and "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + template + class AnyOf : public MatcherImpl, ExpressionT> { + public: + + AnyOf() {} + AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} + + AnyOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( m_matchers[i]->match( expr ) ) + return true; + return false; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " or "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + } + + namespace StdString { + + inline std::string makeString( std::string const& str ) { return str; } + inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + + } + std::string toStringSuffix() const + { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : ""; + } + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct Equals : MatcherImpl { + Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( str, caseSensitivity ) + {} + Equals( Equals const& other ) : m_data( other.m_data ){} + + virtual ~Equals(); + + virtual bool match( std::string const& expr ) const { + return m_data.m_str == m_data.adjustString( expr );; + } + virtual std::string toString() const { + return "equals: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + + struct Contains : MatcherImpl { + Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( substr, caseSensitivity ){} + Contains( Contains const& other ) : m_data( other.m_data ){} + + virtual ~Contains(); + + virtual bool match( std::string const& expr ) const { + return m_data.adjustString( expr ).find( m_data.m_str ) != std::string::npos; + } + virtual std::string toString() const { + return "contains: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + + struct StartsWith : MatcherImpl { + StartsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( substr, caseSensitivity ){} + + StartsWith( StartsWith const& other ) : m_data( other.m_data ){} + + virtual ~StartsWith(); + + virtual bool match( std::string const& expr ) const { + return m_data.adjustString( expr ).find( m_data.m_str ) == 0; + } + virtual std::string toString() const { + return "starts with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + + struct EndsWith : MatcherImpl { + EndsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( substr, caseSensitivity ){} + EndsWith( EndsWith const& other ) : m_data( other.m_data ){} + + virtual ~EndsWith(); + + virtual bool match( std::string const& expr ) const { + return m_data.adjustString( expr ).find( m_data.m_str ) == expr.size() - m_data.m_str.size(); + } + virtual std::string toString() const { + return "ends with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + } // namespace StdString + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + template + inline Impl::Generic::Not Not( Impl::Matcher const& m ) { + return Impl::Generic::Not( m ); + } + + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); + } + + inline Impl::StdString::Equals Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Equals( str, caseSensitivity ); + } + inline Impl::StdString::Equals Equals( const char* str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Equals( Impl::StdString::makeString( str ), caseSensitivity ); + } + inline Impl::StdString::Contains Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Contains( substr, caseSensitivity ); + } + inline Impl::StdString::Contains Contains( const char* substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Contains( Impl::StdString::makeString( substr ), caseSensitivity ); + } + inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { + return Impl::StdString::StartsWith( substr ); + } + inline Impl::StdString::StartsWith StartsWith( const char* substr ) { + return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { + return Impl::StdString::EndsWith( substr ); + } + inline Impl::StdString::EndsWith EndsWith( const char* substr ) { + return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + namespace Catch { struct TestFailureException{}; @@ -784,7 +1121,8 @@ namespace Catch { ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, - ResultDisposition::Flags resultDisposition ); + ResultDisposition::Flags resultDisposition, + char const* secondArg = "" ); template ExpressionLhs operator <= ( T const& operand ); @@ -813,6 +1151,9 @@ namespace Catch { void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); void captureResult( ResultWas::OfType resultType ); void captureExpression(); + void captureExpectedException( std::string const& expectedMessage ); + void captureExpectedException( Matchers::Impl::Matcher const& matcher ); + void handleResult( AssertionResult const& result ); void react(); bool shouldDebugBreak() const; bool allowThrows() const; @@ -991,13 +1332,51 @@ namespace Internal { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + // long long to unsigned X + template bool compare( long long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // unsigned long long to X + template bool compare( unsigned long long lhs, int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long long (when comparing against NULL) + template bool compare( long long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } +#endif // CATCH_CONFIG_CPP11_LONG_LONG + #ifdef CATCH_CONFIG_CPP11_NULLPTR // pointer to nullptr_t (when comparing against nullptr) template bool compare( std::nullptr_t, T* rhs ) { - return Evaluator::evaluate( NULL, rhs ); + return Evaluator::evaluate( nullptr, rhs ); } template bool compare( T* lhs, std::nullptr_t ) { - return Evaluator::evaluate( lhs, NULL ); + return Evaluator::evaluate( lhs, nullptr ); } #endif // CATCH_CONFIG_CPP11_NULLPTR @@ -1095,6 +1474,11 @@ std::string toString( char value ); std::string toString( signed char value ); std::string toString( unsigned char value ); +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ); +std::string toString( unsigned long long value ); +#endif + #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ); #endif @@ -1107,7 +1491,7 @@ std::string toString( std::nullptr_t ); namespace Detail { - extern std::string unprintableString; + extern const std::string unprintableString; struct BorgType { template BorgType( T const& ); @@ -1190,7 +1574,7 @@ struct StringMaker { template static std::string convert( U* p ) { if( !p ) - return INTERNAL_CATCH_STRINGIFY( NULL ); + return "NULL"; else return Detail::rawMemoryToString( p ); } @@ -1200,7 +1584,7 @@ template struct StringMaker { static std::string convert( R C::* p ) { if( !p ) - return INTERNAL_CATCH_STRINGIFY( NULL ); + return "NULL"; else return Detail::rawMemoryToString( p ); } @@ -1472,6 +1856,7 @@ namespace Catch { class AssertionResult; struct AssertionInfo; struct SectionInfo; + struct SectionEndInfo; struct MessageInfo; class ScopedMessageBuilder; struct Counts; @@ -1483,7 +1868,8 @@ namespace Catch { virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; - virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; @@ -1604,16 +1990,16 @@ namespace Catch { } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_THROWS( expr, resultDisposition, matcher, macroName ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \ if( __catchResult.allowThrows() ) \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( ... ) { \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ + __catchResult.captureExpectedException( matcher ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ @@ -1687,21 +2073,6 @@ namespace Catch { // #included from: catch_section_info.h #define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED -namespace Catch { - - struct SectionInfo { - SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description = std::string() ); - - std::string name; - std::string description; - SourceLineInfo lineInfo; - }; - -} // end namespace Catch - // #included from: catch_totals.hpp #define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED @@ -1772,6 +2143,31 @@ namespace Catch { }; } +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) + : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + // #included from: catch_timer.h #define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED @@ -2036,7 +2432,8 @@ namespace Catch { struct IMutableRegistryHub { virtual ~IMutableRegistryHub(); - virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerReporter( std::string const& name, Ptr const& factory ) = 0; + virtual void registerListener( Ptr const& factory ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; }; @@ -2095,317 +2492,92 @@ namespace Catch { }; } -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ - static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ - static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) - -// #included from: internal/catch_approx.hpp -#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED - -#include -#include - -namespace Catch { -namespace Detail { - - class Approx { - public: - explicit Approx ( double value ) - : m_epsilon( std::numeric_limits::epsilon()*100 ), - m_scale( 1.0 ), - m_value( value ) - {} - - Approx( Approx const& other ) - : m_epsilon( other.m_epsilon ), - m_scale( other.m_scale ), - m_value( other.m_value ) - {} - - static Approx custom() { - return Approx( 0 ); - } - - Approx operator()( double value ) { - Approx approx( value ); - approx.epsilon( m_epsilon ); - approx.scale( m_scale ); - return approx; - } - - friend bool operator == ( double lhs, Approx const& rhs ) { - // Thanks to Richard Harris for his help refining this formula - return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); - } - - friend bool operator == ( Approx const& lhs, double rhs ) { - return operator==( rhs, lhs ); - } - - friend bool operator != ( double lhs, Approx const& rhs ) { - return !operator==( lhs, rhs ); - } - - friend bool operator != ( Approx const& lhs, double rhs ) { - return !operator==( rhs, lhs ); - } - - Approx& epsilon( double newEpsilon ) { - m_epsilon = newEpsilon; - return *this; - } - - Approx& scale( double newScale ) { - m_scale = newScale; - return *this; - } - - std::string toString() const { - std::ostringstream oss; - oss << "Approx( " << Catch::toString( m_value ) << " )"; - return oss.str(); - } - - private: - double m_epsilon; - double m_scale; - double m_value; - }; -} - -template<> -inline std::string toString( Detail::Approx const& value ) { - return value.toString(); -} - -} // end namespace Catch - -// #included from: internal/catch_matchers.hpp -#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED - -namespace Catch { -namespace Matchers { - namespace Impl { - - template - struct Matcher : SharedImpl - { - typedef ExpressionT ExpressionType; - - virtual ~Matcher() {} - virtual Ptr clone() const = 0; - virtual bool match( ExpressionT const& expr ) const = 0; - virtual std::string toString() const = 0; - }; - - template - struct MatcherImpl : Matcher { - - virtual Ptr > clone() const { - return Ptr >( new DerivedT( static_cast( *this ) ) ); - } - }; - - namespace Generic { - - template - class AllOf : public MatcherImpl, ExpressionT> { - public: - - AllOf() {} - AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} - - AllOf& add( Matcher const& matcher ) { - m_matchers.push_back( matcher.clone() ); - return *this; - } - virtual bool match( ExpressionT const& expr ) const - { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) - if( !m_matchers[i]->match( expr ) ) - return false; - return true; - } - virtual std::string toString() const { - std::ostringstream oss; - oss << "( "; - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if( i != 0 ) - oss << " and "; - oss << m_matchers[i]->toString(); - } - oss << " )"; - return oss.str(); - } - - private: - std::vector > > m_matchers; - }; - - template - class AnyOf : public MatcherImpl, ExpressionT> { - public: - - AnyOf() {} - AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} - - AnyOf& add( Matcher const& matcher ) { - m_matchers.push_back( matcher.clone() ); - return *this; - } - virtual bool match( ExpressionT const& expr ) const - { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) - if( m_matchers[i]->match( expr ) ) - return true; - return false; - } - virtual std::string toString() const { - std::ostringstream oss; - oss << "( "; - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if( i != 0 ) - oss << " or "; - oss << m_matchers[i]->toString(); - } - oss << " )"; - return oss.str(); - } - - private: - std::vector > > m_matchers; - }; - - } - - namespace StdString { - - inline std::string makeString( std::string const& str ) { return str; } - inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } - - struct Equals : MatcherImpl { - Equals( std::string const& str ) : m_str( str ){} - Equals( Equals const& other ) : m_str( other.m_str ){} - - virtual ~Equals(); - - virtual bool match( std::string const& expr ) const { - return m_str == expr; - } - virtual std::string toString() const { - return "equals: \"" + m_str + "\""; - } - - std::string m_str; - }; +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) - struct Contains : MatcherImpl { - Contains( std::string const& substr ) : m_substr( substr ){} - Contains( Contains const& other ) : m_substr( other.m_substr ){} +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED - virtual ~Contains(); +#include +#include - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) != std::string::npos; - } - virtual std::string toString() const { - return "contains: \"" + m_substr + "\""; - } +namespace Catch { +namespace Detail { - std::string m_substr; - }; + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_scale( 1.0 ), + m_value( value ) + {} - struct StartsWith : MatcherImpl { - StartsWith( std::string const& substr ) : m_substr( substr ){} - StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} - virtual ~StartsWith(); + static Approx custom() { + return Approx( 0 ); + } - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) == 0; - } - virtual std::string toString() const { - return "starts with: \"" + m_substr + "\""; - } + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.scale( m_scale ); + return approx; + } - std::string m_substr; - }; + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + } - struct EndsWith : MatcherImpl { - EndsWith( std::string const& substr ) : m_substr( substr ){} - EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } - virtual ~EndsWith(); + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) == expr.size() - m_substr.size(); - } - virtual std::string toString() const { - return "ends with: \"" + m_substr + "\""; - } + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } - std::string m_substr; - }; - } // namespace StdString - } // namespace Impl + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } - // The following functions create the actual matcher objects. - // This allows the types to be inferred - template - inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, - Impl::Matcher const& m2 ) { - return Impl::Generic::AllOf().add( m1 ).add( m2 ); - } - template - inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, - Impl::Matcher const& m2, - Impl::Matcher const& m3 ) { - return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); - } - template - inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, - Impl::Matcher const& m2 ) { - return Impl::Generic::AnyOf().add( m1 ).add( m2 ); - } - template - inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, - Impl::Matcher const& m2, - Impl::Matcher const& m3 ) { - return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); - } + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } - inline Impl::StdString::Equals Equals( std::string const& str ) { - return Impl::StdString::Equals( str ); - } - inline Impl::StdString::Equals Equals( const char* str ) { - return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); - } - inline Impl::StdString::Contains Contains( std::string const& substr ) { - return Impl::StdString::Contains( substr ); - } - inline Impl::StdString::Contains Contains( const char* substr ) { - return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); - } - inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { - return Impl::StdString::StartsWith( substr ); - } - inline Impl::StdString::StartsWith StartsWith( const char* substr ) { - return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); - } - inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { - return Impl::StdString::EndsWith( substr ); - } - inline Impl::StdString::EndsWith EndsWith( const char* substr ) { - return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); - } + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } -} // namespace Matchers + private: + double m_epsilon; + double m_scale; + double m_value; + }; +} -using namespace Matchers; +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} -} // namespace Catch +} // end namespace Catch // #included from: internal/catch_interfaces_tag_alias_registry.h #define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED @@ -2440,12 +2612,12 @@ namespace Catch { template class Option { public: - Option() : nullableValue( NULL ) {} + Option() : nullableValue( CATCH_NULL ) {} Option( T const& _value ) : nullableValue( new( storage ) T( _value ) ) {} Option( Option const& _other ) - : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) + : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) {} ~Option() { @@ -2469,7 +2641,7 @@ namespace Catch { void reset() { if( nullableValue ) nullableValue->~T(); - nullableValue = NULL; + nullableValue = CATCH_NULL; } T& operator*() { return *nullableValue; } @@ -2481,10 +2653,10 @@ namespace Catch { return nullableValue ? *nullableValue : defaultValue; } - bool some() const { return nullableValue != NULL; } - bool none() const { return nullableValue == NULL; } + bool some() const { return nullableValue != CATCH_NULL; } + bool none() const { return nullableValue == CATCH_NULL; } - bool operator !() const { return nullableValue == NULL; } + bool operator !() const { return nullableValue == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( some() ); } @@ -2542,6 +2714,8 @@ namespace Catch { TestCaseInfo( TestCaseInfo const& other ); + friend void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ); + bool isHidden() const; bool throws() const; bool okToFail() const; @@ -2654,7 +2828,7 @@ namespace Catch { inline size_t registerTestMethods() { size_t noTestMethods = 0; - int noClasses = objc_getClassList( NULL, 0 ); + int noClasses = objc_getClassList( CATCH_NULL, 0 ); Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); objc_getClassList( classes, noClasses ); @@ -2796,7 +2970,7 @@ return @ desc; \ #pragma clang diagnostic ignored "-Wweak-vtables" #endif -// #included from: ../catch_runner.hpp +// #included from: ../catch_session.hpp #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED // #included from: internal/catch_commandline.hpp @@ -2821,6 +2995,67 @@ return @ desc; \ #pragma clang diagnostic ignored "-Wpadded" #endif +// #included from: catch_wildcard_pattern.hpp +#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_wildcard( NoWildcard ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, "*" ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, "*" ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~WildcardPattern(); + virtual bool matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard; + std::string m_pattern; + }; +} + #include #include @@ -2832,50 +3067,18 @@ namespace Catch { virtual bool matches( TestCaseInfo const& testCase ) const = 0; }; class NamePattern : public Pattern { - enum WildcardPosition { - NoWildcard = 0, - WildcardAtStart = 1, - WildcardAtEnd = 2, - WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd - }; - public: - NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { - if( startsWith( m_name, "*" ) ) { - m_name = m_name.substr( 1 ); - m_wildcard = WildcardAtStart; - } - if( endsWith( m_name, "*" ) ) { - m_name = m_name.substr( 0, m_name.size()-1 ); - m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); - } - } + NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} virtual ~NamePattern(); virtual bool matches( TestCaseInfo const& testCase ) const { - switch( m_wildcard ) { - case NoWildcard: - return m_name == toLower( testCase.name ); - case WildcardAtStart: - return endsWith( toLower( testCase.name ), m_name ); - case WildcardAtEnd: - return startsWith( toLower( testCase.name ), m_name ); - case WildcardAtBothEnds: - return contains( toLower( testCase.name ), m_name ); - } - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunreachable-code" -#endif - throw std::logic_error( "Unknown enum" ); -#ifdef __clang__ -#pragma clang diagnostic pop -#endif + return m_wildcardPattern.matches( toLower( testCase.name ) ); } private: - std::string m_name; - WildcardPosition m_wildcard; + WildcardPattern m_wildcardPattern; }; + class TagPattern : public Pattern { public: TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} @@ -2886,6 +3089,7 @@ namespace Catch { private: std::string m_tag; }; + class ExcludedPattern : public Pattern { public: ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} @@ -3132,6 +3336,7 @@ namespace Catch { showHelp( false ), showInvisibles( false ), forceColour( false ), + filenamesAsTags( false ), abortAfter( -1 ), rngSeed( 0 ), verbosity( Verbosity::Normal ), @@ -3151,6 +3356,7 @@ namespace Catch { bool showHelp; bool showInvisibles; bool forceColour; + bool filenamesAsTags; int abortAfter; unsigned int rngSeed; @@ -3160,11 +3366,11 @@ namespace Catch { ShowDurations::OrNot showDurations; RunTests::InWhatOrder runOrder; - std::string reporterName; std::string outputFilename; std::string name; std::string processName; + std::vector reporterNames; std::vector testsOrTags; }; @@ -3224,7 +3430,7 @@ namespace Catch { m_stream = stream; } - std::string getReporterName() const { return m_data.reporterName; } + std::vector getReporterNames() const { return m_data.reporterNames; } int abortAfter() const { return m_data.abortAfter; } @@ -3517,11 +3723,11 @@ namespace Clara { template class BoundArgFunction { public: - BoundArgFunction() : functionObj( NULL ) {} + BoundArgFunction() : functionObj( CATCH_NULL ) {} BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} - BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CATCH_NULL ) {} BoundArgFunction& operator = ( BoundArgFunction const& other ) { - IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CATCH_NULL; delete functionObj; functionObj = newFunctionObj; return *this; @@ -3537,7 +3743,7 @@ namespace Clara { bool takesArg() const { return functionObj->takesArg(); } bool isSet() const { - return functionObj != NULL; + return functionObj != CATCH_NULL; } private: IArgFunction* functionObj; @@ -3755,12 +3961,7 @@ namespace Clara { } }; - // NOTE: std::auto_ptr is deprecated in c++11/c++0x -#if defined(__cplusplus) && __cplusplus > 199711L - typedef std::unique_ptr ArgAutoPtr; -#else - typedef std::auto_ptr ArgAutoPtr; -#endif + typedef CATCH_AUTO_PTR( Arg ) ArgAutoPtr; friend void addOptName( Arg& arg, std::string const& optName ) { @@ -3836,8 +4037,8 @@ namespace Clara { m_arg->description = description; return *this; } - ArgBuilder& detail( std::string const& detail ) { - m_arg->detail = detail; + ArgBuilder& detail( std::string const& _detail ) { + m_arg->detail = _detail; return *this; } @@ -3920,14 +4121,14 @@ namespace Clara { maxWidth = (std::max)( maxWidth, it->commands().size() ); for( it = itBegin; it != itEnd; ++it ) { - Detail::Text usage( it->commands(), Detail::TextAttributes() + Detail::Text usageText( it->commands(), Detail::TextAttributes() .setWidth( maxWidth+indent ) .setIndent( indent ) ); Detail::Text desc( it->description, Detail::TextAttributes() .setWidth( width - maxWidth - 3 ) ); - for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { - std::string usageCol = i < usage.size() ? usage[i] : ""; + for( std::size_t i = 0; i < (std::max)( usageText.size(), desc.size() ); ++i ) { + std::string usageCol = i < usageText.size() ? usageText[i] : ""; os << usageCol; if( i < desc.size() && !desc[i].empty() ) @@ -4133,6 +4334,7 @@ namespace Catch { config.abortAfter = x; } inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } inline void addWarning( ConfigData& config, std::string const& _warning ) { if( _warning == "NoAssertions" ) @@ -4226,7 +4428,7 @@ namespace Catch { cli["-r"]["--reporter"] // .placeholder( "name[:filename]" ) .describe( "reporter to use (defaults to console)" ) - .bind( &ConfigData::reporterName, "name" ); + .bind( &addReporterName, "name" ); cli["-n"]["--name"] .describe( "suite name" ) @@ -4263,6 +4465,10 @@ namespace Catch { .describe( "load test names to run from a file" ) .bind( &loadTestNamesFromFile, "filename" ); + cli["-#"]["--filenames-as-tags"] + .describe( "adds a tag for the filename" ) + .bind( &ConfigData::filenamesAsTags ); + // Less common commands which don't have a short form cli["--list-test-names-only"] .describe( "list all/matching test cases names only" ) @@ -4733,6 +4939,7 @@ namespace Catch // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; @@ -4741,20 +4948,24 @@ namespace Catch virtual void skipTest( TestCaseInfo const& testInfo ) = 0; }; - struct IReporterFactory { + struct IReporterFactory : IShared { virtual ~IReporterFactory(); virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; virtual std::string getDescription() const = 0; }; struct IReporterRegistry { - typedef std::map FactoryMap; + typedef std::map > FactoryMap; + typedef std::vector > Listeners; virtual ~IReporterRegistry(); virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; }; + Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ); + } #include @@ -4777,8 +4988,7 @@ namespace Catch { nameAttr.setInitialIndent( 2 ).setIndent( 4 ); tagsAttr.setIndent( 6 ); - std::vector matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { @@ -4806,8 +5016,7 @@ namespace Catch { if( !config.testSpec().hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); std::size_t matchedTests = 0; - std::vector matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { @@ -4847,8 +5056,7 @@ namespace Catch { std::map tagCounts; - std::vector matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { @@ -4919,7 +5127,7 @@ namespace Catch { } // end namespace Catch -// #included from: internal/catch_runner_impl.hpp +// #included from: internal/catch_run_context.hpp #define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED // #included from: catch_test_case_tracker.hpp @@ -4977,7 +5185,7 @@ namespace SectionTracking { TrackedSections::iterator it = m_children.find( childName ); return it != m_children.end() ? &it->second - : NULL; + : CATCH_NULL; } inline TrackedSection* TrackedSection::acquireChild( std::string const& childName ) { if( TrackedSection* child = findChild( childName ) ) @@ -4999,7 +5207,7 @@ namespace SectionTracking { class TestCaseTracker { public: TestCaseTracker( std::string const& testCaseName ) - : m_testCase( testCaseName, NULL ), + : m_testCase( testCaseName, CATCH_NULL ), m_currentSection( &m_testCase ), m_completedASectionThisRun( false ) {} @@ -5016,7 +5224,7 @@ namespace SectionTracking { void leaveSection() { m_currentSection->leave(); m_currentSection = m_currentSection->getParent(); - assert( m_currentSection != NULL ); + assert( m_currentSection != CATCH_NULL ); m_completedASectionThisRun = true; } @@ -5174,11 +5382,11 @@ namespace Catch { public: - explicit RunContext( Ptr const& config, Ptr const& reporter ) - : m_runInfo( config->name() ), + explicit RunContext( Ptr const& _config, Ptr const& reporter ) + : m_runInfo( _config->name() ), m_context( getCurrentMutableContext() ), - m_activeTestCase( NULL ), - m_config( config ), + m_activeTestCase( CATCH_NULL ), + m_config( _config ), m_reporter( reporter ), m_prevRunner( m_context.getRunner() ), m_prevResultCapture( m_context.getResultCapture() ), @@ -5193,7 +5401,7 @@ namespace Catch { virtual ~RunContext() { m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); m_context.setRunner( m_prevRunner ); - m_context.setConfig( NULL ); + m_context.setConfig( Ptr() ); m_context.setResultCapture( m_prevResultCapture ); m_context.setConfig( m_prevConfig ); } @@ -5234,7 +5442,7 @@ namespace Catch { redirectedCerr, aborting() ) ); - m_activeTestCase = NULL; + m_activeTestCase = CATCH_NULL; m_testCaseTracker.reset(); return deltaTotals; @@ -5291,21 +5499,20 @@ namespace Catch { return true; } - virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { - if( std::uncaught_exception() ) { - m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); - return; - } - - Counts assertions = m_totals.assertions - prevAssertions; + virtual void sectionEnded( SectionEndInfo const& endInfo ) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); m_testCaseTracker->leaveSection(); - m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); + m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); m_messages.clear(); } + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { + m_unfinishedSections.push_back( endInfo ); + } + virtual void pushScopedMessage( MessageInfo const& message ) { m_messages.push_back( message ); } @@ -5373,6 +5580,8 @@ namespace Catch { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); TestCaseTracker::Guard guard( *m_testCaseTracker ); + seedRng( *m_config ); + Timer timer; timer.start(); if( m_reporter->getPreferences().shouldRedirectStdOut ) { @@ -5425,24 +5634,14 @@ namespace Catch { void handleUnfinishedSections() { // If sections ended prematurely due to an exception we stored their // infos here so we can tear them down outside the unwind process. - for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), itEnd = m_unfinishedSections.rend(); it != itEnd; ++it ) - sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); + sectionEnded( *it ); m_unfinishedSections.clear(); } - struct UnfinishedSections { - UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) - : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) - {} - - SectionInfo info; - Counts prevAssertions; - double durationInSeconds; - }; - TestRunInfo m_runInfo; IMutableContext& m_context; TestCase const* m_activeTestCase; @@ -5457,7 +5656,7 @@ namespace Catch { IResultCapture* m_prevResultCapture; Ptr m_prevConfig; AssertionInfo m_lastAssertionInfo; - std::vector m_unfinishedSections; + std::vector m_unfinishedSections; }; IResultCapture& getResultCapture() { @@ -5505,89 +5704,100 @@ namespace Catch { namespace Catch { - class Runner { - - public: - Runner( Ptr const& config ) - : m_config( config ) - { - openStream(); - makeReporter(); + Ptr createReporter( std::string const& reporterName, Ptr const& config ) { + Ptr reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); + if( !reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); } + return reporter; + } - Totals runTests() { - - RunContext context( m_config.get(), m_reporter ); + Ptr makeReporter( Ptr const& config ) { + std::vector reporters = config->getReporterNames(); + if( reporters.empty() ) + reporters.push_back( "console" ); - Totals totals; + Ptr reporter; + for( std::vector::const_iterator it = reporters.begin(), itEnd = reporters.end(); + it != itEnd; + ++it ) + reporter = addReporter( reporter, createReporter( *it, config ) ); + return reporter; + } + Ptr addListeners( Ptr const& config, Ptr reporters ) { + IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); + for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); + it != itEnd; + ++it ) + reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); + return reporters; + } - context.testGroupStarting( "all tests", 1, 1 ); // deprecated? + void openStreamInto( Ptr const& config, std::ofstream& ofs ) { + // Open output file, if specified + if( !config->getFilename().empty() ) { + ofs.open( config->getFilename().c_str() ); + if( ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << config->getFilename() << "'"; + throw std::domain_error( oss.str() ); + } + config->setStreamBuf( ofs.rdbuf() ); + } + } - TestSpec testSpec = m_config->testSpec(); - if( !testSpec.hasFilters() ) - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + Totals runTests( Ptr const& config ) { - std::vector testCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); + std::ofstream ofs; + openStreamInto( config, ofs ); + Ptr reporter = makeReporter( config ); + reporter = addListeners( config.get(), reporter ); - int testsRunForGroup = 0; - for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); - it != itEnd; - ++it ) { - testsRunForGroup++; - if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { + RunContext context( config.get(), reporter ); - if( context.aborting() ) - break; + Totals totals; - totals += context.runTest( *it ); - m_testsAlreadyRun.insert( *it ); - } - } - std::vector skippedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); + context.testGroupStarting( config->name(), 1, 1 ); - for( std::vector::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end(); - it != itEnd; - ++it ) - m_reporter->skipTest( *it ); + TestSpec testSpec = config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests - context.testGroupEnded( "all tests", totals, 1, 1 ); - return totals; + std::vector const& allTestCases = getAllTestCasesSorted( *config ); + for( std::vector::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); + it != itEnd; + ++it ) { + if( !context.aborting() && matchTest( *it, testSpec, *config ) ) + totals += context.runTest( *it ); + else + reporter->skipTest( *it ); } - private: - void openStream() { - // Open output file, if specified - if( !m_config->getFilename().empty() ) { - m_ofs.open( m_config->getFilename().c_str() ); - if( m_ofs.fail() ) { - std::ostringstream oss; - oss << "Unable to open file: '" << m_config->getFilename() << "'"; - throw std::domain_error( oss.str() ); - } - m_config->setStreamBuf( m_ofs.rdbuf() ); - } - } - void makeReporter() { - std::string reporterName = m_config->getReporterName().empty() - ? "console" - : m_config->getReporterName(); + context.testGroupEnded( config->name(), totals, 1, 1 ); + return totals; + } - m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); - if( !m_reporter ) { - std::ostringstream oss; - oss << "No reporter registered with name: '" << reporterName << "'"; - throw std::domain_error( oss.str() ); - } - } + void applyFilenamesAsTags( IConfig const& config ) { + std::vector const& tests = getAllTestCasesSorted( config ); + for(std::size_t i = 0; i < tests.size(); ++i ) { + TestCase& test = const_cast( tests[i] ); + std::set tags = test.tags; - private: - Ptr m_config; - std::ofstream m_ofs; - Ptr m_reporter; - std::set m_testsAlreadyRun; - }; + std::string filename = test.lineInfo.file; + std::string::size_type lastSlash = filename.find_last_of( "\\/" ); + if( lastSlash != std::string::npos ) + filename = filename.substr( lastSlash+1 ); + + std::string::size_type lastDot = filename.find_last_of( "." ); + if( lastDot != std::string::npos ) + filename = filename.substr( 0, lastDot ); + + tags.insert( "#" + filename ); + setTags( test, tags ); + } + } class Session : NonCopyable { static bool alreadyInstantiated; @@ -5659,15 +5869,16 @@ namespace Catch { { config(); // Force config to be constructed - std::srand( m_configData.rngSeed ); + seedRng( *m_config ); - Runner runner( m_config ); + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); // Handle list request if( Option listed = list( config() ) ) return static_cast( *listed ); - return static_cast( runner.runTests().assertions.failed ); + return static_cast( runTests( m_config ).assertions.failed ); } catch( std::exception& ex ) { Catch::cerr() << ex.what() << std::endl; @@ -5689,7 +5900,6 @@ namespace Catch { m_config = new Config( m_configData ); return *m_config; } - private: Clara::CommandLine m_cli; std::vector m_unusedTokens; @@ -5713,16 +5923,73 @@ namespace Catch { #include #include -namespace Catch { +namespace Catch { + + struct LexSort { + bool operator() (TestCase i,TestCase j) const { return (i sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + + std::vector sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end(), LexSort() ); + break; + case RunTests::InRandomOrder: + { + seedRng( config ); + + RandomNumberGenerator rng; + std::random_shuffle( sorted.begin(), sorted.end(), rng ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector const& functions ) { + std::set seenFunctions; + for( std::vector::const_iterator it = functions.begin(), itEnd = functions.end(); + it != itEnd; + ++it ) { + std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); + if( !prev.second ){ + Catch::cerr() + << Colour( Colour::Red ) + << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; + exit(1); + } + } + } - class TestRegistry : public ITestCaseRegistry { - struct LexSort { - bool operator() (TestCase i,TestCase j) const { return (i filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) + if( matchTest( *it, testSpec, config ) ) + filtered.push_back( *it ); + return filtered; + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + class TestRegistry : public ITestCaseRegistry { public: TestRegistry() : m_unnamedCount( 0 ) {} virtual ~TestRegistry(); @@ -5734,69 +6001,29 @@ namespace Catch { oss << "Anonymous test case " << ++m_unnamedCount; return registerTest( testCase.withName( oss.str() ) ); } - - if( m_functions.find( testCase ) == m_functions.end() ) { - m_functions.insert( testCase ); - m_functionsInOrder.push_back( testCase ); - if( !testCase.isHidden() ) - m_nonHiddenFunctions.push_back( testCase ); - } - else { - TestCase const& prev = *m_functions.find( testCase ); - { - Colour colourGuard( Colour::Red ); - Catch::cerr() << "error: TEST_CASE( \"" << name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.getTestCaseInfo().lineInfo << "\n" - << "\tRedefined at " << testCase.getTestCaseInfo().lineInfo << std::endl; - } - exit(1); - } + m_functions.push_back( testCase ); } virtual std::vector const& getAllTests() const { - return m_functionsInOrder; - } - - virtual std::vector const& getAllNonHiddenTests() const { - return m_nonHiddenFunctions; + return m_functions; } + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); - virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const { - - for( std::vector::const_iterator it = m_functionsInOrder.begin(), - itEnd = m_functionsInOrder.end(); - it != itEnd; - ++it ) { - bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ); - if( includeTest != negated ) - matchingTestCases.push_back( *it ); + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); } - sortTests( config, matchingTestCases ); + return m_sortedFunctions; } private: - - static void sortTests( IConfig const& config, std::vector& matchingTestCases ) { - - switch( config.runOrder() ) { - case RunTests::InLexicographicalOrder: - std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() ); - break; - case RunTests::InRandomOrder: - { - RandomNumberGenerator rng; - std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng ); - } - break; - case RunTests::InDeclarationOrder: - // already in declaration order - break; - } - } - std::set m_functions; - std::vector m_functionsInOrder; - std::vector m_nonHiddenFunctions; + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder; + mutable std::vector m_sortedFunctions; size_t m_unnamedCount; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised }; /////////////////////////////////////////////////////////////////////////// @@ -5865,27 +6092,32 @@ namespace Catch { public: - virtual ~ReporterRegistry() { - deleteAllValues( m_factories ); - } + virtual ~ReporterRegistry() CATCH_OVERRIDE {} - virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const CATCH_OVERRIDE { FactoryMap::const_iterator it = m_factories.find( name ); if( it == m_factories.end() ) - return NULL; + return CATCH_NULL; return it->second->create( ReporterConfig( config ) ); } - void registerReporter( std::string const& name, IReporterFactory* factory ) { + void registerReporter( std::string const& name, Ptr const& factory ) { m_factories.insert( std::make_pair( name, factory ) ); } + void registerListener( Ptr const& factory ) { + m_listeners.push_back( factory ); + } - FactoryMap const& getFactories() const { + virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { return m_factories; } + virtual Listeners const& getListeners() const CATCH_OVERRIDE { + return m_listeners; + } private: FactoryMap m_factories; + Listeners m_listeners; }; } @@ -5968,24 +6200,27 @@ namespace Catch { public: // IRegistryHub RegistryHub() { } - virtual IReporterRegistry const& getReporterRegistry() const { + virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { return m_reporterRegistry; } - virtual ITestCaseRegistry const& getTestCaseRegistry() const { + virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { return m_testCaseRegistry; } - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { return m_exceptionTranslatorRegistry; } public: // IMutableRegistryHub - virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + virtual void registerReporter( std::string const& name, Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerReporter( name, factory ); } - virtual void registerTest( TestCase const& testInfo ) { + virtual void registerListener( Ptr const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerListener( factory ); + } + virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { m_testCaseRegistry.registerTest( testInfo ); } - virtual void registerTranslator( const IExceptionTranslator* translator ) { + virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { m_exceptionTranslatorRegistry.registerTranslator( translator ); } @@ -5997,7 +6232,7 @@ namespace Catch { // Single, global, instance inline RegistryHub*& getTheRegistryHub() { - static RegistryHub* theRegistryHub = NULL; + static RegistryHub* theRegistryHub = CATCH_NULL; if( !theRegistryHub ) theRegistryHub = new RegistryHub(); return theRegistryHub; @@ -6012,7 +6247,7 @@ namespace Catch { } void cleanUp() { delete getTheRegistryHub(); - getTheRegistryHub() = NULL; + getTheRegistryHub() = CATCH_NULL; cleanUpContext(); } std::string translateActiveException() { @@ -6113,7 +6348,7 @@ namespace Catch { }; Stream::Stream() - : streamBuf( NULL ), isOwned( false ) + : streamBuf( CATCH_NULL ), isOwned( false ) {} Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) @@ -6123,7 +6358,7 @@ namespace Catch { void Stream::release() { if( isOwned ) { delete streamBuf; - streamBuf = NULL; + streamBuf = CATCH_NULL; isOwned = false; } } @@ -6142,7 +6377,7 @@ namespace Catch { class Context : public IMutableContext { - Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} Context( Context const& ); void operator=( Context const& ); @@ -6188,7 +6423,7 @@ namespace Catch { m_generatorsByTestName.find( testName ); return it != m_generatorsByTestName.end() ? it->second - : NULL; + : CATCH_NULL; } IGeneratorsForTest& getGeneratorsForCurrentTest() { @@ -6209,7 +6444,7 @@ namespace Catch { }; namespace { - Context* currentContext = NULL; + Context* currentContext = CATCH_NULL; } IMutableContext& getCurrentMutableContext() { if( !currentContext ) @@ -6230,7 +6465,7 @@ namespace Catch { void cleanUpContext() { delete currentContext; - currentContext = NULL; + currentContext = CATCH_NULL; } } @@ -6286,12 +6521,13 @@ namespace { { CONSOLE_SCREEN_BUFFER_INFO csbiInfo; GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); - originalAttributes = csbiInfo.wAttributes; + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); } virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { - case Colour::None: return setTextAttribute( originalAttributes ); + case Colour::None: return setTextAttribute( originalForegroundAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Red: return setTextAttribute( FOREGROUND_RED ); case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); @@ -6311,10 +6547,11 @@ namespace { private: void setTextAttribute( WORD _textAttribute ) { - SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); } HANDLE stdoutHandle; - WORD originalAttributes; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; }; IColourImpl* platformColourInstance() { @@ -6640,6 +6877,21 @@ namespace Catch { return TestCase( _testCase, info ); } + void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ) + { + testCaseInfo.tags = tags; + testCaseInfo.lcaseTags.clear(); + + std::ostringstream oss; + for( std::set::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { + oss << "[" << *it << "]"; + std::string lcaseTag = toLower( *it ); + testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.insert( lcaseTag ); + } + testCaseInfo.tagsAsString = oss.str(); + } + TestCaseInfo::TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, @@ -6648,18 +6900,10 @@ namespace Catch { : name( _name ), className( _className ), description( _description ), - tags( _tags ), lineInfo( _lineInfo ), properties( None ) { - std::ostringstream oss; - for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { - oss << "[" << *it << "]"; - std::string lcaseTag = toLower( *it ); - properties = static_cast( properties | parseSpecialTag( lcaseTag ) ); - lcaseTags.insert( lcaseTag ); - } - tagsAsString = oss.str(); + setTags( *this, _tags ); } TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) @@ -6767,7 +7011,7 @@ namespace Catch { return os; } - Version libraryVersion( 1, 2, 1, "", 0 ); + Version libraryVersion( 1, 2, 1, "develop", 15 ); } @@ -6960,7 +7204,7 @@ namespace Catch { #else uint64_t getCurrentTicks() { timeval t; - gettimeofday(&t,NULL); + gettimeofday(&t,CATCH_NULL); return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); } #endif @@ -7059,6 +7303,14 @@ namespace Catch { return line < other.line || ( line == other.line && file < other.file ); } + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) + std::srand( config.rngSeed() ); + } + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { #ifndef __GNUG__ os << info.file << "(" << info.line << ")"; @@ -7098,8 +7350,13 @@ namespace Catch { } Section::~Section() { - if( m_sectionIncluded ) - getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( m_sectionIncluded ) { + SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( std::uncaught_exception() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } } // This indicates whether the section should be executed or not @@ -7151,7 +7408,7 @@ namespace Catch { // Call sysctl. size = sizeof(info); - if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; return false; } @@ -7205,9 +7462,11 @@ namespace Catch { namespace Detail { - std::string unprintableString = "{?}"; + const std::string unprintableString = "{?}"; namespace { + const int hexThreshold = 255; + struct Endianness { enum Arch { Big, Little }; @@ -7289,7 +7548,7 @@ std::string toString( wchar_t* const value ) std::string toString( int value ) { std::ostringstream oss; oss << value; - if( value >= 255 ) + if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } @@ -7297,7 +7556,7 @@ std::string toString( int value ) { std::string toString( unsigned long value ) { std::ostringstream oss; oss << value; - if( value >= 255 ) + if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } @@ -7347,6 +7606,23 @@ std::string toString( unsigned char value ) { return toString( static_cast( value ) ); } +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} +std::string toString( unsigned long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} +#endif + #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ) { return "nullptr"; @@ -7376,11 +7652,17 @@ std::string toString( std::nullptr_t ) { namespace Catch { + std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) { + return secondArg.empty() || secondArg == "\"\"" + ? capturedExpression + : capturedExpression + ", " + secondArg; + } ResultBuilder::ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, - ResultDisposition::Flags resultDisposition ) - : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), + ResultDisposition::Flags resultDisposition, + char const* secondArg ) + : m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ), m_shouldDebugBreak( false ), m_shouldThrow( false ) {} @@ -7421,9 +7703,35 @@ namespace Catch { setResultType( resultType ); captureExpression(); } + void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { + if( expectedMessage.empty() ) + captureExpectedException( Matchers::Impl::Generic::AllOf() ); + else + captureExpectedException( Matchers::Equals( expectedMessage ) ); + } + + void ResultBuilder::captureExpectedException( Matchers::Impl::Matcher const& matcher ) { + + assert( m_exprComponents.testFalse == false ); + AssertionResultData data = m_data; + data.resultType = ResultWas::Ok; + data.reconstructedExpression = m_assertionInfo.capturedExpression; + + std::string actualMessage = Catch::translateActiveException(); + if( !matcher.match( actualMessage ) ) { + data.resultType = ResultWas::ExpressionFailed; + data.reconstructedExpression = actualMessage; + } + AssertionResult result( m_assertionInfo, data ); + handleResult( result ); + } void ResultBuilder::captureExpression() { AssertionResult result = build(); + handleResult( result ); + } + void ResultBuilder::handleResult( AssertionResult const& result ) + { getResultCapture().assertionEnded( result ); if( !result.isOk() ) { @@ -7576,6 +7884,137 @@ namespace Catch { } // end namespace Catch +// #included from: ../reporters/catch_reporter_multi.hpp +#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED + +namespace Catch { + +class MultipleReporters : public SharedImpl { + typedef std::vector > Reporters; + Reporters m_reporters; + +public: + void add( Ptr const& reporter ) { + m_reporters.push_back( reporter ); + } + +public: // IStreamingReporter + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporters[0]->getPreferences(); + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->noMatchingTestCases( spec ); + } + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunStarting( testRunInfo ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupStarting( groupInfo ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseStarting( testInfo ); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionStarting( sectionInfo ); + } + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + bool clearBuffer = false; + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + clearBuffer |= (*it)->assertionEnded( assertionStats ); + return clearBuffer; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionEnded( sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupEnded( testGroupStats ); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunEnded( testRunStats ); + } + + virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->skipTest( testInfo ); + } +}; + +Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { + Ptr resultingReporter; + + if( existingReporter ) { + MultipleReporters* multi = dynamic_cast( existingReporter.get() ); + if( !multi ) { + multi = new MultipleReporters; + resultingReporter = Ptr( multi ); + if( existingReporter ) + multi->add( existingReporter ); + } + else + resultingReporter = existingReporter; + multi->add( additionalReporter ); + } + else + resultingReporter = additionalReporter; + + return resultingReporter; +} + +} // end namespace Catch + // #included from: ../reporters/catch_reporter_xml.hpp #define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED @@ -7591,42 +8030,48 @@ namespace Catch { StreamingReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) - {} + { + m_reporterPrefs.shouldRedirectStdOut = false; + } - virtual ~StreamingReporterBase(); + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } + + virtual ~StreamingReporterBase() CATCH_OVERRIDE; - virtual void noMatchingTestCases( std::string const& ) {} + virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} - virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { currentTestRunInfo = _testRunInfo; } - virtual void testGroupStarting( GroupInfo const& _groupInfo ) { + virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { currentGroupInfo = _groupInfo; } - virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { currentTestCaseInfo = _testInfo; } - virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_sectionStack.push_back( _sectionInfo ); } - virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { m_sectionStack.pop_back(); } - virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); } - virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { currentGroupInfo.reset(); } - virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); currentGroupInfo.reset(); currentTestRunInfo.reset(); } - virtual void skipTest( TestCaseInfo const& ) { + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { // Don't do anything with this by default. // It can optionally be overridden in the derived class. } @@ -7639,6 +8084,7 @@ namespace Catch { LazyStat currentTestCaseInfo; std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; }; struct CumulativeReporterBase : SharedImpl { @@ -7689,15 +8135,21 @@ namespace Catch { CumulativeReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) - {} + { + m_reporterPrefs.shouldRedirectStdOut = false; + } ~CumulativeReporterBase(); - virtual void testRunStarting( TestRunInfo const& ) {} - virtual void testGroupStarting( GroupInfo const& ) {} + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } + + virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} + virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} - virtual void testCaseStarting( TestCaseInfo const& ) {} + virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} - virtual void sectionStarting( SectionInfo const& sectionInfo ) { + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); Ptr node; if( m_sectionStack.empty() ) { @@ -7722,7 +8174,7 @@ namespace Catch { m_deepestSection = node; } - virtual void assertionStarting( AssertionInfo const& ) {} + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} virtual bool assertionEnded( AssertionStats const& assertionStats ) { assert( !m_sectionStack.empty() ); @@ -7730,13 +8182,13 @@ namespace Catch { sectionNode.assertions.push_back( assertionStats ); return true; } - virtual void sectionEnded( SectionStats const& sectionStats ) { + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& node = *m_sectionStack.back(); node.stats = sectionStats; m_sectionStack.pop_back(); } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { Ptr node = new TestCaseNode( testCaseStats ); assert( m_sectionStack.size() == 0 ); node->children.push_back( m_rootSection ); @@ -7747,12 +8199,12 @@ namespace Catch { m_deepestSection->stdOut = testCaseStats.stdOut; m_deepestSection->stdErr = testCaseStats.stdErr; } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { Ptr node = new TestGroupNode( testGroupStats ); node->children.swap( m_testCases ); m_testGroups.push_back( node ); } - virtual void testRunEnded( TestRunStats const& testRunStats ) { + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { Ptr node = new TestRunNode( testRunStats ); node->children.swap( m_testGroups ); m_testRuns.push_back( node ); @@ -7760,7 +8212,7 @@ namespace Catch { } virtual void testRunEndedCumulative() = 0; - virtual void skipTest( TestCaseInfo const& ) {} + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} Ptr m_config; std::ostream& stream; @@ -7774,6 +8226,7 @@ namespace Catch { Ptr m_rootSection; Ptr m_deepestSection; std::vector > m_sectionStack; + ReporterPreferences m_reporterPrefs; }; @@ -7787,6 +8240,17 @@ namespace Catch { return line; } + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { + return false; + } + }; + } // end namespace Catch // #included from: ../internal/catch_reporter_registrars.hpp @@ -7817,7 +8281,7 @@ namespace Catch { template class ReporterRegistrar { - class ReporterFactory : public IReporterFactory { + class ReporterFactory : public SharedImpl { // *** Please Note ***: // - If you end up here looking at a compiler error because it's trying to register @@ -7845,22 +8309,102 @@ namespace Catch { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; + + template + class ListenerRegistrar { + + class ListenerFactory : public SharedImpl { + + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + virtual std::string getDescription() const { + return ""; + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( new ListenerFactory() ); + } + }; } #define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } +#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } + // #included from: ../internal/catch_xmlwriter.hpp #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED #include #include #include +#include namespace Catch { + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void encodeTo( std::ostream& os ) const { + + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t i = 0; i < m_str.size(); ++ i ) { + char c = m_str[i]; + switch( c ) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) + os << ">"; + else + os << c; + break; + + case '\"': + if( m_forWhat == ForAttributes ) + os << """; + else + os << c; + break; + + default: + // Escape control chars - based on contribution by @espenalb in PR #465 + if ( ( c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) + os << "&#x" << std::uppercase << std::hex << static_cast( c ); + else + os << c; + } + } + } + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + private: + std::string m_str; + ForWhat m_forWhat; + }; + class XmlWriter { public: @@ -7872,7 +8416,7 @@ namespace Catch { ScopedElement( ScopedElement const& other ) : m_writer( other.m_writer ){ - other.m_writer = NULL; + other.m_writer = CATCH_NULL; } ~ScopedElement() { @@ -7943,11 +8487,8 @@ namespace Catch { } XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { - if( !name.empty() && !attribute.empty() ) { - stream() << " " << name << "=\""; - writeEncodedText( attribute ); - stream() << "\""; - } + if( !name.empty() && !attribute.empty() ) + stream() << " " << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << "\""; return *this; } @@ -7958,9 +8499,9 @@ namespace Catch { template XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - if( !name.empty() ) - stream() << " " << name << "=\"" << attribute << "\""; - return *this; + std::ostringstream oss; + oss << attribute; + return writeAttribute( name, oss.str() ); } XmlWriter& writeText( std::string const& text, bool indent = true ) { @@ -7969,7 +8510,7 @@ namespace Catch { ensureTagClosed(); if( tagWasOpen && indent ) stream() << m_indent; - writeEncodedText( text ); + stream() << XmlEncode( text ); m_needsNewline = true; } return *this; @@ -8014,30 +8555,6 @@ namespace Catch { } } - void writeEncodedText( std::string const& text ) { - static const char* charsToEncode = "<&\""; - std::string mtext = text; - std::string::size_type pos = mtext.find_first_of( charsToEncode ); - while( pos != std::string::npos ) { - stream() << mtext.substr( 0, pos ); - - switch( mtext[pos] ) { - case '<': - stream() << "<"; - break; - case '&': - stream() << "&"; - break; - case '\"': - stream() << """; - break; - } - mtext = mtext.substr( pos+1 ); - pos = mtext.find_first_of( charsToEncode ); - } - stream() << mtext; - } - bool m_tagIsOpen; bool m_needsNewline; std::vector m_tags; @@ -8046,32 +8563,44 @@ namespace Catch { }; } +// #included from: catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + + namespace Catch { class XmlReporter : public StreamingReporterBase { public: XmlReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_sectionDepth( 0 ) - {} + { + m_reporterPrefs.shouldRedirectStdOut = true; + } - virtual ~XmlReporter(); + virtual ~XmlReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as an XML document"; } public: // StreamingReporterBase - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = true; - return prefs; - } - virtual void noMatchingTestCases( std::string const& s ) { + virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { StreamingReporterBase::noMatchingTestCases( s ); } - virtual void testRunStarting( TestRunInfo const& testInfo ) { + virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testRunStarting( testInfo ); m_xml.setStream( stream ); m_xml.startElement( "Catch" ); @@ -8079,13 +8608,13 @@ namespace Catch { m_xml.writeAttribute( "name", m_config->name() ); } - virtual void testGroupStarting( GroupInfo const& groupInfo ) { + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { StreamingReporterBase::testGroupStarting( groupInfo ); m_xml.startElement( "Group" ) .writeAttribute( "name", groupInfo.name ); } - virtual void testCaseStarting( TestCaseInfo const& testInfo ) { + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testCaseStarting(testInfo); m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); @@ -8093,7 +8622,7 @@ namespace Catch { m_testCaseTimer.start(); } - virtual void sectionStarting( SectionInfo const& sectionInfo ) { + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { StreamingReporterBase::sectionStarting( sectionInfo ); if( m_sectionDepth++ > 0 ) { m_xml.startElement( "Section" ) @@ -8102,9 +8631,9 @@ namespace Catch { } } - virtual void assertionStarting( AssertionInfo const& ) { } + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } - virtual bool assertionEnded( AssertionStats const& assertionStats ) { + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { const AssertionResult& assertionResult = assertionStats.assertionResult; // Print any info messages in tags. @@ -8175,7 +8704,7 @@ namespace Catch { return true; } - virtual void sectionEnded( SectionStats const& sectionStats ) { + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { StreamingReporterBase::sectionEnded( sectionStats ); if( --m_sectionDepth > 0 ) { XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); @@ -8190,7 +8719,7 @@ namespace Catch { } } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( testCaseStats ); XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); @@ -8201,7 +8730,7 @@ namespace Catch { m_xml.endElement(); } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { StreamingReporterBase::testGroupEnded( testGroupStats ); // TODO: Check testGroupStats.aborting and act accordingly. m_xml.scopedElement( "OverallResults" ) @@ -8211,7 +8740,7 @@ namespace Catch { m_xml.endElement(); } - virtual void testRunEnded( TestRunStats const& testRunStats ) { + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { StreamingReporterBase::testRunEnded( testRunStats ); m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testRunStats.totals.assertions.passed ) @@ -8242,28 +8771,24 @@ namespace Catch { JunitReporter( ReporterConfig const& _config ) : CumulativeReporterBase( _config ), xml( _config.stream() ) - {} - - ~JunitReporter(); - - static std::string getDescription() { - return "Reports test results in an XML format that looks like Ant's junitreport target"; + { + m_reporterPrefs.shouldRedirectStdOut = true; } - virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} + virtual ~JunitReporter() CATCH_OVERRIDE; - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = true; - return prefs; + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; } - virtual void testRunStarting( TestRunInfo const& runInfo ) { + virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { CumulativeReporterBase::testRunStarting( runInfo ); xml.startElement( "testsuites" ); } - virtual void testGroupStarting( GroupInfo const& groupInfo ) { + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { suiteTimer.start(); stdOutForSuite.str(""); stdErrForSuite.str(""); @@ -8271,25 +8796,25 @@ namespace Catch { CumulativeReporterBase::testGroupStarting( groupInfo ); } - virtual bool assertionEnded( AssertionStats const& assertionStats ) { + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) unexpectedExceptions++; return CumulativeReporterBase::assertionEnded( assertionStats ); } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { stdOutForSuite << testCaseStats.stdOut; stdErrForSuite << testCaseStats.stdErr; CumulativeReporterBase::testCaseEnded( testCaseStats ); } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { double suiteTime = suiteTimer.getElapsedSeconds(); CumulativeReporterBase::testGroupEnded( testGroupStats ); writeGroup( *m_testGroups.back(), suiteTime ); } - virtual void testRunEndedCumulative() { + virtual void testRunEndedCumulative() CATCH_OVERRIDE { xml.endElement(); } @@ -8454,24 +8979,19 @@ namespace Catch { m_headerPrinted( false ) {} - virtual ~ConsoleReporter(); + virtual ~ConsoleReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as plain lines of text"; } - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = false; - return prefs; - } - virtual void noMatchingTestCases( std::string const& spec ) { + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { stream << "No test cases matched '" << spec << "'" << std::endl; } - virtual void assertionStarting( AssertionInfo const& ) { + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } - virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; @@ -8491,11 +9011,11 @@ namespace Catch { return true; } - virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_headerPrinted = false; StreamingReporterBase::sectionStarting( _sectionInfo ); } - virtual void sectionEnded( SectionStats const& _sectionStats ) { + virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); @@ -8517,11 +9037,11 @@ namespace Catch { StreamingReporterBase::sectionEnded( _sectionStats ); } - virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } - virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { if( currentGroupInfo.used ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; @@ -8530,7 +9050,7 @@ namespace Catch { } StreamingReporterBase::testGroupEnded( _testGroupStats ); } - virtual void testRunEnded( TestRunStats const& _testRunStats ) { + virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { printTotalsDivider( _testRunStats.totals ); printTotals( _testRunStats.totals ); stream << std::endl; @@ -9168,6 +9688,309 @@ namespace Catch { } // end namespace Catch +// #included from: ../reporters/catch_reporter_tap.hpp +#define TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED + +#include + +namespace Catch { + + struct TAPReporter : StreamingReporterBase { + + TAPReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~TAPReporter() {}; + + static std::string getDescription() { + return "Reports test results in TAP format, suitable for test harneses"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "# No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages, ++counter ); + printer.print(); + stream << " # "; + stream << currentTestCaseInfo->name << " " ; + stream << currentTestCaseInfo->tagsAsString; + stream << std::endl; + stream << "# File: " << currentTestCaseInfo->lineInfo.file; + stream << std::endl; + stream << "# Expression: " << result.getExpression(); + + stream << std::endl; + return true; + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + size_t counter = 0; + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages, size_t counter = 0 ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + , counter(counter) + {} + + void print() { + //printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + //printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + //if( result.isOk() ) + // printResultType( Colour::ResultSuccess, failedString() + std::string( " # TODO" ) ); + //else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + //printReconstructedExpression(); + if( result.isOk() ) + printIssue( " # TODO" ); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + + static const char* failedString() { return "not ok"; } + static const char* passedString() { return "ok"; } + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( Colour::Code colour, std::string passOrFail ) const { + if( !passOrFail.empty() ) { + { + //Colour colourGuard( colour ); + stream << passOrFail << " " << counter; + } + stream << " -"; + } + } + + void printIssue( std::string issue ) const { + stream << " " << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ";"; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << " " << result.getExpandedExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + std::string expr = result.getExpandedExpression(); + std::replace( expr.begin(), expr.end(), '\n', ' '); + stream << expr; + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << "'"; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ":"; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << "'"; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + size_t counter; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? "" : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "1..0 # Skipped: No tests ran."; + //stream << "No tests ran."; + } + else { + stream << "1.." << counter; + } + //else if( totals.testCases.failed == totals.testCases.total() ) { + // Colour colour( Colour::ResultError ); + // const std::string qualify_assertions_failed = + // totals.assertions.failed == totals.assertions.total() ? + // bothOrAll( totals.assertions.failed ) : ""; + // stream << + // "Failed " << bothOrAll( totals.testCases.failed ) + // << pluralise( totals.testCases.failed, "test case" ) << ", " + // "failed " << qualify_assertions_failed << + // pluralise( totals.assertions.failed, "assertion" ) << "."; + //} + //else if( totals.assertions.total() == 0 ) { + // stream << + // "Passed " << bothOrAll( totals.testCases.total() ) + // << pluralise( totals.testCases.total(), "test case" ) + // << " (no assertions)."; + //} + //else if( totals.assertions.failed ) { + // Colour colour( Colour::ResultError ); + // stream << + // "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + // "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; + //} + //else { + // Colour colour( Colour::ResultSuccess ); + // stream << + // "Passed " << bothOrAll( totals.testCases.passed ) + // << pluralise( totals.testCases.passed, "test case" ) << + // " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; + //} + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "tap", TAPReporter ) + +} // end namespace Catch + namespace Catch { NonCopyable::~NonCopyable() {} IShared::~IShared() {} @@ -9204,6 +10027,7 @@ namespace Catch { FreeFunctionTestCase::~FreeFunctionTestCase() {} IGeneratorInfo::~IGeneratorInfo() {} IGeneratorsForTest::~IGeneratorsForTest() {} + WildcardPattern::~WildcardPattern() {} TestSpec::Pattern::~Pattern() {} TestSpec::NamePattern::~NamePattern() {} TestSpec::TagPattern::~TagPattern() {} @@ -9268,8 +10092,9 @@ int main (int argc, char * const argv[]) { #define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) #define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) -#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "CATCH_REQUIRE_THROWS" ) #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "CATCH_REQUIRE_THROWS_WITH" ) #define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) #define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) @@ -9280,6 +10105,7 @@ int main (int argc, char * const argv[]) { #define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" ) #define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) @@ -9321,11 +10147,11 @@ int main (int argc, char * const argv[]) { #define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) #define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif -#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) -#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) -#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) -#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) -#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else @@ -9333,8 +10159,9 @@ int main (int argc, char * const argv[]) { #define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) #define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) -#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "REQUIRE_THROWS" ) #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "REQUIRE_THROWS_WITH" ) #define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) #define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) @@ -9343,8 +10170,9 @@ int main (int argc, char * const argv[]) { #define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) #define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) -#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CHECK_THROWS" ) #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CHECK_THROWS_WITH" ) #define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) @@ -9390,27 +10218,13 @@ int main (int argc, char * const argv[]) { #define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) #define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif -#define GIVEN( desc ) SECTION( " Given: " desc, "" ) -#define WHEN( desc ) SECTION( " When: " desc, "" ) -#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) -#define THEN( desc ) SECTION( " Then: " desc, "" ) -#define AND_THEN( desc ) SECTION( " And: " desc, "" ) +#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) +#define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) +#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) +#define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) +#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) using Catch::Detail::Approx; -// #included from: internal/catch_reenable_warnings.h - -#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(pop) -# else -# pragma clang diagnostic pop -# endif -#elif defined __GNUC__ -# pragma GCC diagnostic pop -#endif - #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED diff --git a/tests/catch3.hpp b/tests/catch3.hpp new file mode 100644 index 0000000..de61226 --- /dev/null +++ b/tests/catch3.hpp @@ -0,0 +1,9416 @@ +/* + * Catch v1.2.1 + * Generated: 2015-06-30 18:23:27.961086 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// #included from: internal/catch_suppress_warnings.h + +#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# pragma clang diagnostic ignored "-Wswitch-enum" +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif + +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +#endif + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #included from: catch_common.h +#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include +#include +#include + +// #included from: catch_compiler_capabilities.h +#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? +// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported + +// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Borland +#ifdef __BORLANDC__ + +#endif // __BORLANDC__ + +//////////////////////////////////////////////////////////////////////////////// +// EDG +#ifdef __EDG_VERSION__ + +#endif // __EDG_VERSION__ + +//////////////////////////////////////////////////////////////////////////////// +// Digital Mars +#ifdef __DMC__ + +#endif // __DMC__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#endif // _MSC_VER + +// Use variadic macros if the compiler supports them +#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ + ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ + ( defined __GNUC__ && __GNUC__ >= 3 ) || \ + ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) + +#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if (__cplusplus >= 201103L) + +# define CATCH_CPP11_OR_GREATER + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# endif + +# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NULLPTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_IS_ENUM +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TUPLE +#endif +#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) +#define CATCH_CONFIG_VARIADIC_MACROS +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +namespace Catch { + + class NonCopyable { +#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); + SourceLineInfo( SourceLineInfo const& other ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; + + std::string file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) +#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); + +#include + +namespace Catch { + + class NotImplementedException : public std::exception + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + NotImplementedException( NotImplementedException const& ) {} + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr { + public: + Ptr() : m_p( NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } + T* get() { return m_p; } + const T* get() const{ return m_p; } + T& operator*() const { return *m_p; } + T* operator->() const { return m_p; } + bool operator !() const { return m_p == NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const = 0; + + }; +} + +namespace Catch { + +template +class MethodTestCase : public SharedImpl { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +struct AutoReg { + + AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template + AutoReg( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + registerTestCase( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } + + void registerTestCase( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( ... ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#else + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include + +namespace Catch { + + struct AssertionInfo + { + AssertionInfo() {} + AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ); + + std::string macroName; + SourceLineInfo lineInfo; + std::string capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + + struct AssertionResultData + { + AssertionResultData() : resultType( ResultWas::Unknown ) {} + + std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +namespace Catch { + + struct TestFailureException{}; + + template class ExpressionLhs; + + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct CopyableStream { + CopyableStream() {} + CopyableStream( CopyableStream const& other ) { + oss << other.oss.str(); + } + CopyableStream& operator=( CopyableStream const& other ) { + oss.str(""); + oss << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ); + + template + ExpressionLhs operator <= ( T const& operand ); + ExpressionLhs operator <= ( bool value ); + + template + ResultBuilder& operator << ( T const& value ) { + m_stream.oss << value; + return *this; + } + + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + + ResultBuilder& setResultType( ResultWas::OfType result ); + ResultBuilder& setResultType( bool result ); + ResultBuilder& setLhs( std::string const& lhs ); + ResultBuilder& setRhs( std::string const& rhs ); + ResultBuilder& setOp( std::string const& op ); + + void endExpression(); + + std::string reconstructExpression() const; + AssertionResult build() const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + struct ExprComponents { + ExprComponents() : testFalse( false ) {} + bool testFalse; + std::string lhs, rhs, op; + } m_exprComponents; + CopyableStream m_stream; + + bool m_shouldDebugBreak; + bool m_shouldThrow; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#endif + +#include + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + template + inline T& opCast(T const& t) { return const_cast(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + class Evaluator{}; + + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return opCast( lhs ) == opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) != opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) < opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) > opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) >= opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) <= opCast( rhs ); + } + }; + + template + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template bool compare( long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template bool compare( int lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, int rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +#ifdef CATCH_CONFIG_CPP11_NULLPTR + // pointer to nullptr_t (when comparing against nullptr) + template bool compare( std::nullptr_t, T* rhs ) { + return Evaluator::evaluate( NULL, rhs ); + } + template bool compare( T* lhs, std::nullptr_t ) { + return Evaluator::evaluate( lhs, NULL ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #included from: catch_tostring.h +#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED + +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +#ifdef CATCH_CONFIG_CPP11_TUPLE +#include +#endif + +#ifdef CATCH_CONFIG_CPP11_IS_ENUM +#include +#endif + +namespace Catch { + +// Why we're here. +template +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + +namespace Detail { + + extern std::string unprintableString; + + struct BorgType { + template BorgType( T const& ); + }; + + struct TrueType { char sizer[1]; }; + struct FalseType { char sizer[2]; }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; + +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template + struct EnumStringMaker + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast::type>(v) + ); + } + }; +#endif + template + struct StringMakerBase { +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template + static std::string convert( T const& v ) + { + return EnumStringMaker::convert( v ); + } +#else + template + static std::string convert( T const& ) { return unprintableString; } +#endif + }; + + template<> + struct StringMakerBase { + template + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + inline std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template +struct StringMaker : + Detail::StringMakerBase::value> {}; + +template +struct StringMaker { + template + static std::string convert( U* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +template +struct StringMaker { + static std::string convert( R C::* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ); +} + +//template +//struct StringMaker > { +// static std::string convert( std::vector const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + +template +std::string toString( std::vector const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CONFIG_CPP11_TUPLE + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get(tuple)); + ElementPrinter::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template +struct StringMaker> { + + static std::string convert( const std::tuple& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter>::print( tuple, os ); + os << " }"; + return os.str(); + } +}; +#endif // CATCH_CONFIG_CPP11_TUPLE + +namespace Detail { + template + std::string makeString( T const& value ) { + return StringMaker::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +std::string toString( T const& value ) { + return StringMaker::convert( value ); +} + + namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); + } + oss << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +namespace Catch { + +// Wraps the LHS of an expression and captures the operator and RHS (if any) - +// wrapping them all in a ResultBuilder object +template +class ExpressionLhs { + ExpressionLhs& operator = ( ExpressionLhs const& ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + ExpressionLhs& operator = ( ExpressionLhs && ) = delete; +# endif + +public: + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + ExpressionLhs( ExpressionLhs const& ) = default; + ExpressionLhs( ExpressionLhs && ) = default; +# endif + + template + ResultBuilder& operator == ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator != ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator < ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator > ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator <= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator >= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator == ( bool rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator != ( bool rhs ) { + return captureExpression( rhs ); + } + + void endExpression() { + bool value = m_lhs ? true : false; + m_rb + .setLhs( Catch::toString( value ) ) + .setResultType( value ) + .endExpression(); + } + + // Only simple binary expressions are allowed on the LHS. + // If more complex compositions are required then place the sub expression in parentheses + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + +private: + template + ResultBuilder& captureExpression( RhsT const& rhs ) { + return m_rb + .setResultType( Internal::compare( m_lhs, rhs ) ) + .setLhs( Catch::toString( m_lhs ) ) + .setRhs( Catch::toString( rhs ) ) + .setOp( Internal::OperatorTraits::getName() ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; +}; + +} // end namespace Catch + + +namespace Catch { + + template + inline ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { + return ExpressionLhs( *this, operand ); + } + + inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { + return ExpressionLhs( *this, value ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual void assertionEnded( AssertionResult const& result ) = 0; + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_IPHONE +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CATCH_PLATFORM_WINDOWS +#endif + +#include + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // The following code snippet based on: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #ifdef DEBUG + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_BREAK_INTO_DEBUGGER() \ + if( Catch::isDebuggerActive() ) { \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ); \ + } + #else + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} + #endif + #endif + +#elif defined(_MSC_VER) + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } +#endif + +#ifndef CATCH_BREAK_INTO_DEBUGGER +#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + ( __catchResult <= expr ).endExpression(); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( !Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( log, macroName ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \ + try { \ + std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \ + __catchResult \ + .setLhs( Catch::toString( arg ) ) \ + .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ + .setOp( "matches" ) \ + .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \ + __catchResult.captureExpression(); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #included from: catch_section_info.h +#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// #included from: catch_totals.hpp +#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool allOk() const { + return failed == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef CATCH_PLATFORM_WINDOWS +typedef unsigned long long uint64_t; +#else +#include +#endif + +namespace Catch { + + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedMicroseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + uint64_t m_ticks; + }; + +} // namespace Catch + +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + +template +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template +class BetweenGenerator : public IGenerator { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast( index ); + } + + virtual std::size_t size() const { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector m_values; +}; + +template +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template + CompositeGenerator between( T from, T to ) { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3 ){ + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate() const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate() const { + try { + throw; + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include +#include + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_scale( 1.0 ), + m_value( value ) + {} + + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.scale( m_scale ); + return approx; + } + + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template + struct Matcher : SharedImpl + { + typedef ExpressionT ExpressionType; + + virtual ~Matcher() {} + virtual Ptr clone() const = 0; + virtual bool match( ExpressionT const& expr ) const = 0; + virtual std::string toString() const = 0; + }; + + template + struct MatcherImpl : Matcher { + + virtual Ptr > clone() const { + return Ptr >( new DerivedT( static_cast( *this ) ) ); + } + }; + + namespace Generic { + + template + class AllOf : public MatcherImpl, ExpressionT> { + public: + + AllOf() {} + AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + + AllOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( !m_matchers[i]->match( expr ) ) + return false; + return true; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " and "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + template + class AnyOf : public MatcherImpl, ExpressionT> { + public: + + AnyOf() {} + AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} + + AnyOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( m_matchers[i]->match( expr ) ) + return true; + return false; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " or "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + } + + namespace StdString { + + inline std::string makeString( std::string const& str ) { return str; } + inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } + + struct Equals : MatcherImpl { + Equals( std::string const& str ) : m_str( str ){} + Equals( Equals const& other ) : m_str( other.m_str ){} + + virtual ~Equals(); + + virtual bool match( std::string const& expr ) const { + return m_str == expr; + } + virtual std::string toString() const { + return "equals: \"" + m_str + "\""; + } + + std::string m_str; + }; + + struct Contains : MatcherImpl { + Contains( std::string const& substr ) : m_substr( substr ){} + Contains( Contains const& other ) : m_substr( other.m_substr ){} + + virtual ~Contains(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) != std::string::npos; + } + virtual std::string toString() const { + return "contains: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct StartsWith : MatcherImpl { + StartsWith( std::string const& substr ) : m_substr( substr ){} + StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~StartsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == 0; + } + virtual std::string toString() const { + return "starts with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct EndsWith : MatcherImpl { + EndsWith( std::string const& substr ) : m_substr( substr ){} + EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~EndsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == expr.size() - m_substr.size(); + } + virtual std::string toString() const { + return "ends with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + } // namespace StdString + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); + } + + inline Impl::StdString::Equals Equals( std::string const& str ) { + return Impl::StdString::Equals( str ); + } + inline Impl::StdString::Equals Equals( const char* str ) { + return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); + } + inline Impl::StdString::Contains Contains( std::string const& substr ) { + return Impl::StdString::Contains( substr ); + } + inline Impl::StdString::Contains Contains( const char* substr ) { + return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { + return Impl::StdString::StartsWith( substr ); + } + inline Impl::StdString::StartsWith StartsWith( const char* substr ) { + return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { + return Impl::StdString::EndsWith( substr ); + } + inline Impl::StdString::EndsWith EndsWith( const char* substr ) { + return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != NULL; } + bool none() const { return nullableValue == NULL; } + + bool operator !() const { return nullableValue == NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T* nullableValue; + char storage[sizeof(T)]; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set tags; + std::set lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + template + struct StringHolder : MatcherImpl{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string toString() const { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string toString() const { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string toString() const { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string toString() const { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_IMPL +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: ../catch_runner.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + class NamePattern : public Pattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { + if( startsWith( m_name, "*" ) ) { + m_name = m_name.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_name, "*" ) ) { + m_name = m_name.substr( 0, m_name.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_name == toLower( testCase.name ); + case WildcardAtStart: + return endsWith( toLower( testCase.name ), m_name ); + case WildcardAtEnd: + return startsWith( toLower( testCase.name ), m_name ); + case WildcardAtBothEnds: + return contains( toLower( testCase.name ), m_name ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string m_name; + WildcardPosition m_wildcard; + }; + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr m_underlyingPattern; + }; + + struct Filter { + std::vector > m_patterns; + + bool matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) + if( !(*it)->matches( testCase ) ) + return false; + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + } + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template + void addPattern() { + std::string token = subString(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual bool forceColour() const = 0; + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + class Stream { + public: + Stream(); + Stream( std::streambuf* _streamBuf, bool _isOwned ); + void release(); + + std::streambuf* streamBuf; + + private: + bool isOwned; + }; + + std::ostream& cout(); + std::ostream& cerr(); +} + +#include +#include +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + forceColour( false ), + abortAfter( -1 ), + rngSeed( 0 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + bool forceColour; + + int abortAfter; + unsigned int rngSeed; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + + std::string reporterName; + std::string outputFilename; + std::string name; + std::string processName; + + std::vector testsOrTags; + }; + + class Config : public SharedImpl { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + : m_os( Catch::cout().rdbuf() ) + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_os( Catch::cout().rdbuf() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() { + m_os.rdbuf( Catch::cout().rdbuf() ); + m_stream.release(); + } + + void setFilename( std::string const& filename ) { + m_data.outputFilename = filename; + } + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + + std::string getProcessName() const { return m_data.processName; } + + bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } + + void setStreamBuf( std::streambuf* buf ) { + m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() ); + } + + void useStream( std::string const& streamName ) { + Stream stream = createStream( streamName ); + setStreamBuf( stream.streamBuf ); + m_stream.release(); + m_stream = stream; + } + + std::string getReporterName() const { return m_data.reporterName; } + + int abortAfter() const { return m_data.abortAfter; } + + TestSpec const& testSpec() const { return m_testSpec; } + + bool showHelp() const { return m_data.showHelp; } + bool showInvisibles() const { return m_data.showInvisibles; } + + // IConfig interface + virtual bool allowThrows() const { return !m_data.noThrow; } + virtual std::ostream& stream() const { return m_os; } + virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } + virtual unsigned int rngSeed() const { return m_data.rngSeed; } + virtual bool forceColour() const { return m_data.forceColour; } + + private: + ConfigData m_data; + + Stream m_stream; + mutable std::ostream m_os; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +#include +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + } + inline void convertInto( bool _source, bool& _dest ) { + _dest = _source; + } + template + inline void convertInto( bool, T& ) { + throw std::runtime_error( "Invalid conversion" ); + } + + template + struct IArgFunction { + virtual ~IArgFunction() {} +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +# endif + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual void setFlag( ConfigT& config ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( NULL ) {} + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + void setFlag( ConfigT& config ) const { + functionObj->setFlag( config ); + } + bool takesArg() const { return functionObj->takesArg(); } + + bool isSet() const { + return functionObj != NULL; + } + private: + IArgFunction* functionObj; + }; + + template + struct NullBinder : IArgFunction{ + virtual void set( C&, std::string const& ) const {} + virtual void setFlag( C& ) const {} + virtual bool takesArg() const { return true; } + virtual IArgFunction* clone() const { return new NullBinder( *this ); } + }; + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual void setFlag( C& p ) const { + convertInto( true, p.*member ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual void setFlag( C& p ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual void setFlag( C& p ) const { + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual void setFlag( C& p ) const { + function( p ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual void setFlag( C& obj ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + } // namespace Detail + + struct Parser { + Parser() : separators( " \t=:" ) {} + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + void parseIntoTokens( int argc, char const * const * argv, std::vector& tokens ) const { + const std::string doubleDash = "--"; + for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) + parseIntoTokens( argv[i] , tokens); + } + void parseIntoTokens( std::string arg, std::vector& tokens ) const { + while( !arg.empty() ) { + Parser::Token token( Parser::Token::Positional, arg ); + arg = ""; + if( token.data[0] == '-' ) { + if( token.data.size() > 1 && token.data[1] == '-' ) { + token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); + } + else { + token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); + if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { + arg = "-" + token.data.substr( 1 ); + token.data = token.data.substr( 0, 1 ); + } + } + } + if( token.type != Parser::Token::Positional ) { + std::size_t pos = token.data.find_first_of( separators ); + if( pos != std::string::npos ) { + arg = token.data.substr( pos+1 ); + token.data = token.data.substr( 0, pos ); + } + } + tokens.push_back( token ); + } + } + std::string separators; + }; + + template + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template + class CommandLine { + + struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} + + using CommonArgProperties::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + // NOTE: std::auto_ptr is deprecated in c++11/c++0x +#if defined(__cplusplus) && __cplusplus > 199711L + typedef std::unique_ptr ArgAutoPtr; +#else + typedef std::auto_ptr ArgAutoPtr; +#endif + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg.reset( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember( field ); + } + template + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( int argc, char const * const * argv ) const { + ConfigT config; + parseInto( argc, argv, config ); + return config; + } + + std::vector parseInto( int argc, char const * const * argv, ConfigT& config ) const { + std::string processName = argv[0]; + std::size_t lastSlash = processName.find_last_of( "/\\" ); + if( lastSlash != std::string::npos ) + processName = processName.substr( lastSlash+1 ); + m_boundProcessName.set( config, processName ); + std::vector tokens; + Parser parser; + parser.parseIntoTokens( argc, argv, tokens ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + std::vector errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.setFlag( config ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( errors.empty() && m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction m_boundProcessName; + std::vector m_options; + std::map m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); + } + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, "#" ) ) + addTestOrTags( config, "\"" + line + "\"," ); + } + } + + inline Clara::CommandLine makeCommandLineParser() { + + using namespace Clara; + CommandLine cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &ConfigData::reporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes/no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + // Less common commands which don't have a short form + cli["--list-test-names-only"] + .describe( "list all/matching test cases names only" ) + .bind( &ConfigData::listTestNamesOnly ); + + cli["--list-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output" ) + .bind( &ConfigData::forceColour ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include +#include +#include + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include +#include +#include +#include + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& stream() const { return *m_stream; } + Ptr fullConfig() const { return m_fullConfig; } + + private: + std::ostream* m_stream; + Ptr m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + }; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map FactoryMap; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + }; + +} + +#include +#include + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; + else + Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Catch::cout() << testCaseInfo.name << std::endl; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map tagCounts; + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + Catch::cout() << oss.str() << wrapper << "\n"; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + Catch::cout() << " " + << it->first + << ":" + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + inline Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_runner_impl.hpp +#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + +// #included from: catch_test_case_tracker.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { +namespace SectionTracking { + + class TrackedSection { + + typedef std::map TrackedSections; + + public: + enum RunState { + NotStarted, + Executing, + ExecutingChildren, + Completed + }; + + TrackedSection( std::string const& name, TrackedSection* parent ) + : m_name( name ), m_runState( NotStarted ), m_parent( parent ) + {} + + RunState runState() const { return m_runState; } + + TrackedSection* findChild( std::string const& childName ); + TrackedSection* acquireChild( std::string const& childName ); + + void enter() { + if( m_runState == NotStarted ) + m_runState = Executing; + } + void leave(); + + TrackedSection* getParent() { + return m_parent; + } + bool hasChildren() const { + return !m_children.empty(); + } + + private: + std::string m_name; + RunState m_runState; + TrackedSections m_children; + TrackedSection* m_parent; + }; + + inline TrackedSection* TrackedSection::findChild( std::string const& childName ) { + TrackedSections::iterator it = m_children.find( childName ); + return it != m_children.end() + ? &it->second + : NULL; + } + inline TrackedSection* TrackedSection::acquireChild( std::string const& childName ) { + if( TrackedSection* child = findChild( childName ) ) + return child; + m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); + return findChild( childName ); + } + inline void TrackedSection::leave() { + for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); + it != itEnd; + ++it ) + if( it->second.runState() != Completed ) { + m_runState = ExecutingChildren; + return; + } + m_runState = Completed; + } + + class TestCaseTracker { + public: + TestCaseTracker( std::string const& testCaseName ) + : m_testCase( testCaseName, NULL ), + m_currentSection( &m_testCase ), + m_completedASectionThisRun( false ) + {} + + bool enterSection( std::string const& name ) { + TrackedSection* child = m_currentSection->acquireChild( name ); + if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed ) + return false; + + m_currentSection = child; + m_currentSection->enter(); + return true; + } + void leaveSection() { + m_currentSection->leave(); + m_currentSection = m_currentSection->getParent(); + assert( m_currentSection != NULL ); + m_completedASectionThisRun = true; + } + + bool currentSectionHasChildren() const { + return m_currentSection->hasChildren(); + } + bool isCompleted() const { + return m_testCase.runState() == TrackedSection::Completed; + } + + class Guard { + public: + Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) { + m_tracker.enterTestCase(); + } + ~Guard() { + m_tracker.leaveTestCase(); + } + private: + Guard( Guard const& ); + void operator = ( Guard const& ); + TestCaseTracker& m_tracker; + }; + + private: + void enterTestCase() { + m_currentSection = &m_testCase; + m_completedASectionThisRun = false; + m_testCase.enter(); + } + void leaveTestCase() { + m_testCase.leave(); + } + + TrackedSection m_testCase; + TrackedSection* m_currentSection; + bool m_completedASectionThisRun; + }; + +} // namespace SectionTracking + +using SectionTracking::TestCaseTracker; + +} // namespace Catch + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition then exit the process + inline void fatal( std::string const& message, int exitCode ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + + if( Catch::alwaysTrue() ) // avoids "no return" warnings + exit( exitCode ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { + + struct FatalConditionHandler { + void reset() {} + }; + +} // namespace Catch + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +#include + +namespace Catch { + + struct SignalDefs { int id; const char* name; }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static void handleSignal( int sig ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + if( sig == signalDefs[i].id ) + fatal( signalDefs[i].name, -sig ); + fatal( "", -sig ); + } + + FatalConditionHandler() : m_isSet( true ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, handleSignal ); + } + ~FatalConditionHandler() { + reset(); + } + void reset() { + if( m_isSet ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, SIG_DFL ); + m_isSet = false; + } + } + + bool m_isSet; + }; + +} // namespace Catch + +#endif // not Windows + +#include +#include + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr const& config, Ptr const& reporter ) + : m_runInfo( config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( NULL ), + m_config( config ), + m_reporter( reporter ), + m_prevRunner( m_context.getRunner() ), + m_prevResultCapture( m_context.getResultCapture() ), + m_prevConfig( m_context.getConfig() ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + virtual ~RunContext() { + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + m_context.setRunner( m_prevRunner ); + m_context.setConfig( NULL ); + m_context.setResultCapture( m_prevResultCapture ); + m_context.setConfig( m_prevConfig ); + } + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + m_testCaseTracker = TestCaseTracker( testInfo.name ); + + do { + do { + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isCompleted() && !aborting() ); + } + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = NULL; + m_testCaseTracker.reset(); + + return deltaTotals; + } + + Ptr config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + m_totals.assertions.failed++; + } + + if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) + m_messages.clear(); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + std::ostringstream oss; + oss << sectionInfo.name << "@" << sectionInfo.lineInfo; + + if( !m_testCaseTracker->enterSection( oss.str() ) ) + return false; + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 || + !m_config->warnAboutMissingAssertions() || + m_testCaseTracker->currentSectionHasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { + if( std::uncaught_exception() ) { + m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); + return; + } + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + m_testCaseTracker->leaveSection(); + + m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : ""; + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); + resultBuilder.setResultType( ResultWas::FatalErrorCondition ); + resultBuilder << message; + resultBuilder.captureExpression(); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + "", + "", + false ) ); + m_totals.testCases.failed++; + testGroupEnded( "", m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + TestCaseTracker::Guard guard( *m_testCaseTracker ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); + invokeActiveTestCase(); + } + else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // This just means the test was aborted due to failure + } + catch(...) { + makeUnexpectedResultBuilder().useActiveException(); + } + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( testCaseInfo.okToFail() ) { + std::swap( assertions.failedButOk, assertions.failed ); + m_totals.assertions.failed -= assertions.failedButOk; + m_totals.assertions.failedButOk += assertions.failedButOk; + } + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression.c_str(), + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); + m_unfinishedSections.clear(); + } + + struct UnfinishedSections { + UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) + : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo info; + Counts prevAssertions; + double durationInSeconds; + }; + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + Option m_testCaseTracker; + AssertionResult m_lastResult; + + Ptr m_config; + Totals m_totals; + Ptr m_reporter; + std::vector m_messages; + IRunner* m_prevRunner; + IResultCapture* m_prevResultCapture; + Ptr m_prevConfig; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + struct Version { + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + std::string const& _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + std::string const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + + private: + void operator=( Version const& ); + }; + + extern Version libraryVersion; +} + +#include +#include +#include + +namespace Catch { + + class Runner { + + public: + Runner( Ptr const& config ) + : m_config( config ) + { + openStream(); + makeReporter(); + } + + Totals runTests() { + + RunContext context( m_config.get(), m_reporter ); + + Totals totals; + + context.testGroupStarting( "all tests", 1, 1 ); // deprecated? + + TestSpec testSpec = m_config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector testCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); + + int testsRunForGroup = 0; + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) { + testsRunForGroup++; + if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { + + if( context.aborting() ) + break; + + totals += context.runTest( *it ); + m_testsAlreadyRun.insert( *it ); + } + } + std::vector skippedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); + + for( std::vector::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end(); + it != itEnd; + ++it ) + m_reporter->skipTest( *it ); + + context.testGroupEnded( "all tests", totals, 1, 1 ); + return totals; + } + + private: + void openStream() { + // Open output file, if specified + if( !m_config->getFilename().empty() ) { + m_ofs.open( m_config->getFilename().c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << m_config->getFilename() << "'"; + throw std::domain_error( oss.str() ); + } + m_config->setStreamBuf( m_ofs.rdbuf() ); + } + } + void makeReporter() { + std::string reporterName = m_config->getReporterName().empty() + ? "console" + : m_config->getReporterName(); + + m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); + if( !m_reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + } + + private: + Ptr m_config; + std::ofstream m_ofs; + Ptr m_reporter; + std::set m_testsAlreadyRun; + }; + + class Session : NonCopyable { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + Catch::cerr() << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + void showHelp( std::string const& processName ) { + Catch::cout() << "\nCatch v" << libraryVersion << "\n"; + + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + } + + int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( Catch::cout(), m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char* const argv[] ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + + std::srand( m_configData.rngSeed ); + + Runner runner( m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runner.runTests().assertions.failed ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + + private: + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + + class TestRegistry : public ITestCaseRegistry { + struct LexSort { + bool operator() (TestCase i,TestCase j) const { return (i const& getAllTests() const { + return m_functionsInOrder; + } + + virtual std::vector const& getAllNonHiddenTests() const { + return m_nonHiddenFunctions; + } + + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const { + + for( std::vector::const_iterator it = m_functionsInOrder.begin(), + itEnd = m_functionsInOrder.end(); + it != itEnd; + ++it ) { + bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ); + if( includeTest != negated ) + matchingTestCases.push_back( *it ); + } + sortTests( config, matchingTestCases ); + } + + private: + + static void sortTests( IConfig const& config, std::vector& matchingTestCases ) { + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() ); + break; + case RunTests::InRandomOrder: + { + RandomNumberGenerator rng; + std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + } + std::set m_functions; + std::vector m_functionsInOrder; + std::vector m_nonHiddenFunctions; + size_t m_unnamedCount; + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, "&" ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + } + + AutoReg::~AutoReg() {} + + void AutoReg::registerTestCase( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + getMutableRegistryHub().registerTest + ( makeTestCase( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() { + deleteAllValues( m_factories ); + } + + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + + FactoryMap const& getFactories() const { + return m_factories; + } + + private: + FactoryMap m_factories; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + throw; + } + @catch (NSException *exception) { + return Catch::toString( [exception description] ); + } +#else + throw; +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return tryTranslators( m_translators.begin() ); + } + } + + std::string tryTranslators( std::vector::const_iterator it ) const { + if( it == m_translators.end() ) + return "Unknown exception"; + + try { + return (*it)->translate(); + } + catch(...) { + return tryTranslators( it+1 ); + } + } + + private: + std::vector m_translators; + }; +} + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + return m_exceptionTranslatorRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerTest( TestCase const& testInfo ) { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + +#include + +namespace Catch { + + class StreamBufBase : public std::streambuf { + public: + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} + +#include +#include +#include + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + Stream::Stream() + : streamBuf( NULL ), isOwned( false ) + {} + + Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) + : streamBuf( _streamBuf ), isOwned( _isOwned ) + {} + + void Stream::release() { + if( isOwned ) { + delete streamBuf; + streamBuf = NULL; + isOwned = false; + } + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } +#endif +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; + + namespace { + Context* currentContext = NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + Stream createStream( std::string const& streamName ) { + if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false ); + if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false ); + if( streamName == "debug" ) return Stream( new StreamBufImpl, true ); + + throw std::domain_error( "Unknown stream: " + streamName ); + } + + void cleanUpContext() { + delete currentContext; + currentContext = NULL; + } +} + +// #included from: catch_console_colour_impl.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalAttributes = csbiInfo.wAttributes; + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + } + HANDLE stdoutHandle; + WORD originalAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + return &s_instance; + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0:34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + IColourImpl* platformColourInstance() { + Ptr config = getCurrentContext().getConfig(); + return (config && config->forceColour()) || isatty(STDOUT_FILENO) + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } + Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = isDebuggerActive() + ? NoColourImpl::instance() + : platformColourInstance(); + impl->use( _colourCode ); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!" + m_info.capturedExpression; + else + return m_info.capturedExpression; + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName.empty() ) + return m_info.capturedExpression; + else + return m_info.macroName + "( " + m_info.capturedExpression + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructedExpression; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, "." ) || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); + } + inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + if( isReservedTag( tag ) ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n"; + } + { + Colour colourGuard( Colour::FileName ); + Catch::cerr() << _lineInfo << std::endl; + } + exit(1); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + tags( _tags ), + lineInfo( _lineInfo ), + properties( None ) + { + std::ostringstream oss; + for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { + oss << "[" << *it << "]"; + std::string lcaseTag = toLower( *it ); + properties = static_cast( properties | parseSpecialTag( lcaseTag ) ); + lcaseTags.insert( lcaseTag ); + } + tagsAsString = oss.str(); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + std::string const& _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << "." + << version.minorVersion << "." + << version.patchNumber; + + if( !version.branchName.empty() ) { + os << "-" << version.branchName + << "." << version.buildNumber; + } + return os; + } + + Version libraryVersion( 1, 2, 1, "", 0 ); + +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + + ScopedMessage::~ScopedMessage() { + getResultCapture().popScopedMessage( m_info ); + } + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl + { + public: + LegacyReporterAdapter( Ptr const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); + + private: + Ptr m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef CATCH_PLATFORM_WINDOWS +#include +#else +#include +#endif + +namespace Catch { + + namespace { +#ifdef CATCH_PLATFORM_WINDOWS + uint64_t getCurrentTicks() { + static uint64_t hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency( reinterpret_cast( &hz ) ); + QueryPerformanceCounter( reinterpret_cast( &hzo ) ); + } + uint64_t t; + QueryPerformanceCounter( reinterpret_cast( &t ) ); + return ((t-hzo)*1000000)/hz; + } +#else + uint64_t getCurrentTicks() { + timeval t; + gettimeofday(&t,NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast(getElapsedMicroseconds()/1000); + } + double Timer::getElapsedSeconds() const { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), ::tolower ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << " " << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << "s"; + return os; + } + + SourceLineInfo::SourceLineInfo() : line( 0 ){} + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) + : file( other.file ), + line( other.line ) + {} + bool SourceLineInfo::empty() const { + return file.empty(); + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && file == other.file; + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && file < other.file ); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << "(" << info.line << ")"; +#else + os << info.file << ":" << info.line; +#endif + return os; + } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { + std::ostringstream oss; + oss << locationInfo << ": Internal Catch error: '" << message << "'"; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) + getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#include + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#endif // Platform + +#ifdef CATCH_PLATFORM_WINDOWS + extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + std::string unprintableString = "{?}"; + + namespace { + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return "\"" + s + "\""; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; + return Catch::toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + oss << value; + if( value >= 255 ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + oss << value; + if( value >= 255 ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return Catch::toString( static_cast( value ) ); +} + +template +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + "f"; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + return value < ' ' + ? toString( static_cast( value ) ) + : Detail::makeString( value ); +} + +std::string toString( signed char value ) { + return toString( static_cast( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +namespace Catch { + + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ) + {} + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; + return *this; + } + ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { + m_exprComponents.lhs = lhs; + return *this; + } + ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { + m_exprComponents.rhs = rhs; + return *this; + } + ResultBuilder& ResultBuilder::setOp( std::string const& op ) { + m_exprComponents.op = op; + return *this; + } + + void ResultBuilder::endExpression() { + m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); + captureExpression(); + } + + void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + m_stream.oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) + m_shouldThrow = true; + } + } + void ResultBuilder::react() { + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() const + { + assert( m_data.resultType != ResultWas::Unknown ); + + AssertionResultData data = m_data; + + // Flip bool results if testFalse is set + if( m_exprComponents.testFalse ) { + if( data.resultType == ResultWas::Ok ) + data.resultType = ResultWas::ExpressionFailed; + else if( data.resultType == ResultWas::ExpressionFailed ) + data.resultType = ResultWas::Ok; + } + + data.message = m_stream.oss.str(); + data.reconstructedExpression = reconstructExpression(); + if( m_exprComponents.testFalse ) { + if( m_exprComponents.op == "" ) + data.reconstructedExpression = "!" + data.reconstructedExpression; + else + data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; + } + return AssertionResult( m_assertionInfo, data ); + } + std::string ResultBuilder::reconstructExpression() const { + if( m_exprComponents.op == "" ) + return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; + else if( m_exprComponents.op == "matches" ) + return m_exprComponents.lhs + " " + m_exprComponents.rhs; + else if( m_exprComponents.op != "!" ) { + if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && + m_exprComponents.lhs.find("\n") == std::string::npos && + m_exprComponents.rhs.find("\n") == std::string::npos ) + return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; + else + return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; + } + else + return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; + } + +} // end namespace Catch + +// #included from: catch_tag_alias_registry.hpp +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + static TagAliasRegistry& get(); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +#include +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " << find(alias)->lineInfo << "\n" + << "\tRedefined at " << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + } + + TagAliasRegistry& TagAliasRegistry::get() { + static TagAliasRegistry instance; + return instance; + + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + try { + TagAliasRegistry::get().add( alias, tag, lineInfo ); + } + catch( std::exception& ex ) { + Colour colourGuard( Colour::Red ); + Catch::cerr() << ex.what() << std::endl; + exit(1); + } + } + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_xml.hpp +#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + +// #included from: catch_reporter_bases.hpp +#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + +#include + +namespace Catch { + + struct StreamingReporterBase : SharedImpl { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + + virtual ~StreamingReporterBase(); + + virtual void noMatchingTestCases( std::string const& ) {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { + currentTestCaseInfo.reset(); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + virtual void skipTest( TestCaseInfo const& ) { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + }; + + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() ( Ptr const& node ) const { + return node->stats.sectionInfo.lineInfo == m_other.lineInfo; + } + private: + void operator=( BySectionInfo const& ); + SectionInfo const& m_other; + }; + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + ~CumulativeReporterBase(); + + virtual void testRunStarting( TestRunInfo const& ) {} + virtual void testGroupStarting( GroupInfo const& ) {} + + virtual void testCaseStarting( TestCaseInfo const& ) {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + Ptr node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + Ptr node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + virtual void skipTest( TestCaseInfo const& ) {} + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; + + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; +} + +#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ + namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &Catch::cout() ) + {} + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &os ) + {} + + ~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + stream() << m_indent << "<" << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + stream() << "/>\n"; + m_tagIsOpen = false; + } + else { + stream() << m_indent << "\n"; + } + m_tags.pop_back(); + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) { + stream() << " " << name << "=\""; + writeEncodedText( attribute ); + stream() << "\""; + } + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; + return *this; + } + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + if( !name.empty() ) + stream() << " " << name << "=\"" << attribute << "\""; + return *this; + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + stream() << m_indent; + writeEncodedText( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + stream() << m_indent << ""; + m_needsNewline = true; + return *this; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + stream() << "\n"; + return *this; + } + + void setStream( std::ostream& os ) { + m_os = &os; + } + + private: + XmlWriter( XmlWriter const& ); + void operator=( XmlWriter const& ); + + std::ostream& stream() { + return *m_os; + } + + void ensureTagClosed() { + if( m_tagIsOpen ) { + stream() << ">\n"; + m_tagIsOpen = false; + } + } + + void newlineIfNecessary() { + if( m_needsNewline ) { + stream() << "\n"; + m_needsNewline = false; + } + } + + void writeEncodedText( std::string const& text ) { + static const char* charsToEncode = "<&\""; + std::string mtext = text; + std::string::size_type pos = mtext.find_first_of( charsToEncode ); + while( pos != std::string::npos ) { + stream() << mtext.substr( 0, pos ); + + switch( mtext[pos] ) { + case '<': + stream() << "<"; + break; + case '&': + stream() << "&"; + break; + case '\"': + stream() << """; + break; + } + mtext = mtext.substr( pos+1 ); + pos = mtext.find_first_of( charsToEncode ); + } + stream() << mtext; + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector m_tags; + std::string m_indent; + std::ostream* m_os; + }; + +} +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_sectionDepth( 0 ) + {} + + virtual ~XmlReporter(); + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + public: // StreamingReporterBase + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + m_xml.setStream( stream ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + } + } + + virtual void assertionStarting( AssertionInfo const& ) { } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + const AssertionResult& assertionResult = assertionStats.assertionResult; + + // Print any info messages in tags. + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) + return true; + + // Print the expression if there is one. + if( assertionResult.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", assertionResult.succeeded() ) + .writeAttribute( "type", assertionResult.getTestMacroName() ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ); + + m_xml.scopedElement( "Original" ) + .writeText( assertionResult.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( assertionResult.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( assertionResult.getResultType() ) { + case ResultWas::ThrewException: + m_xml.scopedElement( "Exception" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::FatalErrorCondition: + m_xml.scopedElement( "Fatal Error Condition" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.scopedElement( "Failure" ) + .writeText( assertionResult.getMessage() ); + break; + default: + break; + } + + if( assertionResult.hasExpression() ) + m_xml.endElement(); + + return true; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include + +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + {} + + ~JunitReporter(); + + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", "tbd" ); // !TBD + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + "/" + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << "\n"; + for( std::vector::const_iterator + it = stats.infoMessages.begin(), + itEnd = stats.infoMessages.end(); + it != itEnd; + ++it ) + if( it->type == ResultWas::Info ) + oss << it->message << "\n"; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter(); + static std::string getDescription() { + return "Reports test results as plain lines of text"; + } + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if( m_headerPrinted ) { + if( m_config->showDurations() == ShowDurations::Always ) + stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + m_headerPrinted = false; + } + else { + if( m_config->showDurations() == ShowDurations::Always ) + stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << "\n" << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with message"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << "\n"; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << "\n"; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << "\n"; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + stream << messageLabel << ":" << "\n"; + for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); + it != itEnd; + ++it ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || it->type != ResultWas::Info ) + stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + stream << "\n" << getLineOfChars<'~'>() << "\n"; + Colour colour( Colour::SecondaryText ); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion << " host application.\n" + << "Run with -? for options\n\n"; + + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << "\n"; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << "\n"; + } + stream << getLineOfChars<'.'>() << "\n" << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << "\n"; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << "\n"; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << "\n"; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = " " + *it; + while( it->size() > row.size() ) + row = " " + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ")" + << "\n"; + } + else { + + std::vector columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { + for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << " " << it->label; + } + } + stream << "\n"; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + stream << "\n"; + } + void printSummaryDivider() { + stream << getLineOfChars<'-'>() << "\n"; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( Colour::Code colour, std::string passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << " " << passOrFail; + } + stream << ":"; + } + } + + void printIssue( std::string issue ) const { + stream << " " << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ";"; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << " " << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << "'"; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ":"; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << "'"; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? "" : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : ""; + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + TestSpec::Pattern::~Pattern() {} + TestSpec::NamePattern::~NamePattern() {} + TestSpec::TagPattern::~TagPattern() {} + TestSpec::ExcludedPattern::~ExcludedPattern() {} + + Matchers::Impl::StdString::Equals::~Equals() {} + Matchers::Impl::StdString::Contains::~Contains() {} + Matchers::Impl::StdString::StartsWith::~StartsWith() {} + Matchers::Impl::StdString::EndsWith::~EndsWith() {} + + void Config::dummy() {} +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +// Standard C/C++ main entry point +int main (int argc, char * const argv[]) { + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) + +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) + +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) + +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) + +#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) +#else + #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( " Given: " desc, "" ) +#define WHEN( desc ) SECTION( " When: " desc, "" ) +#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) +#define THEN( desc ) SECTION( " Then: " desc, "" ) +#define AND_THEN( desc ) SECTION( " And: " desc, "" ) + +using Catch::Detail::Approx; + +// #included from: internal/catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/tests/run_tests.sh b/tests/run_tests.sh new file mode 100755 index 0000000..522ea4b --- /dev/null +++ b/tests/run_tests.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd "$(dirname "$0")" +exec ./test_runner -r tap -s diff --git a/tests/test_config.cpp b/tests/test_config.cpp index dd409e5..d683ae4 100644 --- a/tests/test_config.cpp +++ b/tests/test_config.cpp @@ -17,9 +17,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ -#include "config.hpp" +#include #include #include +#include #include "catch.hpp" #include #include "globals.hpp" @@ -35,56 +36,96 @@ size_t strarrlen(char** str) { TEST_CASE( "Testing Configuration Parsing", "[Config::parse]" ) { std::stringstream test; test << - "watch:\n" - " Filter: ['*.cpp', '*.hpp', '*.c', '*.h', 'include/*.hxx']\n" - " Include: Makefile\n" - " Exclude: src/Config.cpp\n" - "compile:\n" - " Command: make\n" - " Enabled: yes\n" - "Test:\n" - " command: make test\n" - " IgnoreStatus: [2,3,4]\n" - " Enabled: no\n" - //"Coverage:\n" - "program:\n" - " command: ./bin/program --help\n" - " Enabled: yes\n" + "Watch CSS {\n" + " files = \"*.css\", \"*.less\"\n" + " exclude = \"/css/compiled_*\"\n" + " dir = \"src/html\"\n" + " step Compile {\n" + " command = \"lessc --clean-css=--compatibility=ie8 --advanced\\\n" + " less/main.less css/compiled_style.css\"\n" + " }\n" + "}\n" + "Watch \"C++ Code Base\" {\n" + " files = \"*.cpp\", \"*.hpp\", \"test_*.rb\", \"Makefile\"\n" + " exclude = \"main.*\", \"test_driver.*\"\n" + " step Compile {\n" + " command = \"make me a sandwich\"\n" + " enabled = true\n" + " }\n" + " step \"Build Tests\" {\n" + " command = \"make test\"\n" + " enabled = yes\n" + " }\n" + " step \"Run Ruby Tests\" {\n" + " command = \"ruby tests/ruby/test_driver.rb\"; enabled = false;\n" + " }\n" + " step \"Run Tests\" {\n" + " command = \"tests/cpp/test_framework\"\n" + " error_status = 5\n" + " ignore_status = 3-9, 27\n" + " }\n" + " step Run {\n" + " command = \"bin/my_program\"\n" + " }\n" + "}\n" ; - ProcessManager pm(mainLogger); - Config cf(mainLogger, &pm, test); - //INFO("Config file: " << test); - INFO("Make sure that our filters are correct."); - REQUIRE(cf.getWatchConfig().filters.size() == 5); - std::string FilterCheck = cf.getWatchConfig().filters.front(); - CAPTURE(FilterCheck); - FilterCheck = cf.getWatchConfig().filters.back(); - CAPTURE(FilterCheck); - CHECK(cf.getWatchConfig().filters.front() == "*.cpp"); - CHECK(cf.getWatchConfig().filters.back() == "include/*.hxx"); - REQUIRE(cf.getWatchConfig().include.size() == 1); - REQUIRE(cf.getWatchConfig().exclude.size() == 1); - CHECK(cf.getWatchConfig().include.front() == "Makefile"); - CHECK(cf.getWatchConfig().exclude.front() == "src/Config.cpp"); + REQUIRE(test.good()); + Parser p(test); + REQUIRE_NOTHROW(p.Parse()); + uint counter = 0; + std::map map; + for(auto watcher : p) { + map[std::string(watcher->name)] = watcher; + counter++; + } + CHECK(counter == 2); + const CfgWatch* css = p.getWatchers(); + CHECK(std::string(css->name) == "CSS"); + REQUIRE(2 == css->filesFilter.size()); + CHECK(std::string(css->filesFilter[0]) == "*.css"); + CHECK(std::string(css->filesFilter[1]) == "*.less"); + REQUIRE(1 == css->excludesFilter.size()); + CHECK(std::string(css->excludesFilter[0]) == "/css/compiled_*"); + REQUIRE(1 == sizeof(css->steps)/sizeof(CfgStep*)); + const CfgStep* step = &css->steps[0]; + CHECK(std::string(step->name) == "Compile"); + CHECK(std::string(step->command).compare(0, 5, "lessc") == 0); - REQUIRE(strarrlen(cf.getCompileConfig().command) == 1); - CHECK(strcmp(cf.getCompileConfig().command[0], "make") == 0); - CHECK(cf.getCompileConfig().enabled == true); - REQUIRE(strarrlen(cf.getTestConfig().command) == 2); - CHECK(strcmp(cf.getTestConfig().command[0], "make") == 0); - CHECK(strcmp(cf.getTestConfig().command[1], "test") == 0); - CHECK(cf.getTestConfig().enabled == false); + //ProcessManager pm(mainLogger); + //Config cf(mainLogger, &pm, test); + ////INFO("Config file: " << test); + //INFO("Make sure that our filters are correct."); + //REQUIRE(cf.getWatchConfig().filters.size() == 5); + //std::string FilterCheck = cf.getWatchConfig().filters.front(); + //CAPTURE(FilterCheck); + //FilterCheck = cf.getWatchConfig().filters.back(); + //CAPTURE(FilterCheck); + //CHECK(cf.getWatchConfig().filters.front() == "*.cpp"); + //CHECK(cf.getWatchConfig().filters.back() == "include/*.hxx"); + //REQUIRE(cf.getWatchConfig().include.size() == 1); + //REQUIRE(cf.getWatchConfig().exclude.size() == 1); + //CHECK(cf.getWatchConfig().include.front() == "Makefile"); + //CHECK(cf.getWatchConfig().exclude.front() == "src/Config.cpp"); - REQUIRE(strarrlen(cf.getProgramConfig().command) == 2); - CHECK(strcmp(cf.getProgramConfig().command[0], "./bin/program") == 0); - CHECK(strcmp(cf.getProgramConfig().command[1], "--help") == 0); - CHECK(cf.getProgramConfig().enabled == true); - //for(std::vector::const_iterator str = cf.sections.begin(); str != cf.sections.end(); ++str) { - // std::cout << std::string(*str) << std::endl; - // INFO( *str ); - //} - //REQUIRE(false); + //REQUIRE(strarrlen(cf.getCompileConfig().command) == 1); + //CHECK(strcmp(cf.getCompileConfig().command[0], "make") == 0); + //CHECK(cf.getCompileConfig().enabled == true); + + //REQUIRE(strarrlen(cf.getTestConfig().command) == 2); + //CHECK(strcmp(cf.getTestConfig().command[0], "make") == 0); + //CHECK(strcmp(cf.getTestConfig().command[1], "test") == 0); + //CHECK(cf.getTestConfig().enabled == false); + + //REQUIRE(strarrlen(cf.getProgramConfig().command) == 2); + //CHECK(strcmp(cf.getProgramConfig().command[0], "./bin/program") == 0); + //CHECK(strcmp(cf.getProgramConfig().command[1], "--help") == 0); + //CHECK(cf.getProgramConfig().enabled == true); + ////for(std::vector::const_iterator str = cf.sections.begin(); str != cf.sections.end(); ++str) { + //// std::cout << std::string(*str) << std::endl; + //// INFO( *str ); + ////} + ////REQUIRE(false); } diff --git a/tests/test_functional.sh b/tests/test_functional.sh new file mode 100755 index 0000000..1d6ae67 --- /dev/null +++ b/tests/test_functional.sh @@ -0,0 +1,60 @@ +#!/bin/bash +set -e +export BASH_TAP_ROOT=./bash-tap/ +source $(dirname $0)/bash-tap/bash-tap-bootstrap +testdir=$(mktemp -d -t test_れんぞく_XXXXX) + +echo "# Changing to test directory: ${testdir}" +cd ${testdir} +cat < config.conf +Watch Dummy { + files = "test_*" + step "Cat dummy files" { + command = "bash -c \\"cat test_*\\"" + } +} +Watch "Second Dummy Listener" { + files = "test2_*" + step "Cat dummy files" { + command = "bash -c \\"cat test2_{1,2} > test3_out\\"" + } +} +EOF +cat <background.sh +#!/bin/bash +sleep 0.2; +echo "Test file 1" > test_1 +echo "Test file 2" > test_2 +echo "Dummy file 1" > test2_1 +echo "Dummy file 2" > test2_2 +sleep 0.2; +EOF +plan no_plan +../src/renzoku & +renzoku=$! +bash background.sh +like "${renzoku}" "[[:digit:]]" "Renzoku startup pid" +is "hi" "hi" "hi is hi" +isnt "hi" "bye" "hi is bye" + +declare -a test_data +test_data=("Dummy file 1" "Dummy file 2") +test_file=test3_out +if [ ! -f "$test_file" ]; then + ok 0 "Failed to find output file $test_file" + for ((i = 0; i < ${#test_data[@]}; i++)); do + is "" "${test_data[$i]}" "Validate output" + done +else + counter=0 + while read -r line; do + skip "v" "v" "meep" + is "$line" "${test_data[$counter]}" "Validate output" + counter = $counter + 1 + done < $testfile + is $counter 2 +fi + +kill ${renzoku} || BAIL_OUT "Failed to stop Renzoku!" +rm -rf ${testdir} || BAIL_OUT "# Failed to remove tmp test directory: ${testdir}" +done_testing diff --git a/tests/test_process.cpp b/tests/test_process.cpp index dc051e4..2a130ac 100644 --- a/tests/test_process.cpp +++ b/tests/test_process.cpp @@ -39,7 +39,7 @@ TEST_CASE( "Testing Sub-process forking", "[Process]" ) { std::string command = "touch "; command += FILENAME; - Process p(mainLogger, command, true); + Process p(mainLogger, command, true, true); INFO("Making sure our test file doesn't exist (After class instantiation)"); CHECK_FALSE(exists_test3(FILENAME)); p.runAndWait(); @@ -47,7 +47,7 @@ TEST_CASE( "Testing Sub-process forking", "[Process]" ) { REQUIRE(exists_test3(FILENAME)); command = "rm "; command += FILENAME; - Process p2(mainLogger, command, true); + Process p2(mainLogger, command, true, true); p2.runAndWait(); INFO("Making sure our test file doesn't exist (Clearnup)"); REQUIRE_FALSE(exists_test3(FILENAME)); From f7834b6b92f9e1742e1568dd06aaeb3e3262bc8f Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Mon, 11 Sep 2017 22:57:12 -0600 Subject: [PATCH 18/28] Update/Fix config parser --- src/parser/main.cpp | 15 ++++++++ src/parser/parser.cpp | 80 +++++++++++++++++++++++++++---------------- src/parser/parser.hpp | 3 +- 3 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/parser/main.cpp b/src/parser/main.cpp index 684c8e4..b2df201 100644 --- a/src/parser/main.cpp +++ b/src/parser/main.cpp @@ -41,5 +41,20 @@ int main(int argc, char *argv[]) Parser p("test.conf"); p.Parse(); printf("\nEOF\n"); + printf("\nFrom main.c:\n"); + //const CfgWatch* watchers = p.getWatchers(); + //CfgWatchIter iter; + CfgStep* step; + for (auto i : p) { + printf("[%s]\n", (*i).name); + step = i->steps; + //printf("%d\n", step->next); + while(step) { + printf("\t%s\n", step->name); + printf("\t\t%s\n", step->command); + printf("\t\t%s\n", step->enabled ? "true" : "false"); + step = step->next; + } + } return 0; } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index d9f9976..1e7789b 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -150,11 +150,21 @@ void Parser::parseWatcher(CfgWatch* watcher) { if( mTokenizer.getToken() == Tokenizer::WORD ) { key = parseKey(); // Parse each valid section - printf("\t"); + //printf("\t"); if (mIsKey("enabled", key)) - parseBooleanOption(watcher, key); + step->enabled = parseBooleanOption(watcher, key); if (mIsKey("command", key)) - parseWatcherOption(watcher, key); + { + std::string val; + char* cmd; + val = parseWatcherOption(watcher, key).c_str(); + cmd = new char[val.length()+1]; + for (int i = 0; i < val.length(); i++) { + cmd[i] = val[i]; + } + cmd[val.length()] = 0; + step->command = cmd; + } if (mIsKey("error_status", key)) parseWatcherOptions(watcher, key); if (mIsKey("ignore_status", key)) @@ -168,7 +178,7 @@ void Parser::parseWatcher(CfgWatch* watcher) { if (!watcher->steps) { watcher->steps = step; } else { - CfgStep* tmpstep = step; + CfgStep* tmpstep = watcher->steps; while(tmpstep->next) { tmpstep = tmpstep->next; } @@ -184,7 +194,7 @@ void Parser::parseWatcher(CfgWatch* watcher) { // Determine if we have the right key bool Parser::mIsKey(const std::string& expectKey, const std::string& key) { //mTokenizer.next(); - printf("(%s != %s) == %d\n", key.c_str(), expectKey.c_str(), ( key != expectKey )); + //printf("(%s != %s) == %d\n", key.c_str(), expectKey.c_str(), ( key != expectKey )); return ( key == expectKey ); } @@ -195,14 +205,16 @@ bool Parser::parseBooleanOption(CfgWatch* watcher, std::string key) { parseEquals(); // Parse out the value - bool values = parseBoolean(); + const char value = parseBoolean(); + bool retval; + retval = value == 'y'; // Debug: Print out all values - printf("\t%s: ", key.c_str()); + printf("\t\t%s: %c", key.c_str(), value); printf("\n"); // Return the values - return values; + return retval; } std::string Parser::parseWatcherOption(CfgWatch* watcher, std::string key) { @@ -214,7 +226,7 @@ std::string Parser::parseWatcherOption(CfgWatch* watcher, std::string key) { char* values = mStrToChar(parseString()); // Debug: Print out all values - printf("\t%s: ", key.c_str()); + printf("\t\t%s: %s", key.c_str(), values); printf("\n"); // Return the values @@ -235,7 +247,7 @@ std::vector Parser::parseWatcherOptions(CfgWatch* watcher, values = parseValue(); // Debug: Print out all values - printf("\t%s: ", key.c_str()); + printf("\t\t%s: ", key.c_str()); for (auto value : values) { printf("[%s]", value.c_str()); } @@ -245,6 +257,19 @@ std::vector Parser::parseWatcherOptions(CfgWatch* watcher, return values; } +short* Parser::parseStatusCodes(std::vector& strs) { + uint count = 0; + size_t pos = 0; + for (auto s : strs) { + pos = s.find("-"); + if( pos != (size_t)-1 ) { + // TODO: Parse and count all numbers in range + } else { + // TODO: Parse single numbers here + } + } +} + // Get the key for the key/value pair const std::string& Parser::parseKey() { //printf("T: %d V: %s\n", mTokenizer.getToken(), @@ -285,7 +310,7 @@ const std::vector Parser::parseValue() { if( t == Tokenizer::EOFTOK || t == Tokenizer::INVALID || t == Tokenizer::SPECIAL && !safeSpecial()) - mTokenizer.invokeError("Unexpected character"); + mTokenizer.invokeError("[e1] Unexpected character"); //printf("V: %s\n", mTokenizer.getValue().c_str()); @@ -310,43 +335,40 @@ const std::vector Parser::parseValue() { const std::string Parser::parseString() { // Initialize the variables - Tokenizer::TokenType t; std::string retval; // Loop until the end of the value if( mTokenizer.getToken() == Tokenizer::WS ) mTokenizer.next(); if( mTokenizer.getToken() != Tokenizer::STRING ) - mTokenizer.invokeError("Unexpected character"); - printf("T: %d V: %s\n", mTokenizer.getToken(), - mTokenizer.getValue().c_str()); + mTokenizer.invokeError("[e2] Unexpected character"); + //printf("\tT: %d V: %s\n", mTokenizer.getToken(), + // mTokenizer.getValue().c_str()); retval = mTokenizer.getValue(); mTokenizer.next(); if( mTokenizer.getToken() == Tokenizer::EOFTOK || mTokenizer.getToken() == Tokenizer::INVALID || mTokenizer.getToken() == Tokenizer::SPECIAL && !safeSpecial()) - mTokenizer.invokeError("Unexpected character"); + mTokenizer.invokeError("[e3] Unexpected character"); return retval; } -const bool Parser::parseBoolean() { +const char Parser::parseBoolean() { // Initialize the variables - Tokenizer::TokenType t; - bool retval; + char retval; // Loop until the end of the value - if( t != Tokenizer::WS ) + if( mTokenizer.getToken() == Tokenizer::WS ) mTokenizer.next(); - if( t != Tokenizer::BOOLEAN ) - mTokenizer.invokeError("Unexpected character"); - retval = mTokenizer.getValue() == "y"; - mTokenizer.next(); - if( t == Tokenizer::EOFTOK || - t == Tokenizer::INVALID || - mTokenizer.getValue() != ";" || - t == Tokenizer::SPECIAL && !safeSpecial()) - mTokenizer.invokeError("Unexpected character"); + if( mTokenizer.getToken() != Tokenizer::BOOLEAN ) + mTokenizer.invokeError("[e4] Unexpected character"); + retval = mTokenizer.getValue().c_str()[0]; mTokenizer.next(); + if( mTokenizer.getValue() != ";" && ( + mTokenizer.getToken() == Tokenizer::EOFTOK || + mTokenizer.getToken() == Tokenizer::INVALID || + mTokenizer.getToken() == Tokenizer::SPECIAL && !safeSpecial())) + mTokenizer.invokeError("[e5] Unexpected character"); return retval; } diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index ffbf56b..14369fa 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -50,9 +50,10 @@ class Parser { const std::string& parseKey(); void parseEquals(); const std::vector parseValue(); - const bool parseBoolean(); + const char parseBoolean(); const std::string parseString(); bool safeSpecial(); + short* parseStatusCodes(std::vector&); std::vector parseWatcherOptions(CfgWatch*, std::string key); std::string parseWatcherOption(CfgWatch*, std::string key); bool parseBooleanOption(CfgWatch*, std::string key); From 8102ce4a37777cd7ca9fcf4432c60365cb7bab9d Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Mon, 11 Sep 2017 22:58:23 -0600 Subject: [PATCH 19/28] Update .gitignore to ignore log files --- .gitignore | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.gitignore b/.gitignore index 1c04ca9..738aefd 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,9 @@ src/librenzoku.a test-driver tests/test-suite.log tests/test_runner* +tests/*.log +tests/*.trs +test_れんぞく # Misc *.swo @@ -32,3 +35,12 @@ tests/test_runner* LICENSE.new coverage ignoreme/ + +build-aux/ +renzoku-0.1.0.tar.gz +renzoku-0.1.0/ +src/parser/parser.swigcxx +src/parser/parser_wrap.cxx +src/parser/renconf.go +src/parser/renconf_gc.c +tests/PATH From 3d6631526066c95bdb2b55ec933d0a8fcd1a84a4 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Tue, 12 Sep 2017 12:14:59 -0600 Subject: [PATCH 20/28] Add comment for status codes function --- src/parser/parser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 1e7789b..c389444 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -257,6 +257,7 @@ std::vector Parser::parseWatcherOptions(CfgWatch* watcher, return values; } +// Parse the string list of values and convert them into short numbers short* Parser::parseStatusCodes(std::vector& strs) { uint count = 0; size_t pos = 0; From c3fd79c80852a8847c85ab375d8e137e7c3643a7 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Tue, 12 Sep 2017 15:12:18 -0600 Subject: [PATCH 21/28] Add a mental note --- src/parser/parser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index c389444..f34731b 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -165,6 +165,7 @@ void Parser::parseWatcher(CfgWatch* watcher) { cmd[val.length()] = 0; step->command = cmd; } + // XXX: For now, we skip these two if (mIsKey("error_status", key)) parseWatcherOptions(watcher, key); if (mIsKey("ignore_status", key)) From b6402f130a8216dc2943d5ebf5308763319d3571 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Tue, 12 Sep 2017 15:48:20 -0600 Subject: [PATCH 22/28] Fixed parser memory leaks --- src/parser/parser.cpp | 26 +++++++++++++------------- src/parser/parser.hpp | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index f34731b..df6afa9 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -48,14 +48,22 @@ Parser::~Parser() { if( mWatchers != NULL ) { CfgWatch *watcher = mWatchers; CfgWatch *tmpwatcher = watcher->next; - while( watcher != mLastWatcher ) { + CfgStep *step = NULL; + CfgStep *tmpstep = NULL; + while( watcher ) { + step = watcher->steps; tmpwatcher = watcher->next; delete [] watcher->name; + while( step ) { + tmpstep = step->next; + delete [] step->name; + delete [] step->command; + delete step; + step = tmpstep; + } delete watcher; watcher = tmpwatcher; } - delete [] watcher->name; - delete watcher; } } @@ -155,15 +163,7 @@ void Parser::parseWatcher(CfgWatch* watcher) { step->enabled = parseBooleanOption(watcher, key); if (mIsKey("command", key)) { - std::string val; - char* cmd; - val = parseWatcherOption(watcher, key).c_str(); - cmd = new char[val.length()+1]; - for (int i = 0; i < val.length(); i++) { - cmd[i] = val[i]; - } - cmd[val.length()] = 0; - step->command = cmd; + step->command = parseWatcherOption(watcher, key); } // XXX: For now, we skip these two if (mIsKey("error_status", key)) @@ -218,7 +218,7 @@ bool Parser::parseBooleanOption(CfgWatch* watcher, std::string key) { return retval; } -std::string Parser::parseWatcherOption(CfgWatch* watcher, std::string key) { +char* Parser::parseWatcherOption(CfgWatch* watcher, std::string key) { // Get rid of the equals parseEquals(); diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 14369fa..359fb8e 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -55,7 +55,7 @@ class Parser { bool safeSpecial(); short* parseStatusCodes(std::vector&); std::vector parseWatcherOptions(CfgWatch*, std::string key); - std::string parseWatcherOption(CfgWatch*, std::string key); + char* parseWatcherOption(CfgWatch*, std::string key); bool parseBooleanOption(CfgWatch*, std::string key); bool mIsKey(const std::string& expectKey, const std::string& key); char* mStrToChar(const std::string&); From 4499e7e423ee8e72a1ef5b435b56142b7fc13d5a Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Tue, 12 Sep 2017 18:34:08 -0600 Subject: [PATCH 23/28] Added number conversion logic --- src/parser/parser.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index df6afa9..9ce4546 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -2,6 +2,8 @@ #include "tokenizer.hpp" #include #include +#include +#include #define LOWER(x) std::transform(x.begin(), x.end(), x.begin(),\ ::tolower); @@ -35,6 +37,12 @@ Parser::Parser(std::string filename) : { } +template +bool str_to_type(T& t, const std::string& s) { + std::istringstream iss(s); + return !(iss >> t).fail(); +} + Parser::Parser(std::istream& file) : mFilename(""), mTokenizer(mFilename, file), @@ -168,6 +176,8 @@ void Parser::parseWatcher(CfgWatch* watcher) { // XXX: For now, we skip these two if (mIsKey("error_status", key)) parseWatcherOptions(watcher, key); + // parseStatusCodes (parse options retval) + // mTokenizer.invokeError(msg); if (mIsKey("ignore_status", key)) parseWatcherOptions(watcher, key); } @@ -260,16 +270,31 @@ std::vector Parser::parseWatcherOptions(CfgWatch* watcher, // Parse the string list of values and convert them into short numbers short* Parser::parseStatusCodes(std::vector& strs) { - uint count = 0; size_t pos = 0; + short tmp; + short* retval = NULL; + std::vector builder; for (auto s : strs) { pos = s.find("-"); if( pos != (size_t)-1 ) { // TODO: Parse and count all numbers in range } else { - // TODO: Parse single numbers here + if( str_to_type( tmp, s ) ) { + builder.push_back(tmp); + } else { + std::string msg = "Unable to find number in '" + s + "'"; + mTokenizer.invokeError(msg); + } } } + if( builder.size() > 0 ) { + pos = 0; + retval = new short[builder.size() + 1]; + for (auto i : builder) { + retval[pos++] = i; + } + } + return retval; } // Get the key for the key/value pair From eb1d5657382f73a4a215fa891b814079f7df964b Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Tue, 12 Sep 2017 19:21:58 -0600 Subject: [PATCH 24/28] Start config migration --- src/Makefile.am | 4 - src/config.cpp | 305 ------------------------------------------------ src/config.hpp | 101 ---------------- src/main.cpp | 3 +- src/watcher.cpp | 40 ++++--- src/watcher.hpp | 7 +- 6 files changed, 28 insertions(+), 432 deletions(-) delete mode 100644 src/config.cpp delete mode 100644 src/config.hpp diff --git a/src/Makefile.am b/src/Makefile.am index fb13d6b..065f33a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -16,12 +16,8 @@ librenzoku_adir = $(pkglibdir)/@PACKAGE@ # util.hpp \ # watcher.hpp librenzoku_a_SOURCES = \ - config.cpp \ - config.hpp \ - config_sections.hpp \ exceptions.cpp \ exceptions.hpp \ - i_configfile.hpp \ log.cpp \ log.hpp \ process.cpp \ diff --git a/src/config.cpp b/src/config.cpp deleted file mode 100644 index 447c940..0000000 --- a/src/config.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Renzoku - Re-build, re-test, and re-run a program whenever the code changes - * Copyright (C) 2015 Colton Wolkins - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ -/* - * ===================================================================================== - * - * Filename: config.cpp - * - * Description: Configuration file retrieval - * - * Version: 1.0 - * Created: 05/23/2015 10:51:55 AM - * Revision: none - * Compiler: gcc - * - * Author: Colton Wolkins (frostyfrog), frostyfrog2@gmail.com - * - * ===================================================================================== - */ -#include "config.hpp" -#include -#include -#include -#include -#include -#include -#include "util.hpp" -#include "log.hpp" - -Config::Config(iLogger *logger, ProcessManager* pm) : - mWatch(), - mCompile(), - mTest(), - mProgram(), - procman(pm), - logger(logger) -{ - std::ifstream file; - this->findConfig(file); - this->parseConfig(file); -} - -Config::Config(iLogger *logger, ProcessManager* pm, std::istream& file) : - mWatch(), - mCompile(), - mTest(), - mProgram(), - procman(pm), - logger(logger) -{ - this->parseConfig(file); -} - -Config::~Config() -{ - size_t i; - if(mCompile.command != NULL) { - for(i = 0; mCompile.command[i]; i++) - delete [] mCompile.command[i]; - delete [] mCompile.command; - } - if(mTest.command != NULL) { - for(i = 0; mTest.command[i]; i++) - delete [] mTest.command[i]; - delete [] mTest.command; - } - if(mProgram.command != NULL) { - for(i = 0; mProgram.command[i]; i++) - delete [] mProgram.command[i]; - delete [] mProgram.command; - } -} - -void Config::findConfig(std::ifstream& is) -{ - // TODO: file discovery like git finds .git files (including mount point) - // http://stackoverflow.com/questions/2337139/where-is-a-file-mounted - is.open(CONFIG_NAME); - if(!is) - throw std::runtime_error("Could not open file"); -} - -void Config::parseConfig(std::istream& file) -{ - int err; - size_t file_length; - char* file_contents = NULL; - file.seekg(0, file.end); - file_length = file.tellg(); - file.seekg(0, file.beg); - file_contents = new char[file_length]; - if(file_contents == NULL) - throw std::runtime_error("Failed to read config [Allocation error]"); - file.read(file_contents, file_length); - err = ( (size_t)file.gcount() != file_length ); - if(err) - throw std::runtime_error("Failed to read config [Failed to read all " - "characters]"); - config.readString(file_contents); - libconfig::Setting &root = config.getRoot(); - - assert(config.exists("watch")); - assert(config.exists("program")); - for (auto it = root.begin(); it != root.end(); ++it) { - assert(it->isGroup()); - //Logger::getLogger()->LOG(logger, DEBUG, "%s - %s\n", it->first.as().c_str(), it->second.as().c_str()); - std::string key = Util::lowercase_r(it->getName()); - if(key == "watch") - { - this->parseWatcher(*it); - } - else if(key == "program") - { - this->parseCommand(*it, mProgram); - if(procman->getProgram()) { - throw std::runtime_error("Program already set!"); - } - if(mProgram.command) - procman->setProgram(new Process(logger, mProgram.command, true, - mProgram.enabled)); - } - else if(key == "test") - { - this->parseCommand(*it, mTest); - if(mTest.command) - procman->addProcess(new Process(logger, mTest.command, true, - mTest.enabled)); - } - else if(key == "compile") - { - this->parseCommand(*it, mCompile); - if(procman->getBuildStep()) { - throw std::runtime_error("BuildStage already set!"); - } - if(mCompile.command) - procman->setBuilder(new Process(logger, mCompile.command, true, - mCompile.enabled)); - } - else - { - std::cerr << "Found an error! -" << key << '\n'; - throw std::runtime_error("Found a config error!"); - } - } -} - -void Config::parseCommand(const libconfig::Setting& node, iCommandConfig& config) -{ - //std::cerr << "Under active development!" << '\n'; - //return; - printf("%d %d %s\n", node.getLength(), node.isArray(), node.getName()); - if(node.getLength() > 0 && node.isGroup()) - { -#if (((LIBCONFIGXX_VER_MAJOR == 1) && (LIBCONFIGXX_VER_MINOR >= 5)) \ - || (LIBCONFIGXX_VER_MAJOR > 1)) - for (auto iter = node.begin(); iter != node.end(); ++iter) { - std::string key = iter->getName(); - const libconfig::Setting &value = *iter; - Util::lowercase(key); - //std::cout << key << std::endl; - if(key == "command") - { - if(value.isScalar()) - { - LOG(logger, DEBUG, "Proc: %s", (const char*)value); - config.command = Util::parseCommand(value); -#if 0 - // TODO: We need to take quotes from the user and send them all as - // a single argument instead of multiple arguments. - std::string cnfval = value.as(); - char* cmd = new char[cnfval.size() + 1]; - cmd[cnfval.size()] = 0; - memcpy(cmd, cnfval.c_str(), cnfval.size()); - size_t size = 1; - for(size_t i=0; irestartAll(); -} - -void Config::parseWatcher(const libconfig::Setting& node) -{ - size_t asterisk_count; - if(node.getLength() > 0 && node.isGroup()) - { -#if (((LIBCONFIGXX_VER_MAJOR == 1) && (LIBCONFIGXX_VER_MINOR >= 5)) \ - || (LIBCONFIGXX_VER_MAJOR > 1)) - for (auto iter = node.begin(); iter != node.end(); ++iter) { - std::string key = iter->getName(); - const libconfig::Setting &value = *iter; - Util::lowercase(key); - if(key == "filter") - { - if(!value.isArray()) - std::cerr << "ERROR!\n" << key; - for(auto filter_iter=value.begin(); - filter_iter != value.end(); - ++filter_iter) - { - asterisk_count = 0; - std::string val = *filter_iter; - for(size_t i = 0; i < val.length(); i++) - if(val[i] == '*') - asterisk_count++; - LOG(logger, DEBUG, "Filter: %s", val.c_str()); - if(asterisk_count > 1) - throw std::runtime_error("Could not open file"); - mWatch.filters.push_back(val); - } - } - else if(key == "include") - { - if(!value.isArray() && !value.isScalar()) - std::cerr << "ERROR!\n"; - if(value.isArray()) - { - for(auto filter_iter=value.begin(); - filter_iter!=value.end(); - ++filter_iter) - { - LOG(logger, DEBUG, "Include: %s", (const char*)*filter_iter); - mWatch.include.push_back(*filter_iter); - } - } - else if(value.isScalar()) - { - LOG(logger, DEBUG, "Include: %s", (const char*)value); - mWatch.include.push_back(value); - } - } - else if(key == "exclude") - { - if(!value.isArray() && !value.isScalar()) - std::cerr << "ERROR!\n"; - if(value.isArray()) - { - for(auto filter_iter=value.begin(); - filter_iter!=value.end(); - ++filter_iter) - { - LOG(logger, DEBUG, "Exclude: %s", (const char*)*filter_iter); - mWatch.exclude.push_back(*filter_iter); - } - } - else if(value.isScalar()) - { - LOG(logger, DEBUG, "Exclude: %s", (const char*)value); - mWatch.exclude.push_back(value); - } - } - else - LOG(logger, DEBUG, "Value: %s\n", (const char*)value); - } -#endif - } -} - diff --git a/src/config.hpp b/src/config.hpp deleted file mode 100644 index ddaa82d..0000000 --- a/src/config.hpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Renzoku - Re-build, re-test, and re-run a program whenever the code changes - * Copyright (C) 2015 Colton Wolkins - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ -/* - * ===================================================================================== - * - * Filename: config.hpp - * - * Description: Configuration file retrieval - * - * Version: 1.0 - * Created: 05/23/2015 10:51:55 AM - * Revision: none - * Compiler: gcc - * - * Author: Colton Wolkins (frostyfrog), frostyfrog2@gmail.com - * - * ===================================================================================== - */ -#pragma once -#include -#include -#include -#include -#include "process.hpp" -#include "log.hpp" -#include "procman.hpp" - -#define CONFIG_NAME "renzoku.conf" - -struct iConfig { - virtual ~iConfig() {} -}; - -struct WatchConfig : public iConfig { - std::vector filters; - std::vector include; - std::vector exclude; -}; -struct iCommandConfig : public iConfig { - bool enabled = true; - char** command = NULL; -}; -struct CompileConfig : public iCommandConfig { -}; -struct TestConfig : public iCommandConfig { - std::vector ignore_states; // TODO: Populate this -}; -struct ProgramConfig : public iCommandConfig { -}; - - -class Config { - /* Config file format: - * project XXXXXX - * \tbuildcmd XXXX XXXX - * \truncmd XXXX - * #vim: set tw=8 sw=8 ai noet: - */ - private: - libconfig::Config config; -#define CONFIG_SECTION(X) X##Config m##X; -#include "config_sections.hpp" -#undef CONFIG_SECTION - ProcessManager* procman; - iLogger* logger; - void parseWatcher(const libconfig::Setting&); - void parseCommand(const libconfig::Setting&, iCommandConfig&); - void parseConfig(std::istream&); - void findConfig(std::ifstream&); - public: - std::vector sections = { -#define CONFIG_SECTION(X) m##X, -#include "config_sections.hpp" -#undef CONFIG_SECTION - }; - Config(iLogger*, ProcessManager*); - Config(iLogger*, ProcessManager*, std::istream&); - ~Config(); - inline WatchConfig getWatchConfig() { return mWatch; }; - inline CompileConfig getCompileConfig() { return mCompile; }; - inline TestConfig getTestConfig() { return mTest; }; - inline ProgramConfig getProgramConfig() { return mProgram; }; - void restartProcesses(); -}; diff --git a/src/main.cpp b/src/main.cpp index 6766ca5..982b7b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -37,7 +37,6 @@ #include #include "main.hpp" #include "signals.hpp" -#include "config.hpp" #include "log.hpp" #include "watcher.hpp" #include "procman.hpp" @@ -70,7 +69,7 @@ int main(int argc, char** argv) { pex.getLine(), pex.getError()); return EXIT_FAILURE; } - //Watcher w(&logger, "./", *config, &procman, true); + Watcher w(&logger, "./", p, &procman, true); //while(gRunning) // w.listen(); diff --git a/src/watcher.cpp b/src/watcher.cpp index 2bc1de3..207aa18 100644 --- a/src/watcher.cpp +++ b/src/watcher.cpp @@ -22,18 +22,17 @@ #include "util.hpp" #include "log.hpp" -Watcher::Watcher(iLogger* logger, std::string dir, Config& conf, ProcessManager* pm, bool recursive) : +Watcher::Watcher(iLogger* logger, std::string dir, Parser& parser, ProcessManager* pm, bool recursive) : logger(logger), mDirectory(dir), mRecursive(recursive), - mConfig(conf), + mParser(parser), mTimer(time(0)-15), mBuilder(pm->getBuildStep()), procman(pm) { mINotify = inotify_init(); watchDirectory(); - } Watcher::~Watcher() @@ -110,20 +109,27 @@ void Watcher::listen() (event->mask & IN_MODIFY) && (! (event->mask & IN_ISDIR)) ){ - for(auto str : mConfig.getWatchConfig().filters) + for(auto watcher : mParser) { - if(Util::strMatch(str, std::string(event->name))) { - LOG(logger, INFO, "\033[0;32m=> File %s was modified!\033[0m", event->name); - //Logger::getLogger()->LOG(logger, DEBUG, "%ju", timer); - //Logger::getLogger()->LOG(logger, DEBUG, "%ju", timer+5); - //Logger::getLogger()->LOG(logger, DEBUG, "%ju", time(0)); - timediff = *localtime(&mTimer); - timediff.tm_sec += 5; - if(mktime(&timediff) <= time(0)) { - if(this->rebuild()) { - LOG(logger, DEBUG, "%ju", mktime(&timediff)); - mTimer = time(0); - this->restartProgram(); + for( auto watcher_path : watcher->excludesFilter ) { + if(Util::strMatch(watcher_path, std::string(event->name))) { + continue; + } + } + for( auto watcher_path : watcher->filesFilter ) { + if(Util::strMatch(watcher_path, std::string(event->name))) { + LOG(logger, INFO, "\033[0;32m=> File %s was modified!\033[0m", event->name); + //Logger::getLogger()->LOG(logger, DEBUG, "%ju", timer); + //Logger::getLogger()->LOG(logger, DEBUG, "%ju", timer+5); + //Logger::getLogger()->LOG(logger, DEBUG, "%ju", time(0)); + timediff = *localtime(&mTimer); + timediff.tm_sec += 5; + if(mktime(&timediff) <= time(0)) { + if(this->rebuild()) { + LOG(logger, DEBUG, "%ju", mktime(&timediff)); + mTimer = time(0); + this->restartProgram(); + } } } } @@ -151,7 +157,7 @@ bool Watcher::rebuild() void Watcher::restartProgram() { - mConfig.restartProcesses(); + //mConfig.restartProcesses(); } #ifdef DEBUG diff --git a/src/watcher.hpp b/src/watcher.hpp index a67a48c..81647a7 100644 --- a/src/watcher.hpp +++ b/src/watcher.hpp @@ -34,8 +34,9 @@ #include #include -#include "config.hpp" +#include "parser/parser.hpp" #include "process.hpp" +#include "procman.hpp" #include "log.hpp" #define EVENT_SIZE ( sizeof (struct inotify_event) ) @@ -49,7 +50,7 @@ class Watcher { std::vector mFiletypes; std::vector mFDs; char mBuffer[EVENT_BUF_LEN]; - Config& mConfig; + Parser &mParser; int mINotify; time_t mTimer; Process* mBuilder; @@ -64,7 +65,7 @@ class Watcher { bool rebuild(); void restartProgram(); public: - Watcher(iLogger* logger, std::string, Config& conf, ProcessManager*, bool recursive); + Watcher(iLogger* logger, std::string, Parser& parser, ProcessManager*, bool recursive); ~Watcher(); void watchFileType(std::string); void listen(); From 2939655573771463499649c41041c4b759c54391 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Wed, 6 Dec 2017 23:29:18 -0700 Subject: [PATCH 25/28] Fix parser code so that it works I fixed the parser code so that: A) It works (for the most part) and, B) It's easy to use and attach to the main renzoku application. This is me working towards #2 --- renzoku.conf => renzoku_old.conf | 0 src/parser/main.cpp | 16 ++++++++++++---- src/parser/parser.cpp | 4 ++-- src/parser/parser.hpp | 2 +- src/parser/tokenizer.cpp | 5 ++++- 5 files changed, 19 insertions(+), 8 deletions(-) rename renzoku.conf => renzoku_old.conf (100%) diff --git a/renzoku.conf b/renzoku_old.conf similarity index 100% rename from renzoku.conf rename to renzoku_old.conf diff --git a/src/parser/main.cpp b/src/parser/main.cpp index b2df201..91b34b6 100644 --- a/src/parser/main.cpp +++ b/src/parser/main.cpp @@ -46,13 +46,21 @@ int main(int argc, char *argv[]) //CfgWatchIter iter; CfgStep* step; for (auto i : p) { - printf("[%s]\n", (*i).name); + printf("[%s] cwd: %s\n", (*i).name, (*i).workingDir); + printf("Files: "); + for (std::string s : (*i).filesFilter) + printf("%s, ", s.c_str()); + printf("\n"); + printf("Exclude: "); + for (std::string s : (*i).excludesFilter) + printf("%s, ", s.c_str()); + printf("\n"); step = i->steps; //printf("%d\n", step->next); while(step) { - printf("\t%s\n", step->name); - printf("\t\t%s\n", step->command); - printf("\t\t%s\n", step->enabled ? "true" : "false"); + printf("\tstep: %s\n", step->name); + printf("\t\tcommand: %s\n", step->command); + printf("\t\tenabled: %s\n", step->enabled ? "true" : "false"); step = step->next; } } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 9ce4546..caf966a 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -129,7 +129,7 @@ void Parser::parseWatcher(CfgWatch* watcher) { if( mIsKey("exclude", key) ) watcher->excludesFilter = parseWatcherOptions(watcher, key); if ( mIsKey("dir", key) ) - parseWatcherOptions(watcher, key); + watcher->workingDir = mStrToChar(parseWatcherOptions(watcher, key).at(0)); // Can throw std::out_of_range // We are now parsing a step if( key == "step" ) @@ -245,7 +245,7 @@ char* Parser::parseWatcherOption(CfgWatch* watcher, std::string key) { } // Parse just the options (key/value) -std::vector Parser::parseWatcherOptions(CfgWatch* watcher, +const std::vector Parser::parseWatcherOptions(CfgWatch* watcher, std::string key) { // Setup our vector diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 359fb8e..eace274 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -54,7 +54,7 @@ class Parser { const std::string parseString(); bool safeSpecial(); short* parseStatusCodes(std::vector&); - std::vector parseWatcherOptions(CfgWatch*, std::string key); + const std::vector parseWatcherOptions(CfgWatch*, std::string key); char* parseWatcherOption(CfgWatch*, std::string key); bool parseBooleanOption(CfgWatch*, std::string key); bool mIsKey(const std::string& expectKey, const std::string& key); diff --git a/src/parser/tokenizer.cpp b/src/parser/tokenizer.cpp index 612b7de..e46c88b 100644 --- a/src/parser/tokenizer.cpp +++ b/src/parser/tokenizer.cpp @@ -197,14 +197,17 @@ void Tokenizer::next() parse_start_pos = mCurrentChar; parse_start_line = mCurrentLine; parse_start_column = mCurrentColumn; + bool escape = false; do { + escape = false; mValue += nextChar(); c = mFile->peek(); if( c == '\\' ) { + escape = true; nextChar(); c = mFile->peek(); } - } while( mFile->good() && c != EOF && c != quotemarker ); + } while( mFile->good() && c != EOF && (escape || c != quotemarker) ); if( c == quotemarker && mFile->good()) nextChar(); if( !mFile->good() ) From b452733eacc11d0e8af65459aa882e65979ce68d Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Wed, 27 Dec 2017 20:45:35 -0700 Subject: [PATCH 26/28] First pass at integrating custom config parser On a side note: 1) it works! :flushed: 2) I can now semi use renzoku for development again :grin: Now... To get it to be more production level and working somewhat decently. :sweat_smile: --- renzoku.conf | 16 +++++++++++++ src/Makefile.am | 4 +++- src/main.cpp | 36 +++++++++++++++++++++------- src/parser/parser.cpp | 2 +- src/procman.cpp | 41 +++++++++---------------------- src/procman.hpp | 7 ++---- src/threadmgr.cpp | 39 ++++++++++++++++++++++++++++++ src/threadmgr.hpp | 22 +++++++++++++++++ src/watcher.cpp | 56 +++++++++++++++++++++++++++++++------------ src/watcher.hpp | 11 +++++++-- 10 files changed, 172 insertions(+), 62 deletions(-) create mode 100644 renzoku.conf create mode 100644 src/threadmgr.cpp create mode 100644 src/threadmgr.hpp diff --git a/renzoku.conf b/renzoku.conf new file mode 100644 index 0000000..93b23b6 --- /dev/null +++ b/renzoku.conf @@ -0,0 +1,16 @@ +Watch "れんぞく C++ Code Base" { + files = "*.cpp", "*.hpp" + exclude = "main.*", "test_driver.*" + dir = "src" + step Compile { + command = "make" + enabled = true + } + step "Build & Run Tests" { + command = "make check" + enabled = yes + } + step Run { + command = "echo 'It built!'" + } +} diff --git a/src/Makefile.am b/src/Makefile.am index 065f33a..830f247 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,6 @@ #CFLAGS = --pedantic -Wall -std=c99 -O2 #CXXFLAGS = -Wall -g -Isrc/ -static -lyaml-cpp -std=c++11 -AM_CXXFLAGS = --pedantic -Wall -O2 -g -std=c++11 -Iparser/ +AM_CXXFLAGS = --pedantic -Wall -O2 -g -std=c++11 -Iparser/ -pthread AM_LDFLAGS = -Bstatic -lconfig++ -Bdynamic SUBDIRS = parser @@ -24,6 +24,8 @@ librenzoku_a_SOURCES = \ process.hpp \ procman.cpp \ procman.hpp \ + threadmgr.cpp \ + threadmgr.hpp \ signals.cpp \ signals.hpp \ util.cpp \ diff --git a/src/main.cpp b/src/main.cpp index 982b7b7..272c95a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,10 +40,12 @@ #include "log.hpp" #include "watcher.hpp" #include "procman.hpp" +#include "threadmgr.hpp" #include #include "parser/parser.hpp" +#include -volatile sig_atomic_t gRunning = 1; +std::mutex gRunning; int main(int argc, char** argv) { struct sigaction int_catcher; @@ -61,7 +63,7 @@ int main(int argc, char** argv) { pex.getLine(), pex.getError()); return EXIT_FAILURE; }*/ - Parser p("config.conf"); + Parser p("renzoku.conf"); try { p.Parse(); } catch ( const libconfig::ParseException &pex ) { @@ -69,7 +71,25 @@ int main(int argc, char** argv) { pex.getLine(), pex.getError()); return EXIT_FAILURE; } - Watcher w(&logger, "./", p, &procman, true); + const CfgWatch* watcher = p.getWatchers(); + CfgStep* step; + Process* proc; + step = watcher->steps; + while(step) { + proc = new Process(&logger, step->command, true, step->enabled); + procman.addProcess(proc); + step = step->next; + } + procman.finalize(); + Watcher w(&logger, watcher->name, watcher->workingDir, watcher, &procman, true); + LOG(&logger, DEBUG, "Testing"); + Threadmgr mgr; + mgr.AddWatcher(&w); + mgr.Start(); + gRunning.lock(); + gRunning.lock(); + gRunning.unlock(); + mgr.Stop(); //while(gRunning) // w.listen(); @@ -78,12 +98,12 @@ int main(int argc, char** argv) { return EXIT_SUCCESS; } -sig_atomic_t program_is_running() -{ - return gRunning; -} +//sig_atomic_t program_is_running() +//{ +// return gRunning; +//} void stop_program() { - gRunning = 0; + gRunning.unlock(); } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index caf966a..5ca0675 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -13,7 +13,7 @@ CfgStep* create_CfgStep() { step->name = NULL; step->command = NULL; step->daemon = false; - step->enabled = false; + step->enabled = true; step->error_status = NULL; step->ignore_status = NULL; step->next = NULL; diff --git a/src/procman.cpp b/src/procman.cpp index 5a2f0bd..6145bb5 100644 --- a/src/procman.cpp +++ b/src/procman.cpp @@ -21,7 +21,6 @@ #include "procman.hpp" ProcessManager::ProcessManager(iLogger *logger) : - mBuildStep(NULL), mProgram(NULL), logger(logger) { @@ -30,31 +29,13 @@ ProcessManager::ProcessManager(iLogger *logger) : ProcessManager::~ProcessManager() { for(Process* proc : mProcesses) delete proc; - delete mBuildStep; delete mProgram; } -Process* ProcessManager::getBuildStep() { - return mBuildStep; -} - Process* ProcessManager::getProgram() { return mProgram; } -bool ProcessManager::runBuild() { - if(mBuildStep->runAndWait()) - { - LOG(logger, SUCCESS, "Successfully ran command"); - return true; - } - else - { - LOG(logger, ERROR, "A command failed to run :("); - return false; - } -} - bool ProcessManager::runProcesses() { for(Process* proc : mProcesses) { @@ -65,6 +46,7 @@ bool ProcessManager::runProcesses() { else { LOG(logger, ERROR, "A command failed to run :("); + return false; } } return true; @@ -73,14 +55,15 @@ bool ProcessManager::runProcesses() { void ProcessManager::haltConstructionProcs() { for(Process* proc : mProcesses) { - proc->kill(); + if(proc) + proc->kill(); } } void ProcessManager::killAll() { - mBuildStep->kill(); haltConstructionProcs(); - mProgram->kill(); + if(mProgram) + mProgram->kill(); } void ProcessManager::startProgram() { @@ -94,19 +77,12 @@ void ProcessManager::haltProgram() { void ProcessManager::restartAll() { haltConstructionProcs(); - if(runBuild()) { - runProcesses(); + if(runProcesses()) { haltProgram(); startProgram(); } } -void ProcessManager::setBuilder(Process* proc) { - if(mBuildStep) - throw std::runtime_error("Build Step already set"); - mBuildStep = proc; -} - void ProcessManager::addProcess(Process* proc) { mProcesses.push_back(proc); } @@ -117,3 +93,8 @@ void ProcessManager::setProgram(Process* proc) { mProgram = proc; } +void ProcessManager::finalize() { + setProgram(mProcesses.back()); + mProcesses.pop_back(); +} + diff --git a/src/procman.hpp b/src/procman.hpp index b8680a1..4829bfc 100644 --- a/src/procman.hpp +++ b/src/procman.hpp @@ -25,23 +25,20 @@ class ProcessManager { private: - Process* mBuildStep; // The actual build step - std::vector mProcesses; // Procs that run between build and launch + std::vector mProcesses; // Procs that run for build before launch Process* mProgram; // The actual program iLogger* logger; public: ProcessManager(iLogger*); ~ProcessManager(); - Process* getBuildStep(); Process* getProgram(); - bool runBuild(); bool runProcesses(); void haltConstructionProcs(); void startProgram(); void haltProgram(); void restartAll(); void killAll(); - void setBuilder(Process*); void addProcess(Process*); void setProgram(Process*); + void finalize(); }; diff --git a/src/threadmgr.cpp b/src/threadmgr.cpp new file mode 100644 index 0000000..6f91c6b --- /dev/null +++ b/src/threadmgr.cpp @@ -0,0 +1,39 @@ +#include "threadmgr.hpp" + +Threadmgr::Threadmgr() : + mWatchers(), + mThreads() +{ +} + +Threadmgr::~Threadmgr() +{ + for (auto th : mThreads) { + if(th) { + th->join(); + delete th; + } + } +} + +void Threadmgr::AddWatcher(Watcher* w) { + mWatchers.push_back(w); +} + +void Threadmgr::Start() { + std::thread* thread; + for (auto w : mWatchers) { + thread = new std::thread(w->Start, w); + if (thread) { + mThreads.push_back(thread); + } else { + fprintf(stderr, "Failed to create new thread for %s\n", w->getName()); + } + } +} + +void Threadmgr::Stop() { + for (auto w : mWatchers) { + w->Stop(); + } +} diff --git a/src/threadmgr.hpp b/src/threadmgr.hpp new file mode 100644 index 0000000..642f29a --- /dev/null +++ b/src/threadmgr.hpp @@ -0,0 +1,22 @@ + +#ifndef THREADMGR_H +#define THREADMGR_H +#include +#include +#include "watcher.hpp" + +class Threadmgr +{ +private: + std::vector mWatchers; + std::vector mThreads; + +public: + Threadmgr(); + virtual ~Threadmgr(); + void AddWatcher(Watcher*); + void Start(); + void Stop(); +}; + +#endif /* THREADMGR_H */ diff --git a/src/watcher.cpp b/src/watcher.cpp index 207aa18..4c5a946 100644 --- a/src/watcher.cpp +++ b/src/watcher.cpp @@ -22,17 +22,20 @@ #include "util.hpp" #include "log.hpp" -Watcher::Watcher(iLogger* logger, std::string dir, Parser& parser, ProcessManager* pm, bool recursive) : +Watcher::Watcher(iLogger* logger, std::string name, std::string dir, const CfgWatch* cfg, ProcessManager* pm, bool recursive) : logger(logger), + mName(name), + mRunning(true), mDirectory(dir), mRecursive(recursive), - mParser(parser), + mWatcher(cfg), mTimer(time(0)-15), - mBuilder(pm->getBuildStep()), procman(pm) { + LOG(logger, DEBUG, "Watcher init"); mINotify = inotify_init(); watchDirectory(); + LOG(logger, DEBUG, "Watcher post-init"); } Watcher::~Watcher() @@ -51,13 +54,13 @@ void Watcher::removeAllWatches() void Watcher::watchDirectory() { - mINotify = inotify_init(); + //mINotify = inotify_init(); if( mINotify < 0 ) perror("inotify_init"); - mFDs.push_back(inotify_add_watch(mINotify, Util::cwd().c_str(), IN_MODIFY)); + mFDs.push_back(inotify_add_watch(mINotify, mDirectory.c_str(), IN_MODIFY)); if(mRecursive) { - this->recursiveWatch(Util::cwd()); + this->recursiveWatch(mDirectory); } } void Watcher::recursiveWatch(std::string maindir) @@ -69,10 +72,10 @@ void Watcher::recursiveWatch(std::string maindir) { if(dir.compare(".") == 0 || dir.compare("..") == 0) continue; - //LOG(logger, DEBUG, "Watching %s", (maindir + '/' + dir).c_str()); + LOG(logger, DEBUG, "Watching %s", (maindir + '/' + dir).c_str()); mFDs.push_back(inotify_add_watch(mINotify, (maindir + '/' + dir).c_str(), IN_MODIFY)); - //LOG(logger, DEBUG, "%s => %s", dir.c_str(), "."); + LOG(logger, DEBUG, "%s => %s", dir.c_str(), "."); this->recursiveWatch(maindir + '/' + dir); } } @@ -92,7 +95,7 @@ void Watcher::listen() mTimeout.tv_sec = 1; mTimeout.tv_usec = 0; int selval = select(mINotify+1, &mReadFDs, NULL, NULL, &mTimeout); - if(selval == 1) { + if(selval >= 1) { int length, i; struct tm timediff = {0}; length = read( mINotify, mBuffer, EVENT_BUF_LEN); @@ -109,14 +112,12 @@ void Watcher::listen() (event->mask & IN_MODIFY) && (! (event->mask & IN_ISDIR)) ){ - for(auto watcher : mParser) - { - for( auto watcher_path : watcher->excludesFilter ) { + for( auto watcher_path : mWatcher->excludesFilter ) { if(Util::strMatch(watcher_path, std::string(event->name))) { continue; } } - for( auto watcher_path : watcher->filesFilter ) { + for( auto watcher_path : mWatcher->filesFilter ) { if(Util::strMatch(watcher_path, std::string(event->name))) { LOG(logger, INFO, "\033[0;32m=> File %s was modified!\033[0m", event->name); //Logger::getLogger()->LOG(logger, DEBUG, "%ju", timer); @@ -132,7 +133,6 @@ void Watcher::listen() } } } - } } } i += EVENT_SIZE + event->len; @@ -147,7 +147,7 @@ void Watcher::listen() bool Watcher::rebuild() { - bool status = mBuilder->runAndWait(); + bool status = procman->runProcesses(); if(status) LOG(logger, SUCCESS, "Successfully rebuilt project"); else @@ -158,6 +158,32 @@ bool Watcher::rebuild() void Watcher::restartProgram() { //mConfig.restartProcesses(); + procman->haltProgram(); + procman->startProgram(); + LOG(logger, SUCCESS, "Started program"); +} + +const std::string Watcher::getName() +{ + return mName; +} + +const bool Watcher::isRunning() +{ + //LOG(logger, DEBUG, "Watcher is running"); + return mRunning; +} + +void Watcher::Start(Watcher* w) +{ + while(w->isRunning()) { + w->listen(); + } +} + +void Watcher::Stop() +{ + mRunning = false; } #ifdef DEBUG diff --git a/src/watcher.hpp b/src/watcher.hpp index 81647a7..8ea6518 100644 --- a/src/watcher.hpp +++ b/src/watcher.hpp @@ -17,6 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ +#pragma once #include #include #include @@ -45,12 +46,14 @@ class Watcher { private: iLogger* logger; + std::string mName; + bool mRunning; std::string mDirectory; bool mRecursive; std::vector mFiletypes; std::vector mFDs; char mBuffer[EVENT_BUF_LEN]; - Parser &mParser; + const CfgWatch* mWatcher; int mINotify; time_t mTimer; Process* mBuilder; @@ -65,8 +68,12 @@ class Watcher { bool rebuild(); void restartProgram(); public: - Watcher(iLogger* logger, std::string, Parser& parser, ProcessManager*, bool recursive); + Watcher(iLogger* logger, std::string, std::string, const CfgWatch* cfg, ProcessManager*, bool recursive); ~Watcher(); void watchFileType(std::string); void listen(); + const std::string getName(); + const bool isRunning(); + void Stop(); + static void Start(Watcher*); }; From 077a31749b93da827c28c9bb93f7bbc9d0cdb27c Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Ogre)" Date: Wed, 27 Dec 2017 20:58:22 -0700 Subject: [PATCH 27/28] Update readme file --- doc/README | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/README b/doc/README index d8320c2..65ad8b5 100644 --- a/doc/README +++ b/doc/README @@ -17,5 +17,4 @@ supposed to do. The goal of this program is as follows: Yes, there are some programs out there that do similar things (like Grunt), but they aren't able to fill my need completely or easily. I just want something -that I can install and use with a few simple tweaks to a config file (like -Travis' yaml configs). +that I can install and use with a few simple tweaks to a config file. From 74a2c41ea54f31355a5b41726b30399f5be33e75 Mon Sep 17 00:00:00 2001 From: Frostyfrog Date: Tue, 9 Jan 2018 15:47:50 -0700 Subject: [PATCH 28/28] Misc fixes for CentOS 7 This should fix #2 as far as CentOS 7 goes. Have not tested on CentOS 6 or on Debian. Those are the next goals. --- Makefile.am | 2 +- configure.ac | 2 +- src/Makefile.am | 2 +- src/main.cpp | 9 +-------- src/threadmgr.cpp | 5 +++-- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Makefile.am b/Makefile.am index e91cf2a..22a2dc8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,4 +2,4 @@ sh_TEST_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ $(top_srcdir)/build-aux/tap-driver.sh AUTOMAKE_OPTIONS = foreign -SUBDIRS = src src/config doc examples man tests +SUBDIRS = src src/parser doc examples man tests diff --git a/configure.ac b/configure.ac index 3b6900a..c2e5475 100644 --- a/configure.ac +++ b/configure.ac @@ -103,5 +103,5 @@ AC_PROG_AWK #AC_CONFIG_FILES([Makefile]) AC_OUTPUT(Makefile src/Makefile doc/Makefile examples/Makefile man/Makefile - tests/Makefile src/config/Makefile src/parser/Makefile) + tests/Makefile src/parser/Makefile) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 830f247..55488f3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ #CFLAGS = --pedantic -Wall -std=c99 -O2 #CXXFLAGS = -Wall -g -Isrc/ -static -lyaml-cpp -std=c++11 AM_CXXFLAGS = --pedantic -Wall -O2 -g -std=c++11 -Iparser/ -pthread -AM_LDFLAGS = -Bstatic -lconfig++ -Bdynamic +AM_LDFLAGS = -Bstatic -Bdynamic SUBDIRS = parser # this lists the binaries to produce, the (non-PHONY, binary) targets in diff --git a/src/main.cpp b/src/main.cpp index 272c95a..a187d41 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,7 +41,6 @@ #include "watcher.hpp" #include "procman.hpp" #include "threadmgr.hpp" -#include #include "parser/parser.hpp" #include @@ -64,13 +63,7 @@ int main(int argc, char** argv) { return EXIT_FAILURE; }*/ Parser p("renzoku.conf"); - try { - p.Parse(); - } catch ( const libconfig::ParseException &pex ) { - fprintf(stderr, "Parse error at %s:%d - %s\n", pex.getFile(), - pex.getLine(), pex.getError()); - return EXIT_FAILURE; - } + p.Parse(); const CfgWatch* watcher = p.getWatchers(); CfgStep* step; Process* proc; diff --git a/src/threadmgr.cpp b/src/threadmgr.cpp index 6f91c6b..a75fcc0 100644 --- a/src/threadmgr.cpp +++ b/src/threadmgr.cpp @@ -23,11 +23,12 @@ void Threadmgr::AddWatcher(Watcher* w) { void Threadmgr::Start() { std::thread* thread; for (auto w : mWatchers) { - thread = new std::thread(w->Start, w); + thread = new std::thread(&Watcher::Start, w); if (thread) { mThreads.push_back(thread); } else { - fprintf(stderr, "Failed to create new thread for %s\n", w->getName()); + fprintf(stderr, "Failed to create new thread for %s\n", + w->getName().c_str()); } } }