diff --git a/.gitignore b/.gitignore
index 259148f..8b4163a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,6 @@
*.exe
*.out
*.app
+
+cmake-build-*
+.idea/workspace.xml
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..30aa626
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libcron.iml b/.idea/libcron.iml
new file mode 100644
index 0000000..f08604b
--- /dev/null
+++ b/.idea/libcron.iml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..79b3c94
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..8ec2a92
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..a6b181a
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,6 @@
+cmake_minimum_required(VERSION 3.9)
+
+add_subdirectory(libcron)
+add_subdirectory(test)
+
+add_dependencies(cron_test libcron)
\ No newline at end of file
diff --git a/libcron/CMakeLists.txt b/libcron/CMakeLists.txt
new file mode 100644
index 0000000..6d21247
--- /dev/null
+++ b/libcron/CMakeLists.txt
@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 3.9)
+project(libcron)
+
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -fsanitize=address")
+
+add_library(${PROJECT_NAME}
+ Cron.h
+ Cron.cpp Task.h CronTime.h TimeTypes.h CronTime.cpp)
diff --git a/libcron/Cron.cpp b/libcron/Cron.cpp
new file mode 100644
index 0000000..6824421
--- /dev/null
+++ b/libcron/Cron.cpp
@@ -0,0 +1,19 @@
+//
+// Created by permal on 3/8/18.
+//
+
+
+#include
+#include "Cron.h"
+
+bool libcron::Cron::add_schedule(const std::string &schedule, std::function work)
+{
+ auto cron = CronTime::create(schedule);
+ bool res = cron.is_valid();
+ if (res)
+ {
+ items.emplace(Task(cron, std::move(work)));
+ }
+
+ return res;
+}
diff --git a/libcron/Cron.h b/libcron/Cron.h
new file mode 100644
index 0000000..17ee640
--- /dev/null
+++ b/libcron/Cron.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include
+#include
+#include
+#include "Task.h"
+
+namespace libcron
+{
+ class Cron
+ {
+
+ public:
+ bool add_schedule(const std::string& schedule, std::function work);
+ private:
+ std::priority_queue items{};
+ };
+}
\ No newline at end of file
diff --git a/libcron/CronTime.cpp b/libcron/CronTime.cpp
new file mode 100644
index 0000000..99b2ea4
--- /dev/null
+++ b/libcron/CronTime.cpp
@@ -0,0 +1,71 @@
+#include "CronTime.h"
+
+namespace libcron
+{
+
+ CronTime CronTime::create(const std::string& cron_expression)
+ {
+ CronTime c;
+ c.parse(cron_expression);
+
+ return std::move(c);
+ }
+
+ CronTime::CronTime()
+ : month_names({"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}),
+ day_names({"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"})
+ {
+ }
+
+ void CronTime::parse(const std::string& cron_expression)
+ {
+ // First, split on white-space. We expect six parts.
+ std::regex split{R"#(^\s*(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s+(.*?)\s*$)#",
+ std::regex_constants::ECMAScript};
+
+ std::smatch match;
+
+ if (std::regex_match(cron_expression.begin(), cron_expression.end(), match, split))
+ {
+ valid = validate_numeric(match[1], seconds);
+ valid &= validate_numeric(match[2], minutes);
+ valid &= validate_numeric(match[3], hours);
+ valid &= validate_numeric(match[4], day_of_month);
+ valid &= validate_numeric(match[5], months)
+ || validate_literal(match[5], months, month_names, 1);
+ valid &= validate_numeric(match[6], day_of_week)
+ || validate_literal(match[6], day_of_week, day_names, 0);
+ }
+ }
+
+ std::vector CronTime::split(const std::string& s, char token)
+ {
+ std::vector res;
+
+ std::string r = "[";
+ r += token;
+ r += "]";
+ std::regex splitter{r, std::regex_constants::ECMAScript};
+
+ std::copy(std::sregex_token_iterator(s.begin(), s.end(), splitter, -1),
+ std::sregex_token_iterator(),
+ std::back_inserter(res));
+
+
+ return res;
+ }
+
+ bool CronTime::is_number(const std::string& s)
+ {
+ // Find any character that isn't a number.
+ return !s.empty()
+ && std::find_if(s.begin(), s.end(),
+ [](char c)
+ { return !std::isdigit(c); }) == s.end();
+ }
+
+ bool CronTime::is_between(int32_t value, int32_t low_limit, int32_t high_limt)
+ {
+ return value >= low_limit && value <= high_limt;
+ }
+}
\ No newline at end of file
diff --git a/libcron/CronTime.h b/libcron/CronTime.h
new file mode 100644
index 0000000..76ffcb1
--- /dev/null
+++ b/libcron/CronTime.h
@@ -0,0 +1,285 @@
+#pragma once
+
+#include
+#include "TimeTypes.h"
+#include
+#include
+#include
+#include
+
+namespace libcron
+{
+ /*
+ Cron format, 6 parts:
+
+ ┌──────────────seconds (0 - 59)
+ │ ┌───────────── minute (0 - 59)
+ │ │ ┌───────────── hour (0 - 23)
+ │ │ │ ┌───────────── day of month (1 - 31)
+ │ │ │ │ ┌───────────── month (1 - 12)
+ │ │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday;
+ │ │ │ │ │ │ 7 is also Sunday on some systems)
+ │ │ │ │ │ │
+ │ │ │ │ │ │
+ * * * * * *
+
+ Allowed formats:
+ Special characters: '*', meaning the entire range.
+
+ Ranges: 1,2,4-6
+ Result: 1,2,4,5,6
+ Steps: 1/2
+ Result: 1,3,5,7...
+
+ For day of month, these strings are valid, case insensitive:
+ SUN, MON, TUE, WED, THU, FRI, SAT
+ Example: MON-THU,SAT
+
+ For month, these strings are valid, case insensitive:
+ JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
+ Example: JAN,MAR,SEP-NOV
+
+ Each part is separated by one or more whitespaces. It is thus important to keep
+ whitespaces out of the respective parts.
+
+ Valid:
+ * * * * * *
+ 0,3,40-50 * * * * *
+
+ Invalid:
+ 0, 3, 40-50 * * * * *
+
+ */
+
+ class CronTime
+ {
+ public:
+ static CronTime create(const std::string& cron_expression);
+
+ CronTime();
+
+
+ bool operator<(const CronTime& other) const
+ {
+ return next_run_time < other.next_run_time;
+ }
+
+ bool is_valid() const
+ {
+ return valid;
+ }
+
+#ifndef EXPOSE_PRIVATE_PARTS
+ private:
+#endif
+
+ void parse(const std::string& cron_expression);
+
+ template
+ bool validate_numeric(const std::string& s, std::set& numbers);
+
+ template
+ bool validate_literal(const std::string& s,
+ std::set& numbers,
+ const std::vector& names,
+ int32_t name_offset);
+
+ template
+ bool process_parts(const std::vector& parts, std::set& numbers);
+
+ template
+ bool add_number(std::set& set, int32_t number);
+
+ template
+ bool is_within_limits(int32_t low, int32_t high);
+
+ template
+ bool get_range(const std::string& s, T& low, T& high);
+
+ template
+ uint8_t value_of(T t)
+ {
+ return static_cast(t);
+ }
+
+ std::vector split(const std::string& s, char token);
+
+ bool is_number(const std::string& s);
+
+ bool is_between(int32_t value, int32_t low_limit, int32_t high_limit);
+
+ std::chrono::system_clock::time_point next_run_time{};
+ std::set seconds{};
+ std::set minutes{};
+ std::set hours{};
+ std::set day_of_month{};
+ std::set months{};
+ std::set day_of_week{};
+ bool valid = false;
+
+ std::vector month_names;
+ std::vector day_names;
+
+ template
+ void add_full_range(std::set& set);
+ };
+
+ template
+ bool CronTime::validate_numeric(const std::string& s, std::set& numbers)
+ {
+ std::vector parts = split(s, ',');
+
+ return process_parts(parts, numbers);
+ }
+
+ template
+ bool CronTime::validate_literal(const std::string& s,
+ std::set& numbers,
+ const std::vector& names,
+ int32_t name_offset)
+ {
+ std::vector parts = split(s, ',');
+
+ // Replace each found name with the corresponding value.
+ for (const auto& name : names)
+ {
+ std::regex m(name, std::regex_constants::ECMAScript | std::regex_constants::icase);
+ for (size_t i = 0; i < parts.size(); ++i)
+ {
+ std::string replaced;
+ std::regex_replace(std::back_inserter(replaced), parts[i].begin(), parts[i].end(), m,
+ std::to_string(name_offset));
+
+ parts[i] = replaced;
+ }
+
+ name_offset++;
+ }
+
+ return process_parts(parts, numbers);
+
+ }
+
+ template
+ bool CronTime::process_parts(const std::vector& parts, std::set& numbers)
+ {
+ bool res = true;
+
+ T left;
+ T right;
+
+ for (const auto& p : parts)
+ {
+ if (p == "*")
+ {
+ add_full_range(numbers);
+ }
+ else if (is_number(p))
+ {
+ res &= add_number(numbers, std::stoi(p));
+ }
+ else if (get_range(p, left, right))
+ {
+ // A range can be written as both 1-22 or 22-1, meaning totally different ranges.
+ // First case is 1...22 while 22-1 is only four hours: 22, 23, 0, 1.
+ if (left <= right)
+ {
+ for (auto v = value_of(left); v <= value_of(right); ++v)
+ {
+ res &= add_number(numbers, v);
+ }
+ }
+ else
+ {
+ // 'left' and 'right' are not in value order. First, get values between 'left' and T::Last, inclusive
+ for (auto v = value_of(left); v <= value_of(T::Last); ++v)
+ {
+ res &= add_number(numbers, v);
+ }
+
+ // Next, get values between T::First and 'right', inclusive.
+ for (auto v = value_of(T::First); v <= value_of(right); ++v)
+ {
+ res &= add_number(numbers, v);
+ }
+ }
+ }
+ else
+ {
+ res = false;
+ }
+ }
+
+ return res;
+ }
+
+ template
+ bool CronTime::get_range(const std::string& s, T& low, T& high)
+ {
+ bool res = false;
+
+ auto value_range = R"#((\d+)-(\d+))#";
+
+ std::regex range(value_range, std::regex_constants::ECMAScript);
+
+ std::smatch match;
+
+ if (std::regex_match(s.begin(), s.end(), match, range))
+ {
+ auto left = std::stoi(match[1].str().c_str());
+ auto right = std::stoi(match[2].str().c_str());
+
+ if (is_within_limits(left, right))
+ {
+ low = static_cast(left);
+ high = static_cast(right);
+ res = true;
+ }
+ }
+
+ return res;
+ }
+
+ template
+ void CronTime::add_full_range(std::set& set)
+ {
+ for (auto v = value_of(T::First); v <= value_of(T::Last); ++v)
+ {
+ if (set.find(static_cast(v)) == set.end())
+ {
+ set.emplace(static_cast(v));
+ }
+ }
+ }
+
+ template
+ bool CronTime::add_number(std::set& set, int32_t number)
+ {
+ bool res = true;
+
+ // Don't add if already there
+ if (set.find(static_cast(number)) == set.end())
+ {
+ // Check range
+ if (is_within_limits(number, number))
+ {
+ set.emplace(static_cast(number));
+ }
+ else
+ {
+ res = false;
+ }
+ }
+
+ return res;
+ }
+
+ template
+ bool CronTime::is_within_limits(int32_t low, int32_t high)
+ {
+ return is_between(low, value_of(T::First), value_of(T::Last))
+ && is_between(high, value_of(T::First), value_of(T::Last));
+ }
+
+
+}
diff --git a/libcron/Task.h b/libcron/Task.h
new file mode 100644
index 0000000..a2754c2
--- /dev/null
+++ b/libcron/Task.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include
+#include "CronTime.h"
+
+namespace libcron
+{
+ class Task
+ {
+ public:
+
+ Task(CronTime time, std::function task)
+ : time(std::move(time)), task(std::move(task))
+ {
+ }
+
+ bool operator<(const Task& other) const
+ {
+ return time < other.time;
+ }
+
+ private:
+ CronTime time{};
+ std::function task;
+ };
+}
\ No newline at end of file
diff --git a/libcron/TimeTypes.h b/libcron/TimeTypes.h
new file mode 100644
index 0000000..a3238d3
--- /dev/null
+++ b/libcron/TimeTypes.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include
+
+namespace libcron
+{
+ enum class Seconds : int8_t
+ {
+ First = 0,
+ Last = 59
+ };
+
+ enum class Minutes : int8_t
+ {
+ First = 0,
+ Last = 59
+ };
+
+ enum class Hours : int8_t
+ {
+ First = 0,
+ Last = 23
+ };
+
+ enum class DayOfMonth : uint8_t
+ {
+ First = 1,
+ Last = 31
+ };
+
+ enum class Months : uint8_t
+ {
+ First = 1,
+ Last = 12
+ };
+
+ enum class DayOfWeek : uint8_t
+ {
+ // Sunday = 0 ... Saturday = 6
+ First = 0,
+ Last = 6,
+ };
+}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..342fa67
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,19 @@
+cmake_minimum_required(VERSION 3.6)
+project(cron_test)
+
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -fsanitize=address -lasan")
+
+add_definitions(-DEXPOSE_PRIVATE_PARTS)
+
+include_directories(
+ externals/Catch2/single_include/
+ ..
+)
+
+add_executable(
+ ${PROJECT_NAME}
+ test.cpp
+)
+
+target_link_libraries(${PROJECT_NAME} libcron)
\ No newline at end of file
diff --git a/test/externals/Catch2/single_include/catch.hpp b/test/externals/Catch2/single_include/catch.hpp
new file mode 100644
index 0000000..c36a987
--- /dev/null
+++ b/test/externals/Catch2/single_include/catch.hpp
@@ -0,0 +1,12851 @@
+/*
+ * Catch v2.2.0
+ * Generated: 2018-03-07 10:56:32.217228
+ * ----------------------------------------------------------
+ * This file has been merged from multiple headers. Please don't edit it directly
+ * Copyright (c) 2018 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
+// start catch.hpp
+
+
+#define CATCH_VERSION_MAJOR 2
+#define CATCH_VERSION_MINOR 2
+#define CATCH_VERSION_PATCH 0
+
+#ifdef __clang__
+# pragma clang system_header
+#elif defined __GNUC__
+# pragma GCC system_header
+#endif
+
+// start catch_suppress_warnings.h
+
+#ifdef __clang__
+# ifdef __ICC // icpc defines the __clang__ macro
+# pragma warning(push)
+# pragma warning(disable: 161 1682)
+# else // __ICC
+# pragma clang diagnostic ignored "-Wunused-variable"
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wpadded"
+# pragma clang diagnostic ignored "-Wswitch-enum"
+# pragma clang diagnostic ignored "-Wcovered-switch-default"
+# endif
+#elif defined __GNUC__
+# pragma GCC diagnostic ignored "-Wunused-variable"
+# pragma GCC diagnostic ignored "-Wparentheses"
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wpadded"
+#endif
+// end catch_suppress_warnings.h
+#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)
+# define CATCH_IMPL
+# define CATCH_CONFIG_ALL_PARTS
+#endif
+
+// In the impl file, we want to have access to all parts of the headers
+// Can also be used to sanely support PCHs
+#if defined(CATCH_CONFIG_ALL_PARTS)
+# define CATCH_CONFIG_EXTERNAL_INTERFACES
+# if defined(CATCH_CONFIG_DISABLE_MATCHERS)
+# undef CATCH_CONFIG_DISABLE_MATCHERS
+# endif
+# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+#endif
+
+#if !defined(CATCH_CONFIG_IMPL_ONLY)
+// start catch_platform.h
+
+#ifdef __APPLE__
+# include
+# if TARGET_OS_OSX == 1
+# define CATCH_PLATFORM_MAC
+# elif TARGET_OS_IPHONE == 1
+# define CATCH_PLATFORM_IPHONE
+# endif
+
+#elif defined(linux) || defined(__linux) || defined(__linux__)
+# define CATCH_PLATFORM_LINUX
+
+#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
+# define CATCH_PLATFORM_WINDOWS
+#endif
+
+// end catch_platform.h
+
+#ifdef CATCH_IMPL
+# ifndef CLARA_CONFIG_MAIN
+# define CLARA_CONFIG_MAIN_NOT_DEFINED
+# define CLARA_CONFIG_MAIN
+# endif
+#endif
+
+// start catch_user_interfaces.h
+
+namespace Catch {
+ unsigned int rngSeed();
+}
+
+// end catch_user_interfaces.h
+// start catch_tag_alias_autoregistrar.h
+
+// start catch_common.h
+
+// start catch_compiler_capabilities.h
+
+// Detect a number of compiler features - by compiler
+// The following features are defined:
+//
+// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported?
+// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported?
+// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals 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_NO_POSIX_SIGNALS) 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.
+
+#ifdef __cplusplus
+
+# if __cplusplus >= 201402L
+# define CATCH_CPP14_OR_GREATER
+# endif
+
+# if __cplusplus >= 201703L
+# define CATCH_CPP17_OR_GREATER
+# endif
+
+#endif
+
+#if defined(CATCH_CPP17_OR_GREATER)
+# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
+#endif
+
+#ifdef __clang__
+
+# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+ _Pragma( "clang diagnostic push" ) \
+ _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \
+ _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"")
+# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+ _Pragma( "clang diagnostic pop" )
+
+# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+ _Pragma( "clang diagnostic push" ) \
+ _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
+# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
+ _Pragma( "clang diagnostic pop" )
+
+#endif // __clang__
+
+////////////////////////////////////////////////////////////////////////////////
+// Assume that non-Windows platforms support posix signals by default
+#if !defined(CATCH_PLATFORM_WINDOWS)
+ #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// We know some environments not to support full POSIX signals
+#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__)
+ #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
+#endif
+
+#ifdef __OS400__
+# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
+# define CATCH_CONFIG_COLOUR_NONE
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Cygwin
+#ifdef __CYGWIN__
+
+// Required for some versions of Cygwin to declare gettimeofday
+// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin
+# define _BSD_SOURCE
+
+#endif // __CYGWIN__
+
+////////////////////////////////////////////////////////////////////////////////
+// Visual C++
+#ifdef _MSC_VER
+
+# if _MSC_VER >= 1900 // Visual Studio 2015 or newer
+# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
+# endif
+
+// Universal Windows platform does not support SEH
+// Or console colours (or console at all...)
+# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
+# define CATCH_CONFIG_COLOUR_NONE
+# else
+# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
+# endif
+
+#endif // _MSC_VER
+
+////////////////////////////////////////////////////////////////////////////////
+
+// DJGPP
+#ifdef __DJGPP__
+# define CATCH_INTERNAL_CONFIG_NO_WCHAR
+#endif // __DJGPP__
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Use of __COUNTER__ is suppressed during code analysis in
+// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly
+// handled by it.
+// Otherwise all supported compilers support COUNTER macro,
+// but user still might want to turn it off
+#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L )
+ #define CATCH_INTERNAL_CONFIG_COUNTER
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER)
+# define CATCH_CONFIG_COUNTER
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH)
+# define CATCH_CONFIG_WINDOWS_SEH
+#endif
+// This is set by default, because we assume that unix compilers are posix-signal-compatible by default.
+#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)
+# define CATCH_CONFIG_POSIX_SIGNALS
+#endif
+// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions.
+#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR)
+# define CATCH_CONFIG_WCHAR
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
+# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
+#endif
+
+#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)
+# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
+# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS
+#endif
+#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS)
+# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
+# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+#endif
+
+// end catch_compiler_capabilities.h
+#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 )
+#ifdef CATCH_CONFIG_COUNTER
+# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
+#else
+# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )
+#endif
+
+#include
+#include
+#include
+
+namespace Catch {
+
+ struct CaseSensitive { enum Choice {
+ Yes,
+ No
+ }; };
+
+ class NonCopyable {
+ NonCopyable( NonCopyable const& ) = delete;
+ NonCopyable( NonCopyable && ) = delete;
+ NonCopyable& operator = ( NonCopyable const& ) = delete;
+ NonCopyable& operator = ( NonCopyable && ) = delete;
+
+ protected:
+ NonCopyable();
+ virtual ~NonCopyable();
+ };
+
+ struct SourceLineInfo {
+
+ SourceLineInfo() = delete;
+ SourceLineInfo( char const* _file, std::size_t _line ) noexcept
+ : file( _file ),
+ line( _line )
+ {}
+
+ SourceLineInfo( SourceLineInfo const& other ) = default;
+ SourceLineInfo( SourceLineInfo && ) = default;
+ SourceLineInfo& operator = ( SourceLineInfo const& ) = default;
+ SourceLineInfo& operator = ( SourceLineInfo && ) = default;
+
+ bool empty() const noexcept;
+ bool operator == ( SourceLineInfo const& other ) const noexcept;
+ bool operator < ( SourceLineInfo const& other ) const noexcept;
+
+ char const* file;
+ std::size_t line;
+ };
+
+ std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info );
+
+ // Use this in variadic streaming macros to allow
+ // >> +StreamEndStop
+ // as well as
+ // >> stuff +StreamEndStop
+ struct StreamEndStop {
+ std::string operator+() const;
+ };
+ template
+ T const& operator + ( T const& value, StreamEndStop ) {
+ return value;
+ }
+}
+
+#define CATCH_INTERNAL_LINEINFO \
+ ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) )
+
+// end catch_common.h
+namespace Catch {
+
+ struct RegistrarForTagAliases {
+ RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
+ };
+
+} // end namespace Catch
+
+#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \
+ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+ namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \
+ CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+// end catch_tag_alias_autoregistrar.h
+// start catch_test_registry.h
+
+// start catch_interfaces_testcase.h
+
+#include
+#include
+
+namespace Catch {
+
+ class TestSpec;
+
+ struct ITestInvoker {
+ virtual void invoke () const = 0;
+ virtual ~ITestInvoker();
+ };
+
+ using ITestCasePtr = std::shared_ptr;
+
+ class TestCase;
+ struct IConfig;
+
+ struct ITestCaseRegistry {
+ virtual ~ITestCaseRegistry();
+ virtual std::vector const& getAllTests() 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 );
+
+}
+
+// end catch_interfaces_testcase.h
+// start catch_stringref.h
+
+#include
+#include
+#include
+
+namespace Catch {
+
+ class StringData;
+
+ /// A non-owning string class (similar to the forthcoming std::string_view)
+ /// Note that, because a StringRef may be a substring of another string,
+ /// it may not be null terminated. c_str() must return a null terminated
+ /// string, however, and so the StringRef will internally take ownership
+ /// (taking a copy), if necessary. In theory this ownership is not externally
+ /// visible - but it does mean (substring) StringRefs should not be shared between
+ /// threads.
+ class StringRef {
+ public:
+ using size_type = std::size_t;
+
+ private:
+ friend struct StringRefTestAccess;
+
+ char const* m_start;
+ size_type m_size;
+
+ char* m_data = nullptr;
+
+ void takeOwnership();
+
+ static constexpr char const* const s_empty = "";
+
+ public: // construction/ assignment
+ StringRef() noexcept
+ : StringRef( s_empty, 0 )
+ {}
+
+ StringRef( StringRef const& other ) noexcept
+ : m_start( other.m_start ),
+ m_size( other.m_size )
+ {}
+
+ StringRef( StringRef&& other ) noexcept
+ : m_start( other.m_start ),
+ m_size( other.m_size ),
+ m_data( other.m_data )
+ {
+ other.m_data = nullptr;
+ }
+
+ StringRef( char const* rawChars ) noexcept;
+
+ StringRef( char const* rawChars, size_type size ) noexcept
+ : m_start( rawChars ),
+ m_size( size )
+ {}
+
+ StringRef( std::string const& stdString ) noexcept
+ : m_start( stdString.c_str() ),
+ m_size( stdString.size() )
+ {}
+
+ ~StringRef() noexcept {
+ delete[] m_data;
+ }
+
+ auto operator = ( StringRef const &other ) noexcept -> StringRef& {
+ delete[] m_data;
+ m_data = nullptr;
+ m_start = other.m_start;
+ m_size = other.m_size;
+ return *this;
+ }
+
+ operator std::string() const;
+
+ void swap( StringRef& other ) noexcept;
+
+ public: // operators
+ auto operator == ( StringRef const& other ) const noexcept -> bool;
+ auto operator != ( StringRef const& other ) const noexcept -> bool;
+
+ auto operator[] ( size_type index ) const noexcept -> char;
+
+ public: // named queries
+ auto empty() const noexcept -> bool {
+ return m_size == 0;
+ }
+ auto size() const noexcept -> size_type {
+ return m_size;
+ }
+
+ auto numberOfCharacters() const noexcept -> size_type;
+ auto c_str() const -> char const*;
+
+ public: // substrings and searches
+ auto substr( size_type start, size_type size ) const noexcept -> StringRef;
+
+ // Returns the current start pointer.
+ // Note that the pointer can change when if the StringRef is a substring
+ auto currentData() const noexcept -> char const*;
+
+ private: // ownership queries - may not be consistent between calls
+ auto isOwned() const noexcept -> bool;
+ auto isSubstring() const noexcept -> bool;
+ };
+
+ auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string;
+ auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string;
+ auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string;
+
+ auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&;
+ auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&;
+
+ inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef {
+ return StringRef( rawChars, size );
+ }
+
+} // namespace Catch
+
+// end catch_stringref.h
+namespace Catch {
+
+template
+class TestInvokerAsMethod : public ITestInvoker {
+ void (C::*m_testAsMethod)();
+public:
+ TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {}
+
+ void invoke() const override {
+ C obj;
+ (obj.*m_testAsMethod)();
+ }
+};
+
+auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*;
+
+template
+auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* {
+ return new(std::nothrow) TestInvokerAsMethod( testAsMethod );
+}
+
+struct NameAndTags {
+ NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept;
+ StringRef name;
+ StringRef tags;
+};
+
+struct AutoReg : NonCopyable {
+ AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept;
+ ~AutoReg();
+};
+
+} // end namespace Catch
+
+#if defined(CATCH_CONFIG_DISABLE)
+ #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \
+ static void TestName()
+ #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \
+ namespace{ \
+ struct TestName : ClassName { \
+ void test(); \
+ }; \
+ } \
+ void TestName::test()
+
+#endif
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \
+ static void TestName(); \
+ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \
+ CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+ static void TestName()
+ #define INTERNAL_CATCH_TESTCASE( ... ) \
+ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ )
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
+ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \
+ CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\
+ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+ namespace{ \
+ struct TestName : ClassName{ \
+ void test(); \
+ }; \
+ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
+ } \
+ CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+ void TestName::test()
+ #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \
+ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ )
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \
+ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
+ CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+// end catch_test_registry.h
+// start catch_capture.hpp
+
+// start catch_assertionhandler.h
+
+// start catch_assertioninfo.h
+
+// start catch_result_type.h
+
+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
+
+ }; };
+
+ bool isOk( ResultWas::OfType resultType );
+ bool isJustInfo( int flags );
+
+ // 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
+ }; };
+
+ ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs );
+
+ bool shouldContinueOnFailure( int flags );
+ inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; }
+ bool shouldSuppressFailure( int flags );
+
+} // end namespace Catch
+
+// end catch_result_type.h
+namespace Catch {
+
+ struct AssertionInfo
+ {
+ StringRef macroName;
+ SourceLineInfo lineInfo;
+ StringRef capturedExpression;
+ ResultDisposition::Flags resultDisposition;
+
+ // We want to delete this constructor but a compiler bug in 4.8 means
+ // the struct is then treated as non-aggregate
+ //AssertionInfo() = delete;
+ };
+
+} // end namespace Catch
+
+// end catch_assertioninfo.h
+// start catch_decomposer.h
+
+// start catch_tostring.h
+
+#include
+#include
+#include
+#include
+// start catch_stream.h
+
+#include
+#include
+#include
+
+namespace Catch {
+
+ std::ostream& cout();
+ std::ostream& cerr();
+ std::ostream& clog();
+
+ class StringRef;
+
+ struct IStream {
+ virtual ~IStream();
+ virtual std::ostream& stream() const = 0;
+ };
+
+ auto makeStream( StringRef const &filename ) -> IStream const*;
+
+ class ReusableStringStream {
+ std::size_t m_index;
+ std::ostream* m_oss;
+ public:
+ ReusableStringStream();
+ ~ReusableStringStream();
+
+ auto str() const -> std::string;
+
+ template
+ auto operator << ( T const& value ) -> ReusableStringStream& {
+ *m_oss << value;
+ return *this;
+ }
+ auto get() -> std::ostream& { return *m_oss; }
+
+ static void cleanup();
+ };
+}
+
+// end catch_stream.h
+
+#ifdef __OBJC__
+// start catch_objc_arc.hpp
+
+#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
+
+// end catch_objc_arc.hpp
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless
+#endif
+
+// We need a dummy global operator<< so we can bring it into Catch namespace later
+struct Catch_global_namespace_dummy {};
+std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy);
+
+namespace Catch {
+ // Bring in operator<< from global namespace into Catch namespace
+ using ::operator<<;
+
+ namespace Detail {
+
+ extern const std::string unprintableString;
+
+ std::string rawMemoryToString( const void *object, std::size_t size );
+
+ template
+ std::string rawMemoryToString( const T& object ) {
+ return rawMemoryToString( &object, sizeof(object) );
+ }
+
+ template
+ class IsStreamInsertable {
+ template
+ static auto test(int)
+ -> decltype(std::declval() << std::declval(), std::true_type());
+
+ template
+ static auto test(...)->std::false_type;
+
+ public:
+ static const bool value = decltype(test(0))::value;
+ };
+
+ template
+ std::string convertUnknownEnumToString( E e );
+
+ template
+ typename std::enable_if::value, std::string>::type convertUnstreamable( T const& value ) {
+#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER)
+ (void)value;
+ return Detail::unprintableString;
+#else
+ return CATCH_CONFIG_FALLBACK_STRINGIFIER(value);
+#endif
+ }
+ template
+ typename std::enable_if::value, std::string>::type convertUnstreamable( T const& value ) {
+ return convertUnknownEnumToString( value );
+ }
+
+ } // namespace Detail
+
+ // If we decide for C++14, change these to enable_if_ts
+ template
+ struct StringMaker {
+ template
+ static
+ typename std::enable_if<::Catch::Detail::IsStreamInsertable::value, std::string>::type
+ convert(const Fake& value) {
+ ReusableStringStream rss;
+ rss << value;
+ return rss.str();
+ }
+
+ template
+ static
+ typename std::enable_if::value, std::string>::type
+ convert( const Fake& value ) {
+ return Detail::convertUnstreamable( value );
+ }
+ };
+
+ namespace Detail {
+
+ // This function dispatches all stringification requests inside of Catch.
+ // Should be preferably called fully qualified, like ::Catch::Detail::stringify
+ template
+ std::string stringify(const T& e) {
+ return ::Catch::StringMaker::type>::type>::convert(e);
+ }
+
+ template
+ std::string convertUnknownEnumToString( E e ) {
+ return ::Catch::Detail::stringify(static_cast::type>(e));
+ }
+
+ } // namespace Detail
+
+ // Some predefined specializations
+
+ template<>
+ struct StringMaker {
+ static std::string convert(const std::string& str);
+ };
+#ifdef CATCH_CONFIG_WCHAR
+ template<>
+ struct StringMaker {
+ static std::string convert(const std::wstring& wstr);
+ };
+#endif
+
+ template<>
+ struct StringMaker {
+ static std::string convert(char const * str);
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(char * str);
+ };
+#ifdef CATCH_CONFIG_WCHAR
+ template<>
+ struct StringMaker {
+ static std::string convert(wchar_t const * str);
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(wchar_t * str);
+ };
+#endif
+
+ template
+ struct StringMaker {
+ static std::string convert(const char* str) {
+ return ::Catch::Detail::stringify(std::string{ str });
+ }
+ };
+ template
+ struct StringMaker {
+ static std::string convert(const char* str) {
+ return ::Catch::Detail::stringify(std::string{ str });
+ }
+ };
+ template
+ struct StringMaker {
+ static std::string convert(const char* str) {
+ return ::Catch::Detail::stringify(std::string{ str });
+ }
+ };
+
+ template<>
+ struct StringMaker {
+ static std::string convert(int value);
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(long value);
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(long long value);
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(unsigned int value);
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(unsigned long value);
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(unsigned long long value);
+ };
+
+ template<>
+ struct StringMaker {
+ static std::string convert(bool b);
+ };
+
+ template<>
+ struct StringMaker {
+ static std::string convert(char c);
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(signed char c);
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(unsigned char c);
+ };
+
+ template<>
+ struct StringMaker {
+ static std::string convert(std::nullptr_t);
+ };
+
+ template<>
+ struct StringMaker {
+ static std::string convert(float value);
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(double value);
+ };
+
+ template
+ struct StringMaker {
+ template
+ static std::string convert(U* p) {
+ if (p) {
+ return ::Catch::Detail::rawMemoryToString(p);
+ } else {
+ return "nullptr";
+ }
+ }
+ };
+
+ template
+ struct StringMaker {
+ static std::string convert(R C::* p) {
+ if (p) {
+ return ::Catch::Detail::rawMemoryToString(p);
+ } else {
+ return "nullptr";
+ }
+ }
+ };
+
+ namespace Detail {
+ template
+ std::string rangeToString(InputIterator first, InputIterator last) {
+ ReusableStringStream rss;
+ rss << "{ ";
+ if (first != last) {
+ rss << ::Catch::Detail::stringify(*first);
+ for (++first; first != last; ++first)
+ rss << ", " << ::Catch::Detail::stringify(*first);
+ }
+ rss << " }";
+ return rss.str();
+ }
+ }
+
+#ifdef __OBJC__
+ template<>
+ struct StringMaker {
+ static std::string convert(NSString * nsstring) {
+ if (!nsstring)
+ return "nil";
+ return std::string("@") + [nsstring UTF8String];
+ }
+ };
+ template<>
+ struct StringMaker {
+ static std::string convert(NSObject* nsObject) {
+ return ::Catch::Detail::stringify([nsObject description]);
+ }
+
+ };
+ namespace Detail {
+ inline std::string stringify( NSString* nsstring ) {
+ return StringMaker::convert( nsstring );
+ }
+
+ } // namespace Detail
+#endif // __OBJC__
+
+} // namespace Catch
+
+//////////////////////////////////////////////////////
+// Separate std-lib types stringification, so it can be selectively enabled
+// This means that we do not bring in
+
+#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS)
+# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
+# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
+# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+#endif
+
+// Separate std::pair specialization
+#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER)
+#include
+namespace Catch {
+ template
+ struct StringMaker > {
+ static std::string convert(const std::pair& pair) {
+ ReusableStringStream rss;
+ rss << "{ "
+ << ::Catch::Detail::stringify(pair.first)
+ << ", "
+ << ::Catch::Detail::stringify(pair.second)
+ << " }";
+ return rss.str();
+ }
+ };
+}
+#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
+
+// Separate std::tuple specialization
+#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)
+#include
+namespace Catch {
+ namespace Detail {
+ template<
+ typename Tuple,
+ std::size_t N = 0,
+ bool = (N < std::tuple_size::value)
+ >
+ struct TupleElementPrinter {
+ static void print(const Tuple& tuple, std::ostream& os) {
+ os << (N ? ", " : " ")
+ << ::Catch::Detail::stringify(std::get(tuple));
+ TupleElementPrinter::print(tuple, os);
+ }
+ };
+
+ template<
+ typename Tuple,
+ std::size_t N
+ >
+ struct TupleElementPrinter {
+ static void print(const Tuple&, std::ostream&) {}
+ };
+
+ }
+
+ template
+ struct StringMaker> {
+ static std::string convert(const std::tuple& tuple) {
+ ReusableStringStream rss;
+ rss << '{';
+ Detail::TupleElementPrinter>::print(tuple, rss.get());
+ rss << " }";
+ return rss.str();
+ }
+ };
+}
+#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
+
+namespace Catch {
+ struct not_this_one {}; // Tag type for detecting which begin/ end are being selected
+
+ // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace
+ using std::begin;
+ using std::end;
+
+ not_this_one begin( ... );
+ not_this_one end( ... );
+
+ template
+ struct is_range {
+ static const bool value =
+ !std::is_same())), not_this_one>::value &&
+ !std::is_same())), not_this_one>::value;
+ };
+
+ template
+ std::string rangeToString( Range const& range ) {
+ return ::Catch::Detail::rangeToString( begin( range ), end( range ) );
+ }
+
+ // Handle vector specially
+ template
+ std::string rangeToString( std::vector const& v ) {
+ ReusableStringStream rss;
+ rss << "{ ";
+ bool first = true;
+ for( bool b : v ) {
+ if( first )
+ first = false;
+ else
+ rss << ", ";
+ rss << ::Catch::Detail::stringify( b );
+ }
+ rss << " }";
+ return rss.str();
+ }
+
+ template
+ struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>::type> {
+ static std::string convert( R const& range ) {
+ return rangeToString( range );
+ }
+ };
+
+ template
+ struct StringMaker {
+ static std::string convert(T const(&arr)[SZ]) {
+ return rangeToString(arr);
+ }
+ };
+
+} // namespace Catch
+
+// Separate std::chrono::duration specialization
+#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)
+#include
+#include
+#include
+
+namespace Catch {
+
+template
+struct ratio_string {
+ static std::string symbol();
+};
+
+template
+std::string ratio_string::symbol() {
+ Catch::ReusableStringStream rss;
+ rss << '[' << Ratio::num << '/'
+ << Ratio::den << ']';
+ return rss.str();
+}
+template <>
+struct ratio_string {
+ static std::string symbol();
+};
+template <>
+struct ratio_string {
+ static std::string symbol();
+};
+template <>
+struct ratio_string {
+ static std::string symbol();
+};
+template <>
+struct ratio_string {
+ static std::string symbol();
+};
+template <>
+struct ratio_string {
+ static std::string symbol();
+};
+template <>
+struct ratio_string {
+ static std::string symbol();
+};
+
+ ////////////
+ // std::chrono::duration specializations
+ template
+ struct StringMaker> {
+ static std::string convert(std::chrono::duration const& duration) {
+ ReusableStringStream rss;
+ rss << duration.count() << ' ' << ratio_string::symbol() << 's';
+ return rss.str();
+ }
+ };
+ template
+ struct StringMaker>> {
+ static std::string convert(std::chrono::duration> const& duration) {
+ ReusableStringStream rss;
+ rss << duration.count() << " s";
+ return rss.str();
+ }
+ };
+ template
+ struct StringMaker>> {
+ static std::string convert(std::chrono::duration> const& duration) {
+ ReusableStringStream rss;
+ rss << duration.count() << " m";
+ return rss.str();
+ }
+ };
+ template
+ struct StringMaker>> {
+ static std::string convert(std::chrono::duration> const& duration) {
+ ReusableStringStream rss;
+ rss << duration.count() << " h";
+ return rss.str();
+ }
+ };
+
+ ////////////
+ // std::chrono::time_point specialization
+ // Generic time_point cannot be specialized, only std::chrono::time_point
+ template
+ struct StringMaker> {
+ static std::string convert(std::chrono::time_point const& time_point) {
+ return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch";
+ }
+ };
+ // std::chrono::time_point specialization
+ template
+ struct StringMaker> {
+ static std::string convert(std::chrono::time_point const& time_point) {
+ auto converted = std::chrono::system_clock::to_time_t(time_point);
+
+#ifdef _MSC_VER
+ std::tm timeInfo = {};
+ gmtime_s(&timeInfo, &converted);
+#else
+ std::tm* timeInfo = std::gmtime(&converted);
+#endif
+
+ auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
+ char timeStamp[timeStampSize];
+ const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+#ifdef _MSC_VER
+ std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
+#else
+ std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
+#endif
+ return std::string(timeStamp);
+ }
+ };
+}
+#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+// end catch_tostring.h
+#include
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4389) // '==' : signed/unsigned mismatch
+#pragma warning(disable:4018) // more "signed/unsigned mismatch"
+#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform)
+#pragma warning(disable:4180) // qualifier applied to function type has no meaning
+#endif
+
+namespace Catch {
+
+ struct ITransientExpression {
+ auto isBinaryExpression() const -> bool { return m_isBinaryExpression; }
+ auto getResult() const -> bool { return m_result; }
+ virtual void streamReconstructedExpression( std::ostream &os ) const = 0;
+
+ ITransientExpression( bool isBinaryExpression, bool result )
+ : m_isBinaryExpression( isBinaryExpression ),
+ m_result( result )
+ {}
+
+ // We don't actually need a virtual destructor, but many static analysers
+ // complain if it's not here :-(
+ virtual ~ITransientExpression();
+
+ bool m_isBinaryExpression;
+ bool m_result;
+
+ };
+
+ void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs );
+
+ template
+ class BinaryExpr : public ITransientExpression {
+ LhsT m_lhs;
+ StringRef m_op;
+ RhsT m_rhs;
+
+ void streamReconstructedExpression( std::ostream &os ) const override {
+ formatReconstructedExpression
+ ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) );
+ }
+
+ public:
+ BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs )
+ : ITransientExpression{ true, comparisonResult },
+ m_lhs( lhs ),
+ m_op( op ),
+ m_rhs( rhs )
+ {}
+ };
+
+ template
+ class UnaryExpr : public ITransientExpression {
+ LhsT m_lhs;
+
+ void streamReconstructedExpression( std::ostream &os ) const override {
+ os << Catch::Detail::stringify( m_lhs );
+ }
+
+ public:
+ explicit UnaryExpr( LhsT lhs )
+ : ITransientExpression{ false, lhs ? true : false },
+ m_lhs( lhs )
+ {}
+ };
+
+ // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int)
+ template
+ auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast(lhs == rhs); }
+ template
+ auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast( rhs ); }
+ template
+ auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast( rhs ); }
+ template
+ auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; }
+ template
+ auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; }
+
+ template
+ auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast(lhs != rhs); }
+ template
+ auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast( rhs ); }
+ template
+ auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast( rhs ); }
+ template
+ auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; }
+ template
+ auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; }
+
+ template
+ class ExprLhs {
+ LhsT m_lhs;
+ public:
+ explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {}
+
+ template
+ auto operator == ( RhsT const& rhs ) -> BinaryExpr const {
+ return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs };
+ }
+ auto operator == ( bool rhs ) -> BinaryExpr const {
+ return { m_lhs == rhs, m_lhs, "==", rhs };
+ }
+
+ template
+ auto operator != ( RhsT const& rhs ) -> BinaryExpr const {
+ return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs };
+ }
+ auto operator != ( bool rhs ) -> BinaryExpr const {
+ return { m_lhs != rhs, m_lhs, "!=", rhs };
+ }
+
+ template
+ auto operator > ( RhsT const& rhs ) -> BinaryExpr const {
+ return { static_cast(m_lhs > rhs), m_lhs, ">", rhs };
+ }
+ template
+ auto operator < ( RhsT const& rhs ) -> BinaryExpr